summaryrefslogtreecommitdiffstats
path: root/mozglue/misc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /mozglue/misc
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mozglue/misc')
-rw-r--r--mozglue/misc/AutoProfilerLabel.cpp109
-rw-r--r--mozglue/misc/AutoProfilerLabel.h71
-rw-r--r--mozglue/misc/AwakeTimeStamp.cpp93
-rw-r--r--mozglue/misc/AwakeTimeStamp.h118
-rw-r--r--mozglue/misc/ConditionVariable_noop.cpp40
-rw-r--r--mozglue/misc/ConditionVariable_posix.cpp159
-rw-r--r--mozglue/misc/ConditionVariable_windows.cpp98
-rw-r--r--mozglue/misc/Debug.cpp123
-rw-r--r--mozglue/misc/Debug.h76
-rw-r--r--mozglue/misc/DynamicallyLinkedFunctionPtr.h137
-rw-r--r--mozglue/misc/GetKnownFolderPath.cpp33
-rw-r--r--mozglue/misc/GetKnownFolderPath.h45
-rw-r--r--mozglue/misc/ImportDir.h94
-rw-r--r--mozglue/misc/IntegerPrintfMacros.h84
-rw-r--r--mozglue/misc/MmapFaultHandler.cpp130
-rw-r--r--mozglue/misc/MmapFaultHandler.h113
-rw-r--r--mozglue/misc/MutexPlatformData_noop.h18
-rw-r--r--mozglue/misc/MutexPlatformData_posix.h18
-rw-r--r--mozglue/misc/MutexPlatformData_windows.h18
-rw-r--r--mozglue/misc/Mutex_noop.cpp34
-rw-r--r--mozglue/misc/Mutex_posix.cpp131
-rw-r--r--mozglue/misc/Mutex_windows.cpp40
-rw-r--r--mozglue/misc/NativeNt.h1758
-rw-r--r--mozglue/misc/PlatformConditionVariable.h71
-rw-r--r--mozglue/misc/PlatformMutex.h66
-rw-r--r--mozglue/misc/PlatformRWLock.h50
-rw-r--r--mozglue/misc/PreXULSkeletonUI.cpp2234
-rw-r--r--mozglue/misc/PreXULSkeletonUI.h183
-rw-r--r--mozglue/misc/Printf.cpp1101
-rw-r--r--mozglue/misc/Printf.h266
-rw-r--r--mozglue/misc/ProcessType.cpp58
-rw-r--r--mozglue/misc/ProcessType.h44
-rw-r--r--mozglue/misc/RWLock_posix.cpp64
-rw-r--r--mozglue/misc/RWLock_windows.cpp48
-rw-r--r--mozglue/misc/RuntimeExceptionModule.cpp107
-rw-r--r--mozglue/misc/RuntimeExceptionModule.h20
-rw-r--r--mozglue/misc/SIMD.cpp565
-rw-r--r--mozglue/misc/SIMD.h81
-rw-r--r--mozglue/misc/SIMD_avx2.cpp294
-rw-r--r--mozglue/misc/SSE.cpp259
-rw-r--r--mozglue/misc/SSE.h388
-rw-r--r--mozglue/misc/Sprintf.h98
-rw-r--r--mozglue/misc/StackWalk.cpp1129
-rw-r--r--mozglue/misc/StackWalk.h215
-rw-r--r--mozglue/misc/StackWalkThread.h34
-rw-r--r--mozglue/misc/StackWalk_windows.h34
-rw-r--r--mozglue/misc/TimeStamp.cpp115
-rw-r--r--mozglue/misc/TimeStamp.h620
-rw-r--r--mozglue/misc/TimeStamp_darwin.cpp189
-rw-r--r--mozglue/misc/TimeStamp_posix.cpp360
-rw-r--r--mozglue/misc/TimeStamp_windows.cpp577
-rw-r--r--mozglue/misc/TimeStamp_windows.h113
-rw-r--r--mozglue/misc/Uptime.cpp155
-rw-r--r--mozglue/misc/Uptime.h26
-rw-r--r--mozglue/misc/WinUtils.h140
-rw-r--r--mozglue/misc/WindowsDllMain.cpp20
-rw-r--r--mozglue/misc/WindowsDpiAwareness.h36
-rw-r--r--mozglue/misc/WindowsDpiInitialization.cpp67
-rw-r--r--mozglue/misc/WindowsDpiInitialization.h51
-rw-r--r--mozglue/misc/WindowsEnumProcessModules.h61
-rw-r--r--mozglue/misc/WindowsMapRemoteView.cpp124
-rw-r--r--mozglue/misc/WindowsMapRemoteView.h25
-rw-r--r--mozglue/misc/WindowsProcessMitigations.cpp101
-rw-r--r--mozglue/misc/WindowsProcessMitigations.h22
-rw-r--r--mozglue/misc/WindowsStackCookie.h63
-rw-r--r--mozglue/misc/WindowsUnicode.cpp59
-rw-r--r--mozglue/misc/WindowsUnicode.h35
-rw-r--r--mozglue/misc/WindowsUnwindInfo.h329
-rw-r--r--mozglue/misc/decimal/Decimal.cpp1017
-rw-r--r--mozglue/misc/decimal/Decimal.h276
-rw-r--r--mozglue/misc/decimal/DoubleConversion.h27
-rw-r--r--mozglue/misc/decimal/UPSTREAM-GIT-SHA1
-rw-r--r--mozglue/misc/decimal/add-doubleconversion-impl.patch42
-rw-r--r--mozglue/misc/decimal/comparison-with-nan.patch67
-rw-r--r--mozglue/misc/decimal/fix-wshadow-warnings.patch171
-rw-r--r--mozglue/misc/decimal/mfbt-abi-markers.patch150
-rw-r--r--mozglue/misc/decimal/moz-constexpr-decimal.patch218
-rw-r--r--mozglue/misc/decimal/moz-decimal-utils.h110
-rw-r--r--mozglue/misc/decimal/to-moz-dependencies.patch224
-rwxr-xr-xmozglue/misc/decimal/update.sh61
-rw-r--r--mozglue/misc/decimal/zero-serialization.patch22
-rw-r--r--mozglue/misc/moz.build157
82 files changed, 16780 insertions, 0 deletions
diff --git a/mozglue/misc/AutoProfilerLabel.cpp b/mozglue/misc/AutoProfilerLabel.cpp
new file mode 100644
index 0000000000..5fd820cc5b
--- /dev/null
+++ b/mozglue/misc/AutoProfilerLabel.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; 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 "mozilla/AutoProfilerLabel.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/PlatformMutex.h"
+
+namespace mozilla {
+
+// RAII class that encapsulates all shared static data, and enforces locking
+// when accessing this data.
+class MOZ_RAII AutoProfilerLabelData {
+ public:
+ AutoProfilerLabelData() { sAPLMutex.Lock(); }
+
+ ~AutoProfilerLabelData() { sAPLMutex.Unlock(); }
+
+ AutoProfilerLabelData(const AutoProfilerLabelData&) = delete;
+ void operator=(const AutoProfilerLabelData&) = delete;
+
+ const ProfilerLabelEnter& EnterCRef() const { return sEnter; }
+ ProfilerLabelEnter& EnterRef() { return sEnter; }
+
+ const ProfilerLabelExit& ExitCRef() const { return sExit; }
+ ProfilerLabelExit& ExitRef() { return sExit; }
+
+ const uint32_t& GenerationCRef() const { return sGeneration; }
+ uint32_t& GenerationRef() { return sGeneration; }
+
+ static bool RacyIsProfilerPresent() { return !!sGeneration; }
+
+ private:
+ // Thin shell around mozglue PlatformMutex, for local internal use.
+ // Does not preserve behavior in JS record/replay.
+ class Mutex : private mozilla::detail::MutexImpl {
+ public:
+ Mutex() = default;
+ void Lock() { mozilla::detail::MutexImpl::lock(); }
+ void Unlock() { mozilla::detail::MutexImpl::unlock(); }
+ };
+
+ // Mutex protecting access to the following static members.
+ static Mutex sAPLMutex MOZ_UNANNOTATED;
+
+ static ProfilerLabelEnter sEnter;
+ static ProfilerLabelExit sExit;
+
+ // Current "generation" of RegisterProfilerLabelEnterExit calls.
+ static uint32_t sGeneration;
+};
+
+/* static */ AutoProfilerLabelData::Mutex AutoProfilerLabelData::sAPLMutex;
+/* static */ ProfilerLabelEnter AutoProfilerLabelData::sEnter = nullptr;
+/* static */ ProfilerLabelExit AutoProfilerLabelData::sExit = nullptr;
+/* static */ uint32_t AutoProfilerLabelData::sGeneration = 0;
+
+void RegisterProfilerLabelEnterExit(ProfilerLabelEnter aEnter,
+ ProfilerLabelExit aExit) {
+ MOZ_ASSERT(!aEnter == !aExit, "Must provide both null or both non-null");
+
+ AutoProfilerLabelData data;
+ MOZ_ASSERT(!aEnter != !data.EnterRef(),
+ "Must go from null to non-null, or from non-null to null");
+ data.EnterRef() = aEnter;
+ data.ExitRef() = aExit;
+ ++data.GenerationRef();
+}
+
+bool IsProfilerPresent() {
+ return AutoProfilerLabelData::RacyIsProfilerPresent();
+}
+
+ProfilerLabel ProfilerLabelBegin(const char* aLabelName,
+ const char* aDynamicString, void* aSp) {
+ const AutoProfilerLabelData data;
+ void* entryContext = (data.EnterCRef())
+ ? data.EnterCRef()(aLabelName, aDynamicString, aSp)
+ : nullptr;
+ uint32_t generation = data.GenerationCRef();
+
+ return std::make_tuple(entryContext, generation);
+}
+
+void ProfilerLabelEnd(const ProfilerLabel& aLabel) {
+ if (!IsValidProfilerLabel(aLabel)) {
+ return;
+ }
+
+ const AutoProfilerLabelData data;
+ if (data.ExitCRef() && (std::get<1>(aLabel) == data.GenerationCRef())) {
+ data.ExitCRef()(std::get<0>(aLabel));
+ }
+}
+
+AutoProfilerLabel::AutoProfilerLabel(const char* aLabel,
+ const char* aDynamicString) {
+ std::tie(mEntryContext, mGeneration) =
+ ProfilerLabelBegin(aLabel, aDynamicString, this);
+}
+
+AutoProfilerLabel::~AutoProfilerLabel() {
+ ProfilerLabelEnd(std::make_tuple(mEntryContext, mGeneration));
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/AutoProfilerLabel.h b/mozglue/misc/AutoProfilerLabel.h
new file mode 100644
index 0000000000..82299c7b15
--- /dev/null
+++ b/mozglue/misc/AutoProfilerLabel.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; 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 mozilla_AutoProfilerLabel_h
+#define mozilla_AutoProfilerLabel_h
+
+#include "mozilla/Attributes.h"
+
+#include "mozilla/Types.h"
+#include <tuple>
+
+// The Gecko Profiler defines AutoProfilerLabel, an RAII class for
+// pushing/popping frames to/from the ProfilingStack.
+//
+// This file defines a class of the same name that does much the same thing,
+// but which can be used in (and only in) mozglue. A different class is
+// necessary because mozglue cannot directly access sProfilingStack.
+//
+// Note that this class is slightly slower than the other AutoProfilerLabel,
+// and it lacks the macro wrappers. It also is effectively hardwired to use
+// JS::ProfilingCategory::OTHER as the category pair, because that's what
+// the callbacks provided by the profiler use. (Specifying the categories in
+// this file would require #including ProfilingCategory.h in mozglue, which we
+// don't want to do.)
+
+namespace mozilla {
+
+// Enter should return a pointer that will be given to Exit.
+typedef void* (*ProfilerLabelEnter)(const char* aLabel,
+ const char* aDynamicString, void* aSp);
+typedef void (*ProfilerLabelExit)(void* EntryContext);
+
+// Register callbacks that do the entry/exit work involving sProfilingStack.
+MFBT_API void RegisterProfilerLabelEnterExit(ProfilerLabelEnter aEnter,
+ ProfilerLabelExit aExit);
+
+// This #ifdef prevents this AutoProfilerLabel from being defined in libxul,
+// which would conflict with the one in the profiler.
+#ifdef IMPL_MFBT
+
+class MOZ_RAII AutoProfilerLabel {
+ public:
+ AutoProfilerLabel(const char* aLabel, const char* aDynamicString);
+ ~AutoProfilerLabel();
+
+ private:
+ void* mEntryContext;
+ // Number of RegisterProfilerLabelEnterExit calls, to avoid giving an entry
+ // context from one generation to the next.
+ uint32_t mGeneration;
+};
+
+using ProfilerLabel = std::tuple<void*, uint32_t>;
+
+bool IsProfilerPresent();
+ProfilerLabel ProfilerLabelBegin(const char* aLabelName,
+ const char* aDynamicString, void* aSp);
+void ProfilerLabelEnd(const ProfilerLabel& aLabel);
+
+inline bool IsValidProfilerLabel(const ProfilerLabel& aLabel) {
+ return !!std::get<0>(aLabel);
+}
+
+#endif
+
+} // namespace mozilla
+
+#endif // mozilla_AutoProfilerLabel_h
diff --git a/mozglue/misc/AwakeTimeStamp.cpp b/mozglue/misc/AwakeTimeStamp.cpp
new file mode 100644
index 0000000000..a5c8ed4abd
--- /dev/null
+++ b/mozglue/misc/AwakeTimeStamp.cpp
@@ -0,0 +1,93 @@
+/* -*- 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 "AwakeTimeStamp.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+
+namespace mozilla {
+
+static constexpr uint64_t kUSperS = 1000000;
+static constexpr uint64_t kUSperMS = 1000;
+#ifndef XP_WIN
+static constexpr uint64_t kNSperUS = 1000;
+#endif
+
+double AwakeTimeDuration::ToSeconds() const {
+ return static_cast<double>(mValueUs) / kUSperS;
+}
+double AwakeTimeDuration::ToMilliseconds() const {
+ return static_cast<double>(mValueUs) / kUSperMS;
+}
+double AwakeTimeDuration::ToMicroseconds() const {
+ return static_cast<double>(mValueUs);
+}
+
+AwakeTimeDuration AwakeTimeStamp::operator-(
+ AwakeTimeStamp const& aOther) const {
+ return AwakeTimeDuration(mValueUs - aOther.mValueUs);
+}
+
+AwakeTimeStamp AwakeTimeStamp::operator+(
+ const AwakeTimeDuration& aDuration) const {
+ return AwakeTimeStamp(mValueUs + aDuration.mValueUs);
+}
+
+void AwakeTimeStamp::operator+=(const AwakeTimeDuration& aOther) {
+ mValueUs += aOther.mValueUs;
+}
+
+void AwakeTimeStamp::operator-=(const AwakeTimeDuration& aOther) {
+ MOZ_ASSERT(mValueUs >= aOther.mValueUs);
+ mValueUs -= aOther.mValueUs;
+}
+
+// Apple things
+#if defined(__APPLE__) && defined(__MACH__)
+# include <time.h>
+# include <sys/time.h>
+# include <sys/types.h>
+# include <mach/mach_time.h>
+
+AwakeTimeStamp AwakeTimeStamp::NowLoRes() {
+ return AwakeTimeStamp(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / kNSperUS);
+}
+
+#elif defined(XP_WIN)
+
+// Number of hundreds of nanoseconds in a microsecond
+static constexpr uint64_t kHNSperUS = 10;
+
+AwakeTimeStamp AwakeTimeStamp::NowLoRes() {
+ ULONGLONG interrupt_time;
+ DebugOnly<bool> rv = QueryUnbiasedInterruptTime(&interrupt_time);
+ MOZ_ASSERT(rv);
+
+ return AwakeTimeStamp(interrupt_time / kHNSperUS);
+}
+
+#else // Linux and other POSIX but not macOS
+# include <time.h>
+
+uint64_t TimespecToMicroseconds(struct timespec aTs) {
+ return aTs.tv_sec * kUSperS + aTs.tv_nsec / kNSperUS;
+}
+
+AwakeTimeStamp AwakeTimeStamp::NowLoRes() {
+ struct timespec ts = {0};
+ DebugOnly<int> rv = clock_gettime(CLOCK_MONOTONIC, &ts);
+ MOZ_ASSERT(!rv);
+ return AwakeTimeStamp(TimespecToMicroseconds(ts));
+}
+
+#endif
+
+}; // namespace mozilla
diff --git a/mozglue/misc/AwakeTimeStamp.h b/mozglue/misc/AwakeTimeStamp.h
new file mode 100644
index 0000000000..b95ef9e77e
--- /dev/null
+++ b/mozglue/misc/AwakeTimeStamp.h
@@ -0,0 +1,118 @@
+/* -*- 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 mozilla_AwakeTimeStamp_h
+#define mozilla_AwakeTimeStamp_h
+
+#include <stdint.h>
+#include <mozilla/Types.h>
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+
+class AwakeTimeDuration;
+
+// Conceptually like mozilla::TimeStamp, but only increments when the device is
+// awake, on all platforms, and with a restricted API.
+//
+// It is always valid, there is no way to acquire an AwakeTimeStamp that is not
+// valid, unlike TimeStamp that can be null.
+//
+// Some arithmetic and ordering operations are supported, when they make sense.
+//
+// This timestamp shouldn't be considered to be high-resolution, and is suitable
+// to measure time from a hundred of milliseconds (because of Windows
+// limitations).
+class AwakeTimeStamp {
+ public:
+ MFBT_API static AwakeTimeStamp NowLoRes();
+ MFBT_API void operator+=(const AwakeTimeDuration& aOther);
+ MFBT_API void operator-=(const AwakeTimeDuration& aOther);
+ MFBT_API bool operator<(const AwakeTimeStamp& aOther) const {
+ return mValueUs < aOther.mValueUs;
+ }
+ MFBT_API bool operator<=(const AwakeTimeStamp& aOther) const {
+ return mValueUs <= aOther.mValueUs;
+ }
+ MFBT_API bool operator>=(const AwakeTimeStamp& aOther) const {
+ return mValueUs >= aOther.mValueUs;
+ }
+ MFBT_API bool operator>(const AwakeTimeStamp& aOther) const {
+ return mValueUs > aOther.mValueUs;
+ }
+ MFBT_API bool operator==(const AwakeTimeStamp& aOther) const {
+ return mValueUs == aOther.mValueUs;
+ }
+ MFBT_API bool operator!=(const AwakeTimeStamp& aOther) const {
+ return !(*this == aOther);
+ }
+ MFBT_API AwakeTimeDuration operator-(AwakeTimeStamp const& aOther) const;
+ MFBT_API AwakeTimeStamp operator+(const AwakeTimeDuration& aDuration) const;
+
+ private:
+ explicit AwakeTimeStamp(uint64_t aValueUs) : mValueUs(aValueUs) {}
+
+ uint64_t mValueUs;
+};
+
+// A duration, only counting the time the computer was awake.
+//
+// Can be obtained via subtracting two AwakeTimeStamp, or default-contructed to
+// mean a empty duration.
+//
+// Arithmetic and ordering operations are defined when they make sense.
+class AwakeTimeDuration {
+ public:
+ MFBT_API AwakeTimeDuration() : mValueUs(0) {}
+
+ MFBT_API double ToSeconds() const;
+ MFBT_API double ToMilliseconds() const;
+ MFBT_API double ToMicroseconds() const;
+ MFBT_API void operator+=(const AwakeTimeDuration& aDuration) {
+ mValueUs += aDuration.mValueUs;
+ }
+ MFBT_API AwakeTimeDuration operator+(const AwakeTimeDuration& aOther) const {
+ return AwakeTimeDuration(mValueUs + aOther.mValueUs);
+ }
+ MFBT_API AwakeTimeDuration operator-(const AwakeTimeDuration& aOther) const {
+ MOZ_ASSERT(mValueUs >= aOther.mValueUs);
+ return AwakeTimeDuration(mValueUs - aOther.mValueUs);
+ }
+ MFBT_API void operator-=(const AwakeTimeDuration& aOther) {
+ MOZ_ASSERT(mValueUs >= aOther.mValueUs);
+ mValueUs -= aOther.mValueUs;
+ }
+ MFBT_API bool operator<(const AwakeTimeDuration& aOther) const {
+ return mValueUs < aOther.mValueUs;
+ }
+ MFBT_API bool operator<=(const AwakeTimeDuration& aOther) const {
+ return mValueUs <= aOther.mValueUs;
+ }
+ MFBT_API bool operator>=(const AwakeTimeDuration& aOther) const {
+ return mValueUs >= aOther.mValueUs;
+ }
+ MFBT_API bool operator>(const AwakeTimeDuration& aOther) const {
+ return mValueUs > aOther.mValueUs;
+ }
+ MFBT_API bool operator==(const AwakeTimeDuration& aOther) const {
+ return mValueUs == aOther.mValueUs;
+ }
+ MFBT_API bool operator!=(const AwakeTimeDuration& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ friend AwakeTimeStamp;
+ // Not using a default value because we want this private, but allow creating
+ // duration that are empty.
+ explicit AwakeTimeDuration(uint64_t aValueUs) : mValueUs(aValueUs) {}
+
+ uint64_t mValueUs;
+};
+
+}; // namespace mozilla
+
+#endif // mozilla_AwakeTimeStamp_h
diff --git a/mozglue/misc/ConditionVariable_noop.cpp b/mozglue/misc/ConditionVariable_noop.cpp
new file mode 100644
index 0000000000..4b28861650
--- /dev/null
+++ b/mozglue/misc/ConditionVariable_noop.cpp
@@ -0,0 +1,40 @@
+/* -*- 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 "mozilla/Assertions.h"
+
+#include "mozilla/PlatformConditionVariable.h"
+#include "mozilla/PlatformMutex.h"
+#include "MutexPlatformData_noop.h"
+
+using mozilla::TimeDuration;
+
+struct mozilla::detail::ConditionVariableImpl::PlatformData {};
+
+mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() {}
+
+mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() {}
+
+void mozilla::detail::ConditionVariableImpl::notify_one() {}
+
+void mozilla::detail::ConditionVariableImpl::notify_all() {}
+
+void mozilla::detail::ConditionVariableImpl::wait(MutexImpl&) {
+ // On WASI, there are no threads, so we never wait (either the condvar must
+ // be ready or there is a deadlock).
+}
+
+mozilla::CVStatus mozilla::detail::ConditionVariableImpl::wait_for(
+ MutexImpl&, const TimeDuration&) {
+ return CVStatus::NoTimeout;
+}
+
+mozilla::detail::ConditionVariableImpl::PlatformData*
+mozilla::detail::ConditionVariableImpl::platformData() {
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/ConditionVariable_posix.cpp b/mozglue/misc/ConditionVariable_posix.cpp
new file mode 100644
index 0000000000..5c98560551
--- /dev/null
+++ b/mozglue/misc/ConditionVariable_posix.cpp
@@ -0,0 +1,159 @@
+/* -*- 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 "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <time.h>
+
+#include "mozilla/PlatformConditionVariable.h"
+#include "mozilla/PlatformMutex.h"
+#include "MutexPlatformData_posix.h"
+
+using mozilla::CheckedInt;
+using mozilla::TimeDuration;
+
+static const long NanoSecPerSec = 1000000000;
+
+// Android 4.4 or earlier & macOS 10.12 has the clock functions, but not
+// pthread_condattr_setclock.
+#if defined(HAVE_CLOCK_MONOTONIC) && \
+ !(defined(__ANDROID__) && __ANDROID_API__ < 21) && !defined(__APPLE__)
+# define CV_USE_CLOCK_API
+#endif
+
+#ifdef CV_USE_CLOCK_API
+// The C++ specification defines std::condition_variable::wait_for in terms of
+// std::chrono::steady_clock, which is closest to CLOCK_MONOTONIC.
+static const clockid_t WhichClock = CLOCK_MONOTONIC;
+
+// While timevaladd is widely available to work with timevals, the newer
+// timespec structure is largely lacking such conveniences. Thankfully, the
+// utilities available in MFBT make implementing our own quite easy.
+static void moz_timespecadd(struct timespec* lhs, struct timespec* rhs,
+ struct timespec* result) {
+ // Add nanoseconds. This may wrap, but not above 2 billion.
+ MOZ_RELEASE_ASSERT(lhs->tv_nsec < NanoSecPerSec);
+ MOZ_RELEASE_ASSERT(rhs->tv_nsec < NanoSecPerSec);
+ result->tv_nsec = lhs->tv_nsec + rhs->tv_nsec;
+
+ // Add seconds, checking for overflow in the platform specific time_t type.
+ CheckedInt<time_t> sec = CheckedInt<time_t>(lhs->tv_sec) + rhs->tv_sec;
+
+ // If nanoseconds overflowed, carry the result over into seconds.
+ if (result->tv_nsec >= NanoSecPerSec) {
+ MOZ_RELEASE_ASSERT(result->tv_nsec < 2 * NanoSecPerSec);
+ result->tv_nsec -= NanoSecPerSec;
+ sec += 1;
+ }
+
+ // Extracting the value asserts that there was no overflow.
+ MOZ_RELEASE_ASSERT(sec.isValid());
+ result->tv_sec = sec.value();
+}
+#endif
+
+struct mozilla::detail::ConditionVariableImpl::PlatformData {
+ pthread_cond_t ptCond;
+};
+
+mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() {
+ pthread_cond_t* ptCond = &platformData()->ptCond;
+
+#ifdef CV_USE_CLOCK_API
+ pthread_condattr_t attr;
+ int r0 = pthread_condattr_init(&attr);
+ MOZ_RELEASE_ASSERT(!r0);
+
+ int r1 = pthread_condattr_setclock(&attr, WhichClock);
+ MOZ_RELEASE_ASSERT(!r1);
+
+ int r2 = pthread_cond_init(ptCond, &attr);
+ MOZ_RELEASE_ASSERT(!r2);
+
+ int r3 = pthread_condattr_destroy(&attr);
+ MOZ_RELEASE_ASSERT(!r3);
+#else
+ int r = pthread_cond_init(ptCond, NULL);
+ MOZ_RELEASE_ASSERT(!r);
+#endif
+}
+
+mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() {
+ int r = pthread_cond_destroy(&platformData()->ptCond);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+void mozilla::detail::ConditionVariableImpl::notify_one() {
+ int r = pthread_cond_signal(&platformData()->ptCond);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+void mozilla::detail::ConditionVariableImpl::notify_all() {
+ int r = pthread_cond_broadcast(&platformData()->ptCond);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+void mozilla::detail::ConditionVariableImpl::wait(MutexImpl& lock) {
+ pthread_cond_t* ptCond = &platformData()->ptCond;
+ pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex;
+
+ int r = pthread_cond_wait(ptCond, ptMutex);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+mozilla::CVStatus mozilla::detail::ConditionVariableImpl::wait_for(
+ MutexImpl& lock, const TimeDuration& a_rel_time) {
+ if (a_rel_time == TimeDuration::Forever()) {
+ wait(lock);
+ return CVStatus::NoTimeout;
+ }
+
+ pthread_cond_t* ptCond = &platformData()->ptCond;
+ pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex;
+ int r;
+
+ // Clamp to 0, as time_t is unsigned.
+ TimeDuration rel_time = a_rel_time < TimeDuration::FromSeconds(0)
+ ? TimeDuration::FromSeconds(0)
+ : a_rel_time;
+
+ // Convert the duration to a timespec.
+ struct timespec rel_ts;
+ rel_ts.tv_sec = static_cast<time_t>(rel_time.ToSeconds());
+ rel_ts.tv_nsec =
+ static_cast<uint64_t>(rel_time.ToMicroseconds() * 1000.0) % NanoSecPerSec;
+
+#ifdef CV_USE_CLOCK_API
+ struct timespec now_ts;
+ r = clock_gettime(WhichClock, &now_ts);
+ MOZ_RELEASE_ASSERT(!r);
+
+ struct timespec abs_ts;
+ moz_timespecadd(&now_ts, &rel_ts, &abs_ts);
+
+ r = pthread_cond_timedwait(ptCond, ptMutex, &abs_ts);
+#else
+ // Our non-clock-supporting platforms, OS X and Android, do support waiting
+ // on a condition variable with a relative timeout.
+ r = pthread_cond_timedwait_relative_np(ptCond, ptMutex, &rel_ts);
+#endif
+
+ if (r == 0) {
+ return CVStatus::NoTimeout;
+ }
+ MOZ_RELEASE_ASSERT(r == ETIMEDOUT);
+ return CVStatus::Timeout;
+}
+
+mozilla::detail::ConditionVariableImpl::PlatformData*
+mozilla::detail::ConditionVariableImpl::platformData() {
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/ConditionVariable_windows.cpp b/mozglue/misc/ConditionVariable_windows.cpp
new file mode 100644
index 0000000000..0c0151f1d3
--- /dev/null
+++ b/mozglue/misc/ConditionVariable_windows.cpp
@@ -0,0 +1,98 @@
+/* -*- 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 "mozilla/Assertions.h"
+
+#include <float.h>
+#include <intrin.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include "mozilla/PlatformConditionVariable.h"
+#include "mozilla/PlatformMutex.h"
+#include "MutexPlatformData_windows.h"
+
+// Some versions of the Windows SDK have a bug where some interlocked functions
+// are not redefined as compiler intrinsics. Fix that for the interlocked
+// functions that are used in this file.
+#if defined(_MSC_VER) && !defined(InterlockedExchangeAdd)
+# define InterlockedExchangeAdd(addend, value) \
+ _InterlockedExchangeAdd((volatile long*)(addend), (long)(value))
+#endif
+
+#if defined(_MSC_VER) && !defined(InterlockedIncrement)
+# define InterlockedIncrement(addend) \
+ _InterlockedIncrement((volatile long*)(addend))
+#endif
+
+// Wrapper for native condition variable APIs.
+struct mozilla::detail::ConditionVariableImpl::PlatformData {
+ CONDITION_VARIABLE cv_;
+};
+
+mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() {
+ InitializeConditionVariable(&platformData()->cv_);
+}
+
+void mozilla::detail::ConditionVariableImpl::notify_one() {
+ WakeConditionVariable(&platformData()->cv_);
+}
+
+void mozilla::detail::ConditionVariableImpl::notify_all() {
+ WakeAllConditionVariable(&platformData()->cv_);
+}
+
+void mozilla::detail::ConditionVariableImpl::wait(MutexImpl& lock) {
+ SRWLOCK* srwlock = &lock.platformData()->lock;
+ bool r =
+ SleepConditionVariableSRW(&platformData()->cv_, srwlock, INFINITE, 0);
+ MOZ_RELEASE_ASSERT(r);
+}
+
+mozilla::CVStatus mozilla::detail::ConditionVariableImpl::wait_for(
+ MutexImpl& lock, const mozilla::TimeDuration& rel_time) {
+ if (rel_time == mozilla::TimeDuration::Forever()) {
+ wait(lock);
+ return CVStatus::NoTimeout;
+ }
+
+ SRWLOCK* srwlock = &lock.platformData()->lock;
+
+ // Note that DWORD is unsigned, so we have to be careful to clamp at 0. If
+ // rel_time is Forever, then ToMilliseconds is +inf, which evaluates as
+ // greater than UINT32_MAX, resulting in the correct INFINITE wait. We also
+ // don't want to round sub-millisecond waits to 0, as that wastes energy (see
+ // bug 1437167 comment 6), so we instead round submillisecond waits to 1ms.
+ double msecd = rel_time.ToMilliseconds();
+ DWORD msec;
+ if (msecd < 0.0) {
+ msec = 0;
+ } else if (msecd > UINT32_MAX) {
+ msec = INFINITE;
+ } else {
+ msec = static_cast<DWORD>(msecd);
+ // Round submillisecond waits to 1ms.
+ if (msec == 0 && !rel_time.IsZero()) {
+ msec = 1;
+ }
+ }
+
+ BOOL r = SleepConditionVariableSRW(&platformData()->cv_, srwlock, msec, 0);
+ if (r) return CVStatus::NoTimeout;
+ MOZ_RELEASE_ASSERT(GetLastError() == ERROR_TIMEOUT);
+ return CVStatus::Timeout;
+}
+
+mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() {
+ // Native condition variables don't require cleanup.
+}
+
+inline mozilla::detail::ConditionVariableImpl::PlatformData*
+mozilla::detail::ConditionVariableImpl::platformData() {
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/Debug.cpp b/mozglue/misc/Debug.cpp
new file mode 100644
index 0000000000..c3a2ca89e0
--- /dev/null
+++ b/mozglue/misc/Debug.cpp
@@ -0,0 +1,123 @@
+/* -*- 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 "mozilla/glue/Debug.h"
+#include "mozilla/Fuzzing.h"
+#include "mozilla/Sprintf.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#ifdef XP_WIN
+# include <io.h>
+# include <windows.h>
+#endif
+
+#ifdef ANDROID
+# include <android/log.h>
+#endif
+
+#ifndef ANDROID
+static void vprintf_stderr_buffered(const char* aFmt, va_list aArgs) {
+ // Avoid interleaving by writing to an on-stack buffer and then writing in one
+ // go with fputs, as long as the output fits into the buffer.
+ char buffer[1024];
+ va_list argsCpy;
+ va_copy(argsCpy, aArgs);
+ int n = VsprintfLiteral(buffer, aFmt, aArgs);
+ if (n < int(sizeof(buffer))) {
+ fputs(buffer, stderr);
+ } else {
+ // Message too long for buffer. Just print it, not worrying about
+ // interleaving. (We could malloc, but the underlying write() syscall could
+ // get interleaved if the output is too big anyway.)
+ vfprintf(stderr, aFmt, argsCpy);
+ }
+ va_end(argsCpy);
+ fflush(stderr);
+}
+#endif
+
+#if defined(XP_WIN)
+MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs) {
+ if (IsDebuggerPresent()) {
+ int lengthNeeded = _vscprintf(aFmt, aArgs);
+ if (lengthNeeded) {
+ lengthNeeded++;
+ auto buf = mozilla::MakeUnique<char[]>(lengthNeeded);
+ if (buf) {
+ va_list argsCpy;
+ va_copy(argsCpy, aArgs);
+ vsnprintf(buf.get(), lengthNeeded, aFmt, argsCpy);
+ buf[lengthNeeded - 1] = '\0';
+ va_end(argsCpy);
+ OutputDebugStringA(buf.get());
+ }
+ }
+ }
+
+ vprintf_stderr_buffered(aFmt, aArgs);
+}
+
+#elif defined(ANDROID)
+MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs) {
+ __android_log_vprint(ANDROID_LOG_INFO, "Gecko", aFmt, aArgs);
+}
+#elif defined(FUZZING_SNAPSHOT)
+MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs) {
+ if (nyx_puts) {
+ auto msgbuf = mozilla::Vsmprintf(aFmt, aArgs);
+ nyx_puts(msgbuf.get());
+ } else {
+ vprintf_stderr_buffered(aFmt, aArgs);
+ }
+}
+#else
+MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs) {
+ vprintf_stderr_buffered(aFmt, aArgs);
+}
+#endif
+
+MFBT_API void printf_stderr(const char* aFmt, ...) {
+ va_list args;
+ va_start(args, aFmt);
+ vprintf_stderr(aFmt, args);
+ va_end(args);
+}
+
+MFBT_API void fprintf_stderr(FILE* aFile, const char* aFmt, ...) {
+ va_list args;
+ va_start(args, aFmt);
+ if (aFile == stderr) {
+ vprintf_stderr(aFmt, args);
+ } else {
+ vfprintf(aFile, aFmt, args);
+ }
+ va_end(args);
+}
+
+MFBT_API void print_stderr(std::stringstream& aStr) {
+#if defined(ANDROID)
+ // On Android logcat output is truncated to 1024 chars per line, and
+ // we usually use std::stringstream to build up giant multi-line gobs
+ // of output. So to avoid the truncation we find the newlines and
+ // print the lines individually.
+ std::string line;
+ while (std::getline(aStr, line)) {
+ printf_stderr("%s\n", line.c_str());
+ }
+#else
+ printf_stderr("%s", aStr.str().c_str());
+#endif
+}
+
+MFBT_API void fprint_stderr(FILE* aFile, std::stringstream& aStr) {
+ if (aFile == stderr) {
+ print_stderr(aStr);
+ } else {
+ fprintf_stderr(aFile, "%s", aStr.str().c_str());
+ }
+}
diff --git a/mozglue/misc/Debug.h b/mozglue/misc/Debug.h
new file mode 100644
index 0000000000..43380878b9
--- /dev/null
+++ b/mozglue/misc/Debug.h
@@ -0,0 +1,76 @@
+/* -*- 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_glue_Debug_h
+#define mozilla_glue_Debug_h
+
+#include "mozilla/Attributes.h" // For MOZ_FORMAT_PRINTF
+#include "mozilla/Types.h" // For MFBT_API
+
+#include <cstdarg>
+#include <sstream>
+
+/* This header file intends to supply debugging utilities for use in code
+ * that cannot use XPCOM debugging facilities like nsDebug.h.
+ * e.g. mozglue, browser/app
+ *
+ * NB: printf_stderr() is in the global namespace, so include this file with
+ * care; avoid including from header files.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/**
+ * printf_stderr(...) is much like fprintf(stderr, ...), except that:
+ * - on Android and Firefox OS, *instead* of printing to stderr, it
+ * prints to logcat. (Newlines in the string lead to multiple lines
+ * of logcat, but each function call implicitly completes a line even
+ * if the string does not end with a newline.)
+ * - on Windows, if a debugger is present, it calls OutputDebugString
+ * in *addition* to writing to stderr
+ */
+MFBT_API void printf_stderr(const char* aFmt, ...) MOZ_FORMAT_PRINTF(1, 2);
+
+/**
+ * Same as printf_stderr, but taking va_list instead of varargs
+ */
+MFBT_API void vprintf_stderr(const char* aFmt, va_list aArgs)
+ MOZ_FORMAT_PRINTF(1, 0);
+
+/**
+ * fprintf_stderr is like fprintf, except that if its file argument
+ * is stderr, it invokes printf_stderr instead.
+ *
+ * This is useful for general debugging code that logs information to a
+ * file, but that you would like to be useful on Android and Firefox OS.
+ * If you use fprintf_stderr instead of fprintf in such debugging code,
+ * then callers can pass stderr to get logging that works on Android and
+ * Firefox OS (and also the other side-effects of using printf_stderr).
+ *
+ * Code that is structured this way needs to be careful not to split a
+ * line of output across multiple calls to fprintf_stderr, since doing
+ * so will cause it to appear in multiple lines in logcat output.
+ * (Producing multiple lines at once is fine.)
+ */
+MFBT_API void fprintf_stderr(FILE* aFile, const char* aFmt, ...)
+ MOZ_FORMAT_PRINTF(2, 3);
+
+/*
+ * print_stderr and fprint_stderr are like printf_stderr and fprintf_stderr,
+ * except they deal with Android logcat line length limitations. They do this
+ * by printing individual lines out of the provided stringstream using separate
+ * calls to logcat.
+ */
+MFBT_API void print_stderr(std::stringstream& aStr);
+MFBT_API void fprint_stderr(FILE* aFile, std::stringstream& aStr);
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // mozilla_glue_Debug_h
diff --git a/mozglue/misc/DynamicallyLinkedFunctionPtr.h b/mozglue/misc/DynamicallyLinkedFunctionPtr.h
new file mode 100644
index 0000000000..4313974ec5
--- /dev/null
+++ b/mozglue/misc/DynamicallyLinkedFunctionPtr.h
@@ -0,0 +1,137 @@
+/* -*- 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 mozilla_DynamicallyLinkedFunctionPtr_h
+#define mozilla_DynamicallyLinkedFunctionPtr_h
+
+#include <windows.h>
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace detail {
+
+template <typename T>
+struct FunctionPtrCracker;
+
+template <typename R, typename... Args>
+struct FunctionPtrCracker<R (*)(Args...)> {
+ using ReturnT = R;
+ using FunctionPtrT = R (*)(Args...);
+};
+
+#if defined(_M_IX86)
+template <typename R, typename... Args>
+struct FunctionPtrCracker<R(__stdcall*)(Args...)> {
+ using ReturnT = R;
+ using FunctionPtrT = R(__stdcall*)(Args...);
+};
+
+template <typename R, typename... Args>
+struct FunctionPtrCracker<R(__fastcall*)(Args...)> {
+ using ReturnT = R;
+ using FunctionPtrT = R(__fastcall*)(Args...);
+};
+#endif // defined(_M_IX86)
+
+template <typename T>
+class DynamicallyLinkedFunctionPtrBase {
+ public:
+ using ReturnT = typename FunctionPtrCracker<T>::ReturnT;
+ using FunctionPtrT = typename FunctionPtrCracker<T>::FunctionPtrT;
+
+ DynamicallyLinkedFunctionPtrBase(const wchar_t* aLibName,
+ const char* aFuncName)
+ : mModule(::LoadLibraryW(aLibName)), mFunction(nullptr) {
+ if (!mModule) {
+ return;
+ }
+
+ mFunction =
+ reinterpret_cast<FunctionPtrT>(::GetProcAddress(mModule, aFuncName));
+
+ if (!mFunction) {
+ // Since the function doesn't exist, there is no point in holding a
+ // reference to mModule anymore.
+ ::FreeLibrary(mModule);
+ mModule = nullptr;
+ }
+ }
+
+ DynamicallyLinkedFunctionPtrBase(const DynamicallyLinkedFunctionPtrBase&) =
+ delete;
+ DynamicallyLinkedFunctionPtrBase& operator=(
+ const DynamicallyLinkedFunctionPtrBase&) = delete;
+
+ DynamicallyLinkedFunctionPtrBase(DynamicallyLinkedFunctionPtrBase&&) = delete;
+ DynamicallyLinkedFunctionPtrBase& operator=(
+ DynamicallyLinkedFunctionPtrBase&&) = delete;
+
+ template <typename... Args>
+ ReturnT operator()(Args&&... args) const {
+ return mFunction(std::forward<Args>(args)...);
+ }
+
+ explicit operator bool() const { return !!mFunction; }
+
+ protected:
+ HMODULE mModule;
+ FunctionPtrT mFunction;
+};
+
+} // namespace detail
+
+/**
+ * In most cases, this class is the one that you want to use for resolving a
+ * dynamically-linked function pointer. It should be instantiated as a static
+ * local variable.
+ *
+ * NB: It has a trivial destructor, so the DLL that is loaded is never freed.
+ * Assuming that this function is called fairly often, this is the most
+ * sensible option. OTOH, if the function you are calling is a one-off, or the
+ * static local requirement is too restrictive, use DynamicallyLinkedFunctionPtr
+ * instead.
+ */
+template <typename T>
+class MOZ_STATIC_LOCAL_CLASS StaticDynamicallyLinkedFunctionPtr final
+ : public detail::DynamicallyLinkedFunctionPtrBase<T> {
+ public:
+ StaticDynamicallyLinkedFunctionPtr(const wchar_t* aLibName,
+ const char* aFuncName)
+ : detail::DynamicallyLinkedFunctionPtrBase<T>(aLibName, aFuncName) {}
+
+ /**
+ * We only offer this operator for the static local case, as it is not
+ * possible for this object to be destroyed while the returned pointer is
+ * being held.
+ */
+ operator typename detail::DynamicallyLinkedFunctionPtrBase<T>::FunctionPtrT()
+ const {
+ return this->mFunction;
+ }
+};
+
+template <typename T>
+class MOZ_NON_PARAM MOZ_NON_TEMPORARY_CLASS DynamicallyLinkedFunctionPtr final
+ : public detail::DynamicallyLinkedFunctionPtrBase<T> {
+ public:
+ DynamicallyLinkedFunctionPtr(const wchar_t* aLibName, const char* aFuncName)
+ : detail::DynamicallyLinkedFunctionPtrBase<T>(aLibName, aFuncName) {}
+
+ ~DynamicallyLinkedFunctionPtr() {
+ if (!this->mModule) {
+ return;
+ }
+
+ ::FreeLibrary(this->mModule);
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_DynamicallyLinkedFunctionPtr_h
diff --git a/mozglue/misc/GetKnownFolderPath.cpp b/mozglue/misc/GetKnownFolderPath.cpp
new file mode 100644
index 0000000000..676a1c8056
--- /dev/null
+++ b/mozglue/misc/GetKnownFolderPath.cpp
@@ -0,0 +1,33 @@
+/* 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 "GetKnownFolderPath.h"
+
+namespace mozilla {
+
+UniquePtr<wchar_t, LoadedCoTaskMemFreeDeleter> GetKnownFolderPath(
+ REFKNOWNFOLDERID folderId) {
+ static decltype(SHGetKnownFolderPath)* shGetKnownFolderPath = nullptr;
+ if (!shGetKnownFolderPath) {
+ // We could go out of our way to `FreeLibrary` on this, decrementing its
+ // ref count and potentially unloading it. However doing so would be either
+ // effectively a no-op, or counterproductive. Just let it get cleaned up
+ // when the process is terminated, because we're going to load it anyway
+ // elsewhere.
+ HMODULE shell32Dll = ::LoadLibraryW(L"shell32");
+ if (!shell32Dll) {
+ return nullptr;
+ }
+ shGetKnownFolderPath = reinterpret_cast<decltype(shGetKnownFolderPath)>(
+ ::GetProcAddress(shell32Dll, "SHGetKnownFolderPath"));
+ if (!shGetKnownFolderPath) {
+ return nullptr;
+ }
+ }
+ PWSTR path = nullptr;
+ shGetKnownFolderPath(folderId, 0, nullptr, &path);
+ return UniquePtr<wchar_t, LoadedCoTaskMemFreeDeleter>(path);
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/GetKnownFolderPath.h b/mozglue/misc/GetKnownFolderPath.h
new file mode 100644
index 0000000000..3fd80440ab
--- /dev/null
+++ b/mozglue/misc/GetKnownFolderPath.h
@@ -0,0 +1,45 @@
+/* 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 mozilla_GetKnownFolderPath_h
+#define mozilla_GetKnownFolderPath_h
+
+#include <windows.h>
+#include <objbase.h>
+#include <shlobj.h>
+
+#include "mozilla/glue/Debug.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+struct LoadedCoTaskMemFreeDeleter {
+ void operator()(void* ptr) {
+ static decltype(CoTaskMemFree)* coTaskMemFree = nullptr;
+ if (!coTaskMemFree) {
+ // Just let this get cleaned up when the process is terminated, because
+ // we're going to load it anyway elsewhere.
+ HMODULE ole32Dll = ::LoadLibraryW(L"ole32");
+ if (!ole32Dll) {
+ printf_stderr(
+ "Could not load ole32 - will not free with CoTaskMemFree");
+ return;
+ }
+ coTaskMemFree = reinterpret_cast<decltype(coTaskMemFree)>(
+ ::GetProcAddress(ole32Dll, "CoTaskMemFree"));
+ if (!coTaskMemFree) {
+ printf_stderr("Could not find CoTaskMemFree");
+ return;
+ }
+ }
+ coTaskMemFree(ptr);
+ }
+};
+
+UniquePtr<wchar_t, LoadedCoTaskMemFreeDeleter> GetKnownFolderPath(
+ REFKNOWNFOLDERID folderId);
+
+} // namespace mozilla
+
+#endif
diff --git a/mozglue/misc/ImportDir.h b/mozglue/misc/ImportDir.h
new file mode 100644
index 0000000000..6f7721d966
--- /dev/null
+++ b/mozglue/misc/ImportDir.h
@@ -0,0 +1,94 @@
+/* -*- 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 "mozilla/NativeNt.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+namespace mozilla {
+namespace detail {
+
+inline LauncherResult<nt::DataDirectoryEntry> GetImageDirectoryViaFileIo(
+ const nsAutoHandle& aImageFile, const uint32_t aOurImportDirectoryRva) {
+ OVERLAPPED ov = {};
+ ov.Offset = aOurImportDirectoryRva;
+
+ DWORD bytesRead;
+ nt::DataDirectoryEntry result;
+ if (!::ReadFile(aImageFile, &result, sizeof(result), &bytesRead, &ov) ||
+ bytesRead != sizeof(result)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ return result;
+}
+
+} // namespace detail
+
+/**
+ * This function ensures that the import directory of a loaded binary image
+ * matches the version that is found in the original file on disk. We do this
+ * to prevent tampering by third-party code.
+ *
+ * Yes, this function may perform file I/O on the critical path during
+ * startup. A mitigating factor here is that this function must be called
+ * immediately after creating a process using the image specified by
+ * |aFullImagePath|; by this point, the system has already paid the price of
+ * pulling the image file's contents into the page cache.
+ *
+ * @param aFullImagePath Wide-character string containing the absolute path
+ * to the binary whose import directory we are touching.
+ * @param aTransferMgr Encapsulating the transfer from the current process to
+ * the child process whose import table we are touching.
+ */
+inline LauncherVoidResult RestoreImportDirectory(
+ const wchar_t* aFullImagePath, nt::CrossExecTransferManager& aTransferMgr) {
+ uint32_t importDirEntryRva;
+ PIMAGE_DATA_DIRECTORY importDirEntry =
+ aTransferMgr.LocalPEHeaders().GetImageDirectoryEntryPtr(
+ IMAGE_DIRECTORY_ENTRY_IMPORT, &importDirEntryRva);
+ if (!importDirEntry) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ nsAutoHandle file(::CreateFileW(aFullImagePath, GENERIC_READ, FILE_SHARE_READ,
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
+ nullptr));
+ if (file.get() == INVALID_HANDLE_VALUE) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ // Why do we use file I/O here instead of a memory mapping? The simple reason
+ // is that we do not want any kernel-mode drivers to start tampering with file
+ // contents under the belief that the file is being mapped for execution.
+ // Windows 8 supports creation of file mappings using the SEC_IMAGE_NO_EXECUTE
+ // flag, which may help to mitigate this, but we might as well just support
+ // a single implementation that works everywhere.
+ LauncherResult<nt::DataDirectoryEntry> realImportDirectory =
+ detail::GetImageDirectoryViaFileIo(file, importDirEntryRva);
+ if (realImportDirectory.isErr()) {
+ return realImportDirectory.propagateErr();
+ }
+
+ nt::DataDirectoryEntry toWrite = realImportDirectory.unwrap();
+
+ { // Scope for prot
+ AutoVirtualProtect prot = aTransferMgr.Protect(
+ importDirEntry, sizeof(IMAGE_DATA_DIRECTORY), PAGE_READWRITE);
+ if (!prot) {
+ return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError());
+ }
+
+ LauncherVoidResult writeResult = aTransferMgr.Transfer(
+ importDirEntry, &toWrite, sizeof(IMAGE_DATA_DIRECTORY));
+ if (writeResult.isErr()) {
+ return writeResult.propagateErr();
+ }
+ }
+
+ return Ok();
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/IntegerPrintfMacros.h b/mozglue/misc/IntegerPrintfMacros.h
new file mode 100644
index 0000000000..acf33eb85c
--- /dev/null
+++ b/mozglue/misc/IntegerPrintfMacros.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/. */
+
+/* Implements the C99 <inttypes.h> interface. */
+
+#ifndef mozilla_IntegerPrintfMacros_h_
+#define mozilla_IntegerPrintfMacros_h_
+
+/*
+ * These macros should not be used with the NSPR printf-like functions or their
+ * users. If you need to use NSPR's facilities, see the comment on
+ * supported formats at the top of nsprpub/pr/include/prprf.h.
+ */
+
+/*
+ * scanf is a footgun: if the input number exceeds the bounds of the target
+ * type, behavior is undefined (in the compiler sense: that is, this code
+ * could overwrite your hard drive with zeroes):
+ *
+ * uint8_t u;
+ * sscanf("256", "%" SCNu8, &u); // BAD
+ *
+ * For this reason, *never* use the SCN* macros provided by this header!
+ */
+
+#include <inttypes.h>
+
+/*
+ * Fix up Android's broken [u]intptr_t inttype macros. Android's PRI*PTR
+ * macros are defined as "ld", but sizeof(long) is 8 and sizeof(intptr_t)
+ * is 4 on 32-bit Android. TestTypeTraits.cpp asserts that these new macro
+ * definitions match the actual type sizes seen at compile time.
+ */
+#if defined(ANDROID) && !defined(__LP64__)
+# undef PRIdPTR /* intptr_t */
+# define PRIdPTR "d" /* intptr_t */
+# undef PRIiPTR /* intptr_t */
+# define PRIiPTR "i" /* intptr_t */
+# undef PRIoPTR /* uintptr_t */
+# define PRIoPTR "o" /* uintptr_t */
+# undef PRIuPTR /* uintptr_t */
+# define PRIuPTR "u" /* uintptr_t */
+# undef PRIxPTR /* uintptr_t */
+# define PRIxPTR "x" /* uintptr_t */
+# undef PRIXPTR /* uintptr_t */
+# define PRIXPTR "X" /* uintptr_t */
+#endif
+
+/*
+ * Fix up Android's broken macros for [u]int_fastN_t. On ARM64, Android's
+ * PRI*FAST16/32 macros are defined as "d", but the types themselves are defined
+ * as long and unsigned long.
+ */
+#if defined(ANDROID) && defined(__LP64__)
+# undef PRIdFAST16 /* int_fast16_t */
+# define PRIdFAST16 PRId64 /* int_fast16_t */
+# undef PRIiFAST16 /* int_fast16_t */
+# define PRIiFAST16 PRIi64 /* int_fast16_t */
+# undef PRIoFAST16 /* uint_fast16_t */
+# define PRIoFAST16 PRIo64 /* uint_fast16_t */
+# undef PRIuFAST16 /* uint_fast16_t */
+# define PRIuFAST16 PRIu64 /* uint_fast16_t */
+# undef PRIxFAST16 /* uint_fast16_t */
+# define PRIxFAST16 PRIx64 /* uint_fast16_t */
+# undef PRIXFAST16 /* uint_fast16_t */
+# define PRIXFAST16 PRIX64 /* uint_fast16_t */
+# undef PRIdFAST32 /* int_fast32_t */
+# define PRIdFAST32 PRId64 /* int_fast32_t */
+# undef PRIiFAST32 /* int_fast32_t */
+# define PRIiFAST32 PRIi64 /* int_fast32_t */
+# undef PRIoFAST32 /* uint_fast32_t */
+# define PRIoFAST32 PRIo64 /* uint_fast32_t */
+# undef PRIuFAST32 /* uint_fast32_t */
+# define PRIuFAST32 PRIu64 /* uint_fast32_t */
+# undef PRIxFAST32 /* uint_fast32_t */
+# define PRIxFAST32 PRIx64 /* uint_fast32_t */
+# undef PRIXFAST32 /* uint_fast32_t */
+# define PRIXFAST32 PRIX64 /* uint_fast32_t */
+#endif
+
+#endif /* mozilla_IntegerPrintfMacros_h_ */
diff --git a/mozglue/misc/MmapFaultHandler.cpp b/mozglue/misc/MmapFaultHandler.cpp
new file mode 100644
index 0000000000..a73b9bd24b
--- /dev/null
+++ b/mozglue/misc/MmapFaultHandler.cpp
@@ -0,0 +1,130 @@
+/* -*- 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 "MmapFaultHandler.h"
+
+#if defined(XP_UNIX) && !defined(XP_DARWIN) && !defined(__wasi__)
+
+# include "mozilla/Assertions.h"
+# include "mozilla/Atomics.h"
+# include "mozilla/ThreadLocal.h"
+# include <signal.h>
+# include <cstring>
+
+static MOZ_THREAD_LOCAL(MmapAccessScope*) sMmapAccessScope;
+
+static struct sigaction sPrevSIGBUSHandler;
+
+static void MmapSIGBUSHandler(int signum, siginfo_t* info, void* context) {
+ MOZ_RELEASE_ASSERT(signum == SIGBUS);
+
+ MmapAccessScope* mas = sMmapAccessScope.get();
+
+ if (mas && mas->IsInsideBuffer(info->si_addr)) {
+ // Temporarily instead of handling the signal, we crash intentionally and
+ // send some diagnostic information to find out why the signal is received.
+ mas->CrashWithInfo(info->si_addr);
+
+ // The address is inside the buffer, handle the failure.
+ siglongjmp(mas->mJmpBuf, signum);
+ }
+
+ // This signal is not caused by accessing region protected by MmapAccessScope.
+ // Forward the signal to the next handler.
+ if (sPrevSIGBUSHandler.sa_flags & SA_SIGINFO) {
+ sPrevSIGBUSHandler.sa_sigaction(signum, info, context);
+ } else if (sPrevSIGBUSHandler.sa_handler == SIG_DFL ||
+ sPrevSIGBUSHandler.sa_handler == SIG_IGN) {
+ // There is no next handler. Uninstalling our handler and returning will
+ // cause a crash.
+ sigaction(signum, &sPrevSIGBUSHandler, nullptr);
+ } else {
+ sPrevSIGBUSHandler.sa_handler(signum);
+ }
+}
+
+mozilla::Atomic<bool> gSIGBUSHandlerInstalled(false);
+mozilla::Atomic<bool> gSIGBUSHandlerInstalling(false);
+
+void InstallMmapFaultHandler() {
+ // This function is called from MmapAccessScope's constructor because there is
+ // no single point where we could install the handler during startup. This
+ // means that it's called quite often, so to minimize using of the mutex we
+ // first check the atomic variable outside the lock.
+ if (gSIGBUSHandlerInstalled) {
+ return;
+ }
+
+ if (gSIGBUSHandlerInstalling.compareExchange(false, true)) {
+ sMmapAccessScope.infallibleInit();
+
+ struct sigaction busHandler;
+ busHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
+ busHandler.sa_sigaction = MmapSIGBUSHandler;
+ sigemptyset(&busHandler.sa_mask);
+ if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler)) {
+ MOZ_CRASH("Unable to install SIGBUS handler");
+ }
+
+ MOZ_ASSERT(!gSIGBUSHandlerInstalled);
+ gSIGBUSHandlerInstalled = true;
+ } else {
+ // Just spin lock here. It should not take a substantial amount
+ // of time, so a mutex would likely be a spin lock anyway, and
+ // this avoids the need to new up a static mutex from within
+ // mozglue/misc, which complicates things with
+ // check_vanilla_allocations.py
+ while (!gSIGBUSHandlerInstalled) {
+ }
+ }
+}
+
+MmapAccessScope::MmapAccessScope(void* aBuf, uint32_t aBufLen,
+ const char* aFilename) {
+ // Install signal handler if it wasn't installed yet.
+ InstallMmapFaultHandler();
+
+ // We'll handle the signal only if the crashing address is inside this buffer.
+ mBuf = aBuf;
+ mBufLen = aBufLen;
+ mFilename = aFilename;
+
+ SetThreadLocalScope();
+}
+
+MmapAccessScope::~MmapAccessScope() {
+ MOZ_RELEASE_ASSERT(sMmapAccessScope.get() == this);
+ sMmapAccessScope.set(mPreviousScope);
+}
+
+void MmapAccessScope::SetThreadLocalScope() {
+ // mJmpBuf is set outside of this classs for reasons mentioned in the header
+ // file, but we need to initialize the member here too to make Coverity happy.
+ memset(mJmpBuf, 0, sizeof(sigjmp_buf));
+
+ // If MmapAccessScopes are nested, save the previous one and restore it in
+ // the destructor.
+ mPreviousScope = sMmapAccessScope.get();
+
+ // MmapAccessScope is now set up (except mJmpBuf for reasons mentioned in the
+ // header file). Store the pointer in a thread-local variable sMmapAccessScope
+ // so we can use it in the handler if the signal is triggered.
+ sMmapAccessScope.set(this);
+}
+
+bool MmapAccessScope::IsInsideBuffer(void* aPtr) {
+ return aPtr >= mBuf && aPtr < (void*)((char*)mBuf + mBufLen);
+}
+
+void MmapAccessScope::CrashWithInfo(void* aPtr) {
+ // All we have is the buffer and the crashing address.
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "SIGBUS received when accessing mmaped file [buffer=%p, "
+ "buflen=%u, address=%p, filename=%s]",
+ mBuf, mBufLen, aPtr, mFilename);
+}
+
+#endif
diff --git a/mozglue/misc/MmapFaultHandler.h b/mozglue/misc/MmapFaultHandler.h
new file mode 100644
index 0000000000..fca356921c
--- /dev/null
+++ b/mozglue/misc/MmapFaultHandler.h
@@ -0,0 +1,113 @@
+/* -*- 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 MmapFaultHandler_h_
+#define MmapFaultHandler_h_
+
+#if defined(XP_WIN)
+// Windows
+
+# ifdef HAVE_SEH_EXCEPTIONS
+# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) __try {
+# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) __try {
+# define MMAP_FAULT_HANDLER_CATCH(retval) \
+ } \
+ __except (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR \
+ ? EXCEPTION_EXECUTE_HANDLER \
+ : EXCEPTION_CONTINUE_SEARCH) { \
+ NS_WARNING("unexpected EXCEPTION_IN_PAGE_ERROR"); \
+ return retval; \
+ }
+# else
+# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) {
+# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) {
+# define MMAP_FAULT_HANDLER_CATCH(retval) }
+# endif
+
+#elif defined(XP_DARWIN)
+// MacOS
+
+# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) {
+# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) {
+# define MMAP_FAULT_HANDLER_CATCH(retval) }
+
+#elif defined(__wasi__)
+
+# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) {
+# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) {
+# define MMAP_FAULT_HANDLER_CATCH(retval) }
+
+#else
+// Linux
+
+# include "mozilla/Attributes.h"
+# include "mozilla/Types.h"
+# include <stdint.h>
+# include <setjmp.h>
+
+class MOZ_RAII MmapAccessScope {
+ public:
+ MFBT_API MmapAccessScope(void* aBuf, uint32_t aBufLen,
+ const char* aFilename = nullptr);
+ MFBT_API ~MmapAccessScope();
+
+ MmapAccessScope(const MmapAccessScope&) = delete;
+ MmapAccessScope& operator=(const MmapAccessScope&) = delete;
+
+ void SetThreadLocalScope();
+ bool IsInsideBuffer(void* aPtr);
+ void CrashWithInfo(void* aPtr);
+
+ // sigsetjmp cannot be called from a method that returns before calling
+ // siglongjmp, so the macro must call sigsetjmp directly and mJmpBuf must be
+ // public.
+ sigjmp_buf mJmpBuf;
+
+ private:
+ void* mBuf;
+ const char* mFilename;
+ uint32_t mBufLen;
+ MmapAccessScope* mPreviousScope;
+};
+
+// Gets around warnings for null-checking in a macro.
+template <typename T>
+inline bool ValidFD(T fd) {
+ return !!fd;
+}
+
+# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) \
+ { \
+ void* mmapScopeBuf = nullptr; \
+ nsCString mmapScopeFilename; \
+ uint32_t mmapScopeBufLen = 0; \
+ if (ValidFD(fd) && fd->mMap) { \
+ mmapScopeBuf = (void*)fd->mFileStart; \
+ mmapScopeBufLen = fd->mTotalLen; \
+ } \
+ if (ValidFD(fd) && fd->mFile) { \
+ nsCOMPtr<nsIFile> file = fd->mFile.GetBaseFile(); \
+ if (file) { \
+ file->GetNativeLeafName(mmapScopeFilename); \
+ } \
+ } \
+ MmapAccessScope mmapScope(mmapScopeBuf, mmapScopeBufLen, \
+ mmapScopeFilename.get()); \
+ if (sigsetjmp(mmapScope.mJmpBuf, 0) == 0) {
+# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) \
+ { \
+ MmapAccessScope mmapScope((void*)(buf), (bufLen)); \
+ if (sigsetjmp(mmapScope.mJmpBuf, 0) == 0) {
+# define MMAP_FAULT_HANDLER_CATCH(retval) \
+ } \
+ else { \
+ NS_WARNING("SIGBUS received when accessing mmapped file"); \
+ return retval; \
+ } \
+ }
+
+#endif
+
+#endif
diff --git a/mozglue/misc/MutexPlatformData_noop.h b/mozglue/misc/MutexPlatformData_noop.h
new file mode 100644
index 0000000000..a2c8d060be
--- /dev/null
+++ b/mozglue/misc/MutexPlatformData_noop.h
@@ -0,0 +1,18 @@
+/* -*- 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 MutexPlatformData_noop_h
+#define MutexPlatformData_noop_h
+
+#if !defined(__wasi__)
+# error This code is for WASI only.
+#endif
+
+#include "mozilla/PlatformMutex.h"
+
+struct mozilla::detail::MutexImpl::PlatformData {};
+
+#endif // MutexPlatformData_noop_h
diff --git a/mozglue/misc/MutexPlatformData_posix.h b/mozglue/misc/MutexPlatformData_posix.h
new file mode 100644
index 0000000000..d1659d8d7b
--- /dev/null
+++ b/mozglue/misc/MutexPlatformData_posix.h
@@ -0,0 +1,18 @@
+/* -*- 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 MutexPlatformData_posix_h
+#define MutexPlatformData_posix_h
+
+#include <pthread.h>
+
+#include "mozilla/PlatformMutex.h"
+
+struct mozilla::detail::MutexImpl::PlatformData {
+ pthread_mutex_t ptMutex;
+};
+
+#endif // MutexPlatformData_posix_h
diff --git a/mozglue/misc/MutexPlatformData_windows.h b/mozglue/misc/MutexPlatformData_windows.h
new file mode 100644
index 0000000000..489f921115
--- /dev/null
+++ b/mozglue/misc/MutexPlatformData_windows.h
@@ -0,0 +1,18 @@
+/* -*- 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 MutexPlatformData_windows_h
+#define MutexPlatformData_windows_h
+
+#include <windows.h>
+
+#include "mozilla/PlatformMutex.h"
+
+struct mozilla::detail::MutexImpl::PlatformData {
+ SRWLOCK lock;
+};
+
+#endif // MutexPlatformData_windows_h
diff --git a/mozglue/misc/Mutex_noop.cpp b/mozglue/misc/Mutex_noop.cpp
new file mode 100644
index 0000000000..eaa7959cc0
--- /dev/null
+++ b/mozglue/misc/Mutex_noop.cpp
@@ -0,0 +1,34 @@
+/* -*- 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 "mozilla/Assertions.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+#include "mozilla/PlatformMutex.h"
+#include "MutexPlatformData_noop.h"
+
+mozilla::detail::MutexImpl::MutexImpl() {}
+
+mozilla::detail::MutexImpl::~MutexImpl() {}
+
+inline void mozilla::detail::MutexImpl::mutexLock() {}
+
+bool mozilla::detail::MutexImpl::tryLock() { return mutexTryLock(); }
+
+bool mozilla::detail::MutexImpl::mutexTryLock() { return true; }
+
+void mozilla::detail::MutexImpl::lock() { mutexLock(); }
+
+void mozilla::detail::MutexImpl::unlock() {}
+
+mozilla::detail::MutexImpl::PlatformData*
+mozilla::detail::MutexImpl::platformData() {
+ static_assert(sizeof(platformData_) >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/Mutex_posix.cpp b/mozglue/misc/Mutex_posix.cpp
new file mode 100644
index 0000000000..e4cbabd5e5
--- /dev/null
+++ b/mozglue/misc/Mutex_posix.cpp
@@ -0,0 +1,131 @@
+/* -*- 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 "mozilla/Assertions.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+
+#if defined(XP_DARWIN)
+# include <pthread_spis.h>
+#endif
+
+#include "mozilla/PlatformMutex.h"
+#include "MutexPlatformData_posix.h"
+
+#define REPORT_PTHREADS_ERROR(result, msg) \
+ { \
+ errno = result; \
+ perror(msg); \
+ MOZ_CRASH(msg); \
+ }
+
+#define TRY_CALL_PTHREADS(call, msg) \
+ { \
+ int result = (call); \
+ if (result != 0) { \
+ REPORT_PTHREADS_ERROR(result, msg); \
+ } \
+ }
+
+mozilla::detail::MutexImpl::MutexImpl() {
+ pthread_mutexattr_t* attrp = nullptr;
+
+#if defined(DEBUG)
+# define MUTEX_KIND PTHREAD_MUTEX_ERRORCHECK
+// Linux with glibc, FreeBSD and macOS 10.14+ support adaptive mutexes that
+// spin for a short number of tries before sleeping. NSPR's locks did this,
+// too, and it seems like a reasonable thing to do.
+#elif (defined(__linux__) && defined(__GLIBC__)) || defined(__FreeBSD__)
+# define MUTEX_KIND PTHREAD_MUTEX_ADAPTIVE_NP
+#elif defined(XP_DARWIN)
+# if defined(PTHREAD_MUTEX_POLICY_FIRSTFIT_NP)
+# define POLICY_KIND PTHREAD_MUTEX_POLICY_FIRSTFIT_NP
+# else
+# define POLICY_KIND (3) // The definition is missing in old SDKs
+# endif
+#endif
+
+#if defined(MUTEX_KIND) || defined(POLICY_KIND)
+# define ATTR_REQUIRED
+#endif
+
+#if defined(ATTR_REQUIRED)
+ pthread_mutexattr_t attr;
+
+ TRY_CALL_PTHREADS(
+ pthread_mutexattr_init(&attr),
+ "mozilla::detail::MutexImpl::MutexImpl: pthread_mutexattr_init failed");
+
+# if defined(MUTEX_KIND)
+ TRY_CALL_PTHREADS(pthread_mutexattr_settype(&attr, MUTEX_KIND),
+ "mozilla::detail::MutexImpl::MutexImpl: "
+ "pthread_mutexattr_settype failed");
+# elif defined(POLICY_KIND)
+ TRY_CALL_PTHREADS(pthread_mutexattr_setpolicy_np(&attr, POLICY_KIND),
+ "mozilla::detail::MutexImpl::MutexImpl: "
+ "pthread_mutexattr_setpolicy_np failed");
+# endif
+ attrp = &attr;
+#endif
+
+ TRY_CALL_PTHREADS(
+ pthread_mutex_init(&platformData()->ptMutex, attrp),
+ "mozilla::detail::MutexImpl::MutexImpl: pthread_mutex_init failed");
+
+#if defined(ATTR_REQUIRED)
+ TRY_CALL_PTHREADS(pthread_mutexattr_destroy(&attr),
+ "mozilla::detail::MutexImpl::MutexImpl: "
+ "pthread_mutexattr_destroy failed");
+#endif
+}
+
+mozilla::detail::MutexImpl::~MutexImpl() {
+ TRY_CALL_PTHREADS(
+ pthread_mutex_destroy(&platformData()->ptMutex),
+ "mozilla::detail::MutexImpl::~MutexImpl: pthread_mutex_destroy failed");
+}
+
+inline void mozilla::detail::MutexImpl::mutexLock() {
+ TRY_CALL_PTHREADS(
+ pthread_mutex_lock(&platformData()->ptMutex),
+ "mozilla::detail::MutexImpl::mutexLock: pthread_mutex_lock failed");
+}
+
+bool mozilla::detail::MutexImpl::tryLock() { return mutexTryLock(); }
+
+bool mozilla::detail::MutexImpl::mutexTryLock() {
+ int result = pthread_mutex_trylock(&platformData()->ptMutex);
+ if (result == 0) {
+ return true;
+ }
+
+ if (result == EBUSY) {
+ return false;
+ }
+
+ REPORT_PTHREADS_ERROR(
+ result,
+ "mozilla::detail::MutexImpl::mutexTryLock: pthread_mutex_trylock failed");
+}
+
+void mozilla::detail::MutexImpl::lock() { mutexLock(); }
+
+void mozilla::detail::MutexImpl::unlock() {
+ TRY_CALL_PTHREADS(
+ pthread_mutex_unlock(&platformData()->ptMutex),
+ "mozilla::detail::MutexImpl::unlock: pthread_mutex_unlock failed");
+}
+
+#undef TRY_CALL_PTHREADS
+
+mozilla::detail::MutexImpl::PlatformData*
+mozilla::detail::MutexImpl::platformData() {
+ static_assert(sizeof(platformData_) >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/Mutex_windows.cpp b/mozglue/misc/Mutex_windows.cpp
new file mode 100644
index 0000000000..c4c78fd4f6
--- /dev/null
+++ b/mozglue/misc/Mutex_windows.cpp
@@ -0,0 +1,40 @@
+/* -*- 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 "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/PlatformMutex.h"
+
+#include <windows.h>
+
+#include "MutexPlatformData_windows.h"
+
+mozilla::detail::MutexImpl::MutexImpl() {
+ InitializeSRWLock(&platformData()->lock);
+}
+
+mozilla::detail::MutexImpl::~MutexImpl() {}
+
+void mozilla::detail::MutexImpl::lock() {
+ AcquireSRWLockExclusive(&platformData()->lock);
+}
+
+bool mozilla::detail::MutexImpl::tryLock() { return mutexTryLock(); }
+
+bool mozilla::detail::MutexImpl::mutexTryLock() {
+ return !!TryAcquireSRWLockExclusive(&platformData()->lock);
+}
+
+void mozilla::detail::MutexImpl::unlock() {
+ ReleaseSRWLockExclusive(&platformData()->lock);
+}
+
+mozilla::detail::MutexImpl::PlatformData*
+mozilla::detail::MutexImpl::platformData() {
+ static_assert(sizeof(platformData_) >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/NativeNt.h b/mozglue/misc/NativeNt.h
new file mode 100644
index 0000000000..932dcd0a7b
--- /dev/null
+++ b/mozglue/misc/NativeNt.h
@@ -0,0 +1,1758 @@
+/* -*- 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_NativeNt_h
+#define mozilla_NativeNt_h
+
+#include <stdint.h>
+#include <windows.h>
+#include <winnt.h>
+#include <winternl.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Range.h"
+#include "mozilla/Span.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "mozilla/interceptor/MMPolicies.h"
+#include "mozilla/interceptor/TargetFunction.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "nsString.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+// The declarations within this #if block are intended to be used for initial
+// process initialization ONLY. You probably don't want to be using these in
+// normal Gecko code!
+#if !defined(MOZILLA_INTERNAL_API)
+
+extern "C" {
+
+# if !defined(STATUS_ACCESS_DENIED)
+# define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
+# endif // !defined(STATUS_ACCESS_DENIED)
+
+# if !defined(STATUS_DLL_NOT_FOUND)
+# define STATUS_DLL_NOT_FOUND ((NTSTATUS)0xC0000135L)
+# endif // !defined(STATUS_DLL_NOT_FOUND)
+
+# if !defined(STATUS_UNSUCCESSFUL)
+# define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
+# endif // !defined(STATUS_UNSUCCESSFUL)
+
+# if !defined(STATUS_INFO_LENGTH_MISMATCH)
+# define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
+# endif
+
+enum SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 };
+
+NTSTATUS NTAPI NtMapViewOfSection(
+ HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits,
+ SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize,
+ SECTION_INHERIT aInheritDisposition, ULONG aAllocationType,
+ ULONG aProtectionFlags);
+
+NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE aProcess, PVOID aBaseAddress);
+
+enum MEMORY_INFORMATION_CLASS {
+ MemoryBasicInformation = 0,
+ MemorySectionName = 2
+};
+
+// NB: When allocating, space for the buffer must also be included
+typedef struct _MEMORY_SECTION_NAME {
+ UNICODE_STRING mSectionFileName;
+} MEMORY_SECTION_NAME, *PMEMORY_SECTION_NAME;
+
+NTSTATUS NTAPI NtQueryVirtualMemory(HANDLE aProcess, PVOID aBaseAddress,
+ MEMORY_INFORMATION_CLASS aMemInfoClass,
+ PVOID aMemInfo, SIZE_T aMemInfoLen,
+ PSIZE_T aReturnLen);
+
+LONG NTAPI RtlCompareUnicodeString(PCUNICODE_STRING aStr1,
+ PCUNICODE_STRING aStr2,
+ BOOLEAN aCaseInsensitive);
+
+BOOLEAN NTAPI RtlEqualUnicodeString(PCUNICODE_STRING aStr1,
+ PCUNICODE_STRING aStr2,
+ BOOLEAN aCaseInsensitive);
+
+NTSTATUS NTAPI RtlGetVersion(PRTL_OSVERSIONINFOW aOutVersionInformation);
+
+VOID NTAPI RtlAcquireSRWLockExclusive(PSRWLOCK aLock);
+VOID NTAPI RtlAcquireSRWLockShared(PSRWLOCK aLock);
+
+VOID NTAPI RtlReleaseSRWLockExclusive(PSRWLOCK aLock);
+VOID NTAPI RtlReleaseSRWLockShared(PSRWLOCK aLock);
+
+NTSTATUS NTAPI RtlSleepConditionVariableSRW(
+ PCONDITION_VARIABLE aConditionVariable, PSRWLOCK aSRWLock,
+ PLARGE_INTEGER aTimeOut, ULONG aFlags);
+VOID NTAPI RtlWakeAllConditionVariable(PCONDITION_VARIABLE aConditionVariable);
+
+ULONG NTAPI RtlNtStatusToDosError(NTSTATUS aStatus);
+VOID NTAPI RtlSetLastWin32Error(DWORD aError);
+DWORD NTAPI RtlGetLastWin32Error();
+
+VOID NTAPI RtlRunOnceInitialize(PRTL_RUN_ONCE aRunOnce);
+
+NTSTATUS NTAPI NtReadVirtualMemory(HANDLE aProcessHandle, PVOID aBaseAddress,
+ PVOID aBuffer, SIZE_T aNumBytesToRead,
+ PSIZE_T aNumBytesRead);
+
+NTSTATUS NTAPI LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
+ PUNICODE_STRING aDllName, PHANDLE aOutHandle);
+
+typedef ULONG(NTAPI* PRTL_RUN_ONCE_INIT_FN)(PRTL_RUN_ONCE, PVOID, PVOID*);
+NTSTATUS NTAPI RtlRunOnceExecuteOnce(PRTL_RUN_ONCE aRunOnce,
+ PRTL_RUN_ONCE_INIT_FN aInitFn,
+ PVOID aContext, PVOID* aParameter);
+
+} // extern "C"
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+extern "C" {
+PVOID NTAPI RtlAllocateHeap(PVOID aHeapHandle, ULONG aFlags, SIZE_T aSize);
+
+PVOID NTAPI RtlReAllocateHeap(PVOID aHeapHandle, ULONG aFlags, LPVOID aMem,
+ SIZE_T aNewSize);
+
+BOOLEAN NTAPI RtlFreeHeap(PVOID aHeapHandle, ULONG aFlags, PVOID aHeapBase);
+
+BOOLEAN NTAPI RtlQueryPerformanceCounter(LARGE_INTEGER* aPerfCount);
+
+#define RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE 1
+#define RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING 2
+NTSTATUS NTAPI RtlDuplicateUnicodeString(ULONG aFlags, PCUNICODE_STRING aSrc,
+ PUNICODE_STRING aDest);
+
+VOID NTAPI RtlFreeUnicodeString(PUNICODE_STRING aUnicodeString);
+} // extern "C"
+
+namespace mozilla {
+namespace nt {
+
+/**
+ * This class encapsulates a UNICODE_STRING that owns its own buffer. The
+ * buffer is always NULL terminated, thus allowing us to cast to a wide C-string
+ * without requiring any mutation.
+ *
+ * We only allow creation of this owned buffer from outside XUL.
+ */
+class AllocatedUnicodeString final {
+ public:
+ AllocatedUnicodeString() : mUnicodeString() {}
+
+#if defined(MOZILLA_INTERNAL_API)
+ AllocatedUnicodeString(const AllocatedUnicodeString& aOther) = delete;
+
+ AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) =
+ delete;
+#else
+ explicit AllocatedUnicodeString(PCUNICODE_STRING aSrc) {
+ if (!aSrc) {
+ mUnicodeString = {};
+ return;
+ }
+
+ Duplicate(aSrc);
+ }
+
+ explicit AllocatedUnicodeString(const char* aSrc) {
+ if (!aSrc) {
+ mUnicodeString = {};
+ return;
+ }
+
+ Duplicate(aSrc);
+ }
+
+ AllocatedUnicodeString(const AllocatedUnicodeString& aOther) {
+ Duplicate(&aOther.mUnicodeString);
+ }
+
+ AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) {
+ Clear();
+ Duplicate(&aOther.mUnicodeString);
+ return *this;
+ }
+
+ AllocatedUnicodeString& operator=(PCUNICODE_STRING aSrc) {
+ Clear();
+ Duplicate(aSrc);
+ return *this;
+ }
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ AllocatedUnicodeString(AllocatedUnicodeString&& aOther)
+ : mUnicodeString(aOther.mUnicodeString) {
+ aOther.mUnicodeString = {};
+ }
+
+ AllocatedUnicodeString& operator=(AllocatedUnicodeString&& aOther) {
+ Clear();
+ mUnicodeString = aOther.mUnicodeString;
+ aOther.mUnicodeString = {};
+ return *this;
+ }
+
+ ~AllocatedUnicodeString() { Clear(); }
+
+ bool IsEmpty() const {
+ return !mUnicodeString.Buffer || !mUnicodeString.Length;
+ }
+
+ operator PCUNICODE_STRING() const { return &mUnicodeString; }
+
+ operator const WCHAR*() const { return mUnicodeString.Buffer; }
+
+ USHORT CharLen() const { return mUnicodeString.Length / sizeof(WCHAR); }
+
+#if defined(MOZILLA_INTERNAL_API)
+ nsDependentString AsString() const {
+ if (!mUnicodeString.Buffer) {
+ return nsDependentString();
+ }
+
+ // We can use nsDependentString here as we guaranteed null termination
+ // when we allocated the string.
+ return nsDependentString(mUnicodeString.Buffer, CharLen());
+ }
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ private:
+#if !defined(MOZILLA_INTERNAL_API)
+ void Duplicate(PCUNICODE_STRING aSrc) {
+ MOZ_ASSERT(aSrc);
+
+ // We duplicate with null termination so that this string may be used
+ // as a wide C-string without any further manipulation.
+ NTSTATUS ntStatus = ::RtlDuplicateUnicodeString(
+ RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE, aSrc, &mUnicodeString);
+ MOZ_ASSERT(NT_SUCCESS(ntStatus));
+ if (!NT_SUCCESS(ntStatus)) {
+ // Make sure that mUnicodeString does not contain bogus data
+ // (since not all callers zero it out before invoking)
+ mUnicodeString = {};
+ }
+ }
+
+ void Duplicate(const char* aSrc) {
+ MOZ_ASSERT(aSrc);
+
+ ANSI_STRING ansiStr;
+ RtlInitAnsiString(&ansiStr, aSrc);
+ NTSTATUS ntStatus =
+ ::RtlAnsiStringToUnicodeString(&mUnicodeString, &ansiStr, TRUE);
+ MOZ_ASSERT(NT_SUCCESS(ntStatus));
+ if (!NT_SUCCESS(ntStatus)) {
+ mUnicodeString = {};
+ }
+ }
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+ void Clear() {
+ if (!mUnicodeString.Buffer) {
+ return;
+ }
+
+ ::RtlFreeUnicodeString(&mUnicodeString);
+ mUnicodeString = {};
+ }
+
+ UNICODE_STRING mUnicodeString;
+};
+
+#if !defined(MOZILLA_INTERNAL_API)
+
+struct MemorySectionNameBuf : public _MEMORY_SECTION_NAME {
+ MemorySectionNameBuf() {
+ mSectionFileName.Length = 0;
+ mSectionFileName.MaximumLength = sizeof(mBuf);
+ mSectionFileName.Buffer = mBuf;
+ }
+
+ MemorySectionNameBuf(const MemorySectionNameBuf& aOther) { *this = aOther; }
+
+ MemorySectionNameBuf(MemorySectionNameBuf&& aOther) {
+ *this = std::move(aOther);
+ }
+
+ // We cannot use default copy here because mSectionFileName.Buffer needs to
+ // be updated to point to |this->mBuf|, not |aOther.mBuf|.
+ MemorySectionNameBuf& operator=(const MemorySectionNameBuf& aOther) {
+ mSectionFileName.Length = aOther.mSectionFileName.Length;
+ mSectionFileName.MaximumLength = sizeof(mBuf);
+ MOZ_ASSERT(mSectionFileName.Length <= mSectionFileName.MaximumLength);
+ mSectionFileName.Buffer = mBuf;
+ memcpy(mBuf, aOther.mBuf, aOther.mSectionFileName.Length);
+ return *this;
+ }
+
+ MemorySectionNameBuf& operator=(MemorySectionNameBuf&& aOther) {
+ mSectionFileName.Length = aOther.mSectionFileName.Length;
+ aOther.mSectionFileName.Length = 0;
+ mSectionFileName.MaximumLength = sizeof(mBuf);
+ MOZ_ASSERT(mSectionFileName.Length <= mSectionFileName.MaximumLength);
+ aOther.mSectionFileName.MaximumLength = sizeof(aOther.mBuf);
+ mSectionFileName.Buffer = mBuf;
+ memmove(mBuf, aOther.mBuf, mSectionFileName.Length);
+ return *this;
+ }
+
+ // Native NT paths, so we can't assume MAX_PATH. Use a larger buffer.
+ WCHAR mBuf[2 * MAX_PATH];
+
+ bool IsEmpty() const {
+ return !mSectionFileName.Buffer || !mSectionFileName.Length;
+ }
+
+ operator PCUNICODE_STRING() const { return &mSectionFileName; }
+};
+
+class MemorySectionNameOnHeap {
+ UniquePtr<uint8_t[]> mBuffer;
+
+ MemorySectionNameOnHeap() = default;
+ explicit MemorySectionNameOnHeap(size_t aBufferLen)
+ : mBuffer(MakeUnique<uint8_t[]>(aBufferLen)) {}
+
+ public:
+ static MemorySectionNameOnHeap GetBackingFilePath(HANDLE aProcess,
+ void* aSectionAddr) {
+ SIZE_T bufferLen = MAX_PATH * 2;
+ do {
+ MemorySectionNameOnHeap sectionName(bufferLen);
+
+ SIZE_T requiredBytes;
+ NTSTATUS ntStatus = ::NtQueryVirtualMemory(
+ aProcess, aSectionAddr, MemorySectionName, sectionName.mBuffer.get(),
+ bufferLen, &requiredBytes);
+ if (NT_SUCCESS(ntStatus)) {
+ return sectionName;
+ }
+
+ if (ntStatus != STATUS_INFO_LENGTH_MISMATCH ||
+ bufferLen >= requiredBytes) {
+ break;
+ }
+
+ bufferLen = requiredBytes;
+ } while (1);
+
+ return MemorySectionNameOnHeap();
+ }
+
+ // Allow move & Disallow copy
+ MemorySectionNameOnHeap(MemorySectionNameOnHeap&&) = default;
+ MemorySectionNameOnHeap& operator=(MemorySectionNameOnHeap&&) = default;
+ MemorySectionNameOnHeap(const MemorySectionNameOnHeap&) = delete;
+ MemorySectionNameOnHeap& operator=(const MemorySectionNameOnHeap&) = delete;
+
+ PCUNICODE_STRING AsUnicodeString() const {
+ return reinterpret_cast<PCUNICODE_STRING>(mBuffer.get());
+ }
+};
+
+inline bool FindCharInUnicodeString(const UNICODE_STRING& aStr, WCHAR aChar,
+ uint16_t& aPos, uint16_t aStartIndex = 0) {
+ const uint16_t aMaxIndex = aStr.Length / sizeof(WCHAR);
+
+ for (uint16_t curIndex = aStartIndex; curIndex < aMaxIndex; ++curIndex) {
+ if (aStr.Buffer[curIndex] == aChar) {
+ aPos = curIndex;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+inline bool IsHexDigit(WCHAR aChar) {
+ return (aChar >= L'0' && aChar <= L'9') || (aChar >= L'A' && aChar <= L'F') ||
+ (aChar >= L'a' && aChar <= L'f');
+}
+
+inline bool MatchUnicodeString(const UNICODE_STRING& aStr,
+ bool (*aPredicate)(WCHAR)) {
+ WCHAR* cur = aStr.Buffer;
+ WCHAR* end = &aStr.Buffer[aStr.Length / sizeof(WCHAR)];
+ while (cur < end) {
+ if (!aPredicate(*cur)) {
+ return false;
+ }
+
+ ++cur;
+ }
+
+ return true;
+}
+
+inline bool Contains12DigitHexString(const UNICODE_STRING& aLeafName) {
+ // Quick check: If the string is too short, don't bother
+ // (We need at least 12 hex digits, one char for '.', and 3 for extension)
+ const USHORT kMinLen = (12 + 1 + 3) * sizeof(wchar_t);
+ if (aLeafName.Length < kMinLen) {
+ return false;
+ }
+
+ uint16_t start, end;
+ if (!FindCharInUnicodeString(aLeafName, L'.', start)) {
+ return false;
+ }
+
+ ++start;
+ if (!FindCharInUnicodeString(aLeafName, L'.', end, start)) {
+ return false;
+ }
+
+ if (end - start != 12) {
+ return false;
+ }
+
+ UNICODE_STRING test;
+ test.Buffer = &aLeafName.Buffer[start];
+ test.Length = (end - start) * sizeof(WCHAR);
+ test.MaximumLength = test.Length;
+
+ return MatchUnicodeString(test, &IsHexDigit);
+}
+
+inline bool IsFileNameAtLeast16HexDigits(const UNICODE_STRING& aLeafName) {
+ // Quick check: If the string is too short, don't bother
+ // (We need 16 hex digits, one char for '.', and 3 for extension)
+ const USHORT kMinLen = (16 + 1 + 3) * sizeof(wchar_t);
+ if (aLeafName.Length < kMinLen) {
+ return false;
+ }
+
+ uint16_t dotIndex;
+ if (!FindCharInUnicodeString(aLeafName, L'.', dotIndex)) {
+ return false;
+ }
+
+ if (dotIndex < 16) {
+ return false;
+ }
+
+ UNICODE_STRING test;
+ test.Buffer = aLeafName.Buffer;
+ test.Length = dotIndex * sizeof(WCHAR);
+ test.MaximumLength = aLeafName.MaximumLength;
+
+ return MatchUnicodeString(test, &IsHexDigit);
+}
+
+inline void GetLeafName(PUNICODE_STRING aDestString,
+ PCUNICODE_STRING aSrcString) {
+ WCHAR* buf = aSrcString->Buffer;
+ WCHAR* end = &aSrcString->Buffer[(aSrcString->Length / sizeof(WCHAR)) - 1];
+ WCHAR* cur = end;
+ while (cur >= buf) {
+ if (*cur == L'\\') {
+ break;
+ }
+
+ --cur;
+ }
+
+ // At this point, either cur points to the final backslash, or it points to
+ // buf - 1. Either way, we're interested in cur + 1 as the desired buffer.
+ aDestString->Buffer = cur + 1;
+ aDestString->Length = (end - aDestString->Buffer + 1) * sizeof(WCHAR);
+ aDestString->MaximumLength = aDestString->Length;
+}
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+#if defined(MOZILLA_INTERNAL_API)
+
+inline const nsDependentSubstring GetLeafName(const nsAString& aString) {
+ auto it = aString.EndReading();
+ size_t pos = aString.Length();
+ while (it > aString.BeginReading()) {
+ if (*(it - 1) == u'\\') {
+ return Substring(aString, pos);
+ }
+
+ MOZ_ASSERT(pos > 0);
+ --pos;
+ --it;
+ }
+
+ return Substring(aString, 0); // No backslash in the string
+}
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+inline char EnsureLowerCaseASCII(char aChar) {
+ if (aChar >= 'A' && aChar <= 'Z') {
+ aChar -= 'A' - 'a';
+ }
+
+ return aChar;
+}
+
+inline int StricmpASCII(const char* aLeft, const char* aRight) {
+ char curLeft, curRight;
+
+ do {
+ curLeft = EnsureLowerCaseASCII(*(aLeft++));
+ curRight = EnsureLowerCaseASCII(*(aRight++));
+ } while (curLeft && curLeft == curRight);
+
+ return curLeft - curRight;
+}
+
+inline int StrcmpASCII(const char* aLeft, const char* aRight) {
+ char curLeft, curRight;
+
+ do {
+ curLeft = *(aLeft++);
+ curRight = *(aRight++);
+ } while (curLeft && curLeft == curRight);
+
+ return curLeft - curRight;
+}
+
+inline size_t StrlenASCII(const char* aStr) {
+ size_t len = 0;
+
+ while (*(aStr++)) {
+ ++len;
+ }
+
+ return len;
+}
+
+struct CodeViewRecord70 {
+ uint32_t signature;
+ GUID pdbSignature;
+ uint32_t pdbAge;
+ // A UTF-8 string, according to
+ // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/locator.cpp#L785
+ char pdbFileName[1];
+};
+
+class MOZ_RAII PEHeaders final {
+ /**
+ * This structure is documented on MSDN as VS_VERSIONINFO, but is not present
+ * in SDK headers because it cannot be specified as a C struct. The following
+ * structure contains the fixed-length fields at the beginning of
+ * VS_VERSIONINFO.
+ */
+ struct VS_VERSIONINFO_HEADER {
+ WORD wLength;
+ WORD wValueLength;
+ WORD wType;
+ WCHAR szKey[16]; // ArrayLength(L"VS_VERSION_INFO")
+ // Additional data goes here, aligned on a 4-byte boundary
+ };
+
+ public:
+ // The lowest two bits of an HMODULE are used as flags. Stripping those bits
+ // from the HMODULE yields the base address of the binary's memory mapping.
+ // (See LoadLibraryEx docs on MSDN)
+ template <typename T>
+ static T HModuleToBaseAddr(HMODULE aModule) {
+ return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(aModule) &
+ ~uintptr_t(3));
+ }
+
+ explicit PEHeaders(void* aBaseAddress)
+ : PEHeaders(reinterpret_cast<PIMAGE_DOS_HEADER>(aBaseAddress)) {}
+
+ explicit PEHeaders(HMODULE aModule)
+ : PEHeaders(HModuleToBaseAddr<PIMAGE_DOS_HEADER>(aModule)) {}
+
+ explicit PEHeaders(PIMAGE_DOS_HEADER aMzHeader)
+ : mMzHeader(aMzHeader),
+ mPeHeader(nullptr),
+ mImageLimit(nullptr),
+ mIsImportDirectoryTampered(false) {
+ if (!mMzHeader || mMzHeader->e_magic != IMAGE_DOS_SIGNATURE) {
+ return;
+ }
+
+ mPeHeader = RVAToPtrUnchecked<PIMAGE_NT_HEADERS>(mMzHeader->e_lfanew);
+ if (!mPeHeader || mPeHeader->Signature != IMAGE_NT_SIGNATURE) {
+ return;
+ }
+
+ if (mPeHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
+ return;
+ }
+
+ DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage;
+ // This is a coarse-grained check to ensure that the image size is
+ // reasonable. It we aren't big enough to contain headers, we have a
+ // problem!
+ if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) {
+ return;
+ }
+
+ mImageLimit = RVAToPtrUnchecked<void*>(imageSize - 1UL);
+
+ PIMAGE_DATA_DIRECTORY importDirEntry =
+ GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_IMPORT);
+ if (!importDirEntry) {
+ return;
+ }
+
+ mIsImportDirectoryTampered = (importDirEntry->VirtualAddress >= imageSize);
+ }
+
+ explicit operator bool() const { return !!mImageLimit; }
+
+ /**
+ * This overload computes absolute virtual addresses relative to the base
+ * address of the binary.
+ */
+ template <typename T, typename R>
+ T RVAToPtr(R aRva) const {
+ return RVAToPtr<T>(mMzHeader, aRva);
+ }
+
+ /**
+ * This overload computes a result by adding aRva to aBase, but also ensures
+ * that the resulting pointer falls within the bounds of this binary's memory
+ * mapping.
+ */
+ template <typename T, typename R>
+ T RVAToPtr(void* aBase, R aRva) const {
+ if (!mImageLimit) {
+ return nullptr;
+ }
+
+ char* absAddress = reinterpret_cast<char*>(aBase) + aRva;
+ if (absAddress < reinterpret_cast<char*>(mMzHeader) ||
+ absAddress > reinterpret_cast<char*>(mImageLimit)) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<T>(absAddress);
+ }
+
+ Maybe<Range<const uint8_t>> GetBounds() const {
+ if (!mImageLimit) {
+ return Nothing();
+ }
+
+ auto base = reinterpret_cast<const uint8_t*>(mMzHeader);
+ DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage;
+ return Some(Range(base, imageSize));
+ }
+
+ DWORD GetFileCharacteristics() const {
+ return mPeHeader ? mPeHeader->FileHeader.Characteristics : 0;
+ }
+
+ bool IsWithinImage(const void* aAddress) const {
+ uintptr_t addr = reinterpret_cast<uintptr_t>(aAddress);
+ uintptr_t imageBase = reinterpret_cast<uintptr_t>(mMzHeader);
+ uintptr_t imageLimit = reinterpret_cast<uintptr_t>(mImageLimit);
+ return addr >= imageBase && addr <= imageLimit;
+ }
+
+ PIMAGE_IMPORT_DESCRIPTOR GetImportDirectory() const {
+ // If the import directory is already tampered, we skip bounds check
+ // because it could be located outside the mapped image.
+ return mIsImportDirectoryTampered
+ ? GetImageDirectoryEntry<PIMAGE_IMPORT_DESCRIPTOR,
+ BoundsCheckPolicy::Skip>(
+ IMAGE_DIRECTORY_ENTRY_IMPORT)
+ : GetImageDirectoryEntry<PIMAGE_IMPORT_DESCRIPTOR>(
+ IMAGE_DIRECTORY_ENTRY_IMPORT);
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY GetResourceTable() const {
+ return GetImageDirectoryEntry<PIMAGE_RESOURCE_DIRECTORY>(
+ IMAGE_DIRECTORY_ENTRY_RESOURCE);
+ }
+
+ PIMAGE_DATA_DIRECTORY GetImageDirectoryEntryPtr(
+ const uint32_t aDirectoryIndex, uint32_t* aOutRva = nullptr) const {
+ if (aOutRva) {
+ *aOutRva = 0;
+ }
+
+ IMAGE_OPTIONAL_HEADER& optionalHeader = mPeHeader->OptionalHeader;
+
+ const uint32_t maxIndex = std::min(optionalHeader.NumberOfRvaAndSizes,
+ DWORD(IMAGE_NUMBEROF_DIRECTORY_ENTRIES));
+ if (aDirectoryIndex >= maxIndex) {
+ return nullptr;
+ }
+
+ PIMAGE_DATA_DIRECTORY dirEntry =
+ &optionalHeader.DataDirectory[aDirectoryIndex];
+ if (aOutRva) {
+ *aOutRva = reinterpret_cast<char*>(dirEntry) -
+ reinterpret_cast<char*>(mMzHeader);
+ MOZ_ASSERT(*aOutRva);
+ }
+
+ return dirEntry;
+ }
+
+ bool GetVersionInfo(uint64_t& aOutVersion) const {
+ // RT_VERSION == 16
+ // Version resources require an id of 1
+ auto root = FindResourceLeaf<VS_VERSIONINFO_HEADER*>(16, 1);
+ if (!root) {
+ return false;
+ }
+
+ VS_FIXEDFILEINFO* fixedInfo = GetFixedFileInfo(root);
+ if (!fixedInfo) {
+ return false;
+ }
+
+ aOutVersion = ((static_cast<uint64_t>(fixedInfo->dwFileVersionMS) << 32) |
+ static_cast<uint64_t>(fixedInfo->dwFileVersionLS));
+ return true;
+ }
+
+ bool GetTimeStamp(DWORD& aResult) const {
+ if (!(*this)) {
+ return false;
+ }
+
+ aResult = mPeHeader->FileHeader.TimeDateStamp;
+ return true;
+ }
+
+ bool GetImageSize(DWORD& aResult) const {
+ if (!(*this)) {
+ return false;
+ }
+
+ aResult = mPeHeader->OptionalHeader.SizeOfImage;
+ return true;
+ }
+
+ bool GetCheckSum(DWORD& aResult) const {
+ if (!(*this)) {
+ return false;
+ }
+
+ aResult = mPeHeader->OptionalHeader.CheckSum;
+ return true;
+ }
+
+ PIMAGE_IMPORT_DESCRIPTOR
+ GetImportDescriptor(const char* aModuleNameASCII) const {
+ for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory();
+ IsValid(curImpDesc); ++curImpDesc) {
+ auto curName = mIsImportDirectoryTampered
+ ? RVAToPtrUnchecked<const char*>(curImpDesc->Name)
+ : RVAToPtr<const char*>(curImpDesc->Name);
+ if (!curName) {
+ return nullptr;
+ }
+
+ if (StricmpASCII(aModuleNameASCII, curName)) {
+ continue;
+ }
+
+ // curImpDesc now points to the IAT for the module we're interested in
+ return curImpDesc;
+ }
+
+ return nullptr;
+ }
+
+ template <typename CallbackT>
+ void EnumImportChunks(const CallbackT& aCallback) const {
+ for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory();
+ IsValid(curImpDesc); ++curImpDesc) {
+ auto curName = mIsImportDirectoryTampered
+ ? RVAToPtrUnchecked<const char*>(curImpDesc->Name)
+ : RVAToPtr<const char*>(curImpDesc->Name);
+ if (!curName) {
+ continue;
+ }
+
+ aCallback(curName);
+ }
+ }
+
+ /**
+ * If |aBoundaries| is given, this method checks whether each IAT entry is
+ * within the given range, and if any entry is out of the range, we return
+ * Nothing().
+ */
+ Maybe<Span<IMAGE_THUNK_DATA>> GetIATThunksForModule(
+ const char* aModuleNameASCII,
+ const Range<const uint8_t>* aBoundaries = nullptr) const {
+ PIMAGE_IMPORT_DESCRIPTOR impDesc = GetImportDescriptor(aModuleNameASCII);
+ if (!impDesc) {
+ return Nothing();
+ }
+
+ auto firstIatThunk =
+ this->template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
+ if (!firstIatThunk) {
+ return Nothing();
+ }
+
+ // Find the length by iterating through the table until we find a null entry
+ PIMAGE_THUNK_DATA curIatThunk = firstIatThunk;
+ while (IsValid(curIatThunk)) {
+ if (aBoundaries) {
+ auto iatEntry =
+ reinterpret_cast<const uint8_t*>(curIatThunk->u1.Function);
+ if (iatEntry < aBoundaries->begin().get() ||
+ iatEntry >= aBoundaries->end().get()) {
+ return Nothing();
+ }
+ }
+
+ ++curIatThunk;
+ }
+
+ return Some(Span(firstIatThunk, curIatThunk));
+ }
+
+ /**
+ * Resources are stored in a three-level tree. To locate a particular entry,
+ * you must supply a resource type, the resource id, and then the language id.
+ * If aLangId == 0, we just resolve the first entry regardless of language.
+ */
+ template <typename T>
+ T FindResourceLeaf(WORD aType, WORD aResId, WORD aLangId = 0) const {
+ PIMAGE_RESOURCE_DIRECTORY topLevel = GetResourceTable();
+ if (!topLevel) {
+ return nullptr;
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY typeEntry =
+ FindResourceEntry(topLevel, aType);
+ if (!typeEntry || !typeEntry->DataIsDirectory) {
+ return nullptr;
+ }
+
+ auto idDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>(
+ topLevel, typeEntry->OffsetToDirectory);
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY idEntry = FindResourceEntry(idDir, aResId);
+ if (!idEntry || !idEntry->DataIsDirectory) {
+ return nullptr;
+ }
+
+ auto langDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>(
+ topLevel, idEntry->OffsetToDirectory);
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY langEntry;
+ if (aLangId) {
+ langEntry = FindResourceEntry(langDir, aLangId);
+ } else {
+ langEntry = FindFirstResourceEntry(langDir);
+ }
+
+ if (!langEntry || langEntry->DataIsDirectory) {
+ return nullptr;
+ }
+
+ auto dataEntry =
+ RVAToPtr<PIMAGE_RESOURCE_DATA_ENTRY>(topLevel, langEntry->OffsetToData);
+ return dataEntry ? RVAToPtr<T>(dataEntry->OffsetToData) : nullptr;
+ }
+
+ template <size_t N>
+ Maybe<Span<const uint8_t>> FindSection(const char (&aSecName)[N],
+ DWORD aCharacteristicsMask) const {
+ static_assert((N - 1) <= IMAGE_SIZEOF_SHORT_NAME,
+ "Section names must be at most 8 characters excluding null "
+ "terminator");
+
+ if (!(*this)) {
+ return Nothing();
+ }
+
+ Span<IMAGE_SECTION_HEADER> sectionTable = GetSectionTable();
+ for (auto&& sectionHeader : sectionTable) {
+ if (strncmp(reinterpret_cast<const char*>(sectionHeader.Name), aSecName,
+ IMAGE_SIZEOF_SHORT_NAME)) {
+ continue;
+ }
+
+ if (!(sectionHeader.Characteristics & aCharacteristicsMask)) {
+ // We found the section but it does not have the expected
+ // characteristics
+ return Nothing();
+ }
+
+ DWORD rva = sectionHeader.VirtualAddress;
+ if (!rva) {
+ return Nothing();
+ }
+
+ DWORD size = sectionHeader.Misc.VirtualSize;
+ if (!size) {
+ return Nothing();
+ }
+
+ auto base = RVAToPtr<const uint8_t*>(rva);
+ return Some(Span(base, size));
+ }
+
+ return Nothing();
+ }
+
+ // There may be other code sections in the binary besides .text
+ Maybe<Span<const uint8_t>> GetTextSectionInfo() const {
+ return FindSection(".text", IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE |
+ IMAGE_SCN_MEM_READ);
+ }
+
+ static bool IsValid(PIMAGE_IMPORT_DESCRIPTOR aImpDesc) {
+ return aImpDesc && aImpDesc->OriginalFirstThunk != 0;
+ }
+
+ static bool IsValid(PIMAGE_THUNK_DATA aImgThunk) {
+ return aImgThunk && aImgThunk->u1.Ordinal != 0;
+ }
+
+ bool IsImportDirectoryTampered() const { return mIsImportDirectoryTampered; }
+
+ FARPROC GetEntryPoint() const {
+ // Use the unchecked version because the entrypoint may be tampered.
+ return RVAToPtrUnchecked<FARPROC>(
+ mPeHeader->OptionalHeader.AddressOfEntryPoint);
+ }
+
+ const CodeViewRecord70* GetPdbInfo() const {
+ PIMAGE_DEBUG_DIRECTORY debugDirectory =
+ GetImageDirectoryEntry<PIMAGE_DEBUG_DIRECTORY>(
+ IMAGE_DIRECTORY_ENTRY_DEBUG);
+ if (!debugDirectory) {
+ return nullptr;
+ }
+
+ const CodeViewRecord70* debugInfo =
+ RVAToPtr<CodeViewRecord70*>(debugDirectory->AddressOfRawData);
+ return (debugInfo && debugInfo->signature == 'SDSR') ? debugInfo : nullptr;
+ }
+
+ private:
+ enum class BoundsCheckPolicy { Default, Skip };
+
+ template <typename T, BoundsCheckPolicy Policy = BoundsCheckPolicy::Default>
+ T GetImageDirectoryEntry(const uint32_t aDirectoryIndex) const {
+ PIMAGE_DATA_DIRECTORY dirEntry = GetImageDirectoryEntryPtr(aDirectoryIndex);
+ if (!dirEntry) {
+ return nullptr;
+ }
+
+ return Policy == BoundsCheckPolicy::Skip
+ ? RVAToPtrUnchecked<T>(dirEntry->VirtualAddress)
+ : RVAToPtr<T>(dirEntry->VirtualAddress);
+ }
+
+ // This private variant does not have bounds checks, because we need to be
+ // able to resolve the bounds themselves.
+ template <typename T, typename R>
+ T RVAToPtrUnchecked(R aRva) const {
+ return reinterpret_cast<T>(reinterpret_cast<char*>(mMzHeader) + aRva);
+ }
+
+ Span<IMAGE_SECTION_HEADER> GetSectionTable() const {
+ MOZ_ASSERT(*this);
+ auto base = RVAToPtr<PIMAGE_SECTION_HEADER>(
+ &mPeHeader->OptionalHeader, mPeHeader->FileHeader.SizeOfOptionalHeader);
+ // The Windows loader has an internal limit of 96 sections (per PE spec)
+ auto numSections =
+ std::min(mPeHeader->FileHeader.NumberOfSections, WORD(96));
+ return Span{base, numSections};
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY
+ FindResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel, WORD aId) const {
+ if (!aCurLevel) {
+ return nullptr;
+ }
+
+ // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array
+ // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. Since this function
+ // searches by ID, we need to skip past any named entries before iterating.
+ auto dirEnt =
+ reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1) +
+ aCurLevel->NumberOfNamedEntries;
+ if (!(IsWithinImage(dirEnt) &&
+ IsWithinImage(&dirEnt[aCurLevel->NumberOfIdEntries - 1].Id))) {
+ return nullptr;
+ }
+
+ for (WORD i = 0; i < aCurLevel->NumberOfIdEntries; ++i) {
+ if (dirEnt[i].Id == aId) {
+ return &dirEnt[i];
+ }
+ }
+
+ return nullptr;
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY
+ FindFirstResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel) const {
+ // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array
+ // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. We just return the first
+ // entry, regardless of whether it is indexed by name or by id.
+ auto dirEnt =
+ reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1);
+ WORD numEntries =
+ aCurLevel->NumberOfNamedEntries + aCurLevel->NumberOfIdEntries;
+ if (!numEntries) {
+ return nullptr;
+ }
+
+ return dirEnt;
+ }
+
+ VS_FIXEDFILEINFO* GetFixedFileInfo(VS_VERSIONINFO_HEADER* aVerInfo) const {
+ WORD length = aVerInfo->wLength;
+ if (length < sizeof(VS_VERSIONINFO_HEADER)) {
+ return nullptr;
+ }
+
+ const wchar_t kVersionInfoKey[] = L"VS_VERSION_INFO";
+ if (::RtlCompareMemory(aVerInfo->szKey, kVersionInfoKey,
+ ArrayLength(kVersionInfoKey)) !=
+ ArrayLength(kVersionInfoKey)) {
+ return nullptr;
+ }
+
+ if (aVerInfo->wValueLength != sizeof(VS_FIXEDFILEINFO)) {
+ // Fixed file info does not exist
+ return nullptr;
+ }
+
+ WORD offset = sizeof(VS_VERSIONINFO_HEADER);
+
+ uintptr_t base = reinterpret_cast<uintptr_t>(aVerInfo);
+ // Align up to 4-byte boundary
+#pragma warning(suppress : 4146)
+ offset += (-(base + offset) & 3);
+
+ if (offset >= length) {
+ return nullptr;
+ }
+
+ auto result = reinterpret_cast<VS_FIXEDFILEINFO*>(base + offset);
+ if (result->dwSignature != 0xFEEF04BD) {
+ return nullptr;
+ }
+
+ return result;
+ }
+
+ private:
+ PIMAGE_DOS_HEADER mMzHeader;
+ PIMAGE_NT_HEADERS mPeHeader;
+ void* mImageLimit;
+ bool mIsImportDirectoryTampered;
+};
+
+// This class represents an export section of a local/remote process.
+template <typename MMPolicy>
+class MOZ_RAII PEExportSection {
+ const MMPolicy& mMMPolicy;
+ uintptr_t mImageBase;
+ DWORD mOrdinalBase;
+ DWORD mRvaDirStart;
+ DWORD mRvaDirEnd;
+ mozilla::interceptor::TargetObjectArray<MMPolicy, DWORD> mExportAddressTable;
+ mozilla::interceptor::TargetObjectArray<MMPolicy, DWORD> mExportNameTable;
+ mozilla::interceptor::TargetObjectArray<MMPolicy, WORD> mExportOrdinalTable;
+
+ explicit PEExportSection(const MMPolicy& aMMPolicy)
+ : mMMPolicy(aMMPolicy),
+ mImageBase(0),
+ mOrdinalBase(0),
+ mRvaDirStart(0),
+ mRvaDirEnd(0),
+ mExportAddressTable(mMMPolicy),
+ mExportNameTable(mMMPolicy),
+ mExportOrdinalTable(mMMPolicy) {}
+
+ PEExportSection(const MMPolicy& aMMPolicy, uintptr_t aImageBase,
+ DWORD aRvaDirStart, DWORD aRvaDirEnd,
+ const IMAGE_EXPORT_DIRECTORY& exportDir)
+ : mMMPolicy(aMMPolicy),
+ mImageBase(aImageBase),
+ mOrdinalBase(exportDir.Base),
+ mRvaDirStart(aRvaDirStart),
+ mRvaDirEnd(aRvaDirEnd),
+ mExportAddressTable(mMMPolicy,
+ mImageBase + exportDir.AddressOfFunctions,
+ exportDir.NumberOfFunctions),
+ mExportNameTable(mMMPolicy, mImageBase + exportDir.AddressOfNames,
+ exportDir.NumberOfNames),
+ mExportOrdinalTable(mMMPolicy,
+ mImageBase + exportDir.AddressOfNameOrdinals,
+ exportDir.NumberOfNames) {}
+
+ static const PEExportSection Get(uintptr_t aImageBase,
+ const MMPolicy& aMMPolicy) {
+ mozilla::interceptor::TargetObject<MMPolicy, IMAGE_DOS_HEADER> mzHeader(
+ aMMPolicy, aImageBase);
+ if (!mzHeader || mzHeader->e_magic != IMAGE_DOS_SIGNATURE) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ mozilla::interceptor::TargetObject<MMPolicy, IMAGE_NT_HEADERS> peHeader(
+ aMMPolicy, aImageBase + mzHeader->e_lfanew);
+ if (!peHeader || peHeader->Signature != IMAGE_NT_SIGNATURE) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ if (peHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ const IMAGE_OPTIONAL_HEADER& optionalHeader = peHeader->OptionalHeader;
+
+ DWORD imageSize = optionalHeader.SizeOfImage;
+ // This is a coarse-grained check to ensure that the image size is
+ // reasonable. It we aren't big enough to contain headers, we have a
+ // problem!
+ if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ if (optionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ const IMAGE_DATA_DIRECTORY& exportDirectoryEntry =
+ optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
+ if (!exportDirectoryEntry.VirtualAddress || !exportDirectoryEntry.Size) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ mozilla::interceptor::TargetObject<MMPolicy, IMAGE_EXPORT_DIRECTORY>
+ exportDirectory(aMMPolicy,
+ aImageBase + exportDirectoryEntry.VirtualAddress);
+ if (!exportDirectory || !exportDirectory->NumberOfFunctions) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ return PEExportSection(
+ aMMPolicy, aImageBase, exportDirectoryEntry.VirtualAddress,
+ exportDirectoryEntry.VirtualAddress + exportDirectoryEntry.Size,
+ *exportDirectory.GetLocalBase());
+ }
+
+ FARPROC GetProcAddressByOrdinal(WORD aOrdinal) const {
+ if (aOrdinal < mOrdinalBase) {
+ return nullptr;
+ }
+
+ auto rvaToFunction = mExportAddressTable[aOrdinal - mOrdinalBase];
+ if (!rvaToFunction) {
+ return nullptr;
+ }
+ return reinterpret_cast<FARPROC>(mImageBase + *rvaToFunction);
+ }
+
+ public:
+ static const PEExportSection Get(HMODULE aModule, const MMPolicy& aMMPolicy) {
+ return Get(PEHeaders::HModuleToBaseAddr<uintptr_t>(aModule), aMMPolicy);
+ }
+
+ explicit operator bool() const {
+ // Because PEExportSection doesn't use MMPolicy::Reserve(), a boolified
+ // mMMPolicy is expected to be false. We don't check mMMPolicy here.
+ return mImageBase && mRvaDirStart && mRvaDirEnd && mExportAddressTable &&
+ mExportNameTable && mExportOrdinalTable;
+ }
+
+ template <typename T>
+ T RVAToPtr(uint32_t aRva) const {
+ return reinterpret_cast<T>(mImageBase + aRva);
+ }
+
+ PIMAGE_EXPORT_DIRECTORY GetExportDirectory() const {
+ if (!*this) {
+ return nullptr;
+ }
+
+ return RVAToPtr<PIMAGE_EXPORT_DIRECTORY>(mRvaDirStart);
+ }
+
+ /**
+ * This functions searches the export table for a given string as
+ * GetProcAddress does, but this returns a matched entry of the Export
+ * Address Table i.e. a pointer to an RVA of a matched function instead
+ * of a function address. If the entry is forwarded, this function
+ * returns nullptr.
+ */
+ const DWORD* FindExportAddressTableEntry(
+ const char* aFunctionNameASCII) const {
+ if (!*this || !aFunctionNameASCII) {
+ return nullptr;
+ }
+
+ struct NameTableComparator {
+ NameTableComparator(const PEExportSection<MMPolicy>& aExportSection,
+ const char* aTarget)
+ : mExportSection(aExportSection),
+ mTargetName(aTarget),
+ mTargetNamelength(StrlenASCII(aTarget)) {}
+
+ int operator()(DWORD aRVAToString) const {
+ mozilla::interceptor::TargetObjectArray<MMPolicy, char> itemString(
+ mExportSection.mMMPolicy, mExportSection.mImageBase + aRVAToString,
+ mTargetNamelength + 1);
+ return StrcmpASCII(mTargetName, itemString[0]);
+ }
+
+ const PEExportSection<MMPolicy>& mExportSection;
+ const char* mTargetName;
+ size_t mTargetNamelength;
+ };
+
+ const NameTableComparator comp(*this, aFunctionNameASCII);
+
+ size_t match;
+ if (!mExportNameTable.BinarySearchIf(comp, &match)) {
+ return nullptr;
+ }
+
+ const WORD* index = mExportOrdinalTable[match];
+ if (!index) {
+ return nullptr;
+ }
+
+ const DWORD* rvaToFunction = mExportAddressTable[*index];
+ if (!rvaToFunction) {
+ return nullptr;
+ }
+
+ if (*rvaToFunction >= mRvaDirStart && *rvaToFunction < mRvaDirEnd) {
+ // If an entry points to an address within the export section, the
+ // field is a forwarder RVA. We return nullptr because the entry is
+ // not a function address but a null-terminated string used for export
+ // forwarding.
+ return nullptr;
+ }
+
+ return rvaToFunction;
+ }
+
+ /**
+ * This functions behaves the same as the native ::GetProcAddress except
+ * the following cases:
+ * - Returns nullptr if a target entry is forwarded to another dll.
+ */
+ FARPROC GetProcAddress(const char* aFunctionNameASCII) const {
+ uintptr_t maybeOdrinal = reinterpret_cast<uintptr_t>(aFunctionNameASCII);
+ // When the high-order word of |aFunctionNameASCII| is zero, it's not
+ // a string but an ordinal value.
+ if (maybeOdrinal < 0x10000) {
+ return GetProcAddressByOrdinal(static_cast<WORD>(maybeOdrinal));
+ }
+
+ auto rvaToFunction = FindExportAddressTableEntry(aFunctionNameASCII);
+ if (!rvaToFunction) {
+ return nullptr;
+ }
+ return reinterpret_cast<FARPROC>(mImageBase + *rvaToFunction);
+ }
+};
+
+inline HANDLE RtlGetProcessHeap() {
+ PTEB teb = ::NtCurrentTeb();
+ PPEB peb = teb->ProcessEnvironmentBlock;
+ return peb->Reserved4[1];
+}
+
+inline PVOID RtlGetThreadLocalStoragePointer() {
+ return ::NtCurrentTeb()->Reserved1[11];
+}
+
+inline void RtlSetThreadLocalStoragePointerForTestingOnly(PVOID aNewValue) {
+ ::NtCurrentTeb()->Reserved1[11] = aNewValue;
+}
+
+inline DWORD RtlGetCurrentThreadId() {
+ PTEB teb = ::NtCurrentTeb();
+ CLIENT_ID* cid = reinterpret_cast<CLIENT_ID*>(&teb->Reserved1[8]);
+ return static_cast<DWORD>(reinterpret_cast<uintptr_t>(cid->UniqueThread) &
+ 0xFFFFFFFFUL);
+}
+
+inline PVOID RtlGetThreadStackBase() {
+ return reinterpret_cast<_NT_TIB*>(::NtCurrentTeb())->StackBase;
+}
+
+inline PVOID RtlGetThreadStackLimit() {
+ return reinterpret_cast<_NT_TIB*>(::NtCurrentTeb())->StackLimit;
+}
+
+const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
+
+inline LauncherResult<DWORD> GetParentProcessId() {
+ struct PROCESS_BASIC_INFORMATION {
+ NTSTATUS ExitStatus;
+ PPEB PebBaseAddress;
+ ULONG_PTR AffinityMask;
+ LONG BasePriority;
+ ULONG_PTR UniqueProcessId;
+ ULONG_PTR InheritedFromUniqueProcessId;
+ };
+
+ ULONG returnLength;
+ PROCESS_BASIC_INFORMATION pbi = {};
+ NTSTATUS status =
+ ::NtQueryInformationProcess(kCurrentProcess, ProcessBasicInformation,
+ &pbi, sizeof(pbi), &returnLength);
+ if (!NT_SUCCESS(status)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(status);
+ }
+
+ return static_cast<DWORD>(pbi.InheritedFromUniqueProcessId & 0xFFFFFFFF);
+}
+
+inline SIZE_T WINAPI VirtualQueryEx(HANDLE aProcess, LPCVOID aAddress,
+ PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen) {
+#if defined(MOZILLA_INTERNAL_API)
+ return ::VirtualQueryEx(aProcess, aAddress, aMemInfo, aMemInfoLen);
+#else
+ SIZE_T returnedLength;
+ NTSTATUS status = ::NtQueryVirtualMemory(
+ aProcess, const_cast<PVOID>(aAddress), MemoryBasicInformation, aMemInfo,
+ aMemInfoLen, &returnedLength);
+ if (!NT_SUCCESS(status)) {
+ ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
+ returnedLength = 0;
+ }
+ return returnedLength;
+#endif // defined(MOZILLA_INTERNAL_API)
+}
+
+inline SIZE_T WINAPI VirtualQuery(LPCVOID aAddress,
+ PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen) {
+ return nt::VirtualQueryEx(kCurrentProcess, aAddress, aMemInfo, aMemInfoLen);
+}
+
+struct DataDirectoryEntry : public _IMAGE_DATA_DIRECTORY {
+ DataDirectoryEntry() : _IMAGE_DATA_DIRECTORY() {}
+
+ MOZ_IMPLICIT DataDirectoryEntry(const _IMAGE_DATA_DIRECTORY& aOther)
+ : _IMAGE_DATA_DIRECTORY(aOther) {}
+
+ DataDirectoryEntry(const DataDirectoryEntry& aOther) = default;
+
+ bool operator==(const DataDirectoryEntry& aOther) const {
+ return VirtualAddress == aOther.VirtualAddress && Size == aOther.Size;
+ }
+
+ bool operator!=(const DataDirectoryEntry& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+inline LauncherResult<void*> GetProcessPebPtr(HANDLE aProcess) {
+ ULONG returnLength;
+ PROCESS_BASIC_INFORMATION pbi;
+ NTSTATUS status = ::NtQueryInformationProcess(
+ aProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &returnLength);
+ if (!NT_SUCCESS(status)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(status);
+ }
+
+ return pbi.PebBaseAddress;
+}
+
+/**
+ * This function relies on a specific offset into the mostly-undocumented PEB
+ * structure. The risk is reduced thanks to the fact that the Chromium sandbox
+ * relies on the location of this field. It is unlikely to change at this point.
+ * To further reduce the risk, we also check for the magic 'MZ' signature that
+ * should indicate the beginning of a PE image.
+ */
+inline LauncherResult<HMODULE> GetProcessExeModule(HANDLE aProcess) {
+ LauncherResult<void*> ppeb = GetProcessPebPtr(aProcess);
+ if (ppeb.isErr()) {
+ return ppeb.propagateErr();
+ }
+
+ PEB peb;
+ SIZE_T bytesRead;
+
+#if defined(MOZILLA_INTERNAL_API)
+ if (!::ReadProcessMemory(aProcess, ppeb.unwrap(), &peb, sizeof(peb),
+ &bytesRead) ||
+ bytesRead != sizeof(peb)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+#else
+ NTSTATUS ntStatus = ::NtReadVirtualMemory(aProcess, ppeb.unwrap(), &peb,
+ sizeof(peb), &bytesRead);
+ if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(peb)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus);
+ }
+#endif
+
+ // peb.ImageBaseAddress
+ void* baseAddress = peb.Reserved3[1];
+
+ char mzMagic[2];
+#if defined(MOZILLA_INTERNAL_API)
+ if (!::ReadProcessMemory(aProcess, baseAddress, mzMagic, sizeof(mzMagic),
+ &bytesRead) ||
+ bytesRead != sizeof(mzMagic)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+#else
+ ntStatus = ::NtReadVirtualMemory(aProcess, baseAddress, mzMagic,
+ sizeof(mzMagic), &bytesRead);
+ if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(mzMagic)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus);
+ }
+#endif
+
+ MOZ_ASSERT(mzMagic[0] == 'M' && mzMagic[1] == 'Z');
+ if (mzMagic[0] != 'M' || mzMagic[1] != 'Z') {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ return static_cast<HMODULE>(baseAddress);
+}
+
+#if defined(_MSC_VER)
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+#endif
+
+// This class manages data transfer from the local process's executable
+// to another process's executable via WriteProcessMemory.
+// Bug 1662560 told us the same executable may be mapped onto a different
+// address in a different process. This means when we transfer data within
+// the mapped executable such as a global variable or IAT from the current
+// process to another process, we need to shift its address by the difference
+// between two executable's mapped imagebase.
+class CrossExecTransferManager final {
+ HANDLE mRemoteProcess;
+ uint8_t* mLocalImagebase;
+ PEHeaders mLocalExec;
+ uint8_t* mRemoteImagebase;
+
+ static HMODULE GetLocalExecModule() {
+#if defined(_MSC_VER)
+ return reinterpret_cast<HMODULE>(&__ImageBase);
+#else
+ return ::GetModuleHandleW(nullptr);
+#endif
+ }
+
+ LauncherVoidResult EnsureRemoteImagebase() {
+ if (!mRemoteImagebase) {
+ LauncherResult<HMODULE> remoteImageBaseResult =
+ GetProcessExeModule(mRemoteProcess);
+ if (remoteImageBaseResult.isErr()) {
+ return remoteImageBaseResult.propagateErr();
+ }
+
+ mRemoteImagebase =
+ reinterpret_cast<uint8_t*>(remoteImageBaseResult.unwrap());
+ }
+ return Ok();
+ }
+
+ template <typename T>
+ T* LocalExecToRemoteExec(T* aLocalAddress) const {
+ MOZ_ASSERT(mRemoteImagebase);
+ MOZ_ASSERT(mLocalExec.IsWithinImage(aLocalAddress));
+
+ if (!mRemoteImagebase || !mLocalExec.IsWithinImage(aLocalAddress)) {
+ return aLocalAddress;
+ }
+
+ uintptr_t offset = reinterpret_cast<uintptr_t>(aLocalAddress) -
+ reinterpret_cast<uintptr_t>(mLocalImagebase);
+ return reinterpret_cast<T*>(mRemoteImagebase + offset);
+ }
+
+ public:
+ explicit CrossExecTransferManager(HANDLE aRemoteProcess)
+ : mRemoteProcess(aRemoteProcess),
+ mLocalImagebase(
+ PEHeaders::HModuleToBaseAddr<uint8_t*>(GetLocalExecModule())),
+ mLocalExec(mLocalImagebase),
+ mRemoteImagebase(nullptr) {}
+
+ CrossExecTransferManager(HANDLE aRemoteProcess, HMODULE aLocalImagebase)
+ : mRemoteProcess(aRemoteProcess),
+ mLocalImagebase(
+ PEHeaders::HModuleToBaseAddr<uint8_t*>(aLocalImagebase)),
+ mLocalExec(mLocalImagebase),
+ mRemoteImagebase(nullptr) {}
+
+ explicit operator bool() const { return !!mLocalExec; }
+ HANDLE RemoteProcess() const { return mRemoteProcess; }
+ const PEHeaders& LocalPEHeaders() const { return mLocalExec; }
+
+ AutoVirtualProtect Protect(void* aLocalAddress, size_t aLength,
+ DWORD aProtFlags) {
+ // If EnsureRemoteImagebase() fails, a subsequent operaion will fail.
+ Unused << EnsureRemoteImagebase();
+ return AutoVirtualProtect(LocalExecToRemoteExec(aLocalAddress), aLength,
+ aProtFlags, mRemoteProcess);
+ }
+
+ LauncherVoidResult Transfer(LPVOID aDestinationAddress,
+ LPCVOID aBufferToWrite, SIZE_T aBufferSize) {
+ LauncherVoidResult result = EnsureRemoteImagebase();
+ if (result.isErr()) {
+ return result.propagateErr();
+ }
+
+ if (!::WriteProcessMemory(mRemoteProcess,
+ LocalExecToRemoteExec(aDestinationAddress),
+ aBufferToWrite, aBufferSize, nullptr)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ return Ok();
+ }
+};
+
+#if !defined(MOZILLA_INTERNAL_API)
+
+inline LauncherResult<HMODULE> GetModuleHandleFromLeafName(
+ const UNICODE_STRING& aTarget) {
+ auto maybePeb = nt::GetProcessPebPtr(kCurrentProcess);
+ if (maybePeb.isErr()) {
+ return maybePeb.propagateErr();
+ }
+
+ const PPEB peb = reinterpret_cast<PPEB>(maybePeb.unwrap());
+ if (!peb->Ldr) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ auto firstItem = &peb->Ldr->InMemoryOrderModuleList;
+ for (auto p = firstItem->Flink; p != firstItem; p = p->Flink) {
+ const auto currentTableEntry =
+ CONTAINING_RECORD(p, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
+
+ UNICODE_STRING leafName;
+ nt::GetLeafName(&leafName, &currentTableEntry->FullDllName);
+
+ if (::RtlCompareUnicodeString(&leafName, &aTarget, TRUE) == 0) {
+ return reinterpret_cast<HMODULE>(currentTableEntry->DllBase);
+ }
+ }
+
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_MOD_NOT_FOUND);
+}
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS SRWLock final {
+ public:
+ constexpr SRWLock() : mLock(SRWLOCK_INIT) {}
+
+ void LockShared() { ::RtlAcquireSRWLockShared(&mLock); }
+
+ void LockExclusive() { ::RtlAcquireSRWLockExclusive(&mLock); }
+
+ void UnlockShared() { ::RtlReleaseSRWLockShared(&mLock); }
+
+ void UnlockExclusive() { ::RtlReleaseSRWLockExclusive(&mLock); }
+
+ SRWLock(const SRWLock&) = delete;
+ SRWLock(SRWLock&&) = delete;
+ SRWLock& operator=(const SRWLock&) = delete;
+ SRWLock& operator=(SRWLock&&) = delete;
+
+ SRWLOCK* operator&() { return &mLock; }
+
+ private:
+ SRWLOCK mLock;
+};
+
+class MOZ_RAII AutoExclusiveLock final {
+ public:
+ explicit AutoExclusiveLock(SRWLock& aLock) : mLock(aLock) {
+ aLock.LockExclusive();
+ }
+
+ ~AutoExclusiveLock() { mLock.UnlockExclusive(); }
+
+ AutoExclusiveLock(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock(AutoExclusiveLock&&) = delete;
+ AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete;
+
+ private:
+ SRWLock& mLock;
+};
+
+class MOZ_RAII AutoSharedLock final {
+ public:
+ explicit AutoSharedLock(SRWLock& aLock) : mLock(aLock) { aLock.LockShared(); }
+
+ ~AutoSharedLock() { mLock.UnlockShared(); }
+
+ AutoSharedLock(const AutoSharedLock&) = delete;
+ AutoSharedLock(AutoSharedLock&&) = delete;
+ AutoSharedLock& operator=(const AutoSharedLock&) = delete;
+ AutoSharedLock& operator=(AutoSharedLock&&) = delete;
+
+ private:
+ SRWLock& mLock;
+};
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+class RtlAllocPolicy {
+ public:
+ template <typename T>
+ T* maybe_pod_malloc(size_t aNumElems) {
+ if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
+ return nullptr;
+ }
+
+ return static_cast<T*>(
+ ::RtlAllocateHeap(RtlGetProcessHeap(), 0, aNumElems * sizeof(T)));
+ }
+
+ template <typename T>
+ T* maybe_pod_calloc(size_t aNumElems) {
+ if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
+ return nullptr;
+ }
+
+ return static_cast<T*>(::RtlAllocateHeap(
+ RtlGetProcessHeap(), HEAP_ZERO_MEMORY, aNumElems * sizeof(T)));
+ }
+
+ template <typename T>
+ T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
+ if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
+ return nullptr;
+ }
+
+ return static_cast<T*>(::RtlReAllocateHeap(RtlGetProcessHeap(), 0, aPtr,
+ aNewSize * sizeof(T)));
+ }
+
+ template <typename T>
+ T* pod_malloc(size_t aNumElems) {
+ return maybe_pod_malloc<T>(aNumElems);
+ }
+
+ template <typename T>
+ T* pod_calloc(size_t aNumElems) {
+ return maybe_pod_calloc<T>(aNumElems);
+ }
+
+ template <typename T>
+ T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
+ return maybe_pod_realloc<T>(aPtr, aOldSize, aNewSize);
+ }
+
+ template <typename T>
+ void free_(T* aPtr, size_t aNumElems = 0) {
+ ::RtlFreeHeap(RtlGetProcessHeap(), 0, aPtr);
+ }
+
+ void reportAllocOverflow() const {}
+
+ [[nodiscard]] bool checkSimulatedOOM() const { return true; }
+};
+
+class AutoMappedView final {
+ void* mView;
+
+ void Unmap() {
+ if (!mView) {
+ return;
+ }
+
+#if defined(MOZILLA_INTERNAL_API)
+ ::UnmapViewOfFile(mView);
+#else
+ NTSTATUS status = ::NtUnmapViewOfSection(nt::kCurrentProcess, mView);
+ if (!NT_SUCCESS(status)) {
+ ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
+ }
+#endif
+ mView = nullptr;
+ }
+
+ public:
+ explicit AutoMappedView(void* aView) : mView(aView) {}
+
+ AutoMappedView(HANDLE aSection, ULONG aProtectionFlags) : mView(nullptr) {
+#if defined(MOZILLA_INTERNAL_API)
+ mView = ::MapViewOfFile(aSection, aProtectionFlags, 0, 0, 0);
+#else
+ SIZE_T viewSize = 0;
+ NTSTATUS status = ::NtMapViewOfSection(aSection, nt::kCurrentProcess,
+ &mView, 0, 0, nullptr, &viewSize,
+ ViewUnmap, 0, aProtectionFlags);
+ if (!NT_SUCCESS(status)) {
+ ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
+ }
+#endif
+ }
+ ~AutoMappedView() { Unmap(); }
+
+ // Allow move & Disallow copy
+ AutoMappedView(AutoMappedView&& aOther) : mView(aOther.mView) {
+ aOther.mView = nullptr;
+ }
+ AutoMappedView& operator=(AutoMappedView&& aOther) {
+ if (this != &aOther) {
+ Unmap();
+ mView = aOther.mView;
+ aOther.mView = nullptr;
+ }
+ return *this;
+ }
+ AutoMappedView(const AutoMappedView&) = delete;
+ AutoMappedView& operator=(const AutoMappedView&) = delete;
+
+ explicit operator bool() const { return !!mView; }
+ template <typename T>
+ T* as() {
+ return reinterpret_cast<T*>(mView);
+ }
+
+ void* release() {
+ void* p = mView;
+ mView = nullptr;
+ return p;
+ }
+};
+
+#if defined(_M_X64)
+// CheckStack ensures that stack memory pages are committed up to a given size
+// in bytes from the current stack pointer. It updates the thread stack limit,
+// which points to the lowest committed stack address.
+MOZ_NEVER_INLINE MOZ_NAKED inline void CheckStack(uint32_t size) {
+ asm volatile(
+ "mov %ecx, %eax;"
+# if defined(__MINGW32__)
+ "jmp ___chkstk_ms;"
+# else
+ "jmp __chkstk;"
+# endif // __MINGW32__
+ );
+}
+#endif // _M_X64
+
+} // namespace nt
+} // namespace mozilla
+
+#endif // mozilla_NativeNt_h
diff --git a/mozglue/misc/PlatformConditionVariable.h b/mozglue/misc/PlatformConditionVariable.h
new file mode 100644
index 0000000000..61fb06ade1
--- /dev/null
+++ b/mozglue/misc/PlatformConditionVariable.h
@@ -0,0 +1,71 @@
+/* -*- 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 mozilla_ConditionVariable_h
+#define mozilla_ConditionVariable_h
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/PlatformMutex.h"
+#include "mozilla/TimeStamp.h"
+#if !defined(XP_WIN) && !defined(__wasi__)
+# include <pthread.h>
+#endif
+
+namespace mozilla {
+
+enum class CVStatus { NoTimeout, Timeout };
+
+namespace detail {
+
+class ConditionVariableImpl {
+ public:
+ struct PlatformData;
+
+ MFBT_API ConditionVariableImpl();
+ MFBT_API ~ConditionVariableImpl();
+
+ // Wake one thread that is waiting on this condition.
+ MFBT_API void notify_one();
+
+ // Wake all threads that are waiting on this condition.
+ MFBT_API void notify_all();
+
+ // Atomically release |lock| and sleep the current thread of execution on
+ // this condition variable.
+ // |lock| will be re-acquired before this function returns.
+ // The thread may be woken from sleep from another thread via notify_one()
+ // or notify_all(), but may also wake spuriously. The caller should recheck
+ // its predicate after this function returns, typically in a while loop.
+ MFBT_API void wait(MutexImpl& lock);
+
+ MFBT_API CVStatus wait_for(MutexImpl& lock,
+ const mozilla::TimeDuration& rel_time);
+
+ private:
+ ConditionVariableImpl(const ConditionVariableImpl&) = delete;
+ ConditionVariableImpl& operator=(const ConditionVariableImpl&) = delete;
+
+ PlatformData* platformData();
+
+#if !defined(XP_WIN) && !defined(__wasi__)
+ void* platformData_[sizeof(pthread_cond_t) / sizeof(void*)];
+ static_assert(sizeof(pthread_cond_t) / sizeof(void*) != 0 &&
+ sizeof(pthread_cond_t) % sizeof(void*) == 0,
+ "pthread_cond_t must have pointer alignment");
+#else
+ void* platformData_[4];
+#endif
+};
+
+} // namespace detail
+
+} // namespace mozilla
+
+#endif // mozilla_ConditionVariable_h
diff --git a/mozglue/misc/PlatformMutex.h b/mozglue/misc/PlatformMutex.h
new file mode 100644
index 0000000000..ac5459cf10
--- /dev/null
+++ b/mozglue/misc/PlatformMutex.h
@@ -0,0 +1,66 @@
+/* -*- 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 mozilla_PlatformMutex_h
+#define mozilla_PlatformMutex_h
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Types.h"
+
+#if !defined(XP_WIN) && !defined(__wasi__)
+# include <pthread.h>
+#endif
+
+namespace mozilla {
+
+namespace detail {
+
+class ConditionVariableImpl;
+
+class MutexImpl {
+ public:
+ struct PlatformData;
+
+ explicit MFBT_API MutexImpl();
+ MFBT_API ~MutexImpl();
+
+ protected:
+ MFBT_API void lock();
+ MFBT_API void unlock();
+ // We have a separate, forwarding API so internal uses don't have to go
+ // through the PLT.
+ MFBT_API bool tryLock();
+
+ private:
+ MutexImpl(const MutexImpl&) = delete;
+ void operator=(const MutexImpl&) = delete;
+ MutexImpl(MutexImpl&&) = delete;
+ void operator=(MutexImpl&&) = delete;
+ bool operator==(const MutexImpl& rhs) = delete;
+
+ void mutexLock();
+ bool mutexTryLock();
+
+ PlatformData* platformData();
+
+#if !defined(XP_WIN) && !defined(__wasi__)
+ void* platformData_[sizeof(pthread_mutex_t) / sizeof(void*)];
+ static_assert(sizeof(pthread_mutex_t) / sizeof(void*) != 0 &&
+ sizeof(pthread_mutex_t) % sizeof(void*) == 0,
+ "pthread_mutex_t must have pointer alignment");
+#else
+ void* platformData_[6];
+#endif
+
+ friend class mozilla::detail::ConditionVariableImpl;
+};
+
+} // namespace detail
+
+} // namespace mozilla
+#endif // mozilla_PlatformMutex_h
diff --git a/mozglue/misc/PlatformRWLock.h b/mozglue/misc/PlatformRWLock.h
new file mode 100644
index 0000000000..0acd09b25b
--- /dev/null
+++ b/mozglue/misc/PlatformRWLock.h
@@ -0,0 +1,50 @@
+/* -*- 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 mozilla_PlatformRWLock_h
+#define mozilla_PlatformRWLock_h
+
+#include "mozilla/Types.h"
+
+#ifndef XP_WIN
+# include <pthread.h>
+#endif
+
+namespace mozilla::detail {
+
+class RWLockImpl {
+ public:
+ explicit MFBT_API RWLockImpl();
+ MFBT_API ~RWLockImpl();
+
+ protected:
+ [[nodiscard]] MFBT_API bool tryReadLock();
+ MFBT_API void readLock();
+ MFBT_API void readUnlock();
+
+ [[nodiscard]] MFBT_API bool tryWriteLock();
+ MFBT_API void writeLock();
+ MFBT_API void writeUnlock();
+
+ private:
+ RWLockImpl(const RWLockImpl&) = delete;
+ void operator=(const RWLockImpl&) = delete;
+ RWLockImpl(RWLockImpl&&) = delete;
+ void operator=(RWLockImpl&&) = delete;
+ bool operator==(const RWLockImpl& rhs) = delete;
+
+#ifndef XP_WIN
+ pthread_rwlock_t mRWLock;
+#else
+ // SRWLock is pointer-sized. We declare it in such a fashion here to avoid
+ // pulling in windows.h wherever this header is used.
+ void* mRWLock;
+#endif
+};
+
+} // namespace mozilla::detail
+
+#endif // mozilla_PlatformRWLock_h
diff --git a/mozglue/misc/PreXULSkeletonUI.cpp b/mozglue/misc/PreXULSkeletonUI.cpp
new file mode 100644
index 0000000000..1b17837fc1
--- /dev/null
+++ b/mozglue/misc/PreXULSkeletonUI.cpp
@@ -0,0 +1,2234 @@
+/* -*- 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 "PreXULSkeletonUI.h"
+
+#include <algorithm>
+#include <math.h>
+#include <limits.h>
+#include <cmath>
+#include <locale>
+#include <string>
+#include <objbase.h>
+#include <shlobj.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BaseProfilerMarkers.h"
+#include "mozilla/CacheNtDllThunk.h"
+#include "mozilla/FStream.h"
+#include "mozilla/GetKnownFolderPath.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/HelperMacros.h"
+#include "mozilla/glue/Debug.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/mscom/ProcessRuntime.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Try.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WindowsDpiAwareness.h"
+#include "mozilla/WindowsProcessMitigations.h"
+
+namespace mozilla {
+
+// ColorRect defines an optionally-rounded, optionally-bordered rectangle of a
+// particular color that we will draw.
+struct ColorRect {
+ uint32_t color;
+ uint32_t borderColor;
+ int x;
+ int y;
+ int width;
+ int height;
+ int borderWidth;
+ int borderRadius;
+ bool flipIfRTL;
+};
+
+// DrawRect is mostly the same as ColorRect, but exists as an implementation
+// detail to simplify drawing borders. We draw borders as a strokeOnly rect
+// underneath an inner rect of a particular color. We also need to keep
+// track of the backgroundColor for rounding rects, in order to correctly
+// anti-alias.
+struct DrawRect {
+ uint32_t color;
+ uint32_t backgroundColor;
+ int x;
+ int y;
+ int width;
+ int height;
+ int borderRadius;
+ int borderWidth;
+ bool strokeOnly;
+};
+
+struct NormalizedRGB {
+ double r;
+ double g;
+ double b;
+};
+
+NormalizedRGB UintToRGB(uint32_t color) {
+ double r = static_cast<double>(color >> 16 & 0xff) / 255.0;
+ double g = static_cast<double>(color >> 8 & 0xff) / 255.0;
+ double b = static_cast<double>(color >> 0 & 0xff) / 255.0;
+ return NormalizedRGB{r, g, b};
+}
+
+uint32_t RGBToUint(const NormalizedRGB& rgb) {
+ return (static_cast<uint32_t>(rgb.r * 255.0) << 16) |
+ (static_cast<uint32_t>(rgb.g * 255.0) << 8) |
+ (static_cast<uint32_t>(rgb.b * 255.0) << 0);
+}
+
+double Lerp(double a, double b, double x) { return a + x * (b - a); }
+
+NormalizedRGB Lerp(const NormalizedRGB& a, const NormalizedRGB& b, double x) {
+ return NormalizedRGB{Lerp(a.r, b.r, x), Lerp(a.g, b.g, x), Lerp(a.b, b.b, x)};
+}
+
+// Produces a smooth curve in [0,1] based on a linear input in [0,1]
+double SmoothStep3(double x) { return x * x * (3.0 - 2.0 * x); }
+
+static const wchar_t kPreXULSkeletonUIKeyPath[] =
+ L"SOFTWARE"
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\PreXULSkeletonUISettings";
+
+static bool sPreXULSkeletonUIShown = false;
+static bool sPreXULSkeletonUIEnabled = false;
+static HWND sPreXULSkeletonUIWindow;
+static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
+static LPWSTR const gIDCWait = MAKEINTRESOURCEW(32514);
+static HANDLE sPreXULSKeletonUIAnimationThread;
+static HANDLE sPreXULSKeletonUILockFile = INVALID_HANDLE_VALUE;
+
+static mozilla::mscom::ProcessRuntime* sProcessRuntime;
+static uint32_t* sPixelBuffer = nullptr;
+static Vector<ColorRect>* sAnimatedRects = nullptr;
+static int sTotalChromeHeight = 0;
+static volatile LONG sAnimationControlFlag = 0;
+static bool sMaximized = false;
+static int sNonClientVerticalMargins = 0;
+static int sNonClientHorizontalMargins = 0;
+static uint32_t sDpi = 0;
+
+// Color values needed by the animation loop
+static uint32_t sAnimationColor;
+static uint32_t sToolbarForegroundColor;
+
+static ThemeMode sTheme = ThemeMode::Invalid;
+
+typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND);
+static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling = NULL;
+typedef int(WINAPI* GetSystemMetricsForDpiProc)(int, UINT);
+GetSystemMetricsForDpiProc sGetSystemMetricsForDpi = NULL;
+typedef UINT(WINAPI* GetDpiForWindowProc)(HWND);
+GetDpiForWindowProc sGetDpiForWindow = NULL;
+typedef ATOM(WINAPI* RegisterClassWProc)(const WNDCLASSW*);
+RegisterClassWProc sRegisterClassW = NULL;
+typedef HICON(WINAPI* LoadIconWProc)(HINSTANCE, LPCWSTR);
+LoadIconWProc sLoadIconW = NULL;
+typedef HICON(WINAPI* LoadCursorWProc)(HINSTANCE, LPCWSTR);
+LoadCursorWProc sLoadCursorW = NULL;
+typedef HWND(WINAPI* CreateWindowExWProc)(DWORD, LPCWSTR, LPCWSTR, DWORD, int,
+ int, int, int, HWND, HMENU, HINSTANCE,
+ LPVOID);
+CreateWindowExWProc sCreateWindowExW = NULL;
+typedef BOOL(WINAPI* ShowWindowProc)(HWND, int);
+ShowWindowProc sShowWindow = NULL;
+typedef BOOL(WINAPI* SetWindowPosProc)(HWND, HWND, int, int, int, int, UINT);
+SetWindowPosProc sSetWindowPos = NULL;
+typedef HDC(WINAPI* GetWindowDCProc)(HWND);
+GetWindowDCProc sGetWindowDC = NULL;
+typedef int(WINAPI* FillRectProc)(HDC, const RECT*, HBRUSH);
+FillRectProc sFillRect = NULL;
+typedef BOOL(WINAPI* DeleteObjectProc)(HGDIOBJ);
+DeleteObjectProc sDeleteObject = NULL;
+typedef int(WINAPI* ReleaseDCProc)(HWND, HDC);
+ReleaseDCProc sReleaseDC = NULL;
+typedef HMONITOR(WINAPI* MonitorFromWindowProc)(HWND, DWORD);
+MonitorFromWindowProc sMonitorFromWindow = NULL;
+typedef BOOL(WINAPI* GetMonitorInfoWProc)(HMONITOR, LPMONITORINFO);
+GetMonitorInfoWProc sGetMonitorInfoW = NULL;
+typedef LONG_PTR(WINAPI* SetWindowLongPtrWProc)(HWND, int, LONG_PTR);
+SetWindowLongPtrWProc sSetWindowLongPtrW = NULL;
+typedef int(WINAPI* StretchDIBitsProc)(HDC, int, int, int, int, int, int, int,
+ int, const VOID*, const BITMAPINFO*,
+ UINT, DWORD);
+StretchDIBitsProc sStretchDIBits = NULL;
+typedef HBRUSH(WINAPI* CreateSolidBrushProc)(COLORREF);
+CreateSolidBrushProc sCreateSolidBrush = NULL;
+
+static int sWindowWidth;
+static int sWindowHeight;
+static double sCSSToDevPixelScaling;
+
+static Maybe<PreXULSkeletonUIError> sErrorReason;
+
+static const int kAnimationCSSPixelsPerFrame = 11;
+static const int kAnimationCSSExtraWindowSize = 300;
+
+// NOTE: these values were pulled out of thin air as round numbers that are
+// likely to be too big to be seen in practice. If we legitimately see windows
+// this big, we probably don't want to be drawing them on the CPU anyway.
+static const uint32_t kMaxWindowWidth = 1 << 16;
+static const uint32_t kMaxWindowHeight = 1 << 16;
+
+static const wchar_t* sEnabledRegSuffix = L"|Enabled";
+static const wchar_t* sScreenXRegSuffix = L"|ScreenX";
+static const wchar_t* sScreenYRegSuffix = L"|ScreenY";
+static const wchar_t* sWidthRegSuffix = L"|Width";
+static const wchar_t* sHeightRegSuffix = L"|Height";
+static const wchar_t* sMaximizedRegSuffix = L"|Maximized";
+static const wchar_t* sUrlbarCSSRegSuffix = L"|UrlbarCSSSpan";
+static const wchar_t* sCssToDevPixelScalingRegSuffix = L"|CssToDevPixelScaling";
+static const wchar_t* sSearchbarRegSuffix = L"|SearchbarCSSSpan";
+static const wchar_t* sSpringsCSSRegSuffix = L"|SpringsCSSSpan";
+static const wchar_t* sThemeRegSuffix = L"|Theme";
+static const wchar_t* sFlagsRegSuffix = L"|Flags";
+static const wchar_t* sProgressSuffix = L"|Progress";
+
+std::wstring GetRegValueName(const wchar_t* prefix, const wchar_t* suffix) {
+ std::wstring result(prefix);
+ result.append(suffix);
+ return result;
+}
+
+// This is paraphrased from WinHeaderOnlyUtils.h. The fact that this file is
+// included in standalone SpiderMonkey builds prohibits us from including that
+// file directly, and it hardly warrants its own header. Bug 1674920 tracks
+// only including this file for gecko-related builds.
+Result<UniquePtr<wchar_t[]>, PreXULSkeletonUIError> GetBinaryPath() {
+ DWORD bufLen = MAX_PATH;
+ UniquePtr<wchar_t[]> buf;
+ while (true) {
+ buf = MakeUnique<wchar_t[]>(bufLen);
+ DWORD retLen = ::GetModuleFileNameW(nullptr, buf.get(), bufLen);
+ if (!retLen) {
+ return Err(PreXULSkeletonUIError::FilesystemFailure);
+ }
+
+ if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ bufLen *= 2;
+ continue;
+ }
+
+ break;
+ }
+
+ return buf;
+}
+
+// PreXULSkeletonUIDisallowed means that we don't even have the capacity to
+// enable the skeleton UI, whether because we're on a platform that doesn't
+// support it or because we launched with command line arguments that we don't
+// support. Some of these situations are transient, so we want to make sure we
+// don't mess with registry values in these scenarios that we may use in
+// other scenarios in which the skeleton UI is actually enabled.
+static bool PreXULSkeletonUIDisallowed() {
+ return sErrorReason.isSome() &&
+ (*sErrorReason == PreXULSkeletonUIError::Cmdline ||
+ *sErrorReason == PreXULSkeletonUIError::EnvVars);
+}
+
+// Note: this is specifically *not* a robust, multi-locale lowercasing
+// operation. It is not intended to be such. It is simply intended to match the
+// way in which we look for other instances of firefox to remote into.
+// See
+// https://searchfox.org/mozilla-central/rev/71621bfa47a371f2b1ccfd33c704913124afb933/toolkit/components/remote/nsRemoteService.cpp#56
+static void MutateStringToLowercase(wchar_t* ptr) {
+ while (*ptr) {
+ wchar_t ch = *ptr;
+ if (ch >= L'A' && ch <= L'Z') {
+ *ptr = ch + (L'a' - L'A');
+ }
+ ++ptr;
+ }
+}
+
+static Result<Ok, PreXULSkeletonUIError> GetSkeletonUILock() {
+ auto localAppDataPath = GetKnownFolderPath(FOLDERID_LocalAppData);
+ if (!localAppDataPath) {
+ return Err(PreXULSkeletonUIError::FilesystemFailure);
+ }
+
+ if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
+ return Ok();
+ }
+
+ // Note: because we're in mozglue, we cannot easily access things from
+ // toolkit, like `GetInstallHash`. We could move `GetInstallHash` into
+ // mozglue, and rip out all of its usage of types defined in toolkit headers.
+ // However, it seems cleaner to just hash the bin path ourselves. We don't
+ // get quite the same robustness that `GetInstallHash` might provide, but
+ // we already don't have that with how we key our registry values, so it
+ // probably makes sense to just match those.
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+
+ // Lowercase the binpath to match how we look for remote instances.
+ MutateStringToLowercase(binPath.get());
+
+ // The number of bytes * 2 characters per byte + 1 for the null terminator
+ uint32_t hexHashSize = sizeof(uint32_t) * 2 + 1;
+ UniquePtr<wchar_t[]> installHash = MakeUnique<wchar_t[]>(hexHashSize);
+ // This isn't perfect - it's a 32-bit hash of the path to our executable. It
+ // could reasonably collide, or casing could potentially affect things, but
+ // the theory is that that should be uncommon enough and the failure case
+ // mild enough that this is fine.
+ uint32_t binPathHash = HashString(binPath.get());
+ swprintf(installHash.get(), hexHashSize, L"%08x", binPathHash);
+
+ std::wstring lockFilePath;
+ lockFilePath.append(localAppDataPath.get());
+ lockFilePath.append(
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\SkeletonUILock-");
+ lockFilePath.append(installHash.get());
+
+ // We intentionally leak this file - that is okay, and (kind of) the point.
+ // We want to hold onto this handle until the application exits, and hold
+ // onto it with exclusive rights. If this check fails, then we assume that
+ // another instance of the executable is holding it, and thus return false.
+ sPreXULSKeletonUILockFile =
+ ::CreateFileW(lockFilePath.c_str(), GENERIC_READ | GENERIC_WRITE,
+ 0, // No sharing - this is how the lock works
+ nullptr, CREATE_ALWAYS,
+ FILE_FLAG_DELETE_ON_CLOSE, // Don't leave this lying around
+ nullptr);
+ if (sPreXULSKeletonUILockFile == INVALID_HANDLE_VALUE) {
+ return Err(PreXULSkeletonUIError::FailedGettingLock);
+ }
+
+ return Ok();
+}
+
+const char kGeneralSection[] = "[General]";
+const char kStartWithLastProfile[] = "StartWithLastProfile=";
+
+static bool ProfileDbHasStartWithLastProfile(IFStream& iniContents) {
+ bool inGeneral = false;
+ std::string line;
+ while (std::getline(iniContents, line)) {
+ size_t whitespace = 0;
+ while (line.length() > whitespace &&
+ (line[whitespace] == ' ' || line[whitespace] == '\t')) {
+ whitespace++;
+ }
+ line.erase(0, whitespace);
+
+ if (line.compare(kGeneralSection) == 0) {
+ inGeneral = true;
+ } else if (inGeneral) {
+ if (line[0] == '[') {
+ inGeneral = false;
+ } else {
+ if (line.find(kStartWithLastProfile) == 0) {
+ char val = line.c_str()[sizeof(kStartWithLastProfile) - 1];
+ if (val == '0') {
+ return false;
+ } else if (val == '1') {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // If we don't find it in the .ini file, we interpret that as true
+ return true;
+}
+
+static Result<Ok, PreXULSkeletonUIError> CheckForStartWithLastProfile() {
+ auto roamingAppData = GetKnownFolderPath(FOLDERID_RoamingAppData);
+ if (!roamingAppData) {
+ return Err(PreXULSkeletonUIError::FilesystemFailure);
+ }
+ std::wstring profileDbPath(roamingAppData.get());
+ profileDbPath.append(
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\profiles.ini");
+ IFStream profileDb(profileDbPath.c_str());
+ if (profileDb.fail()) {
+ return Err(PreXULSkeletonUIError::FilesystemFailure);
+ }
+
+ if (!ProfileDbHasStartWithLastProfile(profileDb)) {
+ return Err(PreXULSkeletonUIError::NoStartWithLastProfile);
+ }
+
+ return Ok();
+}
+
+// We could use nsAutoRegKey, but including nsWindowsHelpers.h causes build
+// failures in random places because we're in mozglue. Overall it should be
+// simpler and cleaner to just step around that issue with this class:
+class MOZ_RAII AutoCloseRegKey {
+ public:
+ explicit AutoCloseRegKey(HKEY key) : mKey(key) {}
+ ~AutoCloseRegKey() { ::RegCloseKey(mKey); }
+
+ private:
+ HKEY mKey;
+};
+
+int CSSToDevPixels(double cssPixels, double scaling) {
+ return floor(cssPixels * scaling + 0.5);
+}
+
+int CSSToDevPixels(int cssPixels, double scaling) {
+ return CSSToDevPixels((double)cssPixels, scaling);
+}
+
+int CSSToDevPixelsFloor(double cssPixels, double scaling) {
+ return floor(cssPixels * scaling);
+}
+
+// Some things appear to floor to device pixels rather than rounding. A good
+// example of this is border widths.
+int CSSToDevPixelsFloor(int cssPixels, double scaling) {
+ return CSSToDevPixelsFloor((double)cssPixels, scaling);
+}
+
+double SignedDistanceToCircle(double x, double y, double radius) {
+ return sqrt(x * x + y * y) - radius;
+}
+
+// For more details, see
+// https://searchfox.org/mozilla-central/rev/a5d9abfda1e26b1207db9549549ab0bdd73f735d/gfx/wr/webrender/res/shared.glsl#141-187
+// which was a reference for this function.
+double DistanceAntiAlias(double signedDistance) {
+ // Distance assumed to be in device pixels. We use an aa range of 0.5 for
+ // reasons detailed in the linked code above.
+ const double aaRange = 0.5;
+ double dist = 0.5 * signedDistance / aaRange;
+ if (dist <= -0.5 + std::numeric_limits<double>::epsilon()) return 1.0;
+ if (dist >= 0.5 - std::numeric_limits<double>::epsilon()) return 0.0;
+ return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
+}
+
+void RasterizeRoundedRectTopAndBottom(const DrawRect& rect) {
+ if (rect.height <= 2 * rect.borderRadius) {
+ MOZ_ASSERT(false, "Skeleton UI rect height too small for border radius.");
+ return;
+ }
+ if (rect.width <= 2 * rect.borderRadius) {
+ MOZ_ASSERT(false, "Skeleton UI rect width too small for border radius.");
+ return;
+ }
+
+ NormalizedRGB rgbBase = UintToRGB(rect.backgroundColor);
+ NormalizedRGB rgbBlend = UintToRGB(rect.color);
+
+ for (int rowIndex = 0; rowIndex < rect.borderRadius; ++rowIndex) {
+ int yTop = rect.y + rect.borderRadius - 1 - rowIndex;
+ int yBottom = rect.y + rect.height - rect.borderRadius + rowIndex;
+
+ uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
+ uint32_t* innermostPixelTopLeft =
+ lineStartTop + rect.x + rect.borderRadius - 1;
+ uint32_t* innermostPixelTopRight =
+ lineStartTop + rect.x + rect.width - rect.borderRadius;
+ uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
+ uint32_t* innermostPixelBottomLeft =
+ lineStartBottom + rect.x + rect.borderRadius - 1;
+ uint32_t* innermostPixelBottomRight =
+ lineStartBottom + rect.x + rect.width - rect.borderRadius;
+
+ // Add 0.5 to x and y to get the pixel center.
+ double pixelY = (double)rowIndex + 0.5;
+ for (int columnIndex = 0; columnIndex < rect.borderRadius; ++columnIndex) {
+ double pixelX = (double)columnIndex + 0.5;
+ double distance =
+ SignedDistanceToCircle(pixelX, pixelY, (double)rect.borderRadius);
+ double alpha = DistanceAntiAlias(distance);
+ NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, alpha);
+ uint32_t color = RGBToUint(rgb);
+
+ innermostPixelTopLeft[-columnIndex] = color;
+ innermostPixelTopRight[columnIndex] = color;
+ innermostPixelBottomLeft[-columnIndex] = color;
+ innermostPixelBottomRight[columnIndex] = color;
+ }
+
+ std::fill(innermostPixelTopLeft + 1, innermostPixelTopRight, rect.color);
+ std::fill(innermostPixelBottomLeft + 1, innermostPixelBottomRight,
+ rect.color);
+ }
+}
+
+void RasterizeAnimatedRoundedRectTopAndBottom(
+ const ColorRect& colorRect, const uint32_t* animationLookup,
+ int priorUpdateAreaMin, int priorUpdateAreaMax, int currentUpdateAreaMin,
+ int currentUpdateAreaMax, int animationMin) {
+ // We iterate through logical pixel rows here, from inside to outside, which
+ // for the top of the rounded rect means from bottom to top, and for the
+ // bottom of the rect means top to bottom. We paint pixels from left to
+ // right on the top and bottom rows at the same time for the entire animation
+ // window. (If the animation window does not overlap any rounded corners,
+ // however, we won't be called at all)
+ for (int rowIndex = 0; rowIndex < colorRect.borderRadius; ++rowIndex) {
+ int yTop = colorRect.y + colorRect.borderRadius - 1 - rowIndex;
+ int yBottom =
+ colorRect.y + colorRect.height - colorRect.borderRadius + rowIndex;
+
+ uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
+ uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
+
+ // Add 0.5 to x and y to get the pixel center.
+ double pixelY = (double)rowIndex + 0.5;
+ for (int x = priorUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
+ // The column index is the distance from the innermost pixel, which
+ // is different depending on whether we're on the left or right
+ // side of the rect. It will always be the max here, and if it's
+ // negative that just means we're outside the rounded area.
+ int columnIndex =
+ std::max((int)colorRect.x + (int)colorRect.borderRadius - x - 1,
+ x - ((int)colorRect.x + (int)colorRect.width -
+ (int)colorRect.borderRadius));
+
+ double alpha = 1.0;
+ if (columnIndex >= 0) {
+ double pixelX = (double)columnIndex + 0.5;
+ double distance = SignedDistanceToCircle(
+ pixelX, pixelY, (double)colorRect.borderRadius);
+ alpha = DistanceAntiAlias(distance);
+ }
+ // We don't do alpha blending for the antialiased pixels at the
+ // shape's border. It is not noticeable in the animation.
+ if (alpha > 1.0 - std::numeric_limits<double>::epsilon()) {
+ // Overwrite the tail end of last frame's animation with the
+ // rect's normal, unanimated color.
+ uint32_t color = x < priorUpdateAreaMax
+ ? colorRect.color
+ : animationLookup[x - animationMin];
+ lineStartTop[x] = color;
+ lineStartBottom[x] = color;
+ }
+ }
+ }
+}
+
+void RasterizeColorRect(const ColorRect& colorRect) {
+ // We sometimes split our rect into two, to simplify drawing borders. If we
+ // have a border, we draw a stroke-only rect first, and then draw the smaller
+ // inner rect on top of it.
+ Vector<DrawRect, 2> drawRects;
+ Unused << drawRects.reserve(2);
+ if (colorRect.borderWidth == 0) {
+ DrawRect rect = {};
+ rect.color = colorRect.color;
+ rect.backgroundColor =
+ sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
+ rect.x = colorRect.x;
+ rect.y = colorRect.y;
+ rect.width = colorRect.width;
+ rect.height = colorRect.height;
+ rect.borderRadius = colorRect.borderRadius;
+ rect.strokeOnly = false;
+ drawRects.infallibleAppend(rect);
+ } else {
+ DrawRect borderRect = {};
+ borderRect.color = colorRect.borderColor;
+ borderRect.backgroundColor =
+ sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
+ borderRect.x = colorRect.x;
+ borderRect.y = colorRect.y;
+ borderRect.width = colorRect.width;
+ borderRect.height = colorRect.height;
+ borderRect.borderRadius = colorRect.borderRadius;
+ borderRect.borderWidth = colorRect.borderWidth;
+ borderRect.strokeOnly = true;
+ drawRects.infallibleAppend(borderRect);
+
+ DrawRect baseRect = {};
+ baseRect.color = colorRect.color;
+ baseRect.backgroundColor = borderRect.color;
+ baseRect.x = colorRect.x + colorRect.borderWidth;
+ baseRect.y = colorRect.y + colorRect.borderWidth;
+ baseRect.width = colorRect.width - 2 * colorRect.borderWidth;
+ baseRect.height = colorRect.height - 2 * colorRect.borderWidth;
+ baseRect.borderRadius =
+ std::max(0, (int)colorRect.borderRadius - (int)colorRect.borderWidth);
+ baseRect.borderWidth = 0;
+ baseRect.strokeOnly = false;
+ drawRects.infallibleAppend(baseRect);
+ }
+
+ for (const DrawRect& rect : drawRects) {
+ if (rect.height <= 0 || rect.width <= 0) {
+ continue;
+ }
+
+ // For rounded rectangles, the first thing we do is draw the top and
+ // bottom of the rectangle, with the more complicated logic below. After
+ // that we can just draw the vertically centered part of the rect like
+ // normal.
+ RasterizeRoundedRectTopAndBottom(rect);
+
+ // We then draw the flat, central portion of the rect (which in the case of
+ // non-rounded rects, is just the entire thing.)
+ int solidRectStartY =
+ std::clamp(rect.y + rect.borderRadius, 0, sTotalChromeHeight);
+ int solidRectEndY = std::clamp(rect.y + rect.height - rect.borderRadius, 0,
+ sTotalChromeHeight);
+ for (int y = solidRectStartY; y < solidRectEndY; ++y) {
+ // For strokeOnly rects (used to draw borders), we just draw the left
+ // and right side here. Looping down a column of pixels is not the most
+ // cache-friendly thing, but it shouldn't be a big deal given the height
+ // of the urlbar.
+ // Also, if borderRadius is less than borderWidth, we need to ensure
+ // that we fully draw the top and bottom lines, so we make sure to check
+ // that we're inside the middle range range before excluding pixels.
+ if (rect.strokeOnly && y - rect.y > rect.borderWidth &&
+ rect.y + rect.height - y > rect.borderWidth) {
+ int startXLeft = std::clamp(rect.x, 0, sWindowWidth);
+ int endXLeft = std::clamp(rect.x + rect.borderWidth, 0, sWindowWidth);
+ int startXRight =
+ std::clamp(rect.x + rect.width - rect.borderWidth, 0, sWindowWidth);
+ int endXRight = std::clamp(rect.x + rect.width, 0, sWindowWidth);
+
+ uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
+ uint32_t* dataStartLeft = lineStart + startXLeft;
+ uint32_t* dataEndLeft = lineStart + endXLeft;
+ uint32_t* dataStartRight = lineStart + startXRight;
+ uint32_t* dataEndRight = lineStart + endXRight;
+ std::fill(dataStartLeft, dataEndLeft, rect.color);
+ std::fill(dataStartRight, dataEndRight, rect.color);
+ } else {
+ int startX = std::clamp(rect.x, 0, sWindowWidth);
+ int endX = std::clamp(rect.x + rect.width, 0, sWindowWidth);
+ uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
+ uint32_t* dataStart = lineStart + startX;
+ uint32_t* dataEnd = lineStart + endX;
+ std::fill(dataStart, dataEnd, rect.color);
+ }
+ }
+ }
+}
+
+// Paints the pixels to sPixelBuffer for the skeleton UI animation (a light
+// gradient which moves from left to right across the grey placeholder rects).
+// Takes in the rect to draw, together with a lookup table for the gradient,
+// and the bounds of the previous and current frame of the animation.
+bool RasterizeAnimatedRect(const ColorRect& colorRect,
+ const uint32_t* animationLookup,
+ int priorAnimationMin, int animationMin,
+ int animationMax) {
+ int rectMin = colorRect.x;
+ int rectMax = colorRect.x + colorRect.width;
+ bool animationWindowOverlaps =
+ rectMax >= priorAnimationMin && rectMin < animationMax;
+
+ int priorUpdateAreaMin = std::max(rectMin, priorAnimationMin);
+ int priorUpdateAreaMax = std::min(rectMax, animationMin);
+ int currentUpdateAreaMin = std::max(rectMin, animationMin);
+ int currentUpdateAreaMax = std::min(rectMax, animationMax);
+
+ if (!animationWindowOverlaps) {
+ return false;
+ }
+
+ bool animationWindowOverlapsBorderRadius =
+ rectMin + colorRect.borderRadius > priorAnimationMin ||
+ rectMax - colorRect.borderRadius <= animationMax;
+
+ // If we don't overlap the left or right side of the rounded rectangle,
+ // just pretend it's not rounded. This is a small optimization but
+ // there's no point in doing all of this rounded rectangle checking if
+ // we aren't even overlapping
+ int borderRadius =
+ animationWindowOverlapsBorderRadius ? colorRect.borderRadius : 0;
+
+ if (borderRadius > 0) {
+ // Similarly to how we draw the rounded rects in DrawSkeletonUI, we
+ // first draw the rounded top and bottom, and then we draw the center
+ // rect.
+ RasterizeAnimatedRoundedRectTopAndBottom(
+ colorRect, animationLookup, priorUpdateAreaMin, priorUpdateAreaMax,
+ currentUpdateAreaMin, currentUpdateAreaMax, animationMin);
+ }
+
+ for (int y = colorRect.y + borderRadius;
+ y < colorRect.y + colorRect.height - borderRadius; ++y) {
+ uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
+ // Overwrite the tail end of last frame's animation with the rect's
+ // normal, unanimated color.
+ for (int x = priorUpdateAreaMin; x < priorUpdateAreaMax; ++x) {
+ lineStart[x] = colorRect.color;
+ }
+ // Then apply the animated color
+ for (int x = currentUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
+ lineStart[x] = animationLookup[x - animationMin];
+ }
+ }
+
+ return true;
+}
+
+Result<Ok, PreXULSkeletonUIError> DrawSkeletonUI(
+ HWND hWnd, CSSPixelSpan urlbarCSSSpan, CSSPixelSpan searchbarCSSSpan,
+ Vector<CSSPixelSpan>& springs, const ThemeColors& currentTheme,
+ const EnumSet<SkeletonUIFlag, uint32_t>& flags) {
+ // NOTE: we opt here to paint a pixel buffer for the application chrome by
+ // hand, without using native UI library methods. Why do we do this?
+ //
+ // 1) It gives us a little bit more control, especially if we want to animate
+ // any of this.
+ // 2) It's actually more portable. We can do this on any platform where we
+ // can blit a pixel buffer to the screen, and it only has to change
+ // insofar as the UI is different on those platforms (and thus would have
+ // to change anyway.)
+ //
+ // The performance impact of this ought to be negligible. As far as has been
+ // observed, on slow reference hardware this might take up to a millisecond,
+ // for a startup which otherwise takes 30 seconds.
+ //
+ // The readability and maintainability are a greater concern. When the
+ // silhouette of Firefox's core UI changes, this code will likely need to
+ // change. However, for the foreseeable future, our skeleton UI will be mostly
+ // axis-aligned geometric shapes, and the thought is that any code which is
+ // manipulating raw pixels should not be *too* hard to maintain and
+ // understand so long as it is only painting such simple shapes.
+
+ sAnimationColor = currentTheme.animationColor;
+ sToolbarForegroundColor = currentTheme.toolbarForegroundColor;
+
+ bool menubarShown = flags.contains(SkeletonUIFlag::MenubarShown);
+ bool bookmarksToolbarShown =
+ flags.contains(SkeletonUIFlag::BookmarksToolbarShown);
+ bool rtlEnabled = flags.contains(SkeletonUIFlag::RtlEnabled);
+
+ int chromeHorMargin = CSSToDevPixels(2, sCSSToDevPixelScaling);
+ int verticalOffset = sMaximized ? sNonClientVerticalMargins : 0;
+ int horizontalOffset =
+ sNonClientHorizontalMargins - (sMaximized ? 0 : chromeHorMargin);
+
+ // found in tabs.inc.css, "--tab-min-height" + 2 * "--tab-block-margin"
+ int tabBarHeight = CSSToDevPixels(44, sCSSToDevPixelScaling);
+ int selectedTabBorderWidth = CSSToDevPixels(2, sCSSToDevPixelScaling);
+ // found in tabs.inc.css, "--tab-block-margin"
+ int titlebarSpacerWidth = horizontalOffset +
+ CSSToDevPixels(2, sCSSToDevPixelScaling) -
+ selectedTabBorderWidth;
+ if (!sMaximized && !menubarShown) {
+ // found in tabs.inc.css, ".titlebar-spacer"
+ titlebarSpacerWidth += CSSToDevPixels(40, sCSSToDevPixelScaling);
+ }
+ // found in tabs.inc.css, "--tab-block-margin"
+ int selectedTabMarginTop =
+ CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
+ int selectedTabMarginBottom =
+ CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
+ int selectedTabBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
+ int selectedTabWidth =
+ CSSToDevPixels(221, sCSSToDevPixelScaling) + 2 * selectedTabBorderWidth;
+ int toolbarHeight = CSSToDevPixels(40, sCSSToDevPixelScaling);
+ // found in browser.css, "#PersonalToolbar"
+ int bookmarkToolbarHeight = CSSToDevPixels(28, sCSSToDevPixelScaling);
+ if (bookmarksToolbarShown) {
+ toolbarHeight += bookmarkToolbarHeight;
+ }
+ // found in urlbar-searchbar.inc.css, "#urlbar[breakout]"
+ int urlbarTopOffset = CSSToDevPixels(4, sCSSToDevPixelScaling);
+ int urlbarHeight = CSSToDevPixels(32, sCSSToDevPixelScaling);
+ // found in browser-aero.css, "#navigator-toolbox::after" border-bottom
+ int chromeContentDividerHeight = CSSToDevPixels(1, sCSSToDevPixelScaling);
+
+ int tabPlaceholderBarMarginTop = CSSToDevPixels(14, sCSSToDevPixelScaling);
+ int tabPlaceholderBarMarginLeft = CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int tabPlaceholderBarHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int tabPlaceholderBarWidth = CSSToDevPixels(120, sCSSToDevPixelScaling);
+
+ int toolbarPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int toolbarPlaceholderMarginRight =
+ rtlEnabled ? CSSToDevPixels(11, sCSSToDevPixelScaling)
+ : CSSToDevPixels(9, sCSSToDevPixelScaling);
+ int toolbarPlaceholderMarginLeft =
+ rtlEnabled ? CSSToDevPixels(9, sCSSToDevPixelScaling)
+ : CSSToDevPixels(11, sCSSToDevPixelScaling);
+ int placeholderMargin = CSSToDevPixels(8, sCSSToDevPixelScaling);
+
+ int menubarHeightDevPixels =
+ menubarShown ? CSSToDevPixels(28, sCSSToDevPixelScaling) : 0;
+
+ // defined in urlbar-searchbar.inc.css as --urlbar-margin-inline: 5px
+ int urlbarMargin =
+ CSSToDevPixels(5, sCSSToDevPixelScaling) + horizontalOffset;
+
+ int urlbarTextPlaceholderMarginTop =
+ CSSToDevPixels(12, sCSSToDevPixelScaling);
+ int urlbarTextPlaceholderMarginLeft =
+ CSSToDevPixels(12, sCSSToDevPixelScaling);
+ int urlbarTextPlaceHolderWidth = CSSToDevPixels(
+ std::clamp(urlbarCSSSpan.end - urlbarCSSSpan.start - 10.0, 0.0, 260.0),
+ sCSSToDevPixelScaling);
+ int urlbarTextPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
+
+ int searchbarTextPlaceholderWidth = CSSToDevPixels(62, sCSSToDevPixelScaling);
+
+ auto scopeExit = MakeScopeExit([&] {
+ delete sAnimatedRects;
+ sAnimatedRects = nullptr;
+ });
+
+ Vector<ColorRect> rects;
+
+ ColorRect menubar = {};
+ menubar.color = currentTheme.tabBarColor;
+ menubar.x = 0;
+ menubar.y = verticalOffset;
+ menubar.width = sWindowWidth;
+ menubar.height = menubarHeightDevPixels;
+ menubar.flipIfRTL = false;
+ if (!rects.append(menubar)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ int placeholderBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
+ // found in browser.css "--toolbarbutton-border-radius"
+ int urlbarBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
+
+ // The (traditionally dark blue on Windows) background of the tab bar.
+ ColorRect tabBar = {};
+ tabBar.color = currentTheme.tabBarColor;
+ tabBar.x = 0;
+ tabBar.y = menubar.y + menubar.height;
+ tabBar.width = sWindowWidth;
+ tabBar.height = tabBarHeight;
+ tabBar.flipIfRTL = false;
+ if (!rects.append(tabBar)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The initial selected tab
+ ColorRect selectedTab = {};
+ selectedTab.color = currentTheme.tabColor;
+ selectedTab.x = titlebarSpacerWidth;
+ selectedTab.y = menubar.y + menubar.height + selectedTabMarginTop;
+ selectedTab.width = selectedTabWidth;
+ selectedTab.height =
+ tabBar.y + tabBar.height - selectedTab.y - selectedTabMarginBottom;
+ selectedTab.borderColor = currentTheme.tabOutlineColor;
+ selectedTab.borderWidth = selectedTabBorderWidth;
+ selectedTab.borderRadius = selectedTabBorderRadius;
+ selectedTab.flipIfRTL = true;
+ if (!rects.append(selectedTab)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // A placeholder rect representing text that will fill the selected tab title
+ ColorRect tabTextPlaceholder = {};
+ tabTextPlaceholder.color = currentTheme.toolbarForegroundColor;
+ tabTextPlaceholder.x = selectedTab.x + tabPlaceholderBarMarginLeft;
+ tabTextPlaceholder.y = selectedTab.y + tabPlaceholderBarMarginTop;
+ tabTextPlaceholder.width = tabPlaceholderBarWidth;
+ tabTextPlaceholder.height = tabPlaceholderBarHeight;
+ tabTextPlaceholder.borderRadius = placeholderBorderRadius;
+ tabTextPlaceholder.flipIfRTL = true;
+ if (!rects.append(tabTextPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The toolbar background
+ ColorRect toolbar = {};
+ toolbar.color = currentTheme.backgroundColor;
+ toolbar.x = 0;
+ toolbar.y = tabBar.y + tabBarHeight;
+ toolbar.width = sWindowWidth;
+ toolbar.height = toolbarHeight;
+ toolbar.flipIfRTL = false;
+ if (!rects.append(toolbar)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The single-pixel divider line below the toolbar
+ ColorRect chromeContentDivider = {};
+ chromeContentDivider.color = currentTheme.chromeContentDividerColor;
+ chromeContentDivider.x = 0;
+ chromeContentDivider.y = toolbar.y + toolbar.height;
+ chromeContentDivider.width = sWindowWidth;
+ chromeContentDivider.height = chromeContentDividerHeight;
+ chromeContentDivider.flipIfRTL = false;
+ if (!rects.append(chromeContentDivider)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The urlbar
+ ColorRect urlbar = {};
+ urlbar.color = currentTheme.urlbarColor;
+ urlbar.x = CSSToDevPixels(urlbarCSSSpan.start, sCSSToDevPixelScaling) +
+ horizontalOffset;
+ urlbar.y = tabBar.y + tabBarHeight + urlbarTopOffset;
+ urlbar.width = CSSToDevPixels((urlbarCSSSpan.end - urlbarCSSSpan.start),
+ sCSSToDevPixelScaling);
+ urlbar.height = urlbarHeight;
+ urlbar.borderColor = currentTheme.urlbarBorderColor;
+ urlbar.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
+ urlbar.borderRadius = urlbarBorderRadius;
+ urlbar.flipIfRTL = false;
+ if (!rects.append(urlbar)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The urlbar placeholder rect representating text that will fill the urlbar
+ // If rtl is enabled, it is flipped relative to the the urlbar rectangle, not
+ // sWindowWidth.
+ ColorRect urlbarTextPlaceholder = {};
+ urlbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
+ urlbarTextPlaceholder.x =
+ rtlEnabled
+ ? ((urlbar.x + urlbar.width) - urlbarTextPlaceholderMarginLeft -
+ urlbarTextPlaceHolderWidth)
+ : (urlbar.x + urlbarTextPlaceholderMarginLeft);
+ urlbarTextPlaceholder.y = urlbar.y + urlbarTextPlaceholderMarginTop;
+ urlbarTextPlaceholder.width = urlbarTextPlaceHolderWidth;
+ urlbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
+ urlbarTextPlaceholder.borderRadius = placeholderBorderRadius;
+ urlbarTextPlaceholder.flipIfRTL = false;
+ if (!rects.append(urlbarTextPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The searchbar and placeholder text, if present
+ // This is y-aligned with the urlbar
+ bool hasSearchbar = searchbarCSSSpan.start != 0 && searchbarCSSSpan.end != 0;
+ ColorRect searchbarRect = {};
+ if (hasSearchbar == true) {
+ searchbarRect.color = currentTheme.urlbarColor;
+ searchbarRect.x =
+ CSSToDevPixels(searchbarCSSSpan.start, sCSSToDevPixelScaling) +
+ horizontalOffset;
+ searchbarRect.y = urlbar.y;
+ searchbarRect.width = CSSToDevPixels(
+ searchbarCSSSpan.end - searchbarCSSSpan.start, sCSSToDevPixelScaling);
+ searchbarRect.height = urlbarHeight;
+ searchbarRect.borderRadius = urlbarBorderRadius;
+ searchbarRect.borderColor = currentTheme.urlbarBorderColor;
+ searchbarRect.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
+ searchbarRect.flipIfRTL = false;
+ if (!rects.append(searchbarRect)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The placeholder rect representating text that will fill the searchbar
+ // This uses the same margins as the urlbarTextPlaceholder
+ // If rtl is enabled, it is flipped relative to the the searchbar rectangle,
+ // not sWindowWidth.
+ ColorRect searchbarTextPlaceholder = {};
+ searchbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
+ searchbarTextPlaceholder.x =
+ rtlEnabled
+ ? ((searchbarRect.x + searchbarRect.width) -
+ urlbarTextPlaceholderMarginLeft - searchbarTextPlaceholderWidth)
+ : (searchbarRect.x + urlbarTextPlaceholderMarginLeft);
+ searchbarTextPlaceholder.y =
+ searchbarRect.y + urlbarTextPlaceholderMarginTop;
+ searchbarTextPlaceholder.width = searchbarTextPlaceholderWidth;
+ searchbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
+ searchbarTextPlaceholder.flipIfRTL = false;
+ if (!rects.append(searchbarTextPlaceholder) ||
+ !sAnimatedRects->append(searchbarTextPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ }
+
+ // Determine where the placeholder rectangles should not go. This is
+ // anywhere occupied by a spring, urlbar, or searchbar
+ Vector<DevPixelSpan> noPlaceholderSpans;
+
+ DevPixelSpan urlbarSpan;
+ urlbarSpan.start = urlbar.x - urlbarMargin;
+ urlbarSpan.end = urlbar.width + urlbar.x + urlbarMargin;
+
+ DevPixelSpan searchbarSpan;
+ if (hasSearchbar) {
+ searchbarSpan.start = searchbarRect.x - urlbarMargin;
+ searchbarSpan.end = searchbarRect.width + searchbarRect.x + urlbarMargin;
+ }
+
+ DevPixelSpan marginLeftPlaceholder;
+ marginLeftPlaceholder.start = toolbarPlaceholderMarginLeft;
+ marginLeftPlaceholder.end = toolbarPlaceholderMarginLeft;
+ if (!noPlaceholderSpans.append(marginLeftPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ if (rtlEnabled) {
+ // If we're RTL, then the springs as ordered in the DOM will be from right
+ // to left, which will break our comparison logic below
+ springs.reverse();
+ }
+
+ for (auto spring : springs) {
+ DevPixelSpan springDevPixels;
+ springDevPixels.start =
+ CSSToDevPixels(spring.start, sCSSToDevPixelScaling) + horizontalOffset;
+ springDevPixels.end =
+ CSSToDevPixels(spring.end, sCSSToDevPixelScaling) + horizontalOffset;
+ if (!noPlaceholderSpans.append(springDevPixels)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ }
+
+ DevPixelSpan marginRightPlaceholder;
+ marginRightPlaceholder.start = sWindowWidth - toolbarPlaceholderMarginRight;
+ marginRightPlaceholder.end = sWindowWidth - toolbarPlaceholderMarginRight;
+ if (!noPlaceholderSpans.append(marginRightPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ Vector<DevPixelSpan, 2> spansToAdd;
+ Unused << spansToAdd.reserve(2);
+ spansToAdd.infallibleAppend(urlbarSpan);
+ if (hasSearchbar) {
+ spansToAdd.infallibleAppend(searchbarSpan);
+ }
+
+ for (auto& toAdd : spansToAdd) {
+ for (auto& span : noPlaceholderSpans) {
+ if (span.start > toAdd.start) {
+ if (!noPlaceholderSpans.insert(&span, toAdd)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ break;
+ }
+ }
+ }
+
+ for (size_t i = 1; i < noPlaceholderSpans.length(); i++) {
+ int start = noPlaceholderSpans[i - 1].end + placeholderMargin;
+ int end = noPlaceholderSpans[i].start - placeholderMargin;
+ if (start + 2 * placeholderBorderRadius >= end) {
+ continue;
+ }
+
+ // The placeholder rects should all be y-aligned.
+ ColorRect placeholderRect = {};
+ placeholderRect.color = currentTheme.toolbarForegroundColor;
+ placeholderRect.x = start;
+ placeholderRect.y = urlbarTextPlaceholder.y;
+ placeholderRect.width = end - start;
+ placeholderRect.height = toolbarPlaceholderHeight;
+ placeholderRect.borderRadius = placeholderBorderRadius;
+ placeholderRect.flipIfRTL = false;
+ if (!rects.append(placeholderRect) ||
+ !sAnimatedRects->append(placeholderRect)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ }
+
+ sTotalChromeHeight = chromeContentDivider.y + chromeContentDivider.height;
+ if (sTotalChromeHeight > sWindowHeight) {
+ return Err(PreXULSkeletonUIError::BadWindowDimensions);
+ }
+
+ if (!sAnimatedRects->append(tabTextPlaceholder) ||
+ !sAnimatedRects->append(urlbarTextPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ sPixelBuffer =
+ (uint32_t*)calloc(sWindowWidth * sTotalChromeHeight, sizeof(uint32_t));
+
+ for (auto& rect : *sAnimatedRects) {
+ if (rtlEnabled && rect.flipIfRTL) {
+ rect.x = sWindowWidth - rect.x - rect.width;
+ }
+ rect.x = std::clamp(rect.x, 0, sWindowWidth);
+ rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
+ rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
+ rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
+ }
+
+ for (auto& rect : rects) {
+ if (rtlEnabled && rect.flipIfRTL) {
+ rect.x = sWindowWidth - rect.x - rect.width;
+ }
+ rect.x = std::clamp(rect.x, 0, sWindowWidth);
+ rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
+ rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
+ rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
+ RasterizeColorRect(rect);
+ }
+
+ HDC hdc = sGetWindowDC(hWnd);
+ if (!hdc) {
+ return Err(PreXULSkeletonUIError::FailedGettingDC);
+ }
+ auto cleanupDC = MakeScopeExit([=] { sReleaseDC(hWnd, hdc); });
+
+ BITMAPINFO chromeBMI = {};
+ chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
+ chromeBMI.bmiHeader.biWidth = sWindowWidth;
+ chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
+ chromeBMI.bmiHeader.biPlanes = 1;
+ chromeBMI.bmiHeader.biBitCount = 32;
+ chromeBMI.bmiHeader.biCompression = BI_RGB;
+
+ // First, we just paint the chrome area with our pixel buffer
+ int scanLinesCopied = sStretchDIBits(
+ hdc, 0, 0, sWindowWidth, sTotalChromeHeight, 0, 0, sWindowWidth,
+ sTotalChromeHeight, sPixelBuffer, &chromeBMI, DIB_RGB_COLORS, SRCCOPY);
+ if (scanLinesCopied == 0) {
+ return Err(PreXULSkeletonUIError::FailedBlitting);
+ }
+
+ // Then, we just fill the rest with FillRect
+ RECT rect = {0, sTotalChromeHeight, sWindowWidth, sWindowHeight};
+ HBRUSH brush =
+ sCreateSolidBrush(RGB((currentTheme.backgroundColor & 0xff0000) >> 16,
+ (currentTheme.backgroundColor & 0x00ff00) >> 8,
+ (currentTheme.backgroundColor & 0x0000ff) >> 0));
+ int fillRectResult = sFillRect(hdc, &rect, brush);
+
+ sDeleteObject(brush);
+
+ if (fillRectResult == 0) {
+ return Err(PreXULSkeletonUIError::FailedFillingBottomRect);
+ }
+
+ scopeExit.release();
+ return Ok();
+}
+
+DWORD WINAPI AnimateSkeletonUI(void* aUnused) {
+ if (!sPixelBuffer || sAnimatedRects->empty()) {
+ return 0;
+ }
+
+ // See the comments above the InterlockedIncrement calls below here - we
+ // atomically flip this up and down around sleep so the main thread doesn't
+ // have to wait for us if we're just sleeping.
+ if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
+ return 0;
+ }
+ // Sleep for two seconds - startups faster than this don't really benefit
+ // from an animation, and we don't want to take away cycles from them.
+ // Startups longer than this, however, are more likely to be blocked on IO,
+ // and thus animating does not substantially impact startup times for them.
+ ::Sleep(2000);
+ if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
+ return 0;
+ }
+
+ // On each of the animated rects (which happen to all be placeholder UI
+ // rects sharing the same color), we want to animate a gradient moving across
+ // the screen from left to right. The gradient starts as the rect's color on,
+ // the left side, changes to the background color of the window by the middle
+ // of the gradient, and then goes back down to the rect's color. To make this
+ // faster than interpolating between the two colors for each pixel for each
+ // frame, we simply create a lookup buffer in which we can look up the color
+ // for a particular offset into the gradient.
+ //
+ // To do this we just interpolate between the two values, and to give the
+ // gradient a smoother transition between colors, we transform the linear
+ // blend amount via the cubic smooth step function (SmoothStep3) to produce
+ // a smooth start and stop for the gradient. We do this for the first half
+ // of the gradient, and then simply copy that backwards for the second half.
+ //
+ // The CSS width of 80 chosen here is effectively is just to match the size
+ // of the animation provided in the design mockup. We define it in CSS pixels
+ // simply because the rest of our UI is based off of CSS scalings.
+ int animationWidth = CSSToDevPixels(80, sCSSToDevPixelScaling);
+ UniquePtr<uint32_t[]> animationLookup =
+ MakeUnique<uint32_t[]>(animationWidth);
+ uint32_t animationColor = sAnimationColor;
+ NormalizedRGB rgbBlend = UintToRGB(animationColor);
+
+ // Build the first half of the lookup table
+ for (int i = 0; i < animationWidth / 2; ++i) {
+ uint32_t baseColor = sToolbarForegroundColor;
+ double blendAmountLinear =
+ static_cast<double>(i) / (static_cast<double>(animationWidth / 2));
+ double blendAmount = SmoothStep3(blendAmountLinear);
+
+ NormalizedRGB rgbBase = UintToRGB(baseColor);
+ NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, blendAmount);
+ animationLookup[i] = RGBToUint(rgb);
+ }
+
+ // Copy the first half of the lookup table into the second half backwards
+ for (int i = animationWidth / 2; i < animationWidth; ++i) {
+ int j = animationWidth - 1 - i;
+ if (j == animationWidth / 2) {
+ // If animationWidth is odd, we'll be left with one pixel at the center.
+ // Just color that as the animation color.
+ animationLookup[i] = animationColor;
+ } else {
+ animationLookup[i] = animationLookup[j];
+ }
+ }
+
+ // The bitmap info remains unchanged throughout the animation - this just
+ // effectively describes the contents of sPixelBuffer
+ BITMAPINFO chromeBMI = {};
+ chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
+ chromeBMI.bmiHeader.biWidth = sWindowWidth;
+ chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
+ chromeBMI.bmiHeader.biPlanes = 1;
+ chromeBMI.bmiHeader.biBitCount = 32;
+ chromeBMI.bmiHeader.biCompression = BI_RGB;
+
+ uint32_t animationIteration = 0;
+
+ int devPixelsPerFrame =
+ CSSToDevPixels(kAnimationCSSPixelsPerFrame, sCSSToDevPixelScaling);
+ int devPixelsExtraWindowSize =
+ CSSToDevPixels(kAnimationCSSExtraWindowSize, sCSSToDevPixelScaling);
+
+ if (::InterlockedCompareExchange(&sAnimationControlFlag, 0, 0)) {
+ // The window got consumed before we were able to draw anything.
+ return 0;
+ }
+
+ while (true) {
+ // The gradient will move across the screen at devPixelsPerFrame at
+ // 60fps, and then loop back to the beginning. However, we add a buffer of
+ // devPixelsExtraWindowSize around the edges so it doesn't immediately
+ // jump back, giving it a more pulsing feel.
+ int animationMin = ((animationIteration * devPixelsPerFrame) %
+ (sWindowWidth + devPixelsExtraWindowSize)) -
+ devPixelsExtraWindowSize / 2;
+ int animationMax = animationMin + animationWidth;
+ // The priorAnimationMin is the beginning of the previous frame's animation.
+ // Since we only want to draw the bits of the image that we updated, we need
+ // to overwrite the left bit of the animation we drew last frame with the
+ // default color.
+ int priorAnimationMin = animationMin - devPixelsPerFrame;
+ animationMin = std::max(0, animationMin);
+ priorAnimationMin = std::max(0, priorAnimationMin);
+ animationMax = std::min((int)sWindowWidth, animationMax);
+
+ // The gradient only affects the specific rects that we put into
+ // sAnimatedRects. So we simply update those rects, and maintain a flag
+ // to avoid drawing when we don't need to.
+ bool updatedAnything = false;
+ for (ColorRect rect : *sAnimatedRects) {
+ bool hadUpdates =
+ RasterizeAnimatedRect(rect, animationLookup.get(), priorAnimationMin,
+ animationMin, animationMax);
+ updatedAnything = updatedAnything || hadUpdates;
+ }
+
+ if (updatedAnything) {
+ HDC hdc = sGetWindowDC(sPreXULSkeletonUIWindow);
+ if (!hdc) {
+ return 0;
+ }
+
+ sStretchDIBits(hdc, priorAnimationMin, 0,
+ animationMax - priorAnimationMin, sTotalChromeHeight,
+ priorAnimationMin, 0, animationMax - priorAnimationMin,
+ sTotalChromeHeight, sPixelBuffer, &chromeBMI,
+ DIB_RGB_COLORS, SRCCOPY);
+
+ sReleaseDC(sPreXULSkeletonUIWindow, hdc);
+ }
+
+ animationIteration++;
+
+ // We coordinate around our sleep here to ensure that the main thread does
+ // not wait on us if we're sleeping. If we don't get 1 here, it means the
+ // window has been consumed and we don't need to sleep. If in
+ // ConsumePreXULSkeletonUIHandle we get a value other than 1 after
+ // incrementing, it means we're sleeping, and that function can assume that
+ // we will safely exit after the sleep because of the observed value of
+ // sAnimationControlFlag.
+ if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
+ return 0;
+ }
+
+ // Note: Sleep does not guarantee an exact time interval. If the system is
+ // busy, for instance, we could easily end up taking several frames longer,
+ // and really we could be left unscheduled for an arbitrarily long time.
+ // This is fine, and we don't really care. We could track how much time this
+ // actually took and jump the animation forward the appropriate amount, but
+ // its not even clear that that's a better user experience. So we leave this
+ // as simple as we can.
+ ::Sleep(16);
+
+ // Here we bring sAnimationControlFlag back down - again, if we don't get a
+ // 0 here it means we consumed the skeleton UI window in the mean time, so
+ // we can simply exit.
+ if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
+ return 0;
+ }
+ }
+}
+
+LRESULT WINAPI PreXULSkeletonUIProc(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam) {
+ // Exposing a generic oleacc proxy for the skeleton isn't useful and may cause
+ // screen readers to report spurious information when the skeleton appears.
+ if (msg == WM_GETOBJECT && sPreXULSkeletonUIWindow) {
+ return E_FAIL;
+ }
+
+ // NOTE: this block was copied from WinUtils.cpp, and needs to be kept in
+ // sync.
+ if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
+ sEnableNonClientDpiScaling(hWnd);
+ }
+
+ // NOTE: this block was paraphrased from the WM_NCCALCSIZE handler in
+ // nsWindow.cpp, and will need to be kept in sync.
+ if (msg == WM_NCCALCSIZE) {
+ RECT* clientRect =
+ wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
+ : (reinterpret_cast<RECT*>(lParam));
+
+ // These match the margins set in browser-tabsintitlebar.js with
+ // default prefs on Windows. Bug 1673092 tracks lining this up with
+ // that more correctly instead of hard-coding it.
+ int horizontalOffset =
+ sNonClientHorizontalMargins -
+ (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
+ int verticalOffset =
+ sNonClientHorizontalMargins -
+ (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
+ clientRect->top = clientRect->top;
+ clientRect->left += horizontalOffset;
+ clientRect->right -= horizontalOffset;
+ clientRect->bottom -= verticalOffset;
+ return 0;
+ }
+
+ return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+}
+
+bool IsSystemDarkThemeEnabled() {
+ DWORD result;
+ HKEY themeKey;
+ DWORD dataLen = sizeof(uint32_t);
+ LPCWSTR keyName =
+ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
+
+ result = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &themeKey);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ AutoCloseRegKey closeKey(themeKey);
+
+ uint32_t lightThemeEnabled;
+ result = ::RegGetValueW(
+ themeKey, nullptr, L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr,
+ reinterpret_cast<PBYTE>(&lightThemeEnabled), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ return !lightThemeEnabled;
+}
+
+ThemeColors GetTheme(ThemeMode themeId) {
+ ThemeColors theme = {};
+ switch (themeId) {
+ case ThemeMode::Dark:
+ // Dark theme or default theme when in dark mode
+
+ // controlled by css variable --toolbar-bgcolor
+ theme.backgroundColor = 0x2b2a33;
+ theme.tabColor = 0x42414d;
+ theme.toolbarForegroundColor = 0x6a6a6d;
+ theme.tabOutlineColor = 0x1c1b22;
+ // controlled by css variable --lwt-accent-color
+ theme.tabBarColor = 0x1c1b22;
+ // controlled by --toolbar-non-lwt-textcolor in browser.css
+ theme.chromeContentDividerColor = 0x0c0c0d;
+ // controlled by css variable --toolbar-field-background-color
+ theme.urlbarColor = 0x42414d;
+ theme.urlbarBorderColor = 0x42414d;
+ theme.animationColor = theme.urlbarColor;
+ return theme;
+ case ThemeMode::Light:
+ case ThemeMode::Default:
+ default:
+ // --toolbar-non-lwt-bgcolor in browser.css
+ theme.backgroundColor = 0xf9f9fb;
+ theme.tabColor = 0xf9f9fb;
+ theme.toolbarForegroundColor = 0xdddde1;
+ theme.tabOutlineColor = 0xdddde1;
+ // found in browser-aero.css ":root[tabsintitlebar]:not(:-moz-lwtheme)"
+ // (set to "hsl(235,33%,19%)")
+ theme.tabBarColor = 0xf0f0f4;
+ // --chrome-content-separator-color in browser.css
+ theme.chromeContentDividerColor = 0xe1e1e2;
+ // controlled by css variable --toolbar-color
+ theme.urlbarColor = 0xffffff;
+ theme.urlbarBorderColor = 0xdddde1;
+ theme.animationColor = theme.backgroundColor;
+ return theme;
+ }
+}
+
+Result<HKEY, PreXULSkeletonUIError> OpenPreXULSkeletonUIRegKey() {
+ HKEY key;
+ DWORD disposition;
+ LSTATUS result =
+ ::RegCreateKeyExW(HKEY_CURRENT_USER, kPreXULSkeletonUIKeyPath, 0, nullptr,
+ 0, KEY_ALL_ACCESS, nullptr, &key, &disposition);
+
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
+ }
+
+ if (disposition == REG_CREATED_NEW_KEY ||
+ disposition == REG_OPENED_EXISTING_KEY) {
+ return key;
+ }
+
+ ::RegCloseKey(key);
+ return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
+}
+
+Result<Ok, PreXULSkeletonUIError> LoadGdi32AndUser32Procedures() {
+ HMODULE user32Dll = ::LoadLibraryW(L"user32");
+ HMODULE gdi32Dll = ::LoadLibraryW(L"gdi32");
+
+ if (!user32Dll || !gdi32Dll) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+
+ auto getThreadDpiAwarenessContext =
+ (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress(
+ user32Dll, "GetThreadDpiAwarenessContext");
+ auto areDpiAwarenessContextsEqual =
+ (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress(
+ user32Dll, "AreDpiAwarenessContextsEqual");
+ if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual &&
+ areDpiAwarenessContextsEqual(getThreadDpiAwarenessContext(),
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
+ // EnableNonClientDpiScaling is optional - we can handle not having it.
+ sEnableNonClientDpiScaling =
+ (EnableNonClientDpiScalingProc)::GetProcAddress(
+ user32Dll, "EnableNonClientDpiScaling");
+ }
+
+ sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress(
+ user32Dll, "GetSystemMetricsForDpi");
+ if (!sGetSystemMetricsForDpi) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sGetDpiForWindow =
+ (GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow");
+ if (!sGetDpiForWindow) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sRegisterClassW =
+ (RegisterClassWProc)::GetProcAddress(user32Dll, "RegisterClassW");
+ if (!sRegisterClassW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sCreateWindowExW =
+ (CreateWindowExWProc)::GetProcAddress(user32Dll, "CreateWindowExW");
+ if (!sCreateWindowExW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sShowWindow = (ShowWindowProc)::GetProcAddress(user32Dll, "ShowWindow");
+ if (!sShowWindow) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sSetWindowPos = (SetWindowPosProc)::GetProcAddress(user32Dll, "SetWindowPos");
+ if (!sSetWindowPos) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sGetWindowDC = (GetWindowDCProc)::GetProcAddress(user32Dll, "GetWindowDC");
+ if (!sGetWindowDC) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sFillRect = (FillRectProc)::GetProcAddress(user32Dll, "FillRect");
+ if (!sFillRect) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sReleaseDC = (ReleaseDCProc)::GetProcAddress(user32Dll, "ReleaseDC");
+ if (!sReleaseDC) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sLoadIconW = (LoadIconWProc)::GetProcAddress(user32Dll, "LoadIconW");
+ if (!sLoadIconW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sLoadCursorW = (LoadCursorWProc)::GetProcAddress(user32Dll, "LoadCursorW");
+ if (!sLoadCursorW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sMonitorFromWindow =
+ (MonitorFromWindowProc)::GetProcAddress(user32Dll, "MonitorFromWindow");
+ if (!sMonitorFromWindow) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sGetMonitorInfoW =
+ (GetMonitorInfoWProc)::GetProcAddress(user32Dll, "GetMonitorInfoW");
+ if (!sGetMonitorInfoW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sSetWindowLongPtrW =
+ (SetWindowLongPtrWProc)::GetProcAddress(user32Dll, "SetWindowLongPtrW");
+ if (!sSetWindowLongPtrW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sStretchDIBits =
+ (StretchDIBitsProc)::GetProcAddress(gdi32Dll, "StretchDIBits");
+ if (!sStretchDIBits) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sCreateSolidBrush =
+ (CreateSolidBrushProc)::GetProcAddress(gdi32Dll, "CreateSolidBrush");
+ if (!sCreateSolidBrush) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sDeleteObject = (DeleteObjectProc)::GetProcAddress(gdi32Dll, "DeleteObject");
+ if (!sDeleteObject) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+
+ return Ok();
+}
+
+// Strips "--", "-", and "/" from the front of the arg if one of those exists,
+// returning `arg + 2`, `arg + 1`, and `arg + 1` respectively. If none of these
+// prefixes are found, the argument is not a flag, and nullptr is returned.
+const char* NormalizeFlag(const char* arg) {
+ if (strstr(arg, "--") == arg) {
+ return arg + 2;
+ }
+
+ if (arg[0] == '-') {
+ return arg + 1;
+ }
+
+ if (arg[0] == '/') {
+ return arg + 1;
+ }
+
+ return nullptr;
+}
+
+static bool EnvHasValue(const char* name) {
+ const char* val = getenv(name);
+ return (val && *val);
+}
+
+// Ensures that we only see arguments in the command line which are acceptable.
+// This is based on manual inspection of the list of arguments listed in the MDN
+// page for Gecko/Firefox commandline options:
+// https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options
+// Broadly speaking, we want to reject any argument which causes us to show
+// something other than the default window at its normal size. Here is a non-
+// exhaustive list of command line options we want to *exclude*:
+//
+// -ProfileManager : This will display the profile manager window, which does
+// not match the skeleton UI at all.
+//
+// -CreateProfile : This will display a firefox window with the default
+// screen position and size, and not the position and size
+// which we have recorded in the registry.
+//
+// -P <profile> : This could cause us to display firefox with a position
+// and size of a different profile than that in which we
+// were previously running.
+//
+// -width, -height : This will cause the width and height values in the
+// registry to be incorrect.
+//
+// -kiosk : See above.
+//
+// -headless : This one should be rather obvious.
+//
+// -migration : This will start with the import wizard, which of course
+// does not match the skeleton UI.
+//
+// -private-window : This is tricky, but the colors of the main content area
+// make this not feel great with the white content of the
+// default skeleton UI.
+//
+// NOTE: we generally want to skew towards erroneous rejections of the command
+// line rather than erroneous approvals. The consequence of a bad rejection
+// is that we don't show the skeleton UI, which is business as usual. The
+// consequence of a bad approval is that we show it when we're not supposed to,
+// which is visually jarring and can also be unpredictable - there's no
+// guarantee that the code which handles the non-default window is set up to
+// properly handle the transition from the skeleton UI window.
+static Result<Ok, PreXULSkeletonUIError> ValidateCmdlineArguments(
+ int argc, char** argv, bool* explicitProfile) {
+ const char* approvedArgumentsArray[] = {
+ // These won't cause the browser to be visualy different in any way
+ "new-instance", "no-remote", "browser", "foreground", "setDefaultBrowser",
+ "attach-console", "wait-for-browser", "osint",
+
+ // These will cause the chrome to be a bit different or extra windows to
+ // be created, but overall the skeleton UI should still be broadly
+ // correct enough.
+ "new-tab", "new-window",
+
+ // To the extent possible, we want to ensure that existing tests cover
+ // the skeleton UI, so we need to allow marionette
+ "marionette",
+
+ // These will cause the content area to appear different, but won't
+ // meaningfully affect the chrome
+ "preferences", "search", "url",
+
+#ifndef MOZILLA_OFFICIAL
+ // On local builds, we want to allow -profile, because it's how `mach run`
+ // operates, and excluding that would create an unnecessary blind spot for
+ // Firefox devs.
+ "profile"
+#endif
+
+ // There are other arguments which are likely okay. However, they are
+ // not included here because this list is not intended to be
+ // exhaustive - it only intends to green-light some somewhat commonly
+ // used arguments. We want to err on the side of an unnecessary
+ // rejection of the command line.
+ };
+
+ int approvedArgumentsArraySize =
+ sizeof(approvedArgumentsArray) / sizeof(approvedArgumentsArray[0]);
+ Vector<const char*> approvedArguments;
+ if (!approvedArguments.reserve(approvedArgumentsArraySize)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ for (int i = 0; i < approvedArgumentsArraySize; ++i) {
+ approvedArguments.infallibleAppend(approvedArgumentsArray[i]);
+ }
+
+#ifdef MOZILLA_OFFICIAL
+ int profileArgIndex = -1;
+ // If we're running mochitests or direct marionette tests, those specify a
+ // temporary profile, and we want to ensure that we get the added coverage
+ // from those.
+ for (int i = 1; i < argc; ++i) {
+ const char* flag = NormalizeFlag(argv[i]);
+ if (flag && !strcmp(flag, "marionette")) {
+ if (!approvedArguments.append("profile")) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ profileArgIndex = approvedArguments.length() - 1;
+
+ break;
+ }
+ }
+#else
+ int profileArgIndex = approvedArguments.length() - 1;
+#endif
+
+ for (int i = 1; i < argc; ++i) {
+ const char* flag = NormalizeFlag(argv[i]);
+ if (!flag) {
+ // If this is not a flag, then we interpret it as a URL, similar to
+ // BrowserContentHandler.sys.mjs. Some command line options take
+ // additional arguments, which may or may not be URLs. We don't need to
+ // know this, because we don't need to parse them out; we just rely on the
+ // assumption that if arg X is actually a parameter for the preceding
+ // arg Y, then X must not look like a flag (starting with "--", "-",
+ // or "/").
+ //
+ // The most important thing here is the assumption that if something is
+ // going to meaningfully alter the appearance of the window itself, it
+ // must be a flag.
+ continue;
+ }
+
+ bool approved = false;
+ for (const char* approvedArg : approvedArguments) {
+ // We do a case-insensitive compare here with _stricmp. Even though some
+ // of these arguments are *not* read as case-insensitive, others *are*.
+ // Similar to the flag logic above, we don't really care about this
+ // distinction, because we don't need to parse the arguments - we just
+ // rely on the assumption that none of the listed flags in our
+ // approvedArguments are overloaded in such a way that a different
+ // casing would visually alter the firefox window.
+ if (!_stricmp(flag, approvedArg)) {
+ approved = true;
+
+ if (i == profileArgIndex) {
+ *explicitProfile = true;
+ }
+ break;
+ }
+ }
+
+ if (!approved) {
+ return Err(PreXULSkeletonUIError::Cmdline);
+ }
+ }
+
+ return Ok();
+}
+
+static Result<Ok, PreXULSkeletonUIError> ValidateEnvVars() {
+ if (EnvHasValue("MOZ_SAFE_MODE_RESTART") ||
+ EnvHasValue("MOZ_APP_SILENT_START") ||
+ EnvHasValue("MOZ_RESET_PROFILE_RESTART") || EnvHasValue("MOZ_HEADLESS") ||
+ (EnvHasValue("XRE_PROFILE_PATH") &&
+ !EnvHasValue("MOZ_SKELETON_UI_RESTARTING"))) {
+ return Err(PreXULSkeletonUIError::EnvVars);
+ }
+
+ return Ok();
+}
+
+static bool VerifyWindowDimensions(uint32_t windowWidth,
+ uint32_t windowHeight) {
+ return windowWidth <= kMaxWindowWidth && windowHeight <= kMaxWindowHeight;
+}
+
+static Result<Vector<CSSPixelSpan>, PreXULSkeletonUIError> ReadRegCSSPixelSpans(
+ HKEY regKey, const std::wstring& valueName) {
+ DWORD dataLen = 0;
+ LSTATUS result = ::RegQueryValueExW(regKey, valueName.c_str(), nullptr,
+ nullptr, nullptr, &dataLen);
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ if (dataLen % (2 * sizeof(double)) != 0) {
+ return Err(PreXULSkeletonUIError::CorruptData);
+ }
+
+ auto buffer = MakeUniqueFallible<wchar_t[]>(dataLen);
+ if (!buffer) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ result =
+ ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
+ nullptr, reinterpret_cast<PBYTE>(buffer.get()), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ Vector<CSSPixelSpan> resultVector;
+ double* asDoubles = reinterpret_cast<double*>(buffer.get());
+ for (size_t i = 0; i < dataLen / (2 * sizeof(double)); i++) {
+ CSSPixelSpan span = {};
+ span.start = *(asDoubles++);
+ span.end = *(asDoubles++);
+ if (!resultVector.append(span)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ }
+
+ return resultVector;
+}
+
+static Result<double, PreXULSkeletonUIError> ReadRegDouble(
+ HKEY regKey, const std::wstring& valueName) {
+ double value = 0;
+ DWORD dataLen = sizeof(double);
+ LSTATUS result =
+ ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
+ nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
+ if (result != ERROR_SUCCESS || dataLen != sizeof(double)) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ return value;
+}
+
+static Result<uint32_t, PreXULSkeletonUIError> ReadRegUint(
+ HKEY regKey, const std::wstring& valueName) {
+ DWORD value = 0;
+ DWORD dataLen = sizeof(uint32_t);
+ LSTATUS result =
+ ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_DWORD,
+ nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ return value;
+}
+
+static Result<bool, PreXULSkeletonUIError> ReadRegBool(
+ HKEY regKey, const std::wstring& valueName) {
+ uint32_t value;
+ MOZ_TRY_VAR(value, ReadRegUint(regKey, valueName));
+ return !!value;
+}
+
+static Result<Ok, PreXULSkeletonUIError> WriteRegCSSPixelSpans(
+ HKEY regKey, const std::wstring& valueName, const CSSPixelSpan* spans,
+ int spansLength) {
+ // No guarantee on the packing of CSSPixelSpan. We could #pragma it, but it's
+ // also trivial to just copy them into a buffer of doubles.
+ auto doubles = MakeUnique<double[]>(spansLength * 2);
+ for (int i = 0; i < spansLength; ++i) {
+ doubles[i * 2] = spans[i].start;
+ doubles[i * 2 + 1] = spans[i].end;
+ }
+
+ LSTATUS result =
+ ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
+ reinterpret_cast<const BYTE*>(doubles.get()),
+ spansLength * sizeof(double) * 2);
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+ return Ok();
+}
+
+static Result<Ok, PreXULSkeletonUIError> WriteRegDouble(
+ HKEY regKey, const std::wstring& valueName, double value) {
+ LSTATUS result =
+ ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
+ reinterpret_cast<const BYTE*>(&value), sizeof(value));
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ return Ok();
+}
+
+static Result<Ok, PreXULSkeletonUIError> WriteRegUint(
+ HKEY regKey, const std::wstring& valueName, uint32_t value) {
+ LSTATUS result =
+ ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_DWORD,
+ reinterpret_cast<PBYTE>(&value), sizeof(value));
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ return Ok();
+}
+
+static Result<Ok, PreXULSkeletonUIError> WriteRegBool(
+ HKEY regKey, const std::wstring& valueName, bool value) {
+ return WriteRegUint(regKey, valueName, value ? 1 : 0);
+}
+
+static Result<Ok, PreXULSkeletonUIError> CreateAndStorePreXULSkeletonUIImpl(
+ HINSTANCE hInstance, int argc, char** argv) {
+ // Initializing COM below may load modules via SetWindowHookEx, some of
+ // which may modify the executable's IAT for ntdll.dll. If that happens,
+ // this browser process fails to launch sandbox processes because we cannot
+ // copy a modified IAT into a remote process (See SandboxBroker::LaunchApp).
+ // To prevent that, we cache the intact IAT before COM initialization.
+ // If EAF+ is enabled, CacheNtDllThunk() causes a crash, but EAF+ will
+ // also prevent an injected module from parsing the PE headers and modifying
+ // the IAT. Therefore, we can skip CacheNtDllThunk().
+ if (!mozilla::IsEafPlusEnabled()) {
+ CacheNtDllThunk();
+ }
+
+ // NOTE: it's important that we initialize sProcessRuntime before showing a
+ // window. Historically we ran into issues where showing the window would
+ // cause an accessibility win event to fire, which could cause in-process
+ // system or third party components to initialize COM and prevent us from
+ // initializing it with important settings we need.
+
+ // Some COM settings are global to the process and must be set before any non-
+ // trivial COM is run in the application. Since these settings may affect
+ // stability, we should instantiate COM ASAP so that we can ensure that these
+ // global settings are configured before anything can interfere.
+ sProcessRuntime = new mscom::ProcessRuntime(
+ mscom::ProcessRuntime::ProcessCategory::GeckoBrowserParent);
+
+ const TimeStamp skeletonStart = TimeStamp::Now();
+
+ HKEY regKey;
+ MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+
+ std::wstring regProgressName =
+ GetRegValueName(binPath.get(), sProgressSuffix);
+ auto progressResult = ReadRegUint(regKey, regProgressName);
+ if (!progressResult.isErr() &&
+ progressResult.unwrap() !=
+ static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed)) {
+ return Err(PreXULSkeletonUIError::CrashedOnce);
+ }
+
+ MOZ_TRY(
+ WriteRegUint(regKey, regProgressName,
+ static_cast<uint32_t>(PreXULSkeletonUIProgress::Started)));
+ auto writeCompletion = MakeScopeExit([&] {
+ Unused << WriteRegUint(
+ regKey, regProgressName,
+ static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed));
+ });
+
+ MOZ_TRY(GetSkeletonUILock());
+
+ bool explicitProfile = false;
+ MOZ_TRY(ValidateCmdlineArguments(argc, argv, &explicitProfile));
+ MOZ_TRY(ValidateEnvVars());
+
+ auto enabledResult =
+ ReadRegBool(regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix));
+ if (enabledResult.isErr()) {
+ return Err(PreXULSkeletonUIError::EnabledKeyDoesNotExist);
+ }
+ if (!enabledResult.unwrap()) {
+ return Err(PreXULSkeletonUIError::Disabled);
+ }
+ sPreXULSkeletonUIEnabled = true;
+
+ MOZ_ASSERT(!sAnimatedRects);
+ sAnimatedRects = new Vector<ColorRect>();
+
+ MOZ_TRY(LoadGdi32AndUser32Procedures());
+
+ if (!explicitProfile) {
+ MOZ_TRY(CheckForStartWithLastProfile());
+ }
+
+ WNDCLASSW wc;
+ wc.style = CS_DBLCLKS;
+ wc.lpfnWndProc = PreXULSkeletonUIProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = hInstance;
+ wc.hIcon = sLoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
+ wc.hCursor = sLoadCursorW(hInstance, gIDCWait);
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+
+ // TODO: just ensure we disable this if we've overridden the window class
+ wc.lpszClassName = L"MozillaWindowClass";
+
+ if (!sRegisterClassW(&wc)) {
+ return Err(PreXULSkeletonUIError::FailedRegisteringWindowClass);
+ }
+
+ uint32_t screenX;
+ MOZ_TRY_VAR(screenX, ReadRegUint(regKey, GetRegValueName(binPath.get(),
+ sScreenXRegSuffix)));
+ uint32_t screenY;
+ MOZ_TRY_VAR(screenY, ReadRegUint(regKey, GetRegValueName(binPath.get(),
+ sScreenYRegSuffix)));
+ uint32_t windowWidth;
+ MOZ_TRY_VAR(
+ windowWidth,
+ ReadRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix)));
+ uint32_t windowHeight;
+ MOZ_TRY_VAR(
+ windowHeight,
+ ReadRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix)));
+ MOZ_TRY_VAR(
+ sMaximized,
+ ReadRegBool(regKey, GetRegValueName(binPath.get(), sMaximizedRegSuffix)));
+ MOZ_TRY_VAR(
+ sCSSToDevPixelScaling,
+ ReadRegDouble(regKey, GetRegValueName(binPath.get(),
+ sCssToDevPixelScalingRegSuffix)));
+ Vector<CSSPixelSpan> urlbar;
+ MOZ_TRY_VAR(urlbar,
+ ReadRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix)));
+ Vector<CSSPixelSpan> searchbar;
+ MOZ_TRY_VAR(searchbar,
+ ReadRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix)));
+ Vector<CSSPixelSpan> springs;
+ MOZ_TRY_VAR(springs, ReadRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(),
+ sSpringsCSSRegSuffix)));
+
+ if (urlbar.empty() || searchbar.empty()) {
+ return Err(PreXULSkeletonUIError::CorruptData);
+ }
+
+ EnumSet<SkeletonUIFlag, uint32_t> flags;
+ uint32_t flagsUint;
+ MOZ_TRY_VAR(flagsUint, ReadRegUint(regKey, GetRegValueName(binPath.get(),
+ sFlagsRegSuffix)));
+ flags.deserialize(flagsUint);
+
+ if (flags.contains(SkeletonUIFlag::TouchDensity) ||
+ flags.contains(SkeletonUIFlag::CompactDensity)) {
+ return Err(PreXULSkeletonUIError::BadUIDensity);
+ }
+
+ uint32_t theme;
+ MOZ_TRY_VAR(theme, ReadRegUint(regKey, GetRegValueName(binPath.get(),
+ sThemeRegSuffix)));
+ ThemeMode themeMode = static_cast<ThemeMode>(theme);
+ if (themeMode == ThemeMode::Default) {
+ if (IsSystemDarkThemeEnabled() == true) {
+ themeMode = ThemeMode::Dark;
+ }
+ }
+ ThemeColors currentTheme = GetTheme(themeMode);
+
+ if (!VerifyWindowDimensions(windowWidth, windowHeight)) {
+ return Err(PreXULSkeletonUIError::BadWindowDimensions);
+ }
+
+ int showCmd = SW_SHOWNORMAL;
+ DWORD windowStyle = kPreXULSkeletonUIWindowStyle;
+ if (sMaximized) {
+ showCmd = SW_SHOWMAXIMIZED;
+ windowStyle |= WS_MAXIMIZE;
+ }
+
+ sPreXULSkeletonUIWindow =
+ sCreateWindowExW(kPreXULSkeletonUIWindowStyleEx, L"MozillaWindowClass",
+ L"", windowStyle, screenX, screenY, windowWidth,
+ windowHeight, nullptr, nullptr, hInstance, nullptr);
+ if (!sPreXULSkeletonUIWindow) {
+ return Err(PreXULSkeletonUIError::CreateWindowFailed);
+ }
+
+ sShowWindow(sPreXULSkeletonUIWindow, showCmd);
+
+ sDpi = sGetDpiForWindow(sPreXULSkeletonUIWindow);
+ sNonClientHorizontalMargins =
+ sGetSystemMetricsForDpi(SM_CXFRAME, sDpi) +
+ sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
+ sNonClientVerticalMargins = sGetSystemMetricsForDpi(SM_CYFRAME, sDpi) +
+ sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
+
+ if (sMaximized) {
+ HMONITOR monitor =
+ sMonitorFromWindow(sPreXULSkeletonUIWindow, MONITOR_DEFAULTTONULL);
+ if (!monitor) {
+ // NOTE: we specifically don't clean up the window here. If we're unable
+ // to finish setting up the window how we want it, we still need to keep
+ // it around and consume it with the first real toplevel window we
+ // create, to avoid flickering.
+ return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
+ }
+ MONITORINFO mi = {sizeof(MONITORINFO)};
+ if (!sGetMonitorInfoW(monitor, &mi)) {
+ return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
+ }
+
+ sWindowWidth =
+ mi.rcWork.right - mi.rcWork.left + sNonClientHorizontalMargins * 2;
+ sWindowHeight =
+ mi.rcWork.bottom - mi.rcWork.top + sNonClientVerticalMargins * 2;
+ } else {
+ sWindowWidth = static_cast<int>(windowWidth);
+ sWindowHeight = static_cast<int>(windowHeight);
+ }
+
+ sSetWindowPos(sPreXULSkeletonUIWindow, 0, 0, 0, 0, 0,
+ SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
+ SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
+ MOZ_TRY(DrawSkeletonUI(sPreXULSkeletonUIWindow, urlbar[0], searchbar[0],
+ springs, currentTheme, flags));
+ if (sAnimatedRects) {
+ sPreXULSKeletonUIAnimationThread = ::CreateThread(
+ nullptr, 256 * 1024, AnimateSkeletonUI, nullptr, 0, nullptr);
+ }
+
+ BASE_PROFILER_MARKER_UNTYPED(
+ "CreatePreXULSkeletonUI", OTHER,
+ MarkerTiming::IntervalUntilNowFrom(skeletonStart));
+
+ return Ok();
+}
+
+void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc,
+ char** argv) {
+ auto result = CreateAndStorePreXULSkeletonUIImpl(hInstance, argc, argv);
+
+ if (result.isErr()) {
+ sErrorReason.emplace(result.unwrapErr());
+ }
+}
+
+void CleanupProcessRuntime() {
+ delete sProcessRuntime;
+ sProcessRuntime = nullptr;
+}
+
+bool WasPreXULSkeletonUIMaximized() { return sMaximized; }
+
+bool GetPreXULSkeletonUIWasShown() {
+ return sPreXULSkeletonUIShown || !!sPreXULSkeletonUIWindow;
+}
+
+HWND ConsumePreXULSkeletonUIHandle() {
+ // NOTE: we need to make sure that everything that runs here is a no-op if
+ // it failed to be set, which is a possibility. If anything fails to be set
+ // we don't want to clean everything up right away, because if we have a
+ // blank window up, we want that to stick around and get consumed by nsWindow
+ // as normal, otherwise the window will flicker in and out, which we imagine
+ // is unpleasant.
+
+ // If we don't get 1 here, it means the thread is actually just sleeping, so
+ // we don't need to worry about giving out ownership of the window, because
+ // the thread will simply exit after its sleep. However, if it is 1, we need
+ // to wait for the thread to exit to be safe, as it could be doing anything.
+ if (InterlockedIncrement(&sAnimationControlFlag) == 1) {
+ ::WaitForSingleObject(sPreXULSKeletonUIAnimationThread, INFINITE);
+ }
+ ::CloseHandle(sPreXULSKeletonUIAnimationThread);
+ sPreXULSKeletonUIAnimationThread = nullptr;
+ HWND result = sPreXULSkeletonUIWindow;
+ sPreXULSkeletonUIWindow = nullptr;
+ free(sPixelBuffer);
+ sPixelBuffer = nullptr;
+ delete sAnimatedRects;
+ sAnimatedRects = nullptr;
+
+ return result;
+}
+
+Maybe<PreXULSkeletonUIError> GetPreXULSkeletonUIErrorReason() {
+ return sErrorReason;
+}
+
+Result<Ok, PreXULSkeletonUIError> PersistPreXULSkeletonUIValues(
+ const SkeletonUISettings& settings) {
+ if (!sPreXULSkeletonUIEnabled) {
+ return Err(PreXULSkeletonUIError::Disabled);
+ }
+
+ HKEY regKey;
+ MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+
+ MOZ_TRY(WriteRegUint(regKey,
+ GetRegValueName(binPath.get(), sScreenXRegSuffix),
+ settings.screenX));
+ MOZ_TRY(WriteRegUint(regKey,
+ GetRegValueName(binPath.get(), sScreenYRegSuffix),
+ settings.screenY));
+ MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix),
+ settings.width));
+ MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix),
+ settings.height));
+
+ MOZ_TRY(WriteRegBool(regKey,
+ GetRegValueName(binPath.get(), sMaximizedRegSuffix),
+ settings.maximized));
+
+ EnumSet<SkeletonUIFlag, uint32_t> flags;
+ if (settings.menubarShown) {
+ flags += SkeletonUIFlag::MenubarShown;
+ }
+ if (settings.bookmarksToolbarShown) {
+ flags += SkeletonUIFlag::BookmarksToolbarShown;
+ }
+ if (settings.rtlEnabled) {
+ flags += SkeletonUIFlag::RtlEnabled;
+ }
+ if (settings.uiDensity == SkeletonUIDensity::Touch) {
+ flags += SkeletonUIFlag::TouchDensity;
+ }
+ if (settings.uiDensity == SkeletonUIDensity::Compact) {
+ flags += SkeletonUIFlag::CompactDensity;
+ }
+
+ uint32_t flagsUint = flags.serialize();
+ MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sFlagsRegSuffix),
+ flagsUint));
+
+ MOZ_TRY(WriteRegDouble(
+ regKey, GetRegValueName(binPath.get(), sCssToDevPixelScalingRegSuffix),
+ settings.cssToDevPixelScaling));
+ MOZ_TRY(WriteRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix),
+ &settings.urlbarSpan, 1));
+ MOZ_TRY(WriteRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix),
+ &settings.searchbarSpan, 1));
+ MOZ_TRY(WriteRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sSpringsCSSRegSuffix),
+ settings.springs.begin(), settings.springs.length()));
+
+ return Ok();
+}
+
+MFBT_API bool GetPreXULSkeletonUIEnabled() { return sPreXULSkeletonUIEnabled; }
+
+MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIEnabledIfAllowed(
+ bool value) {
+ // If the pre-XUL skeleton UI was disallowed for some reason, we just want to
+ // ignore changes to the registry. An example of how things could be bad if
+ // we didn't: someone running firefox with the -profile argument could
+ // turn the skeleton UI on or off for the default profile. Turning it off
+ // maybe isn't so bad (though it's likely still incorrect), but turning it
+ // on could be bad if the user had specifically disabled it for a profile for
+ // some reason. Ultimately there's no correct decision here, and the
+ // messiness of this is just a consequence of sharing the registry values
+ // across profiles. However, whatever ill effects we observe should be
+ // correct themselves after one session.
+ if (PreXULSkeletonUIDisallowed()) {
+ return Err(PreXULSkeletonUIError::Disabled);
+ }
+
+ HKEY regKey;
+ MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+ MOZ_TRY(WriteRegBool(
+ regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix), value));
+
+ if (!sPreXULSkeletonUIEnabled && value) {
+ // We specifically don't care if we fail to get this lock. We just want to
+ // do our best effort to lock it so that future instances don't create
+ // skeleton UIs while we're still running, since they will immediately exit
+ // and tell us to open a new window.
+ Unused << GetSkeletonUILock();
+ }
+
+ sPreXULSkeletonUIEnabled = value;
+
+ return Ok();
+}
+
+MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIThemeId(
+ ThemeMode theme) {
+ if (theme == sTheme) {
+ return Ok();
+ }
+ sTheme = theme;
+
+ // If we fail below, invalidate sTheme
+ auto invalidateTheme = MakeScopeExit([] { sTheme = ThemeMode::Invalid; });
+
+ HKEY regKey;
+ MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+ MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sThemeRegSuffix),
+ static_cast<uint32_t>(theme)));
+
+ invalidateTheme.release();
+ return Ok();
+}
+
+MFBT_API void PollPreXULSkeletonUIEvents() {
+ if (sPreXULSkeletonUIEnabled && sPreXULSkeletonUIWindow) {
+ MSG outMsg = {};
+ PeekMessageW(&outMsg, sPreXULSkeletonUIWindow, 0, 0, 0);
+ }
+}
+
+Result<Ok, PreXULSkeletonUIError> NotePreXULSkeletonUIRestarting() {
+ if (!sPreXULSkeletonUIEnabled) {
+ return Err(PreXULSkeletonUIError::Disabled);
+ }
+
+ ::SetEnvironmentVariableW(L"MOZ_SKELETON_UI_RESTARTING", L"1");
+
+ // We assume that we are going to exit the application very shortly after
+ // this. It should thus be fine to release this lock, and we'll need to,
+ // since during a restart we launch the new instance before closing this
+ // one.
+ if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(sPreXULSKeletonUILockFile);
+ }
+ return Ok();
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/PreXULSkeletonUI.h b/mozglue/misc/PreXULSkeletonUI.h
new file mode 100644
index 0000000000..c0549cd4e6
--- /dev/null
+++ b/mozglue/misc/PreXULSkeletonUI.h
@@ -0,0 +1,183 @@
+/* -*- 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 PreXULSkeletonUI_h_
+#define PreXULSkeletonUI_h_
+
+#include <windows.h>
+#include "mozilla/EnumSet.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/Types.h"
+#include "mozilla/Vector.h"
+
+namespace mozilla {
+
+// These unfortunately need to be kept in sync with the window style and
+// extended window style computations in nsWindow. Luckily those styles seem
+// to not vary based off of any user settings for the initial toplevel window,
+// so we're safe here for now.
+static const DWORD kPreXULSkeletonUIWindowStyle =
+ WS_CLIPCHILDREN | WS_DLGFRAME | WS_BORDER | WS_MAXIMIZEBOX |
+ WS_MINIMIZEBOX | WS_SIZEBOX | WS_SYSMENU;
+static const DWORD kPreXULSkeletonUIWindowStyleEx = WS_EX_WINDOWEDGE;
+
+struct CSSPixelSpan {
+ double start;
+ double end;
+};
+
+struct DevPixelSpan {
+ int start;
+ int end;
+};
+
+enum class SkeletonUIDensity { Default, Touch, Compact };
+
+struct SkeletonUISettings {
+ uint32_t screenX;
+ uint32_t screenY;
+ uint32_t width;
+ uint32_t height;
+ CSSPixelSpan urlbarSpan;
+ CSSPixelSpan searchbarSpan;
+ double cssToDevPixelScaling;
+ Vector<CSSPixelSpan> springs;
+ bool maximized;
+ bool menubarShown;
+ bool bookmarksToolbarShown;
+ bool rtlEnabled;
+ SkeletonUIDensity uiDensity;
+};
+
+enum class ThemeMode : uint32_t { Invalid, Default, Dark, Light };
+
+enum class SkeletonUIFlag : uint8_t {
+ MenubarShown,
+ BookmarksToolbarShown,
+ RtlEnabled,
+ TouchDensity,
+ CompactDensity,
+};
+
+struct ThemeColors {
+ uint32_t backgroundColor;
+ uint32_t toolbarForegroundColor;
+ uint32_t tabBarColor;
+ uint32_t tabColor;
+ uint32_t tabOutlineColor;
+ uint32_t chromeContentDividerColor;
+ uint32_t urlbarColor;
+ uint32_t urlbarBorderColor;
+ uint32_t animationColor;
+};
+
+enum class PreXULSkeletonUIError : uint32_t {
+ None,
+ Disabled,
+ EnabledKeyDoesNotExist,
+ OOM,
+ Cmdline,
+ EnvVars,
+ FailedToOpenRegistryKey,
+ RegistryError,
+ FailedLoadingDynamicProcs,
+ FailedGettingLock,
+ FilesystemFailure,
+ NoStartWithLastProfile,
+ FailedRegisteringWindowClass,
+ CorruptData,
+ BadWindowDimensions,
+ FailedGettingMonitorInfo,
+ CreateWindowFailed,
+ FailedGettingDC,
+ FailedBlitting,
+ FailedFillingBottomRect,
+ CrashedOnce,
+ BadUIDensity,
+ Unknown,
+};
+
+inline const wchar_t* GetPreXULSkeletonUIErrorString(
+ PreXULSkeletonUIError error) {
+ switch (error) {
+ case PreXULSkeletonUIError::None:
+ return L"None";
+ case PreXULSkeletonUIError::Disabled:
+ return L"Disabled";
+ case PreXULSkeletonUIError::OOM:
+ return L"OOM";
+ case PreXULSkeletonUIError::Cmdline:
+ return L"Cmdline";
+ case PreXULSkeletonUIError::EnvVars:
+ return L"EnvVars";
+ case PreXULSkeletonUIError::FailedToOpenRegistryKey:
+ return L"FailedToOpenRegistryKey";
+ case PreXULSkeletonUIError::RegistryError:
+ return L"RegistryError";
+ case PreXULSkeletonUIError::FailedLoadingDynamicProcs:
+ return L"FailedLoadingDynamicProcs";
+ case PreXULSkeletonUIError::FailedGettingLock:
+ return L"FailedGettingLock";
+ case PreXULSkeletonUIError::FilesystemFailure:
+ return L"FilesystemFailure";
+ case PreXULSkeletonUIError::NoStartWithLastProfile:
+ return L"NoStartWithLastProfile";
+ case PreXULSkeletonUIError::FailedRegisteringWindowClass:
+ return L"FailedRegisteringWindowClass";
+ case PreXULSkeletonUIError::CorruptData:
+ return L"CorruptData";
+ case PreXULSkeletonUIError::BadWindowDimensions:
+ return L"BadWindowDimensions";
+ case PreXULSkeletonUIError::FailedGettingMonitorInfo:
+ return L"FailedGettingMonitorInfo";
+ case PreXULSkeletonUIError::EnabledKeyDoesNotExist:
+ return L"EnabledKeyDoesNotExist";
+ case PreXULSkeletonUIError::CreateWindowFailed:
+ return L"CreateWindowFailed";
+ case PreXULSkeletonUIError::FailedGettingDC:
+ return L"FailedGettingDC";
+ case PreXULSkeletonUIError::FailedBlitting:
+ return L"FailedBlitting";
+ case PreXULSkeletonUIError::FailedFillingBottomRect:
+ return L"FailedFillingBottomRect";
+ case PreXULSkeletonUIError::CrashedOnce:
+ return L"CrashedOnce";
+ case PreXULSkeletonUIError::BadUIDensity:
+ return L"BadUIDensity";
+ case PreXULSkeletonUIError::Unknown:
+ return L"Unknown";
+ }
+
+ MOZ_ASSERT_UNREACHABLE();
+ return L"Unknown";
+}
+
+enum class PreXULSkeletonUIProgress : uint32_t {
+ Started,
+ Completed,
+};
+
+MFBT_API void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc,
+ char** argv);
+MFBT_API void CleanupProcessRuntime();
+MFBT_API bool GetPreXULSkeletonUIWasShown();
+MFBT_API HWND ConsumePreXULSkeletonUIHandle();
+MFBT_API Maybe<PreXULSkeletonUIError> GetPreXULSkeletonUIErrorReason();
+MFBT_API bool WasPreXULSkeletonUIMaximized();
+MFBT_API Result<Ok, PreXULSkeletonUIError> PersistPreXULSkeletonUIValues(
+ const SkeletonUISettings& settings);
+MFBT_API bool GetPreXULSkeletonUIEnabled();
+MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIEnabledIfAllowed(
+ bool value);
+MFBT_API void PollPreXULSkeletonUIEvents();
+MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIThemeId(
+ ThemeMode theme);
+MFBT_API Result<Ok, PreXULSkeletonUIError> NotePreXULSkeletonUIRestarting();
+
+} // namespace mozilla
+
+#endif
diff --git a/mozglue/misc/Printf.cpp b/mozglue/misc/Printf.cpp
new file mode 100644
index 0000000000..7313495612
--- /dev/null
+++ b/mozglue/misc/Printf.cpp
@@ -0,0 +1,1101 @@
+/* -*- 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/. */
+
+/*
+ * Portable safe sprintf code.
+ *
+ * Author: Kipp E.B. Hickman
+ */
+
+#include "double-conversion/double-to-string.h"
+#include "mozilla/AllocPolicy.h"
+#include "mozilla/Printf.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Vector.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(XP_WIN)
+# include <windows.h>
+#endif
+
+using double_conversion::DoubleToStringConverter;
+using DTSC = DoubleToStringConverter;
+
+/*
+ * Numbered Argument State
+ */
+struct NumArgState {
+ int type; // type of the current ap
+ va_list ap; // point to the corresponding position on ap
+};
+
+using NumArgStateVector =
+ mozilla::Vector<NumArgState, 20, mozilla::MallocAllocPolicy>;
+
+// For values up to and including TYPE_DOUBLE, the lowest bit indicates
+// whether the type is signed (0) or unsigned (1).
+#define TYPE_SHORT 0
+#define TYPE_USHORT 1
+#define TYPE_INTN 2
+#define TYPE_UINTN 3
+#define TYPE_LONG 4
+#define TYPE_ULONG 5
+#define TYPE_LONGLONG 6
+#define TYPE_ULONGLONG 7
+#define TYPE_DOUBLE 8
+#define TYPE_STRING 9
+#define TYPE_INTSTR 10
+#define TYPE_POINTER 11
+#if defined(XP_WIN)
+# define TYPE_WSTRING 12
+#endif
+#define TYPE_SCHAR 14
+#define TYPE_UCHAR 15
+#define TYPE_UNKNOWN 20
+
+#define FLAG_LEFT 0x1
+#define FLAG_SIGNED 0x2
+#define FLAG_SPACED 0x4
+#define FLAG_ZEROS 0x8
+#define FLAG_NEG 0x10
+
+static const char hex[] = "0123456789abcdef";
+static const char HEX[] = "0123456789ABCDEF";
+
+// Fill into the buffer using the data in src
+bool mozilla::PrintfTarget::fill2(const char* src, int srclen, int width,
+ int flags) {
+ char space = ' ';
+
+ width -= srclen;
+ if (width > 0 && (flags & FLAG_LEFT) == 0) { // Right adjusting
+ if (flags & FLAG_ZEROS) {
+ space = '0';
+ }
+ while (--width >= 0) {
+ if (!emit(&space, 1)) {
+ return false;
+ }
+ }
+ }
+
+ // Copy out the source data
+ if (!emit(src, srclen)) {
+ return false;
+ }
+
+ if (width > 0 && (flags & FLAG_LEFT) != 0) { // Left adjusting
+ while (--width >= 0) {
+ if (!emit(&space, 1)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/*
+ * Fill a number. The order is: optional-sign zero-filling conversion-digits
+ */
+bool mozilla::PrintfTarget::fill_n(const char* src, int srclen, int width,
+ int prec, int type, int flags) {
+ int zerowidth = 0;
+ int precwidth = 0;
+ int leftspaces = 0;
+ int rightspaces = 0;
+ int cvtwidth;
+ char sign = 0;
+
+ if ((type & 1) == 0) {
+ if (flags & FLAG_NEG) {
+ sign = '-';
+ } else if (flags & FLAG_SIGNED) {
+ sign = '+';
+ } else if (flags & FLAG_SPACED) {
+ sign = ' ';
+ }
+ }
+ cvtwidth = (sign ? 1 : 0) + srclen;
+
+ if (prec > 0 && (type != TYPE_DOUBLE)) {
+ if (prec > srclen) {
+ precwidth = prec - srclen; // Need zero filling
+ cvtwidth += precwidth;
+ }
+ }
+
+ if ((flags & FLAG_ZEROS) && ((type == TYPE_DOUBLE) || (prec < 0))) {
+ if (width > cvtwidth) {
+ zerowidth = width - cvtwidth; // Zero filling
+ cvtwidth += zerowidth;
+ }
+ }
+
+ if (flags & FLAG_LEFT) {
+ if (width > cvtwidth) {
+ // Space filling on the right (i.e. left adjusting)
+ rightspaces = width - cvtwidth;
+ }
+ } else {
+ if (width > cvtwidth) {
+ // Space filling on the left (i.e. right adjusting)
+ leftspaces = width - cvtwidth;
+ }
+ }
+ while (--leftspaces >= 0) {
+ if (!emit(" ", 1)) {
+ return false;
+ }
+ }
+ if (sign) {
+ if (!emit(&sign, 1)) {
+ return false;
+ }
+ }
+ while (--precwidth >= 0) {
+ if (!emit("0", 1)) {
+ return false;
+ }
+ }
+ while (--zerowidth >= 0) {
+ if (!emit("0", 1)) {
+ return false;
+ }
+ }
+ if (!emit(src, uint32_t(srclen))) {
+ return false;
+ }
+ while (--rightspaces >= 0) {
+ if (!emit(" ", 1)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// All that the cvt_* functions care about as far as the TYPE_* constants is
+// that the low bit is set to indicate unsigned, or unset to indicate signed.
+// So we don't try to hard to ensure that the passed TYPE_* constant lines
+// up with the actual size of the number being printed here. The main printf
+// code, below, does have to care so that the correct bits are extracted from
+// the varargs list.
+bool mozilla::PrintfTarget::appendIntDec(int32_t num) {
+ int flags = 0;
+ long n = num;
+ if (n < 0) {
+ n = -n;
+ flags |= FLAG_NEG;
+ }
+ return cvt_l(n, -1, -1, 10, TYPE_INTN, flags, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntDec(uint32_t num) {
+ return cvt_l(num, -1, -1, 10, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntOct(uint32_t num) {
+ return cvt_l(num, -1, -1, 8, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntHex(uint32_t num) {
+ return cvt_l(num, -1, -1, 16, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntDec(int64_t num) {
+ int flags = 0;
+ if (num < 0) {
+ num = -num;
+ flags |= FLAG_NEG;
+ }
+ return cvt_ll(num, -1, -1, 10, TYPE_INTN, flags, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntDec(uint64_t num) {
+ return cvt_ll(num, -1, -1, 10, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntOct(uint64_t num) {
+ return cvt_ll(num, -1, -1, 8, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntHex(uint64_t num) {
+ return cvt_ll(num, -1, -1, 16, TYPE_UINTN, 0, hex);
+}
+
+/* Convert a long into its printable form. */
+bool mozilla::PrintfTarget::cvt_l(long num, int width, int prec, int radix,
+ int type, int flags, const char* hexp) {
+ char cvtbuf[100];
+ char* cvt;
+ int digits;
+
+ // according to the man page this needs to happen
+ if ((prec == 0) && (num == 0)) {
+ return fill_n("", 0, width, prec, type, flags);
+ }
+
+ // Converting decimal is a little tricky. In the unsigned case we
+ // need to stop when we hit 10 digits. In the signed case, we can
+ // stop when the number is zero.
+ cvt = cvtbuf + sizeof(cvtbuf);
+ digits = 0;
+ while (num) {
+ int digit = (((unsigned long)num) % radix) & 0xF;
+ *--cvt = hexp[digit];
+ digits++;
+ num = (long)(((unsigned long)num) / radix);
+ }
+ if (digits == 0) {
+ *--cvt = '0';
+ digits++;
+ }
+
+ // Now that we have the number converted without its sign, deal with
+ // the sign and zero padding.
+ return fill_n(cvt, digits, width, prec, type, flags);
+}
+
+/* Convert a 64-bit integer into its printable form. */
+bool mozilla::PrintfTarget::cvt_ll(int64_t num, int width, int prec, int radix,
+ int type, int flags, const char* hexp) {
+ // According to the man page, this needs to happen.
+ if (prec == 0 && num == 0) {
+ return fill_n("", 0, width, prec, type, flags);
+ }
+
+ // Converting decimal is a little tricky. In the unsigned case we
+ // need to stop when we hit 10 digits. In the signed case, we can
+ // stop when the number is zero.
+ int64_t rad = int64_t(radix);
+ char cvtbuf[100];
+ char* cvt = cvtbuf + sizeof(cvtbuf);
+ int digits = 0;
+ while (num != 0) {
+ int64_t quot = uint64_t(num) / rad;
+ int64_t rem = uint64_t(num) % rad;
+ int32_t digit = int32_t(rem);
+ *--cvt = hexp[digit & 0xf];
+ digits++;
+ num = quot;
+ }
+ if (digits == 0) {
+ *--cvt = '0';
+ digits++;
+ }
+
+ // Now that we have the number converted without its sign, deal with
+ // the sign and zero padding.
+ return fill_n(cvt, digits, width, prec, type, flags);
+}
+
+template <size_t N>
+constexpr static size_t lengthof(const char (&)[N]) {
+ return N - 1;
+}
+
+// Longest possible output from ToFixed for positive numbers:
+// [0-9]{kMaxFixedDigitsBeforePoint}\.[0-9]{kMaxFixedDigitsAfterPoint}?
+constexpr int FIXED_MAX_CHARS =
+ DTSC::kMaxFixedDigitsBeforePoint + 1 + DTSC::kMaxFixedDigitsAfterPoint;
+
+// Longest possible output from ToExponential:
+// [1-9]\.[0-9]{kMaxExponentialDigits}e[+-][0-9]{1,3}
+// (std::numeric_limits<double>::max() has exponent 308).
+constexpr int EXPONENTIAL_MAX_CHARS =
+ lengthof("1.") + DTSC::kMaxExponentialDigits + lengthof("e+999");
+
+// Longest possible output from ToPrecise:
+// [0-9\.]{kMaxPrecisionDigits+1} or
+// [1-9]\.[0-9]{kMaxPrecisionDigits-1}e[+-][0-9]{1,3}
+constexpr int PRECISE_MAX_CHARS =
+ lengthof("1.") + DTSC::kMaxPrecisionDigits - 1 + lengthof("e+999");
+
+constexpr int DTSC_MAX_CHARS =
+ std::max({FIXED_MAX_CHARS, EXPONENTIAL_MAX_CHARS, PRECISE_MAX_CHARS});
+
+/*
+ * Convert a double precision floating point number into its printable
+ * form.
+ */
+bool mozilla::PrintfTarget::cvt_f(double d, char c, int width, int prec,
+ int flags) {
+ bool lower = islower(c);
+ const char* inf = lower ? "inf" : "INF";
+ const char* nan = lower ? "nan" : "NAN";
+ char e = lower ? 'e' : 'E';
+ DoubleToStringConverter converter(DTSC::UNIQUE_ZERO | DTSC::NO_TRAILING_ZERO |
+ DTSC::EMIT_POSITIVE_EXPONENT_SIGN,
+ inf, nan, e, 0, 0, 4, 0, 2);
+ // Longest of the above cases, plus space for a terminal nul character.
+ char buf[DTSC_MAX_CHARS + 1];
+ double_conversion::StringBuilder builder(buf, sizeof(buf));
+ bool success = false;
+ if (std::signbit(d)) {
+ d = std::abs(d);
+ flags |= FLAG_NEG;
+ }
+ if (!std::isfinite(d)) {
+ flags &= ~FLAG_ZEROS;
+ }
+ // "If the precision is missing, it shall be taken as 6."
+ if (prec < 0) {
+ prec = 6;
+ }
+ switch (c) {
+ case 'e':
+ case 'E':
+ success = converter.ToExponential(d, prec, &builder);
+ break;
+ case 'f':
+ case 'F':
+ success = converter.ToFixed(d, prec, &builder);
+ break;
+ case 'g':
+ case 'G':
+ // "If an explicit precision is zero, it shall be taken as 1."
+ success = converter.ToPrecision(d, prec ? prec : 1, &builder);
+ break;
+ }
+ if (!success) {
+ return false;
+ }
+ int len = builder.position();
+ char* cvt = builder.Finalize();
+ return fill_n(cvt, len, width, prec, TYPE_DOUBLE, flags);
+}
+
+/*
+ * Convert a string into its printable form. "width" is the output
+ * width. "prec" is the maximum number of characters of "s" to output,
+ * where -1 means until NUL.
+ */
+bool mozilla::PrintfTarget::cvt_s(const char* s, int width, int prec,
+ int flags) {
+ if (prec == 0) {
+ return true;
+ }
+ if (!s) {
+ s = "(null)";
+ }
+
+ // Limit string length by precision value
+ int slen = int(strlen(s));
+ if (0 < prec && prec < slen) {
+ slen = prec;
+ }
+
+ // and away we go
+ return fill2(s, slen, width, flags);
+}
+
+/*
+ * BuildArgArray stands for Numbered Argument list Sprintf
+ * for example,
+ * fmp = "%4$i, %2$d, %3s, %1d";
+ * the number must start from 1, and no gap among them
+ */
+static bool BuildArgArray(const char* fmt, va_list ap, NumArgStateVector& nas) {
+ size_t number = 0, cn = 0, i;
+ const char* p;
+ char c;
+
+ // First pass:
+ // Detemine how many legal % I have got, then allocate space.
+
+ p = fmt;
+ i = 0;
+ while ((c = *p++) != 0) {
+ if (c != '%') {
+ continue;
+ }
+ if ((c = *p++) == '%') { // skip %% case
+ continue;
+ }
+
+ while (c != 0) {
+ if (c > '9' || c < '0') {
+ if (c == '$') { // numbered argument case
+ if (i > 0) {
+ MOZ_CRASH("Bad format string");
+ }
+ number++;
+ } else { // non-numbered argument case
+ if (number > 0) {
+ MOZ_CRASH("Bad format string");
+ }
+ i = 1;
+ }
+ break;
+ }
+
+ c = *p++;
+ }
+ }
+
+ if (number == 0) {
+ return true;
+ }
+
+ // Only allow a limited number of arguments.
+ MOZ_RELEASE_ASSERT(number <= 20);
+
+ if (!nas.growByUninitialized(number)) {
+ return false;
+ }
+
+ for (i = 0; i < number; i++) {
+ nas[i].type = TYPE_UNKNOWN;
+ }
+
+ // Second pass:
+ // Set nas[].type.
+
+ p = fmt;
+ while ((c = *p++) != 0) {
+ if (c != '%') {
+ continue;
+ }
+ c = *p++;
+ if (c == '%') {
+ continue;
+ }
+
+ cn = 0;
+ while (c && c != '$') { // should improve error check later
+ cn = cn * 10 + c - '0';
+ c = *p++;
+ }
+
+ if (!c || cn < 1 || cn > number) {
+ MOZ_CRASH("Bad format string");
+ }
+
+ // nas[cn] starts from 0, and make sure nas[cn].type is not assigned.
+ cn--;
+ if (nas[cn].type != TYPE_UNKNOWN) {
+ continue;
+ }
+
+ c = *p++;
+
+ // flags
+ while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) {
+ c = *p++;
+ }
+
+ // width
+ if (c == '*') {
+ // not supported feature, for the argument is not numbered
+ MOZ_CRASH("Bad format string");
+ }
+
+ while ((c >= '0') && (c <= '9')) {
+ c = *p++;
+ }
+
+ // precision
+ if (c == '.') {
+ c = *p++;
+ if (c == '*') {
+ // not supported feature, for the argument is not numbered
+ MOZ_CRASH("Bad format string");
+ }
+
+ while ((c >= '0') && (c <= '9')) {
+ c = *p++;
+ }
+ }
+
+ // size
+ nas[cn].type = TYPE_INTN;
+ if (c == 'h') {
+ nas[cn].type = TYPE_SHORT;
+ c = *p++;
+ if (c == 'h') {
+ nas[cn].type = TYPE_SCHAR;
+ c = *p++;
+ }
+ } else if (c == 'L') {
+ nas[cn].type = TYPE_LONGLONG;
+ c = *p++;
+ } else if (c == 'l') {
+ nas[cn].type = TYPE_LONG;
+ c = *p++;
+ if (c == 'l') {
+ nas[cn].type = TYPE_LONGLONG;
+ c = *p++;
+ }
+ } else if (c == 'z' || c == 'I') {
+ static_assert(sizeof(size_t) == sizeof(int) ||
+ sizeof(size_t) == sizeof(long) ||
+ sizeof(size_t) == sizeof(long long),
+ "size_t is not one of the expected sizes");
+ nas[cn].type = sizeof(size_t) == sizeof(int) ? TYPE_INTN
+ : sizeof(size_t) == sizeof(long) ? TYPE_LONG
+ : TYPE_LONGLONG;
+ c = *p++;
+ } else if (c == 't') {
+ static_assert(sizeof(ptrdiff_t) == sizeof(int) ||
+ sizeof(ptrdiff_t) == sizeof(long) ||
+ sizeof(ptrdiff_t) == sizeof(long long),
+ "ptrdiff_t is not one of the expected sizes");
+ nas[cn].type = sizeof(ptrdiff_t) == sizeof(int) ? TYPE_INTN
+ : sizeof(ptrdiff_t) == sizeof(long) ? TYPE_LONG
+ : TYPE_LONGLONG;
+ c = *p++;
+ } else if (c == 'j') {
+ static_assert(sizeof(intmax_t) == sizeof(int) ||
+ sizeof(intmax_t) == sizeof(long) ||
+ sizeof(intmax_t) == sizeof(long long),
+ "intmax_t is not one of the expected sizes");
+ nas[cn].type = sizeof(intmax_t) == sizeof(int) ? TYPE_INTN
+ : sizeof(intmax_t) == sizeof(long) ? TYPE_LONG
+ : TYPE_LONGLONG;
+ c = *p++;
+ }
+
+ // format
+ switch (c) {
+ case 'd':
+ case 'c':
+ case 'i':
+ break;
+
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ // Mark as unsigned type.
+ nas[cn].type |= 1;
+ break;
+
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ nas[cn].type = TYPE_DOUBLE;
+ break;
+
+ case 'p':
+ nas[cn].type = TYPE_POINTER;
+ break;
+
+ case 'S':
+#if defined(XP_WIN)
+ nas[cn].type = TYPE_WSTRING;
+#else
+ MOZ_ASSERT(0);
+ nas[cn].type = TYPE_UNKNOWN;
+#endif
+ break;
+
+ case 's':
+#if defined(XP_WIN)
+ if (nas[cn].type == TYPE_LONG) {
+ nas[cn].type = TYPE_WSTRING;
+ break;
+ }
+#endif
+ // Other type sizes are not supported here.
+ MOZ_ASSERT(nas[cn].type == TYPE_INTN);
+ nas[cn].type = TYPE_STRING;
+ break;
+
+ case 'n':
+ nas[cn].type = TYPE_INTSTR;
+ break;
+
+ default:
+ MOZ_ASSERT(0);
+ nas[cn].type = TYPE_UNKNOWN;
+ break;
+ }
+
+ // get a legal para.
+ if (nas[cn].type == TYPE_UNKNOWN) {
+ MOZ_CRASH("Bad format string");
+ }
+ }
+
+ // Third pass:
+ // Fill nas[].ap.
+
+ cn = 0;
+ while (cn < number) {
+ // A TYPE_UNKNOWN here means that the format asked for a
+ // positional argument without specifying the meaning of some
+ // earlier argument.
+ MOZ_ASSERT(nas[cn].type != TYPE_UNKNOWN);
+
+ va_copy(nas[cn].ap, ap);
+
+ switch (nas[cn].type) {
+ case TYPE_SCHAR:
+ case TYPE_UCHAR:
+ case TYPE_SHORT:
+ case TYPE_USHORT:
+ case TYPE_INTN:
+ case TYPE_UINTN:
+ (void)va_arg(ap, int);
+ break;
+ case TYPE_LONG:
+ (void)va_arg(ap, long);
+ break;
+ case TYPE_ULONG:
+ (void)va_arg(ap, unsigned long);
+ break;
+ case TYPE_LONGLONG:
+ (void)va_arg(ap, long long);
+ break;
+ case TYPE_ULONGLONG:
+ (void)va_arg(ap, unsigned long long);
+ break;
+ case TYPE_STRING:
+ (void)va_arg(ap, char*);
+ break;
+ case TYPE_INTSTR:
+ (void)va_arg(ap, int*);
+ break;
+ case TYPE_DOUBLE:
+ (void)va_arg(ap, double);
+ break;
+ case TYPE_POINTER:
+ (void)va_arg(ap, void*);
+ break;
+#if defined(XP_WIN)
+ case TYPE_WSTRING:
+ (void)va_arg(ap, wchar_t*);
+ break;
+#endif
+
+ default:
+ MOZ_CRASH();
+ }
+
+ cn++;
+ }
+
+ return true;
+}
+
+mozilla::PrintfTarget::PrintfTarget() : mEmitted(0) {}
+
+bool mozilla::PrintfTarget::vprint(const char* fmt, va_list ap) {
+ char c;
+ int flags, width, prec, radix, type;
+ union {
+ char ch;
+ int i;
+ long l;
+ long long ll;
+ double d;
+ const char* s;
+ int* ip;
+ void* p;
+#if defined(XP_WIN)
+ const wchar_t* ws;
+#endif
+ } u{};
+ const char* hexp;
+ int i;
+
+ // Build an argument array, IF the fmt is numbered argument
+ // list style, to contain the Numbered Argument list pointers.
+
+ NumArgStateVector nas;
+ if (!BuildArgArray(fmt, ap, nas)) {
+ // the fmt contains error Numbered Argument format, jliu@netscape.com
+ MOZ_CRASH("Bad format string");
+ }
+
+ while ((c = *fmt++) != 0) {
+ if (c != '%') {
+ if (!emit(fmt - 1, 1)) {
+ return false;
+ }
+
+ continue;
+ }
+
+ // Gobble up the % format string. Hopefully we have handled all
+ // of the strange cases!
+ flags = 0;
+ c = *fmt++;
+ if (c == '%') {
+ // quoting a % with %%
+ if (!emit(fmt - 1, 1)) {
+ return false;
+ }
+
+ continue;
+ }
+
+ if (!nas.empty()) {
+ // the fmt contains the Numbered Arguments feature
+ i = 0;
+ while (c && c != '$') { // should improve error check later
+ i = (i * 10) + (c - '0');
+ c = *fmt++;
+ }
+
+ if (nas[i - 1].type == TYPE_UNKNOWN) {
+ MOZ_CRASH("Bad format string");
+ }
+
+ ap = nas[i - 1].ap;
+ c = *fmt++;
+ }
+
+ // Examine optional flags. Note that we do not implement the
+ // '#' flag of sprintf(). The ANSI C spec. of the '#' flag is
+ // somewhat ambiguous and not ideal, which is perhaps why
+ // the various sprintf() implementations are inconsistent
+ // on this feature.
+ while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) {
+ if (c == '-') {
+ flags |= FLAG_LEFT;
+ }
+ if (c == '+') {
+ flags |= FLAG_SIGNED;
+ }
+ if (c == ' ') {
+ flags |= FLAG_SPACED;
+ }
+ if (c == '0') {
+ flags |= FLAG_ZEROS;
+ }
+ c = *fmt++;
+ }
+ if (flags & FLAG_SIGNED) {
+ flags &= ~FLAG_SPACED;
+ }
+ if (flags & FLAG_LEFT) {
+ flags &= ~FLAG_ZEROS;
+ }
+
+ // width
+ if (c == '*') {
+ c = *fmt++;
+ width = va_arg(ap, int);
+ if (width < 0) {
+ width = -width;
+ flags |= FLAG_LEFT;
+ flags &= ~FLAG_ZEROS;
+ }
+ } else {
+ width = 0;
+ while ((c >= '0') && (c <= '9')) {
+ width = (width * 10) + (c - '0');
+ c = *fmt++;
+ }
+ }
+
+ // precision
+ prec = -1;
+ if (c == '.') {
+ c = *fmt++;
+ if (c == '*') {
+ c = *fmt++;
+ prec = va_arg(ap, int);
+ } else {
+ prec = 0;
+ while ((c >= '0') && (c <= '9')) {
+ prec = (prec * 10) + (c - '0');
+ c = *fmt++;
+ }
+ }
+ }
+
+ // size
+ type = TYPE_INTN;
+ if (c == 'h') {
+ type = TYPE_SHORT;
+ c = *fmt++;
+ if (c == 'h') {
+ type = TYPE_SCHAR;
+ c = *fmt++;
+ }
+ } else if (c == 'L') {
+ type = TYPE_LONGLONG;
+ c = *fmt++;
+ } else if (c == 'l') {
+ type = TYPE_LONG;
+ c = *fmt++;
+ if (c == 'l') {
+ type = TYPE_LONGLONG;
+ c = *fmt++;
+ }
+ } else if (c == 'z' || c == 'I') {
+ static_assert(sizeof(size_t) == sizeof(int) ||
+ sizeof(size_t) == sizeof(long) ||
+ sizeof(size_t) == sizeof(long long),
+ "size_t is not one of the expected sizes");
+ type = sizeof(size_t) == sizeof(int) ? TYPE_INTN
+ : sizeof(size_t) == sizeof(long) ? TYPE_LONG
+ : TYPE_LONGLONG;
+ c = *fmt++;
+ } else if (c == 't') {
+ static_assert(sizeof(ptrdiff_t) == sizeof(int) ||
+ sizeof(ptrdiff_t) == sizeof(long) ||
+ sizeof(ptrdiff_t) == sizeof(long long),
+ "ptrdiff_t is not one of the expected sizes");
+ type = sizeof(ptrdiff_t) == sizeof(int) ? TYPE_INTN
+ : sizeof(ptrdiff_t) == sizeof(long) ? TYPE_LONG
+ : TYPE_LONGLONG;
+ c = *fmt++;
+ } else if (c == 'j') {
+ static_assert(sizeof(intmax_t) == sizeof(int) ||
+ sizeof(intmax_t) == sizeof(long) ||
+ sizeof(intmax_t) == sizeof(long long),
+ "intmax_t is not one of the expected sizes");
+ type = sizeof(intmax_t) == sizeof(int) ? TYPE_INTN
+ : sizeof(intmax_t) == sizeof(long) ? TYPE_LONG
+ : TYPE_LONGLONG;
+ c = *fmt++;
+ }
+
+ // format
+ hexp = hex;
+ switch (c) {
+ case 'd':
+ case 'i': // decimal/integer
+ radix = 10;
+ goto fetch_and_convert;
+
+ case 'o': // octal
+ radix = 8;
+ type |= 1;
+ goto fetch_and_convert;
+
+ case 'u': // unsigned decimal
+ radix = 10;
+ type |= 1;
+ goto fetch_and_convert;
+
+ case 'x': // unsigned hex
+ radix = 16;
+ type |= 1;
+ goto fetch_and_convert;
+
+ case 'X': // unsigned HEX
+ radix = 16;
+ hexp = HEX;
+ type |= 1;
+ goto fetch_and_convert;
+
+ fetch_and_convert:
+ switch (type) {
+ case TYPE_SCHAR:
+ u.l = (signed char)va_arg(ap, int);
+ if (u.l < 0) {
+ u.l = -u.l;
+ flags |= FLAG_NEG;
+ }
+ goto do_long;
+ case TYPE_UCHAR:
+ u.l = (unsigned char)va_arg(ap, unsigned int);
+ goto do_long;
+ case TYPE_SHORT:
+ u.l = (short)va_arg(ap, int);
+ if (u.l < 0) {
+ u.l = -u.l;
+ flags |= FLAG_NEG;
+ }
+ goto do_long;
+ case TYPE_USHORT:
+ u.l = (unsigned short)va_arg(ap, unsigned int);
+ goto do_long;
+ case TYPE_INTN:
+ u.l = va_arg(ap, int);
+ if (u.l < 0) {
+ u.l = -u.l;
+ flags |= FLAG_NEG;
+ }
+ goto do_long;
+ case TYPE_UINTN:
+ u.l = (long)va_arg(ap, unsigned int);
+ goto do_long;
+
+ case TYPE_LONG:
+ u.l = va_arg(ap, long);
+ if (u.l < 0) {
+ u.l = -u.l;
+ flags |= FLAG_NEG;
+ }
+ goto do_long;
+ case TYPE_ULONG:
+ u.l = (long)va_arg(ap, unsigned long);
+ do_long:
+ if (!cvt_l(u.l, width, prec, radix, type, flags, hexp)) {
+ return false;
+ }
+
+ break;
+
+ case TYPE_LONGLONG:
+ u.ll = va_arg(ap, long long);
+ if (u.ll < 0) {
+ u.ll = -u.ll;
+ flags |= FLAG_NEG;
+ }
+ goto do_longlong;
+ case TYPE_POINTER:
+ u.ll = (uintptr_t)va_arg(ap, void*);
+ goto do_longlong;
+ case TYPE_ULONGLONG:
+ u.ll = va_arg(ap, unsigned long long);
+ do_longlong:
+ if (!cvt_ll(u.ll, width, prec, radix, type, flags, hexp)) {
+ return false;
+ }
+
+ break;
+ }
+ break;
+
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ u.d = va_arg(ap, double);
+ if (!cvt_f(u.d, c, width, prec, flags)) {
+ return false;
+ }
+
+ break;
+
+ case 'c':
+ if ((flags & FLAG_LEFT) == 0) {
+ while (width-- > 1) {
+ if (!emit(" ", 1)) {
+ return false;
+ }
+ }
+ }
+ switch (type) {
+ case TYPE_SHORT:
+ case TYPE_INTN:
+ u.ch = va_arg(ap, int);
+ if (!emit(&u.ch, 1)) {
+ return false;
+ }
+ break;
+ }
+ if (flags & FLAG_LEFT) {
+ while (width-- > 1) {
+ if (!emit(" ", 1)) {
+ return false;
+ }
+ }
+ }
+ break;
+
+ case 'p':
+ type = TYPE_POINTER;
+ radix = 16;
+ goto fetch_and_convert;
+
+ case 's':
+ if (type == TYPE_INTN) {
+ u.s = va_arg(ap, const char*);
+ if (!cvt_s(u.s, width, prec, flags)) {
+ return false;
+ }
+ break;
+ }
+ MOZ_ASSERT(type == TYPE_LONG);
+ [[fallthrough]];
+ case 'S':
+#if defined(XP_WIN)
+ {
+ u.ws = va_arg(ap, const wchar_t*);
+
+ int rv = WideCharToMultiByte(CP_ACP, 0, u.ws, -1, NULL, 0, NULL, NULL);
+ if (rv == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
+ if (!cvt_s("<unicode errors in string>", width, prec, flags)) {
+ return false;
+ }
+ } else {
+ if (rv == 0) {
+ rv = 1;
+ }
+ UniqueFreePtr<char[]> buf((char*)malloc(rv));
+ WideCharToMultiByte(CP_ACP, 0, u.ws, -1, buf.get(), rv, NULL, NULL);
+ buf[rv - 1] = '\0';
+
+ if (!cvt_s(buf.get(), width, prec, flags)) {
+ return false;
+ }
+ }
+ }
+#else
+ // Not supported here.
+ MOZ_ASSERT(0);
+#endif
+ break;
+
+ case 'n':
+ u.ip = va_arg(ap, int*);
+ if (u.ip) {
+ *u.ip = mEmitted;
+ }
+ break;
+
+ default:
+ // Not a % token after all... skip it
+ if (!emit("%", 1)) {
+ return false;
+ }
+ if (!emit(fmt - 1, 1)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/************************************************************************/
+
+bool mozilla::PrintfTarget::print(const char* format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ bool result = vprint(format, ap);
+ va_end(ap);
+ return result;
+}
+
+#undef TYPE_SHORT
+#undef TYPE_USHORT
+#undef TYPE_INTN
+#undef TYPE_UINTN
+#undef TYPE_LONG
+#undef TYPE_ULONG
+#undef TYPE_LONGLONG
+#undef TYPE_ULONGLONG
+#undef TYPE_STRING
+#undef TYPE_DOUBLE
+#undef TYPE_INTSTR
+#undef TYPE_POINTER
+#undef TYPE_WSTRING
+#undef TYPE_UNKNOWN
+#undef TYPE_SCHAR
+#undef TYPE_UCHAR
+
+#undef FLAG_LEFT
+#undef FLAG_SIGNED
+#undef FLAG_SPACED
+#undef FLAG_ZEROS
+#undef FLAG_NEG
diff --git a/mozglue/misc/Printf.h b/mozglue/misc/Printf.h
new file mode 100644
index 0000000000..3f4fa2c1bb
--- /dev/null
+++ b/mozglue/misc/Printf.h
@@ -0,0 +1,266 @@
+/* -*- 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/. */
+
+/* Printf-like functions, with canned variants that malloc their result. */
+
+#ifndef mozilla_Printf_h
+#define mozilla_Printf_h
+
+/*
+** API for PR printf like routines.
+**
+** These exist partly for historical reasons -- initially they were in
+** NSPR, then forked in tree and modified in js/ -- but now the prime
+** motivation is both closer control over the exact formatting (with
+** one exception, see below) and also the ability to control where
+** exactly the generated results are sent.
+**
+** It might seem that this could all be dispensed with in favor of a
+** wrapper around |vsnprintf| -- except that this implementation
+** guarantees that the %s format will accept a NULL pointer, whereas
+** with standard functions this is undefined.
+**
+** This supports the following formats. It implements a subset of the
+** standard formats; due to the use of MOZ_FORMAT_PRINTF, it is not
+** permissible to extend the standard, aside from relaxing undefined
+** behavior.
+**
+** %d - decimal
+** %u - unsigned decimal
+** %x - unsigned hex
+** %X - unsigned uppercase hex
+** %o - unsigned octal
+** %hd, %hu, %hx, %hX, %ho - "short" versions of above
+** %ld, %lu, %lx, %lX, %lo - "long" versions of above
+** %lld, %llu, %llx, %llX, %llo - "long long" versions of above
+** %zd, %zo, %zu, %zx, %zX - size_t versions of above
+** %Id, %Io, %Iu, %Ix, %IX - size_t versions of above (for Windows compat).
+** Note that MSVC 2015 and newer supports the z length modifier so
+** users should prefer using %z instead of %I. We are supporting %I in
+** addition to %z in case third-party code that uses %I gets routed to
+** use this printf implementation.
+** %s - string
+** %S, %ls - wide string, that is wchar_t*
+** %c - character
+** %p - pointer (deals with machine dependent pointer size)
+** %f - float; note that this is actually formatted using the
+** system's native printf, and so the results may vary
+** %g - float; note that this is actually formatted using the
+** system's native printf, and so the results may vary
+*/
+
+#include "mozilla/AllocPolicy.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+namespace mozilla {
+
+/*
+ * This class may be subclassed to provide a way to get the output of
+ * a printf-like call, as the output is generated.
+ */
+class PrintfTarget {
+ public:
+ /* The Printf-like interface. */
+ bool MFBT_API print(const char* format, ...) MOZ_FORMAT_PRINTF(2, 3);
+
+ /* The Vprintf-like interface. */
+ bool MFBT_API vprint(const char* fmt, va_list) MOZ_FORMAT_PRINTF(2, 0);
+
+ /* Fast paths for formatting integers as though by %d, %o, %u, or %x.
+ Since octal and hex formatting always treat numbers as unsigned, there
+ are no signed overloads for AppendInt{Oct,Hex}. */
+ bool MFBT_API appendIntDec(int32_t);
+ bool MFBT_API appendIntDec(uint32_t);
+ bool MFBT_API appendIntOct(uint32_t);
+ bool MFBT_API appendIntHex(uint32_t);
+ bool MFBT_API appendIntDec(int64_t);
+ bool MFBT_API appendIntDec(uint64_t);
+ bool MFBT_API appendIntOct(uint64_t);
+ bool MFBT_API appendIntHex(uint64_t);
+
+ inline size_t emitted() { return mEmitted; }
+
+ protected:
+ MFBT_API PrintfTarget();
+ virtual ~PrintfTarget() = default;
+
+ /* Subclasses override this. It is called when more output is
+ available. It may be called with len==0. This should return
+ true on success, or false on failure. */
+ virtual bool append(const char* sp, size_t len) = 0;
+
+ private:
+ /* Number of bytes emitted so far. */
+ size_t mEmitted;
+
+ /* The implementation calls this to emit bytes and update
+ mEmitted. */
+ bool emit(const char* sp, size_t len) {
+ mEmitted += len;
+ return append(sp, len);
+ }
+
+ bool fill2(const char* src, int srclen, int width, int flags);
+ bool fill_n(const char* src, int srclen, int width, int prec, int type,
+ int flags);
+ bool cvt_l(long num, int width, int prec, int radix, int type, int flags,
+ const char* hexp);
+ bool cvt_ll(int64_t num, int width, int prec, int radix, int type, int flags,
+ const char* hexp);
+ bool cvt_f(double d, char c, int width, int prec, int flags);
+ bool cvt_s(const char* s, int width, int prec, int flags);
+};
+
+namespace detail {
+
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+struct AllocPolicyBasedFreePolicy {
+ void operator()(const void* ptr) {
+ AllocPolicy policy;
+ policy.free_(const_cast<void*>(ptr));
+ }
+};
+
+} // namespace detail
+
+// The type returned by Smprintf and friends.
+template <typename AllocPolicy>
+using SmprintfPolicyPointer =
+ mozilla::UniquePtr<char, detail::AllocPolicyBasedFreePolicy<AllocPolicy>>;
+
+// The default type if no alloc policy is specified.
+typedef SmprintfPolicyPointer<mozilla::MallocAllocPolicy> SmprintfPointer;
+
+// Used in the implementation of Smprintf et al.
+template <typename AllocPolicy>
+class MOZ_STACK_CLASS SprintfState final : private mozilla::PrintfTarget,
+ private AllocPolicy {
+ public:
+ explicit SprintfState(char* base)
+ : mMaxlen(base ? strlen(base) : 0),
+ mBase(base),
+ mCur(base ? base + mMaxlen : 0) {}
+
+ ~SprintfState() { this->free_(mBase); }
+
+ bool vprint(const char* format, va_list ap_list) MOZ_FORMAT_PRINTF(2, 0) {
+ // The "" here has a single \0 character, which is what we're
+ // trying to append.
+ return mozilla::PrintfTarget::vprint(format, ap_list) && append("", 1);
+ }
+
+ SmprintfPolicyPointer<AllocPolicy> release() {
+ SmprintfPolicyPointer<AllocPolicy> result(mBase);
+ mBase = nullptr;
+ return result;
+ }
+
+ protected:
+ bool append(const char* sp, size_t len) override {
+ ptrdiff_t off;
+ char* newbase;
+ size_t newlen;
+
+ off = mCur - mBase;
+ if (off + len >= mMaxlen) {
+ /* Grow the buffer */
+ newlen = mMaxlen + ((len > 32) ? len : 32);
+ newbase = this->template maybe_pod_malloc<char>(newlen);
+ if (!newbase) {
+ /* Ran out of memory */
+ return false;
+ }
+ memcpy(newbase, mBase, mMaxlen);
+ this->free_(mBase);
+ mBase = newbase;
+ mMaxlen = newlen;
+ mCur = mBase + off;
+ }
+
+ /* Copy data */
+ memcpy(mCur, sp, len);
+ mCur += len;
+ MOZ_ASSERT(size_t(mCur - mBase) <= mMaxlen);
+ return true;
+ }
+
+ private:
+ size_t mMaxlen;
+ char* mBase;
+ char* mCur;
+};
+
+/*
+** sprintf into a malloc'd buffer. Return a pointer to the malloc'd
+** buffer on success, nullptr on failure. Call AllocPolicy::free_ to release
+** the memory returned.
+*/
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+MOZ_FORMAT_PRINTF(1, 2)
+SmprintfPolicyPointer<AllocPolicy> Smprintf(const char* fmt, ...) {
+ SprintfState<AllocPolicy> ss(nullptr);
+ va_list ap;
+ va_start(ap, fmt);
+ bool r = ss.vprint(fmt, ap);
+ va_end(ap);
+ if (!r) {
+ return nullptr;
+ }
+ return ss.release();
+}
+
+/*
+** "append" sprintf into a malloc'd buffer. "last" is the last value of
+** the malloc'd buffer. sprintf will append data to the end of last,
+** growing it as necessary using realloc. If last is nullptr, SmprintfAppend
+** will allocate the initial string. The return value is the new value of
+** last for subsequent calls, or nullptr if there is a malloc failure.
+*/
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+MOZ_FORMAT_PRINTF(2, 3)
+SmprintfPolicyPointer<AllocPolicy> SmprintfAppend(
+ SmprintfPolicyPointer<AllocPolicy>&& last, const char* fmt, ...) {
+ SprintfState<AllocPolicy> ss(last.release());
+ va_list ap;
+ va_start(ap, fmt);
+ bool r = ss.vprint(fmt, ap);
+ va_end(ap);
+ if (!r) {
+ return nullptr;
+ }
+ return ss.release();
+}
+
+/*
+** va_list forms of the above.
+*/
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+MOZ_FORMAT_PRINTF(1, 0)
+SmprintfPolicyPointer<AllocPolicy> Vsmprintf(const char* fmt, va_list ap) {
+ SprintfState<AllocPolicy> ss(nullptr);
+ if (!ss.vprint(fmt, ap)) return nullptr;
+ return ss.release();
+}
+
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+MOZ_FORMAT_PRINTF(2, 0)
+SmprintfPolicyPointer<AllocPolicy> VsmprintfAppend(
+ SmprintfPolicyPointer<AllocPolicy>&& last, const char* fmt, va_list ap) {
+ SprintfState<AllocPolicy> ss(last.release());
+ if (!ss.vprint(fmt, ap)) return nullptr;
+ return ss.release();
+}
+
+} // namespace mozilla
+
+#endif /* mozilla_Printf_h */
diff --git a/mozglue/misc/ProcessType.cpp b/mozglue/misc/ProcessType.cpp
new file mode 100644
index 0000000000..031d242328
--- /dev/null
+++ b/mozglue/misc/ProcessType.cpp
@@ -0,0 +1,58 @@
+/* -*- 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 "ProcessType.h"
+
+#include <cstring>
+
+#include "mozilla/Assertions.h"
+
+using namespace mozilla::startup;
+
+namespace mozilla {
+namespace startup {
+GeckoProcessType sChildProcessType = GeckoProcessType_Default;
+} // namespace startup
+
+void SetGeckoProcessType(const char* aProcessTypeString) {
+#if !defined(DEBUG)
+ // If not a DEBUG build then just return if sChildProcessType has already been
+ // set and is not fork server. In DEBUG builds we will check that process type
+ // matches the one already set if it is not fork server.
+ if (sChildProcessType != GeckoProcessType_Default &&
+ sChildProcessType != GeckoProcessType_ForkServer) {
+ return;
+ }
+#endif
+
+#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ if (std::strcmp(aProcessTypeString, string_name) == 0) { \
+ MOZ_ASSERT_IF( \
+ sChildProcessType != GeckoProcessType_Default && \
+ sChildProcessType != GeckoProcessType_ForkServer, \
+ sChildProcessType == GeckoProcessType::GeckoProcessType_##enum_name); \
+ sChildProcessType = GeckoProcessType::GeckoProcessType_##enum_name; \
+ return; \
+ }
+#define SKIP_PROCESS_TYPE_DEFAULT
+#if !defined(MOZ_ENABLE_FORKSERVER)
+# define SKIP_PROCESS_TYPE_FORKSERVER
+#endif
+#if !defined(ENABLE_TESTS)
+# define SKIP_PROCESS_TYPE_IPDLUNITTEST
+#endif
+#include "mozilla/GeckoProcessTypes.h"
+#undef SKIP_PROCESS_TYPE_IPDLUNITTEST
+#undef SKIP_PROCESS_TYPE_FORKSERVER
+#undef SKIP_PROCESS_TYPE_DEFAULT
+#undef GECKO_PROCESS_TYPE
+
+ MOZ_CRASH("aProcessTypeString is not valid.");
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/ProcessType.h b/mozglue/misc/ProcessType.h
new file mode 100644
index 0000000000..890ed4faac
--- /dev/null
+++ b/mozglue/misc/ProcessType.h
@@ -0,0 +1,44 @@
+/* -*- 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 IPC_PROCESSTYPE_H_
+#define IPC_PROCESSTYPE_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Types.h"
+
+// This enum is not dense. See GeckoProcessTypes.h for details.
+enum GeckoProcessType {
+#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ GeckoProcessType_##enum_name = (enum_value),
+#include "mozilla/GeckoProcessTypes.h"
+#undef GECKO_PROCESS_TYPE
+ GeckoProcessType_End,
+ GeckoProcessType_Invalid = GeckoProcessType_End
+};
+
+namespace mozilla {
+namespace startup {
+extern MFBT_DATA GeckoProcessType sChildProcessType;
+} // namespace startup
+
+/**
+ * @return the GeckoProcessType of the current process.
+ */
+MOZ_ALWAYS_INLINE GeckoProcessType GetGeckoProcessType() {
+ return startup::sChildProcessType;
+}
+
+/**
+ * Set the gecko process type based on a null-terminated byte string.
+ */
+MFBT_API void SetGeckoProcessType(const char* aProcessTypeString);
+
+} // namespace mozilla
+
+#endif // IPC_PROCESSTYPE_H_
diff --git a/mozglue/misc/RWLock_posix.cpp b/mozglue/misc/RWLock_posix.cpp
new file mode 100644
index 0000000000..970bddd1aa
--- /dev/null
+++ b/mozglue/misc/RWLock_posix.cpp
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+#ifdef XP_WIN
+# error This file should only be compiled on non-Windows platforms.
+#endif
+
+#include "mozilla/PlatformRWLock.h"
+
+#include "mozilla/Assertions.h"
+
+#include <errno.h>
+
+// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
+mozilla::detail::RWLockImpl::RWLockImpl() {
+ MOZ_RELEASE_ASSERT(pthread_rwlock_init(&mRWLock, nullptr) == 0,
+ "pthread_rwlock_init failed");
+}
+
+mozilla::detail::RWLockImpl::~RWLockImpl() {
+ MOZ_RELEASE_ASSERT(pthread_rwlock_destroy(&mRWLock) == 0,
+ "pthread_rwlock_destroy failed");
+}
+
+bool mozilla::detail::RWLockImpl::tryReadLock() {
+ int rv = pthread_rwlock_tryrdlock(&mRWLock);
+ // We allow EDEADLK here because it has been observed returned on macos when
+ // the write lock is held by the current thread.
+ MOZ_RELEASE_ASSERT(rv == 0 || rv == EBUSY || rv == EDEADLK,
+ "pthread_rwlock_tryrdlock failed");
+ return rv == 0;
+}
+
+void mozilla::detail::RWLockImpl::readLock() {
+ MOZ_RELEASE_ASSERT(pthread_rwlock_rdlock(&mRWLock) == 0,
+ "pthread_rwlock_rdlock failed");
+}
+
+void mozilla::detail::RWLockImpl::readUnlock() {
+ MOZ_RELEASE_ASSERT(pthread_rwlock_unlock(&mRWLock) == 0,
+ "pthread_rwlock_unlock failed");
+}
+
+bool mozilla::detail::RWLockImpl::tryWriteLock() {
+ int rv = pthread_rwlock_trywrlock(&mRWLock);
+ // We allow EDEADLK here because it has been observed returned on macos when
+ // the write lock is held by the current thread.
+ MOZ_RELEASE_ASSERT(rv == 0 || rv == EBUSY || rv == EDEADLK,
+ "pthread_rwlock_trywrlock failed");
+ return rv == 0;
+}
+
+void mozilla::detail::RWLockImpl::writeLock() {
+ MOZ_RELEASE_ASSERT(pthread_rwlock_wrlock(&mRWLock) == 0,
+ "pthread_rwlock_wrlock failed");
+}
+
+void mozilla::detail::RWLockImpl::writeUnlock() {
+ MOZ_RELEASE_ASSERT(pthread_rwlock_unlock(&mRWLock) == 0,
+ "pthread_rwlock_unlock failed");
+}
diff --git a/mozglue/misc/RWLock_windows.cpp b/mozglue/misc/RWLock_windows.cpp
new file mode 100644
index 0000000000..2c347f218c
--- /dev/null
+++ b/mozglue/misc/RWLock_windows.cpp
@@ -0,0 +1,48 @@
+/* -*- 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 XP_WIN
+# error This file should only be compiled on Windows.
+#endif
+
+#include "mozilla/PlatformRWLock.h"
+
+#include <windows.h>
+
+#define NativeHandle(m) (reinterpret_cast<SRWLOCK*>(&m))
+
+mozilla::detail::RWLockImpl::RWLockImpl() {
+ static_assert(sizeof(SRWLOCK) <= sizeof(mRWLock), "SRWLOCK is too big!");
+ InitializeSRWLock(NativeHandle(mRWLock));
+}
+
+mozilla::detail::RWLockImpl::~RWLockImpl() {}
+
+bool mozilla::detail::RWLockImpl::tryReadLock() {
+ return TryAcquireSRWLockShared(NativeHandle(mRWLock));
+}
+
+void mozilla::detail::RWLockImpl::readLock() {
+ AcquireSRWLockShared(NativeHandle(mRWLock));
+}
+
+void mozilla::detail::RWLockImpl::readUnlock() {
+ ReleaseSRWLockShared(NativeHandle(mRWLock));
+}
+
+bool mozilla::detail::RWLockImpl::tryWriteLock() {
+ return TryAcquireSRWLockExclusive(NativeHandle(mRWLock));
+}
+
+void mozilla::detail::RWLockImpl::writeLock() {
+ AcquireSRWLockExclusive(NativeHandle(mRWLock));
+}
+
+void mozilla::detail::RWLockImpl::writeUnlock() {
+ ReleaseSRWLockExclusive(NativeHandle(mRWLock));
+}
+
+#undef NativeHandle
diff --git a/mozglue/misc/RuntimeExceptionModule.cpp b/mozglue/misc/RuntimeExceptionModule.cpp
new file mode 100644
index 0000000000..b8a41cd7cf
--- /dev/null
+++ b/mozglue/misc/RuntimeExceptionModule.cpp
@@ -0,0 +1,107 @@
+/* -*- 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 "RuntimeExceptionModule.h"
+
+#include <cstdint>
+
+#include "mozilla/ProcessType.h"
+
+#if defined(XP_WIN)
+# include <windows.h>
+# if defined(__MINGW32__) || defined(__MINGW64__)
+// Add missing constants and types for mingw builds
+typedef HANDLE HREPORT;
+# define WerReportSubmit(a, b, c, d) \
+ WerReportSubmit(a, b, c, WER_SUBMIT_RESULT* pSubmitResult)
+# define WER_MAX_PREFERRED_MODULES_BUFFER 256
+# endif // defined(__MINGW32__) || defined(__MINGW64__)
+# include <werapi.h> // For WerRegisterRuntimeExceptionModule()
+# if defined(__MINGW32__) || defined(__MINGW64__)
+# undef WerReportSubmit
+# endif // defined(__MINGW32__) || defined(__MINGW64__)
+# include <stdlib.h>
+
+# include "mozilla/Unused.h"
+
+using mozilla::Unused;
+#endif
+
+namespace CrashReporter {
+
+#ifdef XP_WIN
+
+const static size_t kModulePathLength = MAX_PATH + 1;
+static wchar_t sModulePath[kModulePathLength];
+
+bool GetRuntimeExceptionModulePath(wchar_t* aPath, const size_t aLength) {
+ const wchar_t* kModuleName = L"mozwer.dll";
+ DWORD res = ::GetModuleFileNameW(nullptr, aPath, aLength);
+ if ((res > 0) && (res != aLength)) {
+ wchar_t* last_backslash = wcsrchr(aPath, L'\\');
+ if (last_backslash) {
+ *(last_backslash + 1) = L'\0';
+ if (wcscat_s(aPath, aLength, kModuleName) == 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+#endif // XP_WIN
+
+void RegisterRuntimeExceptionModule() {
+#ifdef XP_WIN
+# if defined(DEBUG)
+ // In debug builds, disable the crash reporter by default, and allow to
+ // enable it with the MOZ_CRASHREPORTER environment variable.
+ const char* envvar = getenv("MOZ_CRASHREPORTER");
+ if (!envvar || !*envvar) {
+ return;
+ }
+# else
+ // In other builds, enable the crash reporter by default, and allow
+ // disabling it with the MOZ_CRASHREPORTER_DISABLE environment variable.
+ const char* envvar = getenv("MOZ_CRASHREPORTER_DISABLE");
+ if (envvar && *envvar) {
+ return;
+ }
+# endif
+
+ // If sModulePath is set we have already registerd the module.
+ if (*sModulePath) {
+ return;
+ }
+
+ // If we fail to get the path just return.
+ if (!GetRuntimeExceptionModulePath(sModulePath, kModulePathLength)) {
+ return;
+ }
+
+ if (FAILED(::WerRegisterRuntimeExceptionModule(
+ sModulePath,
+ reinterpret_cast<PVOID>(mozilla::GetGeckoProcessType())))) {
+ // The registration failed null out sModulePath to record this.
+ *sModulePath = L'\0';
+ return;
+ }
+#endif // XP_WIN
+}
+
+void UnregisterRuntimeExceptionModule() {
+#ifdef XP_WIN
+ // If sModulePath is set then we have registered the module.
+ if (*sModulePath) {
+ Unused << ::WerUnregisterRuntimeExceptionModule(
+ sModulePath, reinterpret_cast<PVOID>(mozilla::GetGeckoProcessType()));
+ *sModulePath = L'\0';
+ }
+#endif // XP_WIN
+}
+
+} // namespace CrashReporter
diff --git a/mozglue/misc/RuntimeExceptionModule.h b/mozglue/misc/RuntimeExceptionModule.h
new file mode 100644
index 0000000000..47468e4f36
--- /dev/null
+++ b/mozglue/misc/RuntimeExceptionModule.h
@@ -0,0 +1,20 @@
+/* -*- 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 RUNTIMEEXCEPTIONMODULE_H_
+#define RUNTIMEEXCEPTIONMODULE_H_
+
+#include "mozilla/Types.h"
+
+namespace CrashReporter {
+
+MFBT_API void RegisterRuntimeExceptionModule();
+
+MFBT_API void UnregisterRuntimeExceptionModule();
+
+} // namespace CrashReporter
+
+#endif // RUNTIMEEXCEPTIONMODULE_H_
diff --git a/mozglue/misc/SIMD.cpp b/mozglue/misc/SIMD.cpp
new file mode 100644
index 0000000000..3893de57b3
--- /dev/null
+++ b/mozglue/misc/SIMD.cpp
@@ -0,0 +1,565 @@
+/* -*- 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 "mozilla/SIMD.h"
+
+#include <cstring>
+#include <stdint.h>
+#include <type_traits>
+
+#include "mozilla/EndianUtils.h"
+#include "mozilla/SSE.h"
+
+#ifdef MOZILLA_PRESUME_SSE2
+
+# include <immintrin.h>
+
+#endif
+
+namespace mozilla {
+
+template <typename TValue>
+const TValue* FindInBufferNaive(const TValue* ptr, TValue value,
+ size_t length) {
+ const TValue* end = ptr + length;
+ while (ptr < end) {
+ if (*ptr == value) {
+ return ptr;
+ }
+ ptr++;
+ }
+ return nullptr;
+}
+
+#ifdef MOZILLA_PRESUME_SSE2
+
+const __m128i* Cast128(uintptr_t ptr) {
+ return reinterpret_cast<const __m128i*>(ptr);
+}
+
+template <typename T>
+T GetAs(uintptr_t ptr) {
+ return *reinterpret_cast<const T*>(ptr);
+}
+
+// Akin to ceil/floor, AlignDown/AlignUp will return the original pointer if it
+// is already aligned.
+uintptr_t AlignDown16(uintptr_t ptr) { return ptr & ~0xf; }
+
+uintptr_t AlignUp16(uintptr_t ptr) { return AlignDown16(ptr + 0xf); }
+
+template <typename TValue>
+__m128i CmpEq128(__m128i a, __m128i b) {
+ static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2);
+ if (sizeof(TValue) == 1) {
+ return _mm_cmpeq_epi8(a, b);
+ }
+ return _mm_cmpeq_epi16(a, b);
+}
+
+# ifdef __GNUC__
+
+// Earlier versions of GCC are missing the _mm_loadu_si32 instruction. This
+// workaround from Peter Cordes (https://stackoverflow.com/a/72837992) compiles
+// down to the same instructions. We could just replace _mm_loadu_si32
+__m128i Load32BitsIntoXMM(uintptr_t ptr) {
+ int tmp;
+ memcpy(&tmp, reinterpret_cast<const void*>(ptr),
+ sizeof(tmp)); // unaligned aliasing-safe load
+ return _mm_cvtsi32_si128(tmp); // efficient on GCC/clang/MSVC
+}
+
+# else
+
+__m128i Load32BitsIntoXMM(uintptr_t ptr) {
+ return _mm_loadu_si32(Cast128(ptr));
+}
+
+# endif
+
+const char* Check4x4Chars(__m128i needle, uintptr_t a, uintptr_t b, uintptr_t c,
+ uintptr_t d) {
+ __m128i haystackA = Load32BitsIntoXMM(a);
+ __m128i cmpA = CmpEq128<char>(needle, haystackA);
+ __m128i haystackB = Load32BitsIntoXMM(b);
+ __m128i cmpB = CmpEq128<char>(needle, haystackB);
+ __m128i haystackC = Load32BitsIntoXMM(c);
+ __m128i cmpC = CmpEq128<char>(needle, haystackC);
+ __m128i haystackD = Load32BitsIntoXMM(d);
+ __m128i cmpD = CmpEq128<char>(needle, haystackD);
+ __m128i or_ab = _mm_or_si128(cmpA, cmpB);
+ __m128i or_cd = _mm_or_si128(cmpC, cmpD);
+ __m128i or_abcd = _mm_or_si128(or_ab, or_cd);
+ int orMask = _mm_movemask_epi8(or_abcd);
+ if (orMask & 0xf) {
+ int cmpMask;
+ cmpMask = _mm_movemask_epi8(cmpA);
+ if (cmpMask & 0xf) {
+ return reinterpret_cast<const char*>(a + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm_movemask_epi8(cmpB);
+ if (cmpMask & 0xf) {
+ return reinterpret_cast<const char*>(b + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm_movemask_epi8(cmpC);
+ if (cmpMask & 0xf) {
+ return reinterpret_cast<const char*>(c + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm_movemask_epi8(cmpD);
+ if (cmpMask & 0xf) {
+ return reinterpret_cast<const char*>(d + __builtin_ctz(cmpMask));
+ }
+ }
+
+ return nullptr;
+}
+
+template <typename TValue>
+const TValue* Check4x16Bytes(__m128i needle, uintptr_t a, uintptr_t b,
+ uintptr_t c, uintptr_t d) {
+ __m128i haystackA = _mm_loadu_si128(Cast128(a));
+ __m128i cmpA = CmpEq128<TValue>(needle, haystackA);
+ __m128i haystackB = _mm_loadu_si128(Cast128(b));
+ __m128i cmpB = CmpEq128<TValue>(needle, haystackB);
+ __m128i haystackC = _mm_loadu_si128(Cast128(c));
+ __m128i cmpC = CmpEq128<TValue>(needle, haystackC);
+ __m128i haystackD = _mm_loadu_si128(Cast128(d));
+ __m128i cmpD = CmpEq128<TValue>(needle, haystackD);
+ __m128i or_ab = _mm_or_si128(cmpA, cmpB);
+ __m128i or_cd = _mm_or_si128(cmpC, cmpD);
+ __m128i or_abcd = _mm_or_si128(or_ab, or_cd);
+ int orMask = _mm_movemask_epi8(or_abcd);
+ if (orMask) {
+ int cmpMask;
+ cmpMask = _mm_movemask_epi8(cmpA);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(a + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm_movemask_epi8(cmpB);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(b + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm_movemask_epi8(cmpC);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(c + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm_movemask_epi8(cmpD);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(d + __builtin_ctz(cmpMask));
+ }
+ }
+
+ return nullptr;
+}
+
+enum class HaystackOverlap {
+ Overlapping,
+ Sequential,
+};
+
+// Check two 16-byte chunks for the two-byte sequence loaded into needle1
+// followed by needle1. `carryOut` is an optional pointer which we will
+// populate based on whether the last character of b matches needle1. This
+// should be provided on subsequent calls via `carryIn` so we can detect cases
+// where the last byte of b's 16-byte chunk is needle1 and the first byte of
+// the next a's 16-byte chunk is needle2. `overlap` and whether
+// `carryIn`/`carryOut` are NULL should be knowable at compile time to avoid
+// branching.
+template <typename TValue>
+const TValue* Check2x2x16Bytes(__m128i needle1, __m128i needle2, uintptr_t a,
+ uintptr_t b, __m128i* carryIn, __m128i* carryOut,
+ HaystackOverlap overlap) {
+ const int shiftRightAmount = 16 - sizeof(TValue);
+ const int shiftLeftAmount = sizeof(TValue);
+ __m128i haystackA = _mm_loadu_si128(Cast128(a));
+ __m128i cmpA1 = CmpEq128<TValue>(needle1, haystackA);
+ __m128i cmpA2 = CmpEq128<TValue>(needle2, haystackA);
+ __m128i cmpA;
+ if (carryIn) {
+ cmpA = _mm_and_si128(
+ _mm_or_si128(_mm_bslli_si128(cmpA1, shiftLeftAmount), *carryIn), cmpA2);
+ } else {
+ cmpA = _mm_and_si128(_mm_bslli_si128(cmpA1, shiftLeftAmount), cmpA2);
+ }
+ __m128i haystackB = _mm_loadu_si128(Cast128(b));
+ __m128i cmpB1 = CmpEq128<TValue>(needle1, haystackB);
+ __m128i cmpB2 = CmpEq128<TValue>(needle2, haystackB);
+ __m128i cmpB;
+ if (overlap == HaystackOverlap::Overlapping) {
+ cmpB = _mm_and_si128(_mm_bslli_si128(cmpB1, shiftLeftAmount), cmpB2);
+ } else {
+ MOZ_ASSERT(overlap == HaystackOverlap::Sequential);
+ __m128i carryAB = _mm_bsrli_si128(cmpA1, shiftRightAmount);
+ cmpB = _mm_and_si128(
+ _mm_or_si128(_mm_bslli_si128(cmpB1, shiftLeftAmount), carryAB), cmpB2);
+ }
+ __m128i or_ab = _mm_or_si128(cmpA, cmpB);
+ int orMask = _mm_movemask_epi8(or_ab);
+ if (orMask) {
+ int cmpMask;
+ cmpMask = _mm_movemask_epi8(cmpA);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(a + __builtin_ctz(cmpMask) -
+ shiftLeftAmount);
+ }
+ cmpMask = _mm_movemask_epi8(cmpB);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(b + __builtin_ctz(cmpMask) -
+ shiftLeftAmount);
+ }
+ }
+
+ if (carryOut) {
+ _mm_store_si128(carryOut, _mm_bsrli_si128(cmpB1, shiftRightAmount));
+ }
+
+ return nullptr;
+}
+
+template <typename TValue>
+const TValue* FindInBuffer(const TValue* ptr, TValue value, size_t length) {
+ static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2);
+ static_assert(std::is_unsigned<TValue>::value);
+ uint64_t splat64;
+ if (sizeof(TValue) == 1) {
+ splat64 = 0x0101010101010101llu;
+ } else {
+ splat64 = 0x0001000100010001llu;
+ }
+
+ // Load our needle into a 16-byte register
+ uint64_t u64_value = static_cast<uint64_t>(value) * splat64;
+ int64_t i64_value = *reinterpret_cast<int64_t*>(&u64_value);
+ __m128i needle = _mm_set_epi64x(i64_value, i64_value);
+
+ size_t numBytes = length * sizeof(TValue);
+ uintptr_t cur = reinterpret_cast<uintptr_t>(ptr);
+ uintptr_t end = cur + numBytes;
+
+ if ((sizeof(TValue) > 1 && numBytes < 16) || numBytes < 4) {
+ while (cur < end) {
+ if (GetAs<TValue>(cur) == value) {
+ return reinterpret_cast<const TValue*>(cur);
+ }
+ cur += sizeof(TValue);
+ }
+ return nullptr;
+ }
+
+ if (numBytes < 16) {
+ // NOTE: here and below, we have some bit fiddling which could look a
+ // little weird. The important thing to note though is it's just a trick
+ // for getting the number 4 if numBytes is greater than or equal to 8,
+ // and 0 otherwise. This lets us fully cover the range without any
+ // branching for the case where numBytes is in [4,8), and [8,16). We get
+ // four ranges from this - if numbytes > 8, we get:
+ // [0,4), [4,8], [end - 8), [end - 4)
+ // and if numbytes < 8, we get
+ // [0,4), [0,4), [end - 4), [end - 4)
+ uintptr_t a = cur;
+ uintptr_t b = cur + ((numBytes & 8) >> 1);
+ uintptr_t c = end - 4 - ((numBytes & 8) >> 1);
+ uintptr_t d = end - 4;
+ const char* charResult = Check4x4Chars(needle, a, b, c, d);
+ // Note: we ensure above that sizeof(TValue) == 1 here, so this is
+ // either char to char or char to something like a uint8_t.
+ return reinterpret_cast<const TValue*>(charResult);
+ }
+
+ if (numBytes < 64) {
+ // NOTE: see the above explanation of the similar chunk of code, but in
+ // this case, replace 8 with 32 and 4 with 16.
+ uintptr_t a = cur;
+ uintptr_t b = cur + ((numBytes & 32) >> 1);
+ uintptr_t c = end - 16 - ((numBytes & 32) >> 1);
+ uintptr_t d = end - 16;
+ return Check4x16Bytes<TValue>(needle, a, b, c, d);
+ }
+
+ // Get the initial unaligned load out of the way. This will overlap with the
+ // aligned stuff below, but the overlapped part should effectively be free
+ // (relative to a mispredict from doing a byte-by-byte loop).
+ __m128i haystack = _mm_loadu_si128(Cast128(cur));
+ __m128i cmp = CmpEq128<TValue>(needle, haystack);
+ int cmpMask = _mm_movemask_epi8(cmp);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(cur + __builtin_ctz(cmpMask));
+ }
+
+ // Now we're working with aligned memory. Hooray! \o/
+ cur = AlignUp16(cur);
+
+ // The address of the final 48-63 bytes. We overlap this with what we check in
+ // our hot loop below to avoid branching. Again, the overlap should be
+ // negligible compared with a branch mispredict.
+ uintptr_t tailStartPtr = AlignDown16(end - 48);
+ uintptr_t tailEndPtr = end - 16;
+
+ while (cur < tailStartPtr) {
+ uintptr_t a = cur;
+ uintptr_t b = cur + 16;
+ uintptr_t c = cur + 32;
+ uintptr_t d = cur + 48;
+ const TValue* result = Check4x16Bytes<TValue>(needle, a, b, c, d);
+ if (result) {
+ return result;
+ }
+ cur += 64;
+ }
+
+ uintptr_t a = tailStartPtr;
+ uintptr_t b = tailStartPtr + 16;
+ uintptr_t c = tailStartPtr + 32;
+ uintptr_t d = tailEndPtr;
+ return Check4x16Bytes<TValue>(needle, a, b, c, d);
+}
+
+template <typename TValue>
+const TValue* TwoElementLoop(uintptr_t start, uintptr_t end, TValue v1,
+ TValue v2) {
+ static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2);
+
+ const TValue* cur = reinterpret_cast<const TValue*>(start);
+ const TValue* preEnd = reinterpret_cast<const TValue*>(end - sizeof(TValue));
+
+ uint32_t expected = static_cast<uint32_t>(v1) |
+ (static_cast<uint32_t>(v2) << (sizeof(TValue) * 8));
+ while (cur < preEnd) {
+ // NOTE: this should only ever be called on little endian architectures.
+ static_assert(MOZ_LITTLE_ENDIAN());
+ // We or cur[0] and cur[1] together explicitly and compare to expected,
+ // in order to avoid UB from just loading them as a uint16_t/uint32_t.
+ // However, it will compile down the same code after optimizations on
+ // little endian systems which support unaligned loads. Comparing them
+ // value-by-value, however, will not, and seems to perform worse in local
+ // microbenchmarking. Even after bitwise or'ing the comparison values
+ // together to avoid the short circuit, the compiler doesn't seem to get
+ // the hint and creates two branches, the first of which might be
+ // frequently mispredicted.
+ uint32_t actual = static_cast<uint32_t>(cur[0]) |
+ (static_cast<uint32_t>(cur[1]) << (sizeof(TValue) * 8));
+ if (actual == expected) {
+ return cur;
+ }
+ cur++;
+ }
+ return nullptr;
+}
+
+template <typename TValue>
+const TValue* FindTwoInBuffer(const TValue* ptr, TValue v1, TValue v2,
+ size_t length) {
+ static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2);
+ static_assert(std::is_unsigned<TValue>::value);
+ uint64_t splat64;
+ if (sizeof(TValue) == 1) {
+ splat64 = 0x0101010101010101llu;
+ } else {
+ splat64 = 0x0001000100010001llu;
+ }
+
+ // Load our needle into a 16-byte register
+ uint64_t u64_v1 = static_cast<uint64_t>(v1) * splat64;
+ int64_t i64_v1 = *reinterpret_cast<int64_t*>(&u64_v1);
+ __m128i needle1 = _mm_set_epi64x(i64_v1, i64_v1);
+ uint64_t u64_v2 = static_cast<uint64_t>(v2) * splat64;
+ int64_t i64_v2 = *reinterpret_cast<int64_t*>(&u64_v2);
+ __m128i needle2 = _mm_set_epi64x(i64_v2, i64_v2);
+
+ size_t numBytes = length * sizeof(TValue);
+ uintptr_t cur = reinterpret_cast<uintptr_t>(ptr);
+ uintptr_t end = cur + numBytes;
+
+ if (numBytes < 16) {
+ return TwoElementLoop<TValue>(cur, end, v1, v2);
+ }
+
+ if (numBytes < 32) {
+ uintptr_t a = cur;
+ uintptr_t b = end - 16;
+ return Check2x2x16Bytes<TValue>(needle1, needle2, a, b, nullptr, nullptr,
+ HaystackOverlap::Overlapping);
+ }
+
+ // Get the initial unaligned load out of the way. This will likely overlap
+ // with the aligned stuff below, but the overlapped part should effectively
+ // be free.
+ __m128i haystack = _mm_loadu_si128(Cast128(cur));
+ __m128i cmp1 = CmpEq128<TValue>(needle1, haystack);
+ __m128i cmp2 = CmpEq128<TValue>(needle2, haystack);
+ int cmpMask1 = _mm_movemask_epi8(cmp1);
+ int cmpMask2 = _mm_movemask_epi8(cmp2);
+ int cmpMask = (cmpMask1 << sizeof(TValue)) & cmpMask2;
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(cur + __builtin_ctz(cmpMask) -
+ sizeof(TValue));
+ }
+
+ // Now we're working with aligned memory. Hooray! \o/
+ cur = AlignUp16(cur);
+
+ // The address of the final 48-63 bytes. We overlap this with what we check in
+ // our hot loop below to avoid branching. Again, the overlap should be
+ // negligible compared with a branch mispredict.
+ uintptr_t tailEndPtr = end - 16;
+ uintptr_t tailStartPtr = AlignDown16(tailEndPtr);
+
+ __m128i cmpMaskCarry = _mm_set1_epi32(0);
+ while (cur < tailStartPtr) {
+ uintptr_t a = cur;
+ uintptr_t b = cur + 16;
+ const TValue* result =
+ Check2x2x16Bytes<TValue>(needle1, needle2, a, b, &cmpMaskCarry,
+ &cmpMaskCarry, HaystackOverlap::Sequential);
+ if (result) {
+ return result;
+ }
+ cur += 32;
+ }
+
+ uint32_t carry = (cur == tailStartPtr) ? 0xffffffff : 0;
+ __m128i wideCarry = Load32BitsIntoXMM(reinterpret_cast<uintptr_t>(&carry));
+ cmpMaskCarry = _mm_and_si128(cmpMaskCarry, wideCarry);
+ uintptr_t a = tailStartPtr;
+ uintptr_t b = tailEndPtr;
+ return Check2x2x16Bytes<TValue>(needle1, needle2, a, b, &cmpMaskCarry,
+ nullptr, HaystackOverlap::Overlapping);
+}
+
+const char* SIMD::memchr8SSE2(const char* ptr, char value, size_t length) {
+ // Signed chars are just really annoying to do bit logic with. Convert to
+ // unsigned at the outermost scope so we don't have to worry about it.
+ const unsigned char* uptr = reinterpret_cast<const unsigned char*>(ptr);
+ unsigned char uvalue = static_cast<unsigned char>(value);
+ const unsigned char* uresult =
+ FindInBuffer<unsigned char>(uptr, uvalue, length);
+ return reinterpret_cast<const char*>(uresult);
+}
+
+// So, this is a bit awkward. It generally simplifies things if we can just
+// assume all the AVX2 code is 64-bit, so we have this preprocessor guard
+// in SIMD_avx2 over all of its actual code, and it also defines versions
+// of its endpoints that just assert false if the guard is not satisfied.
+// A 32 bit processor could implement the AVX2 instruction set though, which
+// would result in it passing the supports_avx2() check and landing in an
+// assertion failure. Accordingly, we just don't allow that to happen. We
+// are not particularly concerned about ensuring that newer 32 bit processors
+// get access to the AVX2 functions exposed here.
+# if defined(MOZILLA_MAY_SUPPORT_AVX2) && defined(__x86_64__)
+
+bool SupportsAVX2() { return supports_avx2(); }
+
+# else
+
+bool SupportsAVX2() { return false; }
+
+# endif
+
+const char* SIMD::memchr8(const char* ptr, char value, size_t length) {
+ if (SupportsAVX2()) {
+ return memchr8AVX2(ptr, value, length);
+ }
+ return memchr8SSE2(ptr, value, length);
+}
+
+const char16_t* SIMD::memchr16SSE2(const char16_t* ptr, char16_t value,
+ size_t length) {
+ return FindInBuffer<char16_t>(ptr, value, length);
+}
+
+const char16_t* SIMD::memchr16(const char16_t* ptr, char16_t value,
+ size_t length) {
+ if (SupportsAVX2()) {
+ return memchr16AVX2(ptr, value, length);
+ }
+ return memchr16SSE2(ptr, value, length);
+}
+
+const uint64_t* SIMD::memchr64(const uint64_t* ptr, uint64_t value,
+ size_t length) {
+ if (SupportsAVX2()) {
+ return memchr64AVX2(ptr, value, length);
+ }
+ return FindInBufferNaive<uint64_t>(ptr, value, length);
+}
+
+const char* SIMD::memchr2x8(const char* ptr, char v1, char v2, size_t length) {
+ // Signed chars are just really annoying to do bit logic with. Convert to
+ // unsigned at the outermost scope so we don't have to worry about it.
+ const unsigned char* uptr = reinterpret_cast<const unsigned char*>(ptr);
+ unsigned char uv1 = static_cast<unsigned char>(v1);
+ unsigned char uv2 = static_cast<unsigned char>(v2);
+ const unsigned char* uresult =
+ FindTwoInBuffer<unsigned char>(uptr, uv1, uv2, length);
+ return reinterpret_cast<const char*>(uresult);
+}
+
+const char16_t* SIMD::memchr2x16(const char16_t* ptr, char16_t v1, char16_t v2,
+ size_t length) {
+ return FindTwoInBuffer<char16_t>(ptr, v1, v2, length);
+}
+
+#else
+
+const char* SIMD::memchr8(const char* ptr, char value, size_t length) {
+ const void* result = ::memchr(reinterpret_cast<const void*>(ptr),
+ static_cast<int>(value), length);
+ return reinterpret_cast<const char*>(result);
+}
+
+const char* SIMD::memchr8SSE2(const char* ptr, char value, size_t length) {
+ return memchr8(ptr, value, length);
+}
+
+const char16_t* SIMD::memchr16(const char16_t* ptr, char16_t value,
+ size_t length) {
+ return FindInBufferNaive<char16_t>(ptr, value, length);
+}
+
+const char16_t* SIMD::memchr16SSE2(const char16_t* ptr, char16_t value,
+ size_t length) {
+ return memchr16(ptr, value, length);
+}
+
+const uint64_t* SIMD::memchr64(const uint64_t* ptr, uint64_t value,
+ size_t length) {
+ return FindInBufferNaive<uint64_t>(ptr, value, length);
+}
+
+const char* SIMD::memchr2x8(const char* ptr, char v1, char v2, size_t length) {
+ const char* end = ptr + length - 1;
+ while (ptr < end) {
+ ptr = memchr8(ptr, v1, end - ptr);
+ if (!ptr) {
+ return nullptr;
+ }
+ if (ptr[1] == v2) {
+ return ptr;
+ }
+ ptr++;
+ }
+ return nullptr;
+}
+
+const char16_t* SIMD::memchr2x16(const char16_t* ptr, char16_t v1, char16_t v2,
+ size_t length) {
+ const char16_t* end = ptr + length - 1;
+ while (ptr < end) {
+ ptr = memchr16(ptr, v1, end - ptr);
+ if (!ptr) {
+ return nullptr;
+ }
+ if (ptr[1] == v2) {
+ return ptr;
+ }
+ ptr++;
+ }
+ return nullptr;
+}
+
+#endif
+
+} // namespace mozilla
diff --git a/mozglue/misc/SIMD.h b/mozglue/misc/SIMD.h
new file mode 100644
index 0000000000..3d17185656
--- /dev/null
+++ b/mozglue/misc/SIMD.h
@@ -0,0 +1,81 @@
+/* -*- 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 mozilla_SIMD_h
+#define mozilla_SIMD_h
+
+#include "mozilla/Types.h"
+
+namespace mozilla {
+// A collection of SIMD-implemented algorithms. Some of these exist in the CRT.
+// However, the quality of the C runtime implementation varies wildly across
+// platforms, so these should at least ensure consistency.
+//
+// NOTE: these are currently only implemented with hand-written SIMD for x86
+// and AMD64 platforms, and fallback to the the C runtime or naive loops on
+// other architectures. Please consider this before switching an already
+// optimized loop to these helpers.
+class SIMD {
+ public:
+ // NOTE: for memchr we have a goofy void* signature just to be an easy drop
+ // in replacement for the CRT version. We also give memchr8 which is just a
+ // typed version of memchr.
+ static const void* memchr(const void* ptr, int value, size_t num) {
+ return memchr8(reinterpret_cast<const char*>(ptr), static_cast<char>(value),
+ num);
+ }
+
+ // Search through `ptr[0..length]` for the first occurrence of `value` and
+ // return the pointer to it, or nullptr if it cannot be found.
+ static MFBT_API const char* memchr8(const char* ptr, char value,
+ size_t length);
+
+ // This function just restricts our execution to the SSE2 path
+ static MFBT_API const char* memchr8SSE2(const char* ptr, char value,
+ size_t length);
+
+ // This function just restricts our execution to the AVX2 path
+ static MFBT_API const char* memchr8AVX2(const char* ptr, char value,
+ size_t length);
+
+ // Search through `ptr[0..length]` for the first occurrence of `value` and
+ // return the pointer to it, or nullptr if it cannot be found.
+ static MFBT_API const char16_t* memchr16(const char16_t* ptr, char16_t value,
+ size_t length);
+
+ // This function just restricts our execution to the SSE2 path
+ static MFBT_API const char16_t* memchr16SSE2(const char16_t* ptr,
+ char16_t value, size_t length);
+
+ // This function just restricts our execution to the AVX2 path
+ static MFBT_API const char16_t* memchr16AVX2(const char16_t* ptr,
+ char16_t value, size_t length);
+
+ // Search through `ptr[0..length]` for the first occurrence of `value` and
+ // return the pointer to it, or nullptr if it cannot be found.
+ static MFBT_API const uint64_t* memchr64(const uint64_t* ptr, uint64_t value,
+ size_t length);
+
+ // This function just restricts our execution to the AVX2 path
+ static MFBT_API const uint64_t* memchr64AVX2(const uint64_t* ptr,
+ uint64_t value, size_t length);
+
+ // Search through `ptr[0..length]` for the first occurrence of `v1` which is
+ // immediately followed by `v2` and return the pointer to the occurrence of
+ // `v1`.
+ static MFBT_API const char* memchr2x8(const char* ptr, char v1, char v2,
+ size_t length);
+
+ // Search through `ptr[0..length]` for the first occurrence of `v1` which is
+ // immediately followed by `v2` and return the pointer to the occurrence of
+ // `v1`.
+ static MFBT_API const char16_t* memchr2x16(const char16_t* ptr, char16_t v1,
+ char16_t v2, size_t length);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SIMD_h
diff --git a/mozglue/misc/SIMD_avx2.cpp b/mozglue/misc/SIMD_avx2.cpp
new file mode 100644
index 0000000000..a1467c7a55
--- /dev/null
+++ b/mozglue/misc/SIMD_avx2.cpp
@@ -0,0 +1,294 @@
+/* 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 "mozilla/SIMD.h"
+
+#include "mozilla/SSE.h"
+#include "mozilla/Assertions.h"
+
+// Restricting to x86_64 simplifies things, and we're not particularly
+// worried about slightly degraded performance on 32 bit processors which
+// support AVX2, as this should be quite a minority.
+#if defined(MOZILLA_MAY_SUPPORT_AVX2) && defined(__x86_64__)
+
+# include <cstring>
+# include <immintrin.h>
+# include <stdint.h>
+# include <type_traits>
+
+# include "mozilla/EndianUtils.h"
+
+namespace mozilla {
+
+const __m256i* Cast256(uintptr_t ptr) {
+ return reinterpret_cast<const __m256i*>(ptr);
+}
+
+template <typename T>
+T GetAs(uintptr_t ptr) {
+ return *reinterpret_cast<const T*>(ptr);
+}
+
+uintptr_t AlignDown32(uintptr_t ptr) { return ptr & ~0x1f; }
+
+uintptr_t AlignUp32(uintptr_t ptr) { return AlignDown32(ptr + 0x1f); }
+
+template <typename TValue>
+__m128i CmpEq128(__m128i a, __m128i b) {
+ static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2);
+ if (sizeof(TValue) == 1) {
+ return _mm_cmpeq_epi8(a, b);
+ }
+ return _mm_cmpeq_epi16(a, b);
+}
+
+template <typename TValue>
+__m256i CmpEq256(__m256i a, __m256i b) {
+ static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2 ||
+ sizeof(TValue) == 8);
+ if (sizeof(TValue) == 1) {
+ return _mm256_cmpeq_epi8(a, b);
+ }
+ if (sizeof(TValue) == 2) {
+ return _mm256_cmpeq_epi16(a, b);
+ }
+
+ return _mm256_cmpeq_epi64(a, b);
+}
+
+# if defined(__GNUC__) && !defined(__clang__)
+
+// See the comment in SIMD.cpp over Load32BitsIntoXMM. This is just adapted
+// from that workaround. Testing this, it also yields the correct instructions
+// across all tested compilers.
+__m128i Load64BitsIntoXMM(uintptr_t ptr) {
+ int64_t tmp;
+ memcpy(&tmp, reinterpret_cast<const void*>(ptr), sizeof(tmp));
+ return _mm_cvtsi64_si128(tmp);
+}
+
+# else
+
+__m128i Load64BitsIntoXMM(uintptr_t ptr) {
+ return _mm_loadu_si64(reinterpret_cast<const __m128i*>(ptr));
+}
+
+# endif
+
+template <typename TValue>
+const TValue* Check4x8Bytes(__m128i needle, uintptr_t a, uintptr_t b,
+ uintptr_t c, uintptr_t d) {
+ __m128i haystackA = Load64BitsIntoXMM(a);
+ __m128i cmpA = CmpEq128<TValue>(needle, haystackA);
+ __m128i haystackB = Load64BitsIntoXMM(b);
+ __m128i cmpB = CmpEq128<TValue>(needle, haystackB);
+ __m128i haystackC = Load64BitsIntoXMM(c);
+ __m128i cmpC = CmpEq128<TValue>(needle, haystackC);
+ __m128i haystackD = Load64BitsIntoXMM(d);
+ __m128i cmpD = CmpEq128<TValue>(needle, haystackD);
+ __m128i or_ab = _mm_or_si128(cmpA, cmpB);
+ __m128i or_cd = _mm_or_si128(cmpC, cmpD);
+ __m128i or_abcd = _mm_or_si128(or_ab, or_cd);
+ int orMask = _mm_movemask_epi8(or_abcd);
+ if (orMask & 0xff) {
+ int cmpMask;
+ cmpMask = _mm_movemask_epi8(cmpA);
+ if (cmpMask & 0xff) {
+ return reinterpret_cast<const TValue*>(a + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm_movemask_epi8(cmpB);
+ if (cmpMask & 0xff) {
+ return reinterpret_cast<const TValue*>(b + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm_movemask_epi8(cmpC);
+ if (cmpMask & 0xff) {
+ return reinterpret_cast<const TValue*>(c + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm_movemask_epi8(cmpD);
+ if (cmpMask & 0xff) {
+ return reinterpret_cast<const TValue*>(d + __builtin_ctz(cmpMask));
+ }
+ }
+
+ return nullptr;
+}
+
+template <typename TValue>
+const TValue* Check4x32Bytes(__m256i needle, uintptr_t a, uintptr_t b,
+ uintptr_t c, uintptr_t d) {
+ __m256i haystackA = _mm256_loadu_si256(Cast256(a));
+ __m256i cmpA = CmpEq256<TValue>(needle, haystackA);
+ __m256i haystackB = _mm256_loadu_si256(Cast256(b));
+ __m256i cmpB = CmpEq256<TValue>(needle, haystackB);
+ __m256i haystackC = _mm256_loadu_si256(Cast256(c));
+ __m256i cmpC = CmpEq256<TValue>(needle, haystackC);
+ __m256i haystackD = _mm256_loadu_si256(Cast256(d));
+ __m256i cmpD = CmpEq256<TValue>(needle, haystackD);
+ __m256i or_ab = _mm256_or_si256(cmpA, cmpB);
+ __m256i or_cd = _mm256_or_si256(cmpC, cmpD);
+ __m256i or_abcd = _mm256_or_si256(or_ab, or_cd);
+ int orMask = _mm256_movemask_epi8(or_abcd);
+ if (orMask) {
+ int cmpMask;
+ cmpMask = _mm256_movemask_epi8(cmpA);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(a + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm256_movemask_epi8(cmpB);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(b + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm256_movemask_epi8(cmpC);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(c + __builtin_ctz(cmpMask));
+ }
+ cmpMask = _mm256_movemask_epi8(cmpD);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(d + __builtin_ctz(cmpMask));
+ }
+ }
+
+ return nullptr;
+}
+
+template <typename TValue>
+const TValue* FindInBufferAVX2(const TValue* ptr, TValue value, size_t length) {
+ static_assert(sizeof(TValue) == 1 || sizeof(TValue) == 2 ||
+ sizeof(TValue) == 8);
+ static_assert(std::is_unsigned<TValue>::value);
+
+ // Load our needle into a 32-byte register
+ __m256i needle;
+ if (sizeof(TValue) == 1) {
+ needle = _mm256_set1_epi8(value);
+ } else if (sizeof(TValue) == 2) {
+ needle = _mm256_set1_epi16(value);
+ } else {
+ needle = _mm256_set1_epi64x(value);
+ }
+
+ size_t numBytes = length * sizeof(TValue);
+ uintptr_t cur = reinterpret_cast<uintptr_t>(ptr);
+ uintptr_t end = cur + numBytes;
+
+ if (numBytes < 8 || (sizeof(TValue) == 8 && numBytes < 32)) {
+ while (cur < end) {
+ if (GetAs<TValue>(cur) == value) {
+ return reinterpret_cast<const TValue*>(cur);
+ }
+ cur += sizeof(TValue);
+ }
+ return nullptr;
+ }
+
+ if constexpr (sizeof(TValue) != 8) {
+ if (numBytes < 32) {
+ __m128i needle_narrow;
+ if (sizeof(TValue) == 1) {
+ needle_narrow = _mm_set1_epi8(value);
+ } else {
+ needle_narrow = _mm_set1_epi16(value);
+ }
+ uintptr_t a = cur;
+ uintptr_t b = cur + ((numBytes & 16) >> 1);
+ uintptr_t c = end - 8 - ((numBytes & 16) >> 1);
+ uintptr_t d = end - 8;
+ return Check4x8Bytes<TValue>(needle_narrow, a, b, c, d);
+ }
+ }
+
+ if (numBytes < 128) {
+ // NOTE: here and below, we have some bit fiddling which could look a
+ // little weird. The important thing to note though is it's just a trick
+ // for getting the number 32 if numBytes is greater than or equal to 64,
+ // and 0 otherwise. This lets us fully cover the range without any
+ // branching for the case where numBytes is in [32,64), and [64,128). We get
+ // four ranges from this - if numbytes > 64, we get:
+ // [0,32), [32,64], [end - 64), [end - 32)
+ // and if numbytes < 64, we get
+ // [0,32), [0,32), [end - 32), [end - 32)
+ uintptr_t a = cur;
+ uintptr_t b = cur + ((numBytes & 64) >> 1);
+ uintptr_t c = end - 32 - ((numBytes & 64) >> 1);
+ uintptr_t d = end - 32;
+ return Check4x32Bytes<TValue>(needle, a, b, c, d);
+ }
+
+ // Get the initial unaligned load out of the way. This will overlap with the
+ // aligned stuff below, but the overlapped part should effectively be free
+ // (relative to a mispredict from doing a byte-by-byte loop).
+ __m256i haystack = _mm256_loadu_si256(Cast256(cur));
+ __m256i cmp = CmpEq256<TValue>(needle, haystack);
+ int cmpMask = _mm256_movemask_epi8(cmp);
+ if (cmpMask) {
+ return reinterpret_cast<const TValue*>(cur + __builtin_ctz(cmpMask));
+ }
+
+ // Now we're working with aligned memory. Hooray! \o/
+ cur = AlignUp32(cur);
+
+ uintptr_t tailStartPtr = AlignDown32(end - 96);
+ uintptr_t tailEndPtr = end - 32;
+
+ while (cur < tailStartPtr) {
+ uintptr_t a = cur;
+ uintptr_t b = cur + 32;
+ uintptr_t c = cur + 64;
+ uintptr_t d = cur + 96;
+ const TValue* result = Check4x32Bytes<TValue>(needle, a, b, c, d);
+ if (result) {
+ return result;
+ }
+ cur += 128;
+ }
+
+ uintptr_t a = tailStartPtr;
+ uintptr_t b = tailStartPtr + 32;
+ uintptr_t c = tailStartPtr + 64;
+ uintptr_t d = tailEndPtr;
+ return Check4x32Bytes<TValue>(needle, a, b, c, d);
+}
+
+const char* SIMD::memchr8AVX2(const char* ptr, char value, size_t length) {
+ const unsigned char* uptr = reinterpret_cast<const unsigned char*>(ptr);
+ unsigned char uvalue = static_cast<unsigned char>(value);
+ const unsigned char* uresult =
+ FindInBufferAVX2<unsigned char>(uptr, uvalue, length);
+ return reinterpret_cast<const char*>(uresult);
+}
+
+const char16_t* SIMD::memchr16AVX2(const char16_t* ptr, char16_t value,
+ size_t length) {
+ return FindInBufferAVX2<char16_t>(ptr, value, length);
+}
+
+const uint64_t* SIMD::memchr64AVX2(const uint64_t* ptr, uint64_t value,
+ size_t length) {
+ return FindInBufferAVX2<uint64_t>(ptr, value, length);
+}
+
+} // namespace mozilla
+
+#else
+
+namespace mozilla {
+
+const char* SIMD::memchr8AVX2(const char* ptr, char value, size_t length) {
+ MOZ_RELEASE_ASSERT(false, "AVX2 not supported in this binary.");
+}
+
+const char16_t* SIMD::memchr16AVX2(const char16_t* ptr, char16_t value,
+ size_t length) {
+ MOZ_RELEASE_ASSERT(false, "AVX2 not supported in this binary.");
+}
+
+const uint64_t* SIMD::memchr64AVX2(const uint64_t* ptr, uint64_t value,
+ size_t length) {
+ MOZ_RELEASE_ASSERT(false, "AVX2 not supported in this binary.");
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/mozglue/misc/SSE.cpp b/mozglue/misc/SSE.cpp
new file mode 100644
index 0000000000..74f3917788
--- /dev/null
+++ b/mozglue/misc/SSE.cpp
@@ -0,0 +1,259 @@
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+/* 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/. */
+
+/* compile-time and runtime tests for whether to use SSE instructions */
+
+#include "SSE.h"
+
+#ifdef HAVE_CPUID_H
+// cpuid.h is available on gcc 4.3 and higher on i386 and x86_64
+# include <cpuid.h>
+#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
+// MSVC 2005 or newer on x86-32 or x86-64
+# include <intrin.h>
+#endif
+
+namespace {
+
+// SSE.h has parallel #ifs which declare MOZILLA_SSE_HAVE_CPUID_DETECTION.
+// We can't declare these functions in the header file, however, because
+// <intrin.h> conflicts with <windows.h> on MSVC 2005, and some files want to
+// include both SSE.h and <windows.h>.
+
+#ifdef HAVE_CPUID_H
+
+enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 };
+
+static bool has_cpuid_bits(unsigned int level, CPUIDRegister reg,
+ unsigned int bits) {
+ unsigned int regs[4];
+ unsigned int eax, ebx, ecx, edx;
+ unsigned max = __get_cpuid_max(level & 0x80000000u, nullptr);
+ if (level > max) return false;
+ __cpuid_count(level, 0, eax, ebx, ecx, edx);
+ regs[0] = eax;
+ regs[1] = ebx;
+ regs[2] = ecx;
+ regs[3] = edx;
+ return (regs[reg] & bits) == bits;
+}
+
+static bool has_cpuid_bits_ex(unsigned int level, CPUIDRegister reg,
+ unsigned int bits) {
+ unsigned int regs[4];
+ unsigned int eax, ebx, ecx, edx;
+ unsigned max = __get_cpuid_max(level & 0x80000000u, nullptr);
+ if (level > max) return false;
+ __cpuid_count(level, 1, eax, ebx, ecx, edx);
+ regs[0] = eax;
+ regs[1] = ebx;
+ regs[2] = ecx;
+ regs[3] = edx;
+ return (regs[reg] & bits) == bits;
+}
+
+#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
+
+enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 };
+
+static bool has_cpuid_bits(unsigned int level, CPUIDRegister reg,
+ unsigned int bits) {
+ // Check that the level in question is supported.
+ int regs[4];
+ __cpuid_ex(regs, level & 0x80000000u, 1);
+ if (unsigned(regs[0]) < level) return false;
+
+ // "The __cpuid intrinsic clears the ECX register before calling the cpuid
+ // instruction."
+ __cpuid_ex(regs, level, 1);
+ return (unsigned(regs[reg]) & bits) == bits;
+}
+
+#elif (defined(__GNUC__) || defined(__SUNPRO_CC)) && \
+ (defined(__i386) || defined(__x86_64__))
+
+enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 };
+
+# ifdef __i386
+static void moz_cpuid(int CPUInfo[4], int InfoType) {
+ asm("xchg %esi, %ebx\n"
+ "xor %ecx, %ecx\n" // ecx is the sub-leaf (we only ever need 0)
+ "cpuid\n"
+ "movl %eax, (%edi)\n"
+ "movl %ebx, 4(%edi)\n"
+ "movl %ecx, 8(%edi)\n"
+ "movl %edx, 12(%edi)\n"
+ "xchg %esi, %ebx\n"
+ :
+ : "a"(InfoType), // %eax
+ "D"(CPUInfo) // %edi
+ : "%ecx", "%edx", "%esi");
+}
+static void moz_cpuid_ex(int CPUInfo[4], int InfoType) {
+ asm("xchg %esi, %ebx\n"
+ "movl 1, %ecx\n"
+ "cpuid\n"
+ "movl %eax, (%edi)\n"
+ "movl %ebx, 4(%edi)\n"
+ "movl %ecx, 8(%edi)\n"
+ "movl %edx, 12(%edi)\n"
+ "xchg %esi, %ebx\n"
+ :
+ : "a"(InfoType), // %eax
+ "D"(CPUInfo) // %edi
+ : "%ecx", "%edx", "%esi");
+}
+# else
+static void moz_cpuid(int CPUInfo[4], int InfoType) {
+ asm("xchg %rsi, %rbx\n"
+ "xor %ecx, %ecx\n" // ecx is the sub-leaf (we only ever need 0)
+ "cpuid\n"
+ "movl %eax, (%rdi)\n"
+ "movl %ebx, 4(%rdi)\n"
+ "movl %ecx, 8(%rdi)\n"
+ "movl %edx, 12(%rdi)\n"
+ "xchg %rsi, %rbx\n"
+ :
+ : "a"(InfoType), // %eax
+ "D"(CPUInfo) // %rdi
+ : "%ecx", "%edx", "%rsi");
+}
+static void moz_cpuid_ex(int CPUInfo[4], int InfoType) {
+ asm("xchg %rsi, %rbx\n"
+ "movl 1, %ecx\n"
+ "cpuid\n"
+ "movl %eax, (%rdi)\n"
+ "movl %ebx, 4(%rdi)\n"
+ "movl %ecx, 8(%rdi)\n"
+ "movl %edx, 12(%rdi)\n"
+ "xchg %rsi, %rbx\n"
+ :
+ : "a"(InfoType), // %eax
+ "D"(CPUInfo) // %rdi
+ : "%ecx", "%edx", "%rsi");
+}
+# endif
+
+static bool has_cpuid_bits(unsigned int level, CPUIDRegister reg,
+ unsigned int bits) {
+ // Check that the level in question is supported.
+ volatile int regs[4];
+ moz_cpuid((int*)regs, level & 0x80000000u);
+ if (unsigned(regs[0]) < level) return false;
+
+ moz_cpuid((int*)regs, level);
+ return (unsigned(regs[reg]) & bits) == bits;
+}
+
+static bool has_cpuid_bits_ex(unsigned int level, CPUIDRegister reg,
+ unsigned int bits) {
+ // Check that the level in question is supported.
+ volatile int regs[4];
+ moz_cpuid_ex((int*)regs, level & 0x80000000u);
+ if (unsigned(regs[0]) < level) return false;
+
+ moz_cpuid_ex((int*)regs, level);
+ return (unsigned(regs[reg]) & bits) == bits;
+}
+
+#endif // end CPUID declarations
+
+} // namespace
+
+namespace mozilla {
+
+namespace sse_private {
+
+#if defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+
+# if !defined(MOZILLA_PRESUME_MMX)
+bool mmx_enabled = has_cpuid_bits(1u, edx, (1u << 23));
+# endif
+
+# if !defined(MOZILLA_PRESUME_SSE)
+bool sse_enabled = has_cpuid_bits(1u, edx, (1u << 25));
+# endif
+
+# if !defined(MOZILLA_PRESUME_SSE2)
+bool sse2_enabled = has_cpuid_bits(1u, edx, (1u << 26));
+# endif
+
+# if !defined(MOZILLA_PRESUME_SSE3)
+bool sse3_enabled = has_cpuid_bits(1u, ecx, (1u << 0));
+# endif
+
+# if !defined(MOZILLA_PRESUME_SSSE3)
+bool ssse3_enabled = has_cpuid_bits(1u, ecx, (1u << 9));
+# endif
+
+# if !defined(MOZILLA_PRESUME_SSE4A)
+bool sse4a_enabled = has_cpuid_bits(0x80000001u, ecx, (1u << 6));
+# endif
+
+# if !defined(MOZILLA_PRESUME_SSE4_1)
+bool sse4_1_enabled = has_cpuid_bits(1u, ecx, (1u << 19));
+# endif
+
+# if !defined(MOZILLA_PRESUME_SSE4_2)
+bool sse4_2_enabled = has_cpuid_bits(1u, ecx, (1u << 20));
+# endif
+
+# if !defined(MOZILLA_PRESUME_FMA3)
+bool fma3_enabled = has_cpuid_bits(1u, ecx, (1u << 12));
+# endif
+
+# if !defined(MOZILLA_PRESUME_AVX) || !defined(MOZILLA_PRESUME_AVX2)
+static bool has_avx() {
+# if defined(MOZILLA_PRESUME_AVX)
+ return true;
+# else
+ const unsigned AVX = 1u << 28;
+ const unsigned OSXSAVE = 1u << 27;
+ const unsigned XSAVE = 1u << 26;
+
+ const unsigned XMM_STATE = 1u << 1;
+ const unsigned YMM_STATE = 1u << 2;
+ const unsigned AVX_STATE = XMM_STATE | YMM_STATE;
+
+ return has_cpuid_bits(1u, ecx, AVX | OSXSAVE | XSAVE) &&
+ // ensure the OS supports XSAVE of YMM registers
+ (xgetbv(0) & AVX_STATE) == AVX_STATE;
+# endif // MOZILLA_PRESUME_AVX
+}
+# endif // !MOZILLA_PRESUME_AVX || !MOZILLA_PRESUME_AVX2
+
+# if !defined(MOZILLA_PRESUME_AVX)
+bool avx_enabled = has_avx();
+# endif
+
+# if !defined(MOZILLA_PRESUME_AVX2)
+bool avx2_enabled = has_avx() && has_cpuid_bits(7u, ebx, (1u << 5));
+# endif
+
+# if !defined(MOZILLA_PRESUME_AVXVNNI)
+bool avxvnni_enabled = has_cpuid_bits_ex(7u, eax, (1u << 4));
+# endif
+
+# if !defined(MOZILLA_PRESUME_AES)
+bool aes_enabled = has_cpuid_bits(1u, ecx, (1u << 25));
+# endif
+
+bool has_constant_tsc = has_cpuid_bits(0x80000007u, edx, (1u << 8));
+
+#endif
+
+} // namespace sse_private
+
+#ifdef HAVE_CPUID_H
+
+uint64_t xgetbv(uint32_t xcr) {
+ uint32_t eax, edx;
+ __asm__(".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c"(xcr));
+ return (uint64_t)(edx) << 32 | eax;
+}
+
+#endif
+
+} // namespace mozilla
diff --git a/mozglue/misc/SSE.h b/mozglue/misc/SSE.h
new file mode 100644
index 0000000000..d7c7e4ae97
--- /dev/null
+++ b/mozglue/misc/SSE.h
@@ -0,0 +1,388 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* 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/. */
+
+/* compile-time and runtime tests for whether to use SSE instructions */
+
+#ifndef mozilla_SSE_h_
+#define mozilla_SSE_h_
+
+// for definition of MFBT_DATA
+#include "mozilla/Types.h"
+
+/**
+ * The public interface of this header consists of a set of macros and
+ * functions for Intel CPU features.
+ *
+ * DETECTING ISA EXTENSIONS
+ * ========================
+ *
+ * This header provides the following functions for determining whether the
+ * current CPU supports a particular instruction set extension:
+ *
+ * mozilla::supports_mmx
+ * mozilla::supports_sse
+ * mozilla::supports_sse2
+ * mozilla::supports_sse3
+ * mozilla::supports_ssse3
+ * mozilla::supports_sse4a
+ * mozilla::supports_sse4_1
+ * mozilla::supports_sse4_2
+ * mozilla::supports_avx
+ * mozilla::supports_avx2
+ * mozilla::supports_aes
+ * mozilla::has_constant_tsc
+ *
+ * If you're writing code using inline assembly, you should guard it with a
+ * call to one of these functions. For instance:
+ *
+ * if (mozilla::supports_sse2()) {
+ * asm(" ... ");
+ * }
+ * else {
+ * ...
+ * }
+ *
+ * Note that these functions depend on cpuid intrinsics only available in gcc
+ * 4.3 or later and MSVC 8.0 (Visual C++ 2005) or later, so they return false
+ * in older compilers. (This could be fixed by replacing the code with inline
+ * assembly.)
+ *
+ *
+ * USING INTRINSICS
+ * ================
+ *
+ * This header also provides support for coding using CPU intrinsics.
+ *
+ * For each mozilla::supports_abc function, we define a MOZILLA_MAY_SUPPORT_ABC
+ * macro which indicates that the target/compiler combination we're using is
+ * compatible with the ABC extension. For instance, x86_64 with MSVC 2003 is
+ * compatible with SSE2 but not SSE3, since although there exist x86_64 CPUs
+ * with SSE3 support, MSVC 2003 only supports through SSE2.
+ *
+ * Until gcc fixes #pragma target [1] [2] or our x86 builds require SSE2,
+ * you'll need to separate code using intrinsics into a file separate from your
+ * regular code. Here's the recommended pattern:
+ *
+ * #ifdef MOZILLA_MAY_SUPPORT_ABC
+ * namespace mozilla {
+ * namespace ABC {
+ * void foo();
+ * }
+ * }
+ * #endif
+ *
+ * void foo() {
+ * #ifdef MOZILLA_MAY_SUPPORT_ABC
+ * if (mozilla::supports_abc()) {
+ * mozilla::ABC::foo(); // in a separate file
+ * return;
+ * }
+ * #endif
+ *
+ * foo_unvectorized();
+ * }
+ *
+ * You'll need to define mozilla::ABC::foo() in a separate file and add the
+ * -mabc flag when using gcc.
+ *
+ * [1] http://gcc.gnu.org/bugzilla/show_bug.cgi?id=39787 and
+ * [2] http://gcc.gnu.org/bugzilla/show_bug.cgi?id=41201 being fixed.
+ *
+ */
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+
+# ifdef __MMX__
+// It's ok to use MMX instructions based on the -march option (or
+// the default for x86_64 or for Intel Mac).
+# define MOZILLA_PRESUME_MMX 1
+# endif
+# ifdef __SSE__
+// It's ok to use SSE instructions based on the -march option (or
+// the default for x86_64 or for Intel Mac).
+# define MOZILLA_PRESUME_SSE 1
+# endif
+# ifdef __SSE2__
+// It's ok to use SSE2 instructions based on the -march option (or
+// the default for x86_64 or for Intel Mac).
+# define MOZILLA_PRESUME_SSE2 1
+# endif
+# ifdef __SSE3__
+// It's ok to use SSE3 instructions based on the -march option (or the
+// default for Intel Mac).
+# define MOZILLA_PRESUME_SSE3 1
+# endif
+# ifdef __SSSE3__
+// It's ok to use SSSE3 instructions based on the -march option.
+# define MOZILLA_PRESUME_SSSE3 1
+# endif
+# ifdef __SSE4A__
+// It's ok to use SSE4A instructions based on the -march option.
+# define MOZILLA_PRESUME_SSE4A 1
+# endif
+# ifdef __SSE4_1__
+// It's ok to use SSE4.1 instructions based on the -march option.
+# define MOZILLA_PRESUME_SSE4_1 1
+# endif
+# ifdef __SSE4_2__
+// It's ok to use SSE4.2 instructions based on the -march option.
+# define MOZILLA_PRESUME_SSE4_2 1
+# endif
+# ifdef __AVX__
+// It's ok to use AVX instructions based on the -march option.
+# define MOZILLA_PRESUME_AVX 1
+# endif
+# ifdef __AVX2__
+// It's ok to use AVX instructions based on the -march option.
+# define MOZILLA_PRESUME_AVX2 1
+# endif
+# ifdef __AVXVNNI__
+// It's ok to use AVX instructions based on the -march option.
+# define MOZILLA_PRESUME_AVXVNNI 1
+# endif
+# ifdef __AES__
+// It's ok to use AES instructions based on the -march option.
+# define MOZILLA_PRESUME_AES 1
+# endif
+
+# ifdef HAVE_CPUID_H
+# define MOZILLA_SSE_HAVE_CPUID_DETECTION
+# endif
+
+#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
+
+# define MOZILLA_SSE_HAVE_CPUID_DETECTION
+
+# if defined(_M_IX86_FP)
+
+# if _M_IX86_FP >= 1
+// It's ok to use SSE instructions based on the /arch option
+# define MOZILLA_PRESUME_SSE
+# endif
+# if _M_IX86_FP >= 2
+// It's ok to use SSE2 instructions based on the /arch option
+# define MOZILLA_PRESUME_SSE2
+# endif
+
+# elif defined(_M_AMD64)
+// MSVC for AMD64 doesn't support MMX, so don't presume it here.
+
+// SSE is always available on AMD64.
+# define MOZILLA_PRESUME_SSE
+// SSE2 is always available on AMD64.
+# define MOZILLA_PRESUME_SSE2
+# endif
+
+#elif defined(__SUNPRO_CC) && (defined(__i386) || defined(__x86_64__))
+// Sun Studio on x86 or amd64
+
+# define MOZILLA_SSE_HAVE_CPUID_DETECTION
+
+# if defined(__x86_64__)
+// MMX is always available on AMD64.
+# define MOZILLA_PRESUME_MMX
+// SSE is always available on AMD64.
+# define MOZILLA_PRESUME_SSE
+// SSE2 is always available on AMD64.
+# define MOZILLA_PRESUME_SSE2
+# endif
+
+#endif
+
+namespace mozilla {
+
+namespace sse_private {
+#if defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# if !defined(MOZILLA_PRESUME_MMX)
+extern bool MFBT_DATA mmx_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_SSE)
+extern bool MFBT_DATA sse_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_SSE2)
+extern bool MFBT_DATA sse2_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_SSE3)
+extern bool MFBT_DATA sse3_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_SSSE3)
+extern bool MFBT_DATA ssse3_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_SSE4A)
+extern bool MFBT_DATA sse4a_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_SSE4_1)
+extern bool MFBT_DATA sse4_1_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_SSE4_2)
+extern bool MFBT_DATA sse4_2_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_FMA3)
+extern bool MFBT_DATA fma3_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_AVX)
+extern bool MFBT_DATA avx_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_AVX2)
+extern bool MFBT_DATA avx2_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_AVXVNNI)
+extern bool MFBT_DATA avxvnni_enabled;
+# endif
+# if !defined(MOZILLA_PRESUME_AES)
+extern bool MFBT_DATA aes_enabled;
+# endif
+extern bool MFBT_DATA has_constant_tsc;
+
+#endif
+} // namespace sse_private
+
+#ifdef HAVE_CPUID_H
+MOZ_EXPORT uint64_t xgetbv(uint32_t xcr);
+#endif
+
+#if defined(MOZILLA_PRESUME_MMX)
+# define MOZILLA_MAY_SUPPORT_MMX 1
+inline bool supports_mmx() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# if !(defined(_MSC_VER) && defined(_M_AMD64))
+// Define MOZILLA_MAY_SUPPORT_MMX only if we're not on MSVC for
+// AMD64, since that compiler doesn't support MMX.
+# define MOZILLA_MAY_SUPPORT_MMX 1
+# endif
+inline bool supports_mmx() { return sse_private::mmx_enabled; }
+#else
+inline bool supports_mmx() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_SSE)
+# define MOZILLA_MAY_SUPPORT_SSE 1
+inline bool supports_sse() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_SSE 1
+inline bool supports_sse() { return sse_private::sse_enabled; }
+#else
+inline bool supports_sse() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_SSE2)
+# define MOZILLA_MAY_SUPPORT_SSE2 1
+inline bool supports_sse2() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_SSE2 1
+inline bool supports_sse2() { return sse_private::sse2_enabled; }
+#else
+inline bool supports_sse2() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_SSE3)
+# define MOZILLA_MAY_SUPPORT_SSE3 1
+inline bool supports_sse3() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_SSE3 1
+inline bool supports_sse3() { return sse_private::sse3_enabled; }
+#else
+inline bool supports_sse3() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_SSSE3)
+# define MOZILLA_MAY_SUPPORT_SSSE3 1
+inline bool supports_ssse3() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_SSSE3 1
+inline bool supports_ssse3() { return sse_private::ssse3_enabled; }
+#else
+inline bool supports_ssse3() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_SSE4A)
+# define MOZILLA_MAY_SUPPORT_SSE4A 1
+inline bool supports_sse4a() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_SSE4A 1
+inline bool supports_sse4a() { return sse_private::sse4a_enabled; }
+#else
+inline bool supports_sse4a() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_SSE4_1)
+# define MOZILLA_MAY_SUPPORT_SSE4_1 1
+inline bool supports_sse4_1() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_SSE4_1 1
+inline bool supports_sse4_1() { return sse_private::sse4_1_enabled; }
+#else
+inline bool supports_sse4_1() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_SSE4_2)
+# define MOZILLA_MAY_SUPPORT_SSE4_2 1
+inline bool supports_sse4_2() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_SSE4_2 1
+inline bool supports_sse4_2() { return sse_private::sse4_2_enabled; }
+#else
+inline bool supports_sse4_2() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_FMA3)
+# define MOZILLA_MAY_SUPPORT_FMA3 1
+inline bool supports_fma3() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_FMA3 1
+inline bool supports_fma3() { return sse_private::fma3_enabled; }
+#else
+inline bool supports_fma3() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_AVX)
+# define MOZILLA_MAY_SUPPORT_AVX 1
+inline bool supports_avx() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_AVX 1
+inline bool supports_avx() { return sse_private::avx_enabled; }
+#else
+inline bool supports_avx() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_AVX2)
+# define MOZILLA_MAY_SUPPORT_AVX2 1
+inline bool supports_avx2() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_AVX2 1
+inline bool supports_avx2() { return sse_private::avx2_enabled; }
+#else
+inline bool supports_avx2() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_AVXVNNI)
+# define MOZILLA_MAY_SUPPORT_AVXVNNI 1
+inline bool supports_avxvnni() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_AVXVNNI 1
+inline bool supports_avxvnni() { return sse_private::avxvnni_enabled; }
+#else
+inline bool supports_avxvnni() { return false; }
+#endif
+
+#if defined(MOZILLA_PRESUME_AES)
+# define MOZILLA_MAY_SUPPORT_AES 1
+inline bool supports_aes() { return true; }
+#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION)
+# define MOZILLA_MAY_SUPPORT_AES 1
+inline bool supports_aes() { return sse_private::aes_enabled; }
+#else
+inline bool supports_aes() { return false; }
+#endif
+
+#ifdef MOZILLA_SSE_HAVE_CPUID_DETECTION
+inline bool has_constant_tsc() { return sse_private::has_constant_tsc; }
+#else
+inline bool has_constant_tsc() { return false; }
+#endif
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_SSE_h_) */
diff --git a/mozglue/misc/Sprintf.h b/mozglue/misc/Sprintf.h
new file mode 100644
index 0000000000..4b459de82d
--- /dev/null
+++ b/mozglue/misc/Sprintf.h
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+
+/* Provides a safer sprintf for printing to fixed-size character arrays. */
+
+#ifndef mozilla_Sprintf_h_
+#define mozilla_Sprintf_h_
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <algorithm>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Printf.h"
+
+#ifdef __cplusplus
+
+# ifndef SPRINTF_H_USES_VSNPRINTF
+namespace mozilla {
+namespace detail {
+
+struct MOZ_STACK_CLASS SprintfAppend final : public mozilla::PrintfTarget {
+ explicit SprintfAppend(char* aBuf, size_t aBufLen)
+ : mBuf(aBuf), mBufLen(aBufLen) {}
+
+ bool append(const char* aStr, size_t aLen) override {
+ if (aLen == 0) {
+ return true;
+ }
+ // Don't copy more than what's left to use.
+ size_t copy = std::min(mBufLen, aLen);
+ if (copy > 0) {
+ memcpy(mBuf, aStr, copy);
+ mBuf += copy;
+ mBufLen -= copy;
+ }
+ return true;
+ }
+
+ private:
+ char* mBuf;
+ size_t mBufLen;
+};
+
+} // namespace detail
+} // namespace mozilla
+# endif // SPRINTF_H_USES_VSNPRINTF
+
+MOZ_FORMAT_PRINTF(3, 0)
+MOZ_MAYBE_UNUSED
+static int VsprintfBuf(char* buffer, size_t bufsize, const char* format,
+ va_list args) {
+ MOZ_ASSERT(format != buffer);
+# ifdef SPRINTF_H_USES_VSNPRINTF
+ int result = vsnprintf(buffer, bufsize, format, args);
+ buffer[bufsize - 1] = '\0';
+ return result;
+# else
+ mozilla::detail::SprintfAppend ss(buffer, bufsize);
+ ss.vprint(format, args);
+ size_t len = ss.emitted();
+ buffer[std::min(len, bufsize - 1)] = '\0';
+ return len;
+# endif
+}
+
+MOZ_FORMAT_PRINTF(3, 4)
+MOZ_MAYBE_UNUSED
+static int SprintfBuf(char* buffer, size_t bufsize, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ int result = VsprintfBuf(buffer, bufsize, format, args);
+ va_end(args);
+ return result;
+}
+
+template <size_t N>
+MOZ_FORMAT_PRINTF(2, 0)
+int VsprintfLiteral(char (&buffer)[N], const char* format, va_list args) {
+ return VsprintfBuf(buffer, N, format, args);
+}
+
+template <size_t N>
+MOZ_FORMAT_PRINTF(2, 3)
+int SprintfLiteral(char (&buffer)[N], const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ int result = VsprintfLiteral(buffer, format, args);
+ va_end(args);
+ return result;
+}
+
+#endif
+#endif /* mozilla_Sprintf_h_ */
diff --git a/mozglue/misc/StackWalk.cpp b/mozglue/misc/StackWalk.cpp
new file mode 100644
index 0000000000..2fefc5bf4d
--- /dev/null
+++ b/mozglue/misc/StackWalk.cpp
@@ -0,0 +1,1129 @@
+/* -*- 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/. */
+
+/* API for getting a stack trace of the C/C++ stack on the current thread */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StackWalk.h"
+#ifdef XP_WIN
+# include "mozilla/StackWalkThread.h"
+# include <io.h>
+#else
+# include <unistd.h>
+#endif
+#include "mozilla/Sprintf.h"
+
+#include <string.h>
+
+#if defined(ANDROID) && defined(MOZ_LINKER)
+# include "Linker.h"
+# include <android/log.h>
+#endif
+
+using namespace mozilla;
+
+// for _Unwind_Backtrace from libcxxrt or libunwind
+// cxxabi.h from libcxxrt implicitly includes unwind.h first
+#if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE)
+# define _GNU_SOURCE
+#endif
+
+#if defined(HAVE_DLOPEN) || defined(XP_DARWIN)
+# include <dlfcn.h>
+#endif
+
+#if (defined(XP_DARWIN) && \
+ (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE)))
+# define MOZ_STACKWALK_SUPPORTS_MACOSX 1
+#else
+# define MOZ_STACKWALK_SUPPORTS_MACOSX 0
+#endif
+
+#if (defined(linux) && \
+ ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \
+ defined(HAVE__UNWIND_BACKTRACE)))
+# define MOZ_STACKWALK_SUPPORTS_LINUX 1
+#else
+# define MOZ_STACKWALK_SUPPORTS_LINUX 0
+#endif
+
+#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1)
+# define HAVE___LIBC_STACK_END 1
+#else
+# define HAVE___LIBC_STACK_END 0
+#endif
+
+#if HAVE___LIBC_STACK_END
+extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so
+#endif
+
+#ifdef ANDROID
+# include <algorithm>
+# include <unistd.h>
+# include <pthread.h>
+#endif
+
+class FrameSkipper {
+ public:
+ constexpr FrameSkipper() : mSkipUntilAddr(0) {}
+ static uintptr_t AddressFromPC(const void* aPC) {
+#ifdef __arm__
+ // On 32-bit ARM, mask off the thumb bit to get the instruction address.
+ return uintptr_t(aPC) & ~1;
+#else
+ return uintptr_t(aPC);
+#endif
+ }
+ bool ShouldSkipPC(void* aPC) {
+ // Skip frames until we encounter the one we were initialized with,
+ // and then never skip again.
+ uintptr_t instructionAddress = AddressFromPC(aPC);
+ if (mSkipUntilAddr != 0) {
+ if (mSkipUntilAddr != instructionAddress) {
+ return true;
+ }
+ mSkipUntilAddr = 0;
+ }
+ return false;
+ }
+ explicit FrameSkipper(const void* aPC) : mSkipUntilAddr(AddressFromPC(aPC)) {}
+
+ private:
+ uintptr_t mSkipUntilAddr;
+};
+
+#ifdef XP_WIN
+
+# include <windows.h>
+# include <process.h>
+# include <stdio.h>
+# include <malloc.h>
+# include "mozilla/ArrayUtils.h"
+# include "mozilla/Atomics.h"
+# include "mozilla/StackWalk_windows.h"
+# include "mozilla/WindowsVersion.h"
+
+# include <imagehlp.h>
+// We need a way to know if we are building for WXP (or later), as if we are, we
+// need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill.
+// A value of 9 indicates we want to use the new APIs.
+# if API_VERSION_NUMBER < 9
+# error Too old imagehlp.h
+# endif
+
+// DbgHelp functions are not thread-safe and should therefore be protected by
+// using this critical section. Only use the critical section after a
+// successful call to InitializeDbgHelp().
+CRITICAL_SECTION gDbgHelpCS;
+
+# if defined(_M_AMD64) || defined(_M_ARM64)
+// Because various Win64 APIs acquire function-table locks, we need a way of
+// preventing stack walking while those APIs are being called. Otherwise, the
+// stack walker may suspend a thread holding such a lock, and deadlock when the
+// stack unwind code attempts to wait for that lock.
+//
+// We're using an atomic counter rather than a critical section because we
+// don't require mutual exclusion with the stack walker. If the stack walker
+// determines that it's safe to start unwinding the suspended thread (i.e.
+// there are no suppressions when the unwind begins), then it's safe to
+// continue unwinding that thread even if other threads request suppressions
+// in the meantime, because we can't deadlock with those other threads.
+//
+// XXX: This global variable is a larger-than-necessary hammer. A more scoped
+// solution would be to maintain a counter per thread, but then it would be
+// more difficult for WalkStackMain64 to read the suspended thread's counter.
+static Atomic<size_t> sStackWalkSuppressions;
+
+void SuppressStackWalking() { ++sStackWalkSuppressions; }
+
+void DesuppressStackWalking() {
+ auto previousValue = sStackWalkSuppressions--;
+ // We should never desuppress from 0. See bug 1687510 comment 10 for an
+ // example in which this occured.
+ MOZ_RELEASE_ASSERT(previousValue);
+}
+
+MFBT_API
+AutoSuppressStackWalking::AutoSuppressStackWalking() { SuppressStackWalking(); }
+
+MFBT_API
+AutoSuppressStackWalking::~AutoSuppressStackWalking() {
+ DesuppressStackWalking();
+}
+
+static uint8_t* sJitCodeRegionStart;
+static size_t sJitCodeRegionSize;
+uint8_t* sMsMpegJitCodeRegionStart;
+size_t sMsMpegJitCodeRegionSize;
+
+MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t aSize) {
+ // Currently we can only handle one JIT code region at a time
+ MOZ_RELEASE_ASSERT(!sJitCodeRegionStart);
+
+ sJitCodeRegionStart = aStart;
+ sJitCodeRegionSize = aSize;
+}
+
+MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t aSize) {
+ // Currently we can only handle one JIT code region at a time
+ MOZ_RELEASE_ASSERT(sJitCodeRegionStart && sJitCodeRegionStart == aStart &&
+ sJitCodeRegionSize == aSize);
+
+ sJitCodeRegionStart = nullptr;
+ sJitCodeRegionSize = 0;
+}
+
+# endif // _M_AMD64 || _M_ARM64
+
+// Routine to print an error message to standard error.
+static void PrintError(const char* aPrefix) {
+ LPSTR lpMsgBuf;
+ DWORD lastErr = GetLastError();
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, lastErr,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
+ (LPSTR)&lpMsgBuf, 0, nullptr);
+ fprintf(stderr, "### ERROR: %s: %s", aPrefix,
+ lpMsgBuf ? lpMsgBuf : "(null)\n");
+ fflush(stderr);
+ LocalFree(lpMsgBuf);
+}
+
+enum class DbgHelpInitFlags : bool {
+ BasicInit,
+ WithSymbolSupport,
+};
+
+// This function ensures that DbgHelp.dll is loaded in the current process,
+// and initializes the gDbgHelpCS critical section that we use to protect calls
+// to DbgHelp functions. If DbgHelpInitFlags::WithSymbolSupport is set, we
+// additionally call the symbol initialization functions from DbgHelp so that
+// symbol-related functions can be used.
+//
+// This function is thread-safe and reentrancy-safe. In debug and fuzzing
+// builds, MOZ_ASSERT and MOZ_CRASH walk the stack to print it before actually
+// crashing. Hence *any* MOZ_ASSERT or MOZ_CRASH failure reached from
+// InitializeDbgHelp() leads to rentrancy (see bug 1869997 for an example).
+// Such failures can occur indirectly when we load dbghelp.dll, because we
+// override various Microsoft-internal functions that are called upon DLL
+// loading.
+[[nodiscard]] static bool InitializeDbgHelp(
+ DbgHelpInitFlags aInitFlags = DbgHelpInitFlags::BasicInit) {
+ // In the code below, it is only safe to reach MOZ_ASSERT or MOZ_CRASH while
+ // sInitializationThreadId is set to the current thread id.
+ static Atomic<DWORD> sInitializationThreadId{0};
+ DWORD currentThreadId = ::GetCurrentThreadId();
+
+ // This code relies on Windows never giving us a current thread ID of zero.
+ // We make this assumption explicit, by failing if that should ever occur.
+ if (!currentThreadId) {
+ return false;
+ }
+
+ if (sInitializationThreadId == currentThreadId) {
+ // This is a reentrant call and we must abort here.
+ return false;
+ }
+
+ static const bool sHasInitializedDbgHelp = [currentThreadId]() {
+ sInitializationThreadId = currentThreadId;
+
+ ::InitializeCriticalSection(&gDbgHelpCS);
+ bool dbgHelpLoaded = static_cast<bool>(::LoadLibraryW(L"dbghelp.dll"));
+
+ MOZ_ASSERT(dbgHelpLoaded);
+ sInitializationThreadId = 0;
+ return dbgHelpLoaded;
+ }();
+
+ // If we don't need symbol initialization, we are done. If we need it, we
+ // can only proceed if DbgHelp initialization was successful.
+ if (aInitFlags == DbgHelpInitFlags::BasicInit || !sHasInitializedDbgHelp) {
+ return sHasInitializedDbgHelp;
+ }
+
+ static const bool sHasInitializedSymbols = [currentThreadId]() {
+ sInitializationThreadId = currentThreadId;
+
+ EnterCriticalSection(&gDbgHelpCS);
+ SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
+ bool symbolsInitialized = SymInitialize(GetCurrentProcess(), nullptr, TRUE);
+ /* XXX At some point we need to arrange to call SymCleanup */
+ LeaveCriticalSection(&gDbgHelpCS);
+
+ if (!symbolsInitialized) {
+ PrintError("SymInitialize");
+ }
+
+ MOZ_ASSERT(symbolsInitialized);
+ sInitializationThreadId = 0;
+ return symbolsInitialized;
+ }();
+
+ return sHasInitializedSymbols;
+}
+
+// Wrapper around a reference to a CONTEXT, to simplify access to main
+// platform-specific execution registers.
+// It also avoids using CONTEXT* nullable pointers.
+class CONTEXTGenericAccessors {
+ public:
+ explicit CONTEXTGenericAccessors(CONTEXT& aCONTEXT) : mCONTEXT(aCONTEXT) {}
+
+ CONTEXT* CONTEXTPtr() { return &mCONTEXT; }
+
+ inline auto& PC() {
+# if defined(_M_AMD64)
+ return mCONTEXT.Rip;
+# elif defined(_M_ARM64)
+ return mCONTEXT.Pc;
+# elif defined(_M_IX86)
+ return mCONTEXT.Eip;
+# else
+# error "unknown platform"
+# endif
+ }
+
+ inline auto& SP() {
+# if defined(_M_AMD64)
+ return mCONTEXT.Rsp;
+# elif defined(_M_ARM64)
+ return mCONTEXT.Sp;
+# elif defined(_M_IX86)
+ return mCONTEXT.Esp;
+# else
+# error "unknown platform"
+# endif
+ }
+
+ inline auto& BP() {
+# if defined(_M_AMD64)
+ return mCONTEXT.Rbp;
+# elif defined(_M_ARM64)
+ return mCONTEXT.Fp;
+# elif defined(_M_IX86)
+ return mCONTEXT.Ebp;
+# else
+# error "unknown platform"
+# endif
+ }
+
+ private:
+ CONTEXT& mCONTEXT;
+};
+
+/**
+ * Walk the stack, translating PC's found into strings and recording the
+ * chain in aBuffer. For this to work properly, the DLLs must be rebased
+ * so that the address in the file agrees with the address in memory.
+ * Otherwise StackWalk will return FALSE when it hits a frame in a DLL
+ * whose in memory address doesn't match its in-file address.
+ */
+
+static void DoMozStackWalkThread(MozWalkStackCallback aCallback,
+ const void* aFirstFramePC, uint32_t aMaxFrames,
+ void* aClosure, HANDLE aThread,
+ CONTEXT* aContext) {
+# if defined(_M_IX86)
+ if (!InitializeDbgHelp()) {
+ return;
+ }
+# endif
+
+ HANDLE targetThread = aThread;
+ bool walkCallingThread;
+ if (!targetThread) {
+ targetThread = ::GetCurrentThread();
+ walkCallingThread = true;
+ } else {
+ DWORD targetThreadId = ::GetThreadId(targetThread);
+ DWORD currentThreadId = ::GetCurrentThreadId();
+ walkCallingThread = (targetThreadId == currentThreadId);
+ }
+
+ // If not already provided, get a context for the specified thread.
+ CONTEXT context_buf;
+ if (!aContext) {
+ memset(&context_buf, 0, sizeof(CONTEXT));
+ context_buf.ContextFlags = CONTEXT_FULL;
+ if (walkCallingThread) {
+ ::RtlCaptureContext(&context_buf);
+ } else if (!GetThreadContext(targetThread, &context_buf)) {
+ return;
+ }
+ }
+ CONTEXTGenericAccessors context{aContext ? *aContext : context_buf};
+
+# if defined(_M_IX86)
+ // Setup initial stack frame to walk from.
+ STACKFRAME64 frame64;
+ memset(&frame64, 0, sizeof(frame64));
+ frame64.AddrPC.Offset = context.PC();
+ frame64.AddrStack.Offset = context.SP();
+ frame64.AddrFrame.Offset = context.BP();
+ frame64.AddrPC.Mode = AddrModeFlat;
+ frame64.AddrStack.Mode = AddrModeFlat;
+ frame64.AddrFrame.Mode = AddrModeFlat;
+ frame64.AddrReturn.Mode = AddrModeFlat;
+# endif
+
+# if defined(_M_AMD64) || defined(_M_ARM64)
+ // If there are any active suppressions, then at least one thread (we don't
+ // know which) is holding a lock that can deadlock RtlVirtualUnwind. Since
+ // that thread may be the one that we're trying to unwind, we can't proceed.
+ //
+ // But if there are no suppressions, then our target thread can't be holding
+ // a lock, and it's safe to proceed. By virtue of being suspended, the target
+ // thread can't acquire any new locks during the unwind process, so we only
+ // need to do this check once. After that, sStackWalkSuppressions can be
+ // changed by other threads while we're unwinding, and that's fine because
+ // we can't deadlock with those threads.
+ if (sStackWalkSuppressions) {
+ return;
+ }
+
+ bool firstFrame = true;
+# endif
+
+ FrameSkipper skipper(aFirstFramePC);
+
+ uint32_t frames = 0;
+
+ // Now walk the stack.
+ while (true) {
+ DWORD64 addr;
+ DWORD64 spaddr;
+
+# if defined(_M_IX86)
+ // 32-bit frame unwinding.
+ // Debug routines are not threadsafe, so grab the lock.
+ EnterCriticalSection(&gDbgHelpCS);
+ BOOL ok =
+ StackWalk64(IMAGE_FILE_MACHINE_I386, ::GetCurrentProcess(),
+ targetThread, &frame64, context.CONTEXTPtr(), nullptr,
+ SymFunctionTableAccess64, // function table access routine
+ SymGetModuleBase64, // module base routine
+ 0);
+ LeaveCriticalSection(&gDbgHelpCS);
+
+ if (ok) {
+ addr = frame64.AddrPC.Offset;
+ spaddr = frame64.AddrStack.Offset;
+ } else {
+ addr = 0;
+ spaddr = 0;
+ if (walkCallingThread) {
+ PrintError("WalkStack64");
+ }
+ }
+
+ if (!ok) {
+ break;
+ }
+
+# elif defined(_M_AMD64) || defined(_M_ARM64)
+
+ auto currentInstr = context.PC();
+
+ // If we reach a frame in JIT code, we don't have enough information to
+ // unwind, so we have to give up.
+ if (sJitCodeRegionStart && (uint8_t*)currentInstr >= sJitCodeRegionStart &&
+ (uint8_t*)currentInstr < sJitCodeRegionStart + sJitCodeRegionSize) {
+ break;
+ }
+
+ // We must also avoid msmpeg2vdec.dll's JIT region: they don't generate
+ // unwind data, so their JIT unwind callback just throws up its hands and
+ // terminates the process.
+ if (sMsMpegJitCodeRegionStart &&
+ (uint8_t*)currentInstr >= sMsMpegJitCodeRegionStart &&
+ (uint8_t*)currentInstr <
+ sMsMpegJitCodeRegionStart + sMsMpegJitCodeRegionSize) {
+ break;
+ }
+
+ // 64-bit frame unwinding.
+ // Try to look up unwind metadata for the current function.
+ ULONG64 imageBase;
+ PRUNTIME_FUNCTION runtimeFunction =
+ RtlLookupFunctionEntry(currentInstr, &imageBase, NULL);
+
+ if (runtimeFunction) {
+ PVOID dummyHandlerData;
+ ULONG64 dummyEstablisherFrame;
+ RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, currentInstr,
+ runtimeFunction, context.CONTEXTPtr(), &dummyHandlerData,
+ &dummyEstablisherFrame, nullptr);
+ } else if (firstFrame) {
+ // Leaf functions can be unwound by hand.
+ context.PC() = *reinterpret_cast<DWORD64*>(context.SP());
+ context.SP() += sizeof(void*);
+ } else {
+ // Something went wrong.
+ break;
+ }
+
+ addr = context.PC();
+ spaddr = context.SP();
+ firstFrame = false;
+# else
+# error "unknown platform"
+# endif
+
+ if (addr == 0) {
+ break;
+ }
+
+ if (skipper.ShouldSkipPC((void*)addr)) {
+ continue;
+ }
+
+ aCallback(++frames, (void*)addr, (void*)spaddr, aClosure);
+
+ if (aMaxFrames != 0 && frames == aMaxFrames) {
+ break;
+ }
+
+# if defined(_M_IX86)
+ if (frame64.AddrReturn.Offset == 0) {
+ break;
+ }
+# endif
+ }
+}
+
+MFBT_API void MozStackWalkThread(MozWalkStackCallback aCallback,
+ uint32_t aMaxFrames, void* aClosure,
+ HANDLE aThread, CONTEXT* aContext) {
+ // We don't pass a aFirstFramePC because we walk the stack for another
+ // thread.
+ DoMozStackWalkThread(aCallback, nullptr, aMaxFrames, aClosure, aThread,
+ aContext);
+}
+
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
+ const void* aFirstFramePC, uint32_t aMaxFrames,
+ void* aClosure) {
+ DoMozStackWalkThread(aCallback, aFirstFramePC ? aFirstFramePC : CallerPC(),
+ aMaxFrames, aClosure, nullptr, nullptr);
+}
+
+static BOOL CALLBACK callbackEspecial64(PCSTR aModuleName, DWORD64 aModuleBase,
+ ULONG aModuleSize, PVOID aUserContext) {
+ BOOL retval = TRUE;
+ DWORD64 addr = *(DWORD64*)aUserContext;
+
+ /*
+ * You'll want to control this if we are running on an
+ * architecture where the addresses go the other direction.
+ * Not sure this is even a realistic consideration.
+ */
+ const BOOL addressIncreases = TRUE;
+
+ /*
+ * If it falls in side the known range, load the symbols.
+ */
+ if (addressIncreases
+ ? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize))
+ : (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize))) {
+ retval = !!SymLoadModule64(GetCurrentProcess(), nullptr, (PSTR)aModuleName,
+ nullptr, aModuleBase, aModuleSize);
+ if (!retval) {
+ PrintError("SymLoadModule64");
+ }
+ }
+
+ return retval;
+}
+
+/*
+ * SymGetModuleInfoEspecial
+ *
+ * Attempt to determine the module information.
+ * Bug 112196 says this DLL may not have been loaded at the time
+ * SymInitialize was called, and thus the module information
+ * and symbol information is not available.
+ * This code rectifies that problem.
+ */
+
+// New members were added to IMAGEHLP_MODULE64 (that show up in the
+// Platform SDK that ships with VC8, but not the Platform SDK that ships
+// with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to
+// use them, and it's useful to be able to function correctly with the
+// older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll
+// version 5.1.) Since Platform SDK version need not correspond to
+// compiler version, and the version number in debughlp.h was NOT bumped
+// when these changes were made, ifdef based on a constant that was
+// added between these versions.
+# ifdef SSRVOPT_SETCONTEXT
+# define NS_IMAGEHLP_MODULE64_SIZE \
+ (((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / \
+ sizeof(DWORD64)) * \
+ sizeof(DWORD64))
+# else
+# define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64)
+# endif
+
+BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr,
+ PIMAGEHLP_MODULE64 aModuleInfo,
+ PIMAGEHLP_LINE64 aLineInfo) {
+ BOOL retval = FALSE;
+
+ /*
+ * Init the vars if we have em.
+ */
+ aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE;
+ if (aLineInfo) {
+ aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+ }
+
+ /*
+ * Give it a go.
+ * It may already be loaded.
+ */
+ retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo);
+ if (retval == FALSE) {
+ /*
+ * Not loaded, here's the magic.
+ * Go through all the modules.
+ */
+ // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the
+ // constness of the first parameter of
+ // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from
+ // non-const to const over time). See bug 391848 and bug
+ // 415426.
+ BOOL enumRes = EnumerateLoadedModules64(
+ aProcess, (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64,
+ (PVOID)&aAddr);
+ if (enumRes != FALSE) {
+ /*
+ * One final go.
+ * If it fails, then well, we have other problems.
+ */
+ retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo);
+ }
+ }
+
+ /*
+ * If we got module info, we may attempt line info as well.
+ * We will not report failure if this does not work.
+ */
+ if (retval != FALSE && aLineInfo) {
+ DWORD displacement = 0;
+ BOOL lineRes = FALSE;
+ lineRes = SymGetLineFromAddr64(aProcess, aAddr, &displacement, aLineInfo);
+ if (!lineRes) {
+ // Clear out aLineInfo to indicate that it's not valid
+ memset(aLineInfo, 0, sizeof(*aLineInfo));
+ }
+ }
+
+ return retval;
+}
+
+MFBT_API bool MozDescribeCodeAddress(void* aPC,
+ MozCodeAddressDetails* aDetails) {
+ aDetails->library[0] = '\0';
+ aDetails->loffset = 0;
+ aDetails->filename[0] = '\0';
+ aDetails->lineno = 0;
+ aDetails->function[0] = '\0';
+ aDetails->foffset = 0;
+
+ if (!InitializeDbgHelp(DbgHelpInitFlags::WithSymbolSupport)) {
+ return false;
+ }
+
+ HANDLE myProcess = ::GetCurrentProcess();
+ BOOL ok;
+
+ // debug routines are not threadsafe, so grab the lock.
+ EnterCriticalSection(&gDbgHelpCS);
+
+ //
+ // Attempt to load module info before we attempt to resolve the symbol.
+ // This just makes sure we get good info if available.
+ //
+
+ DWORD64 addr = (DWORD64)aPC;
+ IMAGEHLP_MODULE64 modInfo;
+ IMAGEHLP_LINE64 lineInfo;
+ BOOL modInfoRes;
+ modInfoRes = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo);
+
+ if (modInfoRes) {
+ strncpy(aDetails->library, modInfo.LoadedImageName,
+ sizeof(aDetails->library));
+ aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0';
+ aDetails->loffset = (char*)aPC - (char*)modInfo.BaseOfImage;
+
+ if (lineInfo.FileName) {
+ strncpy(aDetails->filename, lineInfo.FileName,
+ sizeof(aDetails->filename));
+ aDetails->filename[mozilla::ArrayLength(aDetails->filename) - 1] = '\0';
+ aDetails->lineno = lineInfo.LineNumber;
+ }
+ }
+
+ ULONG64 buffer[(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR) +
+ sizeof(ULONG64) - 1) /
+ sizeof(ULONG64)];
+ PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
+ pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
+ pSymbol->MaxNameLen = MAX_SYM_NAME;
+
+ DWORD64 displacement;
+ ok = SymFromAddr(myProcess, addr, &displacement, pSymbol);
+
+ if (ok) {
+ strncpy(aDetails->function, pSymbol->Name, sizeof(aDetails->function));
+ aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0';
+ aDetails->foffset = static_cast<ptrdiff_t>(displacement);
+ }
+
+ LeaveCriticalSection(&gDbgHelpCS); // release our lock
+ return true;
+}
+
+// i386 or PPC Linux stackwalking code
+//
+// Changes to to OS/Architecture support here should be reflected in
+// build/moz.configure/memory.configure
+#elif HAVE_DLADDR && \
+ (HAVE__UNWIND_BACKTRACE || MOZ_STACKWALK_SUPPORTS_LINUX || \
+ MOZ_STACKWALK_SUPPORTS_MACOSX)
+
+# include <stdlib.h>
+# include <stdio.h>
+
+// On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed
+// if __USE_GNU is defined. I suppose its some kind of standards
+// adherence thing.
+//
+# if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU)
+# define __USE_GNU
+# endif
+
+// This thing is exported by libstdc++
+// Yes, this is a gcc only hack
+# if defined(MOZ_DEMANGLE_SYMBOLS)
+# include <cxxabi.h>
+# endif // MOZ_DEMANGLE_SYMBOLS
+
+namespace mozilla {
+
+void DemangleSymbol(const char* aSymbol, char* aBuffer, int aBufLen) {
+ aBuffer[0] = '\0';
+
+# if defined(MOZ_DEMANGLE_SYMBOLS)
+ /* See demangle.h in the gcc source for the voodoo */
+ char* demangled = abi::__cxa_demangle(aSymbol, 0, 0, 0);
+
+ if (demangled) {
+ strncpy(aBuffer, demangled, aBufLen);
+ aBuffer[aBufLen - 1] = '\0';
+ free(demangled);
+ }
+# endif // MOZ_DEMANGLE_SYMBOLS
+}
+
+} // namespace mozilla
+
+// {x86, ppc} x {Linux, Mac} stackwalking code.
+//
+// Changes to to OS/Architecture support here should be reflected in
+// build/moz.configure/memory.configure
+# if ((defined(__i386) || defined(PPC) || defined(__ppc__)) && \
+ (MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX))
+
+static void DoFramePointerStackWalk(MozWalkStackCallback aCallback,
+ const void* aFirstFramePC,
+ uint32_t aMaxFrames, void* aClosure,
+ void** aBp, void* aStackEnd);
+
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
+ const void* aFirstFramePC, uint32_t aMaxFrames,
+ void* aClosure) {
+ // Get the frame pointer
+ void** bp = (void**)__builtin_frame_address(0);
+
+ void* stackEnd;
+# if HAVE___LIBC_STACK_END
+ stackEnd = __libc_stack_end;
+# elif defined(XP_DARWIN)
+ stackEnd = pthread_get_stackaddr_np(pthread_self());
+# elif defined(ANDROID)
+ pthread_attr_t sattr;
+ pthread_attr_init(&sattr);
+ pthread_getattr_np(pthread_self(), &sattr);
+ void* stackBase = stackEnd = nullptr;
+ size_t stackSize = 0;
+ if (gettid() != getpid()) {
+ // bionic's pthread_attr_getstack doesn't tell the truth for the main
+ // thread (see bug 846670). So don't use it for the main thread.
+ if (!pthread_attr_getstack(&sattr, &stackBase, &stackSize)) {
+ stackEnd = static_cast<char*>(stackBase) + stackSize;
+ } else {
+ stackEnd = nullptr;
+ }
+ }
+ if (!stackEnd) {
+ // So consider the current frame pointer + an arbitrary size of 8MB
+ // (modulo overflow ; not really arbitrary as it's the default stack
+ // size for the main thread) if pthread_attr_getstack failed for
+ // some reason (or was skipped).
+ static const uintptr_t kMaxStackSize = 8 * 1024 * 1024;
+ uintptr_t maxStackStart = uintptr_t(-1) - kMaxStackSize;
+ uintptr_t stackStart = std::max(maxStackStart, uintptr_t(bp));
+ stackEnd = reinterpret_cast<void*>(stackStart + kMaxStackSize);
+ }
+# else
+# error Unsupported configuration
+# endif
+ DoFramePointerStackWalk(aCallback, aFirstFramePC, aMaxFrames, aClosure, bp,
+ stackEnd);
+}
+
+# elif defined(HAVE__UNWIND_BACKTRACE)
+
+// libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0
+# include <unwind.h>
+
+struct unwind_info {
+ MozWalkStackCallback callback;
+ FrameSkipper skipper;
+ int maxFrames;
+ int numFrames;
+ void* closure;
+};
+
+static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context,
+ void* closure) {
+ unwind_info* info = static_cast<unwind_info*>(closure);
+ void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context));
+ // TODO Use something like '_Unwind_GetGR()' to get the stack pointer.
+ if (!info->skipper.ShouldSkipPC(pc)) {
+ info->numFrames++;
+ (*info->callback)(info->numFrames, pc, nullptr, info->closure);
+ if (info->maxFrames != 0 && info->numFrames == info->maxFrames) {
+ // Again, any error code that stops the walk will do.
+ return _URC_FOREIGN_EXCEPTION_CAUGHT;
+ }
+ }
+ return _URC_NO_REASON;
+}
+
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
+ const void* aFirstFramePC, uint32_t aMaxFrames,
+ void* aClosure) {
+ unwind_info info;
+ info.callback = aCallback;
+ info.skipper = FrameSkipper(aFirstFramePC ? aFirstFramePC : CallerPC());
+ info.maxFrames = aMaxFrames;
+ info.numFrames = 0;
+ info.closure = aClosure;
+
+ // We ignore the return value from _Unwind_Backtrace. There are three main
+ // reasons for this.
+ // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns
+ // _URC_FAILURE. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110.
+ // - If aMaxFrames != 0, we want to stop early, and the only way to do that
+ // is to make unwind_callback return something other than _URC_NO_REASON,
+ // which causes _Unwind_Backtrace to return a non-success code.
+ // - MozStackWalk doesn't have a return value anyway.
+ (void)_Unwind_Backtrace(unwind_callback, &info);
+}
+
+# endif
+
+bool MFBT_API MozDescribeCodeAddress(void* aPC,
+ MozCodeAddressDetails* aDetails) {
+ aDetails->library[0] = '\0';
+ aDetails->loffset = 0;
+ aDetails->filename[0] = '\0';
+ aDetails->lineno = 0;
+ aDetails->function[0] = '\0';
+ aDetails->foffset = 0;
+
+ Dl_info info;
+
+# if defined(ANDROID) && defined(MOZ_LINKER)
+ int ok = __wrap_dladdr(aPC, &info);
+# else
+ int ok = dladdr(aPC, &info);
+# endif
+
+ if (!ok) {
+ return true;
+ }
+
+ strncpy(aDetails->library, info.dli_fname, sizeof(aDetails->library));
+ aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0';
+ aDetails->loffset = (char*)aPC - (char*)info.dli_fbase;
+
+# if !defined(XP_FREEBSD)
+ // On FreeBSD, dli_sname is unusably bad, it often returns things like
+ // 'gtk_xtbin_new' or 'XRE_GetBootstrap' instead of long C++ symbols. Just let
+ // GetFunction do the lookup directly in the ELF image.
+
+ const char* symbol = info.dli_sname;
+ if (!symbol || symbol[0] == '\0') {
+ return true;
+ }
+
+ DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function));
+
+ if (aDetails->function[0] == '\0') {
+ // Just use the mangled symbol if demangling failed.
+ strncpy(aDetails->function, symbol, sizeof(aDetails->function));
+ aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0';
+ }
+
+ aDetails->foffset = (char*)aPC - (char*)info.dli_saddr;
+# endif
+
+ return true;
+}
+
+#else // unsupported platform.
+
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
+ const void* aFirstFramePC, uint32_t aMaxFrames,
+ void* aClosure) {}
+
+MFBT_API bool MozDescribeCodeAddress(void* aPC,
+ MozCodeAddressDetails* aDetails) {
+ aDetails->library[0] = '\0';
+ aDetails->loffset = 0;
+ aDetails->filename[0] = '\0';
+ aDetails->lineno = 0;
+ aDetails->function[0] = '\0';
+ aDetails->foffset = 0;
+ return false;
+}
+
+#endif
+
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
+
+# if defined(XP_MACOSX) && defined(__aarch64__)
+// On macOS arm64, system libraries are arm64e binaries, and arm64e can do
+// pointer authentication: The low bits of the pointer are the actual pointer
+// value, and the high bits are an encrypted hash. During stackwalking, we need
+// to strip off this hash. In theory, ptrauth_strip would be the right function
+// to call for this. However, that function is a no-op unless it's called from
+// code which also builds as arm64e - which we do not. So we cannot use it. So
+// for now, we hardcode a mask that seems to work today: 40 bits for the pointer
+// and 24 bits for the hash seems to do the trick. We can worry about
+// dynamically computing the correct mask if this ever stops working.
+const uintptr_t kPointerMask =
+ (uintptr_t(1) << 40) - 1; // 40 bits pointer, 24 bit PAC
+# else
+const uintptr_t kPointerMask = ~uintptr_t(0);
+# endif
+
+MOZ_ASAN_IGNORE
+static void DoFramePointerStackWalk(MozWalkStackCallback aCallback,
+ const void* aFirstFramePC,
+ uint32_t aMaxFrames, void* aClosure,
+ void** aBp, void* aStackEnd) {
+ // Stack walking code courtesy Kipp's "leaky".
+
+ FrameSkipper skipper(aFirstFramePC);
+ uint32_t numFrames = 0;
+
+ // Sanitize the given aBp. Assume that something reasonably close to
+ // but before the stack end is going be a valid frame pointer. Also
+ // check that it is an aligned address. This increases the chances
+ // that if the pointer is not valid (which might happen if the caller
+ // called __builtin_frame_address(1) and its frame is busted for some
+ // reason), we won't read it, leading to a crash. Because the calling
+ // code is not using frame pointers when returning, it might actually
+ // recover just fine.
+ static const uintptr_t kMaxStackSize = 8 * 1024 * 1024;
+ if (uintptr_t(aBp) < uintptr_t(aStackEnd) -
+ std::min(kMaxStackSize, uintptr_t(aStackEnd)) ||
+ aBp >= aStackEnd || (uintptr_t(aBp) & 3)) {
+ return;
+ }
+
+ while (aBp) {
+ void** next = (void**)*aBp;
+ // aBp may not be a frame pointer on i386 if code was compiled with
+ // -fomit-frame-pointer, so do some sanity checks.
+ // (aBp should be a frame pointer on ppc(64) but checking anyway may help
+ // a little if the stack has been corrupted.)
+ // We don't need to check against the begining of the stack because
+ // we can assume that aBp > sp
+ if (next <= aBp || next >= aStackEnd || (uintptr_t(next) & 3)) {
+ break;
+ }
+# if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__)
+ // ppc mac or powerpc64 linux
+ void* pc = *(aBp + 2);
+ aBp += 3;
+# else // i386 or powerpc32 linux
+ void* pc = *(aBp + 1);
+ aBp += 2;
+# endif
+
+ // Strip off pointer authentication hash, if present. For now, it looks
+ // like only return addresses require stripping, and stack pointers do
+ // not. This might change in the future.
+ pc = (void*)((uintptr_t)pc & kPointerMask);
+
+ if (!skipper.ShouldSkipPC(pc)) {
+ // Assume that the SP points to the BP of the function
+ // it called. We can't know the exact location of the SP
+ // but this should be sufficient for our use the SP
+ // to order elements on the stack.
+ numFrames++;
+ (*aCallback)(numFrames, pc, aBp, aClosure);
+ if (aMaxFrames != 0 && numFrames == aMaxFrames) {
+ break;
+ }
+ }
+ aBp = next;
+ }
+}
+
+namespace mozilla {
+
+MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
+ uint32_t aMaxFrames, void* aClosure,
+ void** aBp, void* aStackEnd) {
+ // We don't pass a aFirstFramePC because we start walking the stack from the
+ // frame at aBp.
+ DoFramePointerStackWalk(aCallback, nullptr, aMaxFrames, aClosure, aBp,
+ aStackEnd);
+}
+
+} // namespace mozilla
+
+#else
+
+namespace mozilla {
+MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
+ uint32_t aMaxFrames, void* aClosure,
+ void** aBp, void* aStackEnd) {}
+} // namespace mozilla
+
+#endif
+
+MFBT_API int MozFormatCodeAddressDetails(
+ char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, void* aPC,
+ const MozCodeAddressDetails* aDetails) {
+ return MozFormatCodeAddress(aBuffer, aBufferSize, aFrameNumber, aPC,
+ aDetails->function, aDetails->library,
+ aDetails->loffset, aDetails->filename,
+ aDetails->lineno);
+}
+
+MFBT_API int MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize,
+ uint32_t aFrameNumber, const void* aPC,
+ const char* aFunction, const char* aLibrary,
+ ptrdiff_t aLOffset, const char* aFileName,
+ uint32_t aLineNo) {
+ const char* function = aFunction && aFunction[0] ? aFunction : "???";
+ if (aFileName && aFileName[0]) {
+ // We have a filename and (presumably) a line number. Use them.
+ return SprintfBuf(aBuffer, aBufferSize, "#%02u: %s (%s:%u)", aFrameNumber,
+ function, aFileName, aLineNo);
+ } else if (aLibrary && aLibrary[0]) {
+ // We have no filename, but we do have a library name. Use it and the
+ // library offset, and print them in a way that `fix_stacks.py` can
+ // post-process.
+ return SprintfBuf(aBuffer, aBufferSize, "#%02u: %s[%s +0x%" PRIxPTR "]",
+ aFrameNumber, function, aLibrary,
+ static_cast<uintptr_t>(aLOffset));
+ } else {
+ // We have nothing useful to go on. (The format string is split because
+ // '??)' is a trigraph and causes a warning, sigh.)
+ return SprintfBuf(aBuffer, aBufferSize,
+ "#%02u: ??? (???:???"
+ ")",
+ aFrameNumber);
+ }
+}
+
+static void EnsureWrite(FILE* aStream, const char* aBuf, size_t aLen) {
+#ifdef XP_WIN
+ int fd = _fileno(aStream);
+#else
+ int fd = fileno(aStream);
+#endif
+ while (aLen > 0) {
+#ifdef XP_WIN
+ auto written = _write(fd, aBuf, aLen);
+#else
+ auto written = write(fd, aBuf, aLen);
+#endif
+ if (written <= 0 || size_t(written) > aLen) {
+ break;
+ }
+ aBuf += written;
+ aLen -= written;
+ }
+}
+
+template <int N>
+static int PrintStackFrameBuf(char (&aBuf)[N], uint32_t aFrameNumber, void* aPC,
+ void* aSP) {
+ MozCodeAddressDetails details;
+ MozDescribeCodeAddress(aPC, &details);
+ int len =
+ MozFormatCodeAddressDetails(aBuf, N - 1, aFrameNumber, aPC, &details);
+ len = std::min(len, N - 2);
+ aBuf[len++] = '\n';
+ aBuf[len] = '\0';
+ return len;
+}
+
+static void PrintStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP,
+ void* aClosure) {
+ FILE* stream = (FILE*)aClosure;
+ char buf[1025]; // 1024 + 1 for trailing '\n'
+ int len = PrintStackFrameBuf(buf, aFrameNumber, aPC, aSP);
+ fflush(stream);
+ EnsureWrite(stream, buf, len);
+}
+
+static bool WalkTheStackEnabled() {
+ static bool result = [] {
+ char* value = getenv("MOZ_DISABLE_WALKTHESTACK");
+ return !(value && value[0]);
+ }();
+ return result;
+}
+
+MFBT_API void MozWalkTheStack(FILE* aStream, const void* aFirstFramePC,
+ uint32_t aMaxFrames) {
+ if (WalkTheStackEnabled()) {
+ MozStackWalk(PrintStackFrame, aFirstFramePC ? aFirstFramePC : CallerPC(),
+ aMaxFrames, aStream);
+ }
+}
+
+static void WriteStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP,
+ void* aClosure) {
+ auto writer = (void (*)(const char*))aClosure;
+ char buf[1024];
+ PrintStackFrameBuf(buf, aFrameNumber, aPC, aSP);
+ writer(buf);
+}
+
+MFBT_API void MozWalkTheStackWithWriter(void (*aWriter)(const char*),
+ const void* aFirstFramePC,
+ uint32_t aMaxFrames) {
+ if (WalkTheStackEnabled()) {
+ MozStackWalk(WriteStackFrame, aFirstFramePC ? aFirstFramePC : CallerPC(),
+ aMaxFrames, (void*)aWriter);
+ }
+}
diff --git a/mozglue/misc/StackWalk.h b/mozglue/misc/StackWalk.h
new file mode 100644
index 0000000000..250160934f
--- /dev/null
+++ b/mozglue/misc/StackWalk.h
@@ -0,0 +1,215 @@
+/* -*- 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/. */
+
+/* APIs for getting a stack trace of the current thread */
+
+#ifndef mozilla_StackWalk_h
+#define mozilla_StackWalk_h
+
+#include "mozilla/Types.h"
+#include <stdint.h>
+#include <stdio.h>
+
+MOZ_BEGIN_EXTERN_C
+
+/**
+ * Returns the position of the Program Counter for the caller of the current
+ * function. This is meant to be used to feed the aFirstFramePC argument to
+ * MozStackWalk or MozWalkTheStack*, and should be used in the last function
+ * that should be skipped in the trace, and passed down to MozStackWalk or
+ * MozWalkTheStack*, through any intermediaries.
+ *
+ * THIS DOES NOT 100% RELIABLY GIVE THE CALLER PC, but marking functions
+ * calling this macro with MOZ_NEVER_INLINE gets us close. In cases it doesn't
+ * give the caller's PC, it may give the caller of the caller, or its caller,
+ * etc. depending on tail call optimization.
+ *
+ * Past versions of stackwalking relied on passing a constant number of frames
+ * to skip to MozStackWalk or MozWalkTheStack, which fell short in more cases
+ * (inlining of intermediaries, tail call optimization).
+ */
+#define CallerPC() __builtin_extract_return_addr(__builtin_return_address(0))
+
+/**
+ * The callback for MozStackWalk and MozStackWalkThread.
+ *
+ * @param aFrameNumber The frame number (starts at 1, not 0).
+ * @param aPC The program counter value.
+ * @param aSP The best approximation possible of what the stack
+ * pointer will be pointing to when the execution returns
+ * to executing that at aPC. If no approximation can
+ * be made it will be nullptr.
+ * @param aClosure Extra data passed in from MozStackWalk() or
+ * MozStackWalkThread().
+ */
+typedef void (*MozWalkStackCallback)(uint32_t aFrameNumber, void* aPC,
+ void* aSP, void* aClosure);
+
+/**
+ * Call aCallback for each stack frame on the current thread, from
+ * the caller of MozStackWalk to main (or above).
+ *
+ * @param aCallback Callback function, called once per frame.
+ * @param aFirstFramePC Position of the Program Counter where the trace
+ * starts from. All frames seen before reaching that
+ * address are skipped. Nullptr means that the first
+ * callback will be for the caller of MozStackWalk.
+ * @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
+ * @param aClosure Caller-supplied data passed through to aCallback.
+ *
+ * May skip some stack frames due to compiler optimizations or code
+ * generation.
+ */
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
+ const void* aFirstFramePC, uint32_t aMaxFrames,
+ void* aClosure);
+
+typedef struct {
+ /*
+ * The name of the shared library or executable containing an
+ * address and the address's offset within that library, or empty
+ * string and zero if unknown.
+ */
+ char library[256];
+ ptrdiff_t loffset;
+ /*
+ * The name of the file name and line number of the code
+ * corresponding to the address, or empty string and zero if
+ * unknown.
+ */
+ char filename[256];
+ unsigned long lineno;
+ /*
+ * The name of the function containing an address and the address's
+ * offset within that function, or empty string and zero if unknown.
+ */
+ char function[256];
+ ptrdiff_t foffset;
+} MozCodeAddressDetails;
+
+/**
+ * For a given pointer to code, fill in the pieces of information used
+ * when printing a stack trace.
+ *
+ * @param aPC The code address.
+ * @param aDetails A structure to be filled in with the result.
+ */
+MFBT_API bool MozDescribeCodeAddress(void* aPC,
+ MozCodeAddressDetails* aDetails);
+
+/**
+ * Format the information about a code address in a format suitable for
+ * stack traces on the current platform. When available, this string
+ * should contain the function name, source file, and line number. When
+ * these are not available, library and offset should be reported, if
+ * possible.
+ *
+ * Note that this output is parsed by several scripts including the fix*.py and
+ * make-tree.pl scripts in tools/rb/. It should only be change with care, and
+ * in conjunction with those scripts.
+ *
+ * @param aBuffer A string to be filled in with the description.
+ * The string will always be null-terminated.
+ * @param aBufferSize The size, in bytes, of aBuffer, including
+ * room for the terminating null. If the information
+ * to be printed would be larger than aBuffer, it
+ * will be truncated so that aBuffer[aBufferSize-1]
+ * is the terminating null.
+ * @param aFrameNumber The frame number.
+ * @param aPC The code address.
+ * @param aFunction The function name. Possibly null or the empty string.
+ * @param aLibrary The library name. Possibly null or the empty string.
+ * @param aLOffset The library offset.
+ * @param aFileName The filename. Possibly null or the empty string.
+ * @param aLineNo The line number. Possibly zero.
+ * @return The minimum number of characters necessary to format
+ * the frame information, without the terminating null.
+ * The buffer will have been truncated if the returned
+ * value is greater than aBufferSize-1.
+ */
+MFBT_API int MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize,
+ uint32_t aFrameNumber, const void* aPC,
+ const char* aFunction, const char* aLibrary,
+ ptrdiff_t aLOffset, const char* aFileName,
+ uint32_t aLineNo);
+
+/**
+ * Format the information about a code address in the same fashion as
+ * MozFormatCodeAddress.
+ *
+ * @param aBuffer A string to be filled in with the description.
+ * The string will always be null-terminated.
+ * @param aBufferSize The size, in bytes, of aBuffer, including
+ * room for the terminating null. If the information
+ * to be printed would be larger than aBuffer, it
+ * will be truncated so that aBuffer[aBufferSize-1]
+ * is the terminating null.
+ * @param aFrameNumber The frame number.
+ * @param aPC The code address.
+ * @param aDetails The value filled in by MozDescribeCodeAddress(aPC).
+ * @return The minimum number of characters necessary to format
+ * the frame information, without the terminating null.
+ * The buffer will have been truncated if the returned
+ * value is greater than aBufferSize-1.
+ */
+MFBT_API int MozFormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize,
+ uint32_t aFrameNumber, void* aPC,
+ const MozCodeAddressDetails* aDetails);
+
+#ifdef __cplusplus
+# define FRAMES_DEFAULT = 0
+#else
+# define FRAMES_DEFAULT
+#endif
+/**
+ * Walk the stack and print the stack trace to the given stream.
+ *
+ * @param aStream A stdio stream.
+ * @param aFirstFramePC Position of the Program Counter where the trace
+ * starts from. All frames seen before reaching that
+ * address are skipped. Nullptr means that the first
+ * callback will be for the caller of MozWalkTheStack.
+ * @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
+ */
+MFBT_API void MozWalkTheStack(FILE* aStream,
+ const void* aFirstFramePC FRAMES_DEFAULT,
+ uint32_t aMaxFrames FRAMES_DEFAULT);
+
+/**
+ * Walk the stack and send each stack trace line to a callback writer.
+ * Each line string is null terminated but doesn't contain a '\n' character.
+ *
+ * @param aWriter The callback.
+ * @param aFirstFramePC Position of the Program Counter where the trace
+ * starts from. All frames seen before reaching that
+ * address are skipped. Nullptr means that the first
+ * callback will be for the caller of
+ * MozWalkTheStackWithWriter.
+ * @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
+ */
+MFBT_API void MozWalkTheStackWithWriter(
+ void (*aWriter)(const char*), const void* aFirstFramePC FRAMES_DEFAULT,
+ uint32_t aMaxFrames FRAMES_DEFAULT);
+
+#undef FRAMES_DEFAULT
+
+MOZ_END_EXTERN_C
+
+#ifdef __cplusplus
+namespace mozilla {
+
+MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
+ uint32_t aMaxFrames, void* aClosure,
+ void** aBp, void* aStackEnd);
+
+# if defined(XP_LINUX) || defined(XP_FREEBSD)
+MFBT_API void DemangleSymbol(const char* aSymbol, char* aBuffer, int aBufLen);
+# endif
+
+} // namespace mozilla
+#endif
+
+#endif
diff --git a/mozglue/misc/StackWalkThread.h b/mozglue/misc/StackWalkThread.h
new file mode 100644
index 0000000000..1db789de35
--- /dev/null
+++ b/mozglue/misc/StackWalkThread.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+/* APIs for getting a stack trace of an arbitrary thread */
+
+#ifndef mozilla_StackWalkThread_h
+#define mozilla_StackWalkThread_h
+
+#include "mozilla/StackWalk.h"
+
+#include <windows.h>
+
+/**
+ * Like MozStackWalk, but walks the stack for another thread.
+ * Call aCallback for each stack frame on the current thread, from
+ * the caller of MozStackWalk to main (or above).
+ *
+ * @param aCallback Same as for MozStackWalk().
+ * @param aMaxFrames Same as for MozStackWalk().
+ * @param aClosure Same as for MozStackWalk().
+ * @param aThread The handle of the thread whose stack is to be walked.
+ * If 0, walks the current thread.
+ * @param aContext A CONTEXT, presumably obtained with GetThreadContext()
+ * after suspending the thread with SuspendThread(). If
+ * null, the CONTEXT will be re-obtained.
+ */
+MFBT_API void MozStackWalkThread(MozWalkStackCallback aCallback,
+ uint32_t aMaxFrames, void* aClosure,
+ HANDLE aThread, CONTEXT* aContext);
+
+#endif
diff --git a/mozglue/misc/StackWalk_windows.h b/mozglue/misc/StackWalk_windows.h
new file mode 100644
index 0000000000..81c8125781
--- /dev/null
+++ b/mozglue/misc/StackWalk_windows.h
@@ -0,0 +1,34 @@
+/* -*- 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 mozilla_StackWalk_windows_h
+#define mozilla_StackWalk_windows_h
+
+#include "mozilla/Types.h"
+
+#if defined(_M_AMD64) || defined(_M_ARM64)
+/**
+ * Allow stack walkers to work around the egregious win64 dynamic lookup table
+ * list API by locking around SuspendThread to avoid deadlock.
+ *
+ * See comment in StackWalk.cpp
+ */
+struct MOZ_RAII AutoSuppressStackWalking {
+ MFBT_API AutoSuppressStackWalking();
+ MFBT_API ~AutoSuppressStackWalking();
+};
+
+# if defined(IMPL_MFBT)
+void SuppressStackWalking();
+void DesuppressStackWalking();
+# endif // defined(IMPL_MFBT)
+
+MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t size);
+
+MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t size);
+#endif // _M_AMD64 || _M_ARM64
+
+#endif // mozilla_StackWalk_windows_h
diff --git a/mozglue/misc/TimeStamp.cpp b/mozglue/misc/TimeStamp.cpp
new file mode 100644
index 0000000000..f77cf63132
--- /dev/null
+++ b/mozglue/misc/TimeStamp.cpp
@@ -0,0 +1,115 @@
+/* -*- 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/. */
+
+/*
+ * Implementation of the OS-independent methods of the TimeStamp class
+ */
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Uptime.h"
+#include <string.h>
+#include <stdlib.h>
+
+namespace mozilla {
+
+/**
+ * Wrapper class used to initialize static data used by the TimeStamp class
+ */
+struct TimeStampInitialization {
+ /**
+ * First timestamp taken when the class static initializers are run. This
+ * timestamp is used to sanitize timestamps coming from different sources.
+ */
+ TimeStamp mFirstTimeStamp;
+
+ /**
+ * Timestamp representing the time when the process was created. This field
+ * is populated lazily the first time this information is required and is
+ * replaced every time the process is restarted.
+ */
+ TimeStamp mProcessCreation;
+
+ TimeStampInitialization() {
+ TimeStamp::Startup();
+ mFirstTimeStamp = TimeStamp::Now();
+ // On Windows < 10, initializing the uptime requires `mFirstTimeStamp` to be
+ // valid.
+ mozilla::InitializeUptime();
+ }
+
+ ~TimeStampInitialization() { TimeStamp::Shutdown(); }
+};
+
+static TimeStampInitialization sInitOnce;
+
+MFBT_API TimeStamp TimeStamp::ProcessCreation() {
+ if (sInitOnce.mProcessCreation.IsNull()) {
+ char* mozAppRestart = getenv("MOZ_APP_RESTART");
+ TimeStamp ts;
+
+ /* When calling PR_SetEnv() with an empty value the existing variable may
+ * be unset or set to the empty string depending on the underlying platform
+ * thus we have to check if the variable is present and not empty. */
+ if (mozAppRestart && (strcmp(mozAppRestart, "") != 0)) {
+ /* Firefox was restarted, use the first time-stamp we've taken as the new
+ * process startup time. */
+ ts = sInitOnce.mFirstTimeStamp;
+ } else {
+ TimeStamp now = Now();
+ uint64_t uptime = ComputeProcessUptime();
+
+ ts = now - TimeDuration::FromMicroseconds(static_cast<double>(uptime));
+
+ if ((ts > sInitOnce.mFirstTimeStamp) || (uptime == 0)) {
+ ts = sInitOnce.mFirstTimeStamp;
+ }
+ }
+
+ sInitOnce.mProcessCreation = ts;
+ }
+
+ return sInitOnce.mProcessCreation;
+}
+
+void TimeStamp::RecordProcessRestart() {
+ sInitOnce.mProcessCreation = TimeStamp();
+}
+
+MFBT_API TimeStamp TimeStamp::FirstTimeStamp() {
+ return sInitOnce.mFirstTimeStamp;
+}
+
+class TimeStampTests {
+ // Check that nullity is set/not set correctly.
+ static_assert(TimeStamp{TimeStampValue{0}}.IsNull());
+ static_assert(!TimeStamp{TimeStampValue{1}}.IsNull());
+
+ // Check that some very basic comparisons work correctly.
+ static constexpr uint64_t sMidTime = (uint64_t)1 << 63;
+ static_assert(TimeStampValue{sMidTime + 5} > TimeStampValue{sMidTime - 5});
+ static_assert(TimeStampValue{sMidTime + 5} >= TimeStampValue{sMidTime - 5});
+ static_assert(TimeStampValue{sMidTime - 5} < TimeStampValue{sMidTime + 5});
+ static_assert(TimeStampValue{sMidTime - 5} <= TimeStampValue{sMidTime + 5});
+ static_assert(TimeStampValue{sMidTime} == TimeStampValue{sMidTime});
+ static_assert(TimeStampValue{sMidTime} >= TimeStampValue{sMidTime});
+ static_assert(TimeStampValue{sMidTime} <= TimeStampValue{sMidTime});
+ static_assert(TimeStampValue{sMidTime - 5} != TimeStampValue{sMidTime + 5});
+
+ // Check that comparisons involving very large and very small TimeStampValue's
+ // work correctly. This may seem excessive, but these asserts would have
+ // failed in the past due to a comparison such as "a > b" being implemented as
+ // "<cast to signed 64-bit value>(a - b) > 0". When a-b didn't fit into a
+ // signed 64-bit value, this would have given an incorrect result.
+ static_assert(TimeStampValue{UINT64_MAX} > TimeStampValue{1});
+ static_assert(TimeStampValue{1} < TimeStampValue{UINT64_MAX});
+
+ // NOTE/TODO: It would be nice to add some additional tests here that involve
+ // arithmetic between TimeStamps and TimeDurations (and verifying some of the
+ // special behaviors in some cases such as not wrapping around below zero) but
+ // that is not possible right now because those operators are not constexpr.
+};
+
+} // namespace mozilla
diff --git a/mozglue/misc/TimeStamp.h b/mozglue/misc/TimeStamp.h
new file mode 100644
index 0000000000..b38882b250
--- /dev/null
+++ b/mozglue/misc/TimeStamp.h
@@ -0,0 +1,620 @@
+/* -*- 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 mozilla_TimeStamp_h
+#define mozilla_TimeStamp_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Types.h"
+#include <algorithm> // for std::min, std::max
+#include <ostream>
+#include <stdint.h>
+#include <type_traits>
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+} // namespace IPC
+
+#ifdef XP_WIN
+// defines TimeStampValue as a complex value keeping both
+// GetTickCount and QueryPerformanceCounter values
+# include "TimeStamp_windows.h"
+
+# include "mozilla/Maybe.h" // For TimeStamp::RawQueryPerformanceCounterValue
+#endif
+
+namespace mozilla {
+
+#ifndef XP_WIN
+typedef uint64_t TimeStampValue;
+#endif
+
+class TimeStamp;
+class TimeStampTests;
+
+/**
+ * Platform-specific implementation details of BaseTimeDuration.
+ */
+class BaseTimeDurationPlatformUtils {
+ public:
+ static MFBT_API double ToSeconds(int64_t aTicks);
+ static MFBT_API double ToSecondsSigDigits(int64_t aTicks);
+ static MFBT_API int64_t TicksFromMilliseconds(double aMilliseconds);
+ static MFBT_API int64_t ResolutionInTicks();
+};
+
+/**
+ * Instances of this class represent the length of an interval of time.
+ * Negative durations are allowed, meaning the end is before the start.
+ *
+ * Internally the duration is stored as a int64_t in units of
+ * PR_TicksPerSecond() when building with NSPR interval timers, or a
+ * system-dependent unit when building with system clocks. The
+ * system-dependent unit must be constant, otherwise the semantics of
+ * this class would be broken.
+ *
+ * The ValueCalculator template parameter determines how arithmetic
+ * operations are performed on the integer count of ticks (mValue).
+ */
+template <typename ValueCalculator>
+class BaseTimeDuration {
+ public:
+ // The default duration is 0.
+ constexpr BaseTimeDuration() : mValue(0) {}
+ // Allow construction using '0' as the initial value, for readability,
+ // but no other numbers (so we don't have any implicit unit conversions).
+ struct _SomethingVeryRandomHere;
+ MOZ_IMPLICIT BaseTimeDuration(_SomethingVeryRandomHere* aZero) : mValue(0) {
+ MOZ_ASSERT(!aZero, "Who's playing funny games here?");
+ }
+ // Default copy-constructor and assignment are OK
+
+ // Converting copy-constructor and assignment operator
+ template <typename E>
+ explicit BaseTimeDuration(const BaseTimeDuration<E>& aOther)
+ : mValue(aOther.mValue) {}
+
+ template <typename E>
+ BaseTimeDuration& operator=(const BaseTimeDuration<E>& aOther) {
+ mValue = aOther.mValue;
+ return *this;
+ }
+
+ double ToSeconds() const {
+ if (mValue == INT64_MAX) {
+ return PositiveInfinity<double>();
+ }
+ if (mValue == INT64_MIN) {
+ return NegativeInfinity<double>();
+ }
+ return BaseTimeDurationPlatformUtils::ToSeconds(mValue);
+ }
+ // Return a duration value that includes digits of time we think to
+ // be significant. This method should be used when displaying a
+ // time to humans.
+ double ToSecondsSigDigits() const {
+ if (mValue == INT64_MAX) {
+ return PositiveInfinity<double>();
+ }
+ if (mValue == INT64_MIN) {
+ return NegativeInfinity<double>();
+ }
+ return BaseTimeDurationPlatformUtils::ToSecondsSigDigits(mValue);
+ }
+ double ToMilliseconds() const { return ToSeconds() * 1000.0; }
+ double ToMicroseconds() const { return ToMilliseconds() * 1000.0; }
+
+ // Using a double here is safe enough; with 53 bits we can represent
+ // durations up to over 280,000 years exactly. If the units of
+ // mValue do not allow us to represent durations of that length,
+ // long durations are clamped to the max/min representable value
+ // instead of overflowing.
+ static inline BaseTimeDuration FromSeconds(double aSeconds) {
+ return FromMilliseconds(aSeconds * 1000.0);
+ }
+ static BaseTimeDuration FromMilliseconds(double aMilliseconds) {
+ if (aMilliseconds == PositiveInfinity<double>()) {
+ return Forever();
+ }
+ if (aMilliseconds == NegativeInfinity<double>()) {
+ return FromTicks(INT64_MIN);
+ }
+ return FromTicks(
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aMilliseconds));
+ }
+ static inline BaseTimeDuration FromMicroseconds(double aMicroseconds) {
+ return FromMilliseconds(aMicroseconds / 1000.0);
+ }
+
+ static constexpr BaseTimeDuration Zero() { return BaseTimeDuration(); }
+ static constexpr BaseTimeDuration Forever() { return FromTicks(INT64_MAX); }
+
+ BaseTimeDuration operator+(const BaseTimeDuration& aOther) const {
+ return FromTicks(ValueCalculator::Add(mValue, aOther.mValue));
+ }
+ BaseTimeDuration operator-(const BaseTimeDuration& aOther) const {
+ return FromTicks(ValueCalculator::Subtract(mValue, aOther.mValue));
+ }
+ BaseTimeDuration& operator+=(const BaseTimeDuration& aOther) {
+ mValue = ValueCalculator::Add(mValue, aOther.mValue);
+ return *this;
+ }
+ BaseTimeDuration& operator-=(const BaseTimeDuration& aOther) {
+ mValue = ValueCalculator::Subtract(mValue, aOther.mValue);
+ return *this;
+ }
+ BaseTimeDuration operator-() const {
+ // We don't just use FromTicks(ValueCalculator::Subtract(0, mValue))
+ // since that won't give the correct result for -TimeDuration::Forever().
+ int64_t ticks;
+ if (mValue == INT64_MAX) {
+ ticks = INT64_MIN;
+ } else if (mValue == INT64_MIN) {
+ ticks = INT64_MAX;
+ } else {
+ ticks = -mValue;
+ }
+
+ return FromTicks(ticks);
+ }
+
+ static BaseTimeDuration Max(const BaseTimeDuration& aA,
+ const BaseTimeDuration& aB) {
+ return FromTicks(std::max(aA.mValue, aB.mValue));
+ }
+ static BaseTimeDuration Min(const BaseTimeDuration& aA,
+ const BaseTimeDuration& aB) {
+ return FromTicks(std::min(aA.mValue, aB.mValue));
+ }
+
+#if defined(DEBUG)
+ int64_t GetValue() const { return mValue; }
+#endif
+
+ private:
+ // Block double multiplier (slower, imprecise if long duration) - Bug 853398.
+ // If required, use MultDouble explicitly and with care.
+ BaseTimeDuration operator*(const double aMultiplier) const = delete;
+
+ // Block double divisor (for the same reason, and because dividing by
+ // fractional values would otherwise invoke the int64_t variant, and rounding
+ // the passed argument can then cause divide-by-zero) - Bug 1147491.
+ BaseTimeDuration operator/(const double aDivisor) const = delete;
+
+ public:
+ BaseTimeDuration MultDouble(double aMultiplier) const {
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator*(const int32_t aMultiplier) const {
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator*(const uint32_t aMultiplier) const {
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator*(const int64_t aMultiplier) const {
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator*(const uint64_t aMultiplier) const {
+ if (aMultiplier > INT64_MAX) {
+ return Forever();
+ }
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator/(const int64_t aDivisor) const {
+ MOZ_ASSERT(aDivisor != 0, "Division by zero");
+ return FromTicks(ValueCalculator::Divide(mValue, aDivisor));
+ }
+ double operator/(const BaseTimeDuration& aOther) const {
+ MOZ_ASSERT(aOther.mValue != 0, "Division by zero");
+ return ValueCalculator::DivideDouble(mValue, aOther.mValue);
+ }
+ BaseTimeDuration operator%(const BaseTimeDuration& aOther) const {
+ MOZ_ASSERT(aOther.mValue != 0, "Division by zero");
+ return FromTicks(ValueCalculator::Modulo(mValue, aOther.mValue));
+ }
+
+ template <typename E>
+ bool operator<(const BaseTimeDuration<E>& aOther) const {
+ return mValue < aOther.mValue;
+ }
+ template <typename E>
+ bool operator<=(const BaseTimeDuration<E>& aOther) const {
+ return mValue <= aOther.mValue;
+ }
+ template <typename E>
+ bool operator>=(const BaseTimeDuration<E>& aOther) const {
+ return mValue >= aOther.mValue;
+ }
+ template <typename E>
+ bool operator>(const BaseTimeDuration<E>& aOther) const {
+ return mValue > aOther.mValue;
+ }
+ template <typename E>
+ bool operator==(const BaseTimeDuration<E>& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ template <typename E>
+ bool operator!=(const BaseTimeDuration<E>& aOther) const {
+ return mValue != aOther.mValue;
+ }
+ bool IsZero() const { return mValue == 0; }
+ explicit operator bool() const { return mValue != 0; }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const BaseTimeDuration& aDuration) {
+ return aStream << aDuration.ToMilliseconds() << " ms";
+ }
+
+ // Return a best guess at the system's current timing resolution,
+ // which might be variable. BaseTimeDurations below this order of
+ // magnitude are meaningless, and those at the same order of
+ // magnitude or just above are suspect.
+ static BaseTimeDuration Resolution() {
+ return FromTicks(BaseTimeDurationPlatformUtils::ResolutionInTicks());
+ }
+
+ // We could define additional operators here:
+ // -- convert to/from other time units
+ // -- scale duration by a float
+ // but let's do that on demand.
+ // Comparing durations for equality will only lead to bugs on
+ // platforms with high-resolution timers.
+
+ private:
+ friend class TimeStamp;
+ friend struct IPC::ParamTraits<mozilla::BaseTimeDuration<ValueCalculator>>;
+ template <typename>
+ friend class BaseTimeDuration;
+
+ static constexpr BaseTimeDuration FromTicks(int64_t aTicks) {
+ BaseTimeDuration t;
+ t.mValue = aTicks;
+ return t;
+ }
+
+ static BaseTimeDuration FromTicks(double aTicks) {
+ // NOTE: this MUST be a >= test, because int64_t(double(INT64_MAX))
+ // overflows and gives INT64_MIN.
+ if (aTicks >= double(INT64_MAX)) {
+ return FromTicks(INT64_MAX);
+ }
+
+ // This MUST be a <= test.
+ if (aTicks <= double(INT64_MIN)) {
+ return FromTicks(INT64_MIN);
+ }
+
+ return FromTicks(int64_t(aTicks));
+ }
+
+ // Duration, result is implementation-specific difference of two TimeStamps
+ int64_t mValue;
+};
+
+/**
+ * Perform arithmetic operations on the value of a BaseTimeDuration without
+ * doing strict checks on the range of values.
+ */
+class TimeDurationValueCalculator {
+ public:
+ static int64_t Add(int64_t aA, int64_t aB) { return aA + aB; }
+ static int64_t Subtract(int64_t aA, int64_t aB) { return aA - aB; }
+
+ template <typename T>
+ static int64_t Multiply(int64_t aA, T aB) {
+ static_assert(std::is_integral_v<T>,
+ "Using integer multiplication routine with non-integer type."
+ " Further specialization required");
+ return aA * static_cast<int64_t>(aB);
+ }
+
+ static int64_t Divide(int64_t aA, int64_t aB) { return aA / aB; }
+ static double DivideDouble(int64_t aA, int64_t aB) {
+ return static_cast<double>(aA) / aB;
+ }
+ static int64_t Modulo(int64_t aA, int64_t aB) { return aA % aB; }
+};
+
+template <>
+inline int64_t TimeDurationValueCalculator::Multiply<double>(int64_t aA,
+ double aB) {
+ return static_cast<int64_t>(aA * aB);
+}
+
+/**
+ * Specialization of BaseTimeDuration that uses TimeDurationValueCalculator for
+ * arithmetic on the mValue member.
+ *
+ * Use this class for time durations that are *not* expected to hold values of
+ * Forever (or the negative equivalent) or when such time duration are *not*
+ * expected to be used in arithmetic operations.
+ */
+typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration;
+
+/**
+ * Instances of this class represent moments in time, or a special
+ * "null" moment. We do not use the non-monotonic system clock or
+ * local time, since they can be reset, causing apparent backward
+ * travel in time, which can confuse algorithms. Instead we measure
+ * elapsed time according to the system. This time can never go
+ * backwards (i.e. it never wraps around, at least not in less than
+ * five million years of system elapsed time). It might not advance
+ * while the system is sleeping. If TimeStamp::SetNow() is not called
+ * at all for hours or days, we might not notice the passage of some
+ * of that time.
+ *
+ * We deliberately do not expose a way to convert TimeStamps to some
+ * particular unit. All you can do is compute a difference between two
+ * TimeStamps to get a TimeDuration. You can also add a TimeDuration
+ * to a TimeStamp to get a new TimeStamp. You can't do something
+ * meaningless like add two TimeStamps.
+ *
+ * Internally this is implemented as either a wrapper around
+ * - high-resolution, monotonic, system clocks if they exist on this
+ * platform
+ * - PRIntervalTime otherwise. We detect wraparounds of
+ * PRIntervalTime and work around them.
+ *
+ * This class is similar to C++11's time_point, however it is
+ * explicitly nullable and provides an IsNull() method. time_point
+ * is initialized to the clock's epoch and provides a
+ * time_since_epoch() method that functions similiarly. i.e.
+ * t.IsNull() is equivalent to t.time_since_epoch() ==
+ * decltype(t)::duration::zero();
+ *
+ * Note that, since TimeStamp objects are small, prefer to pass them by value
+ * unless there is a specific reason not to do so.
+ */
+#if defined(XP_WIN)
+// If this static_assert fails then possibly the warning comment below is no
+// longer valid and should be removed.
+static_assert(sizeof(TimeStampValue) > 8);
+#endif
+/*
+ * WARNING: On Windows, each TimeStamp is represented internally by two
+ * different raw values (one from GTC and one from QPC) and which value gets
+ * used for a given operation depends on whether both operands have QPC values
+ * or not. This duality of values can lead to some surprising results when
+ * mixing TimeStamps with and without QPC values, such as comparisons being
+ * non-transitive (ie, a > b > c might not imply a > c). See bug 1829983 for
+ * more details/an example.
+ */
+class TimeStamp {
+ public:
+ /**
+ * Initialize to the "null" moment
+ */
+ constexpr TimeStamp() : mValue(0) {}
+ // Default copy-constructor and assignment are OK
+
+ /**
+ * The system timestamps are the same as the TimeStamp
+ * retrieved by mozilla::TimeStamp. Since we need this for
+ * vsync timestamps, we enable the creation of mozilla::TimeStamps
+ * on platforms that support vsync aligned refresh drivers / compositors
+ * Verified true as of Jan 31, 2015: B2G and OS X
+ * False on Windows 7
+ * Android's event time uses CLOCK_MONOTONIC via SystemClock.uptimeMilles.
+ * So it is same value of TimeStamp posix implementation.
+ * Wayland/GTK event time also uses CLOCK_MONOTONIC on Weston/Mutter
+ * compositors.
+ * UNTESTED ON OTHER PLATFORMS
+ */
+#if defined(XP_DARWIN) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK)
+ static TimeStamp FromSystemTime(int64_t aSystemTime) {
+ static_assert(sizeof(aSystemTime) == sizeof(TimeStampValue),
+ "System timestamp should be same units as TimeStampValue");
+ return TimeStamp(aSystemTime);
+ }
+#endif
+
+ /**
+ * Return true if this is the "null" moment
+ */
+ constexpr bool IsNull() const { return mValue == 0; }
+
+ /**
+ * Return true if this is not the "null" moment, may be used in tests, e.g.:
+ * |if (timestamp) { ... }|
+ */
+ explicit operator bool() const { return mValue != 0; }
+
+ /**
+ * Return a timestamp reflecting the current elapsed system time. This
+ * is monotonically increasing (i.e., does not decrease) over the
+ * lifetime of this process' XPCOM session.
+ *
+ * Now() is trying to ensure the best possible precision on each platform,
+ * at least one millisecond.
+ *
+ * NowLoRes() has been introduced to workaround performance problems of
+ * QueryPerformanceCounter on the Windows platform. NowLoRes() is giving
+ * lower precision, usually 15.6 ms, but with very good performance benefit.
+ * Use it for measurements of longer times, like >200ms timeouts.
+ */
+ static TimeStamp Now() { return Now(true); }
+ static TimeStamp NowLoRes() { return Now(false); }
+
+ /**
+ * Return a timestamp representing the time when the current process was
+ * created which will be comparable with other timestamps taken with this
+ * class.
+ *
+ * @returns A timestamp representing the time when the process was created
+ */
+ static MFBT_API TimeStamp ProcessCreation();
+
+ /**
+ * Return the very first timestamp that was taken. This can be used instead
+ * of TimeStamp::ProcessCreation() by code that might not allow running the
+ * complex logic required to compute the real process creation. This will
+ * necessarily have been recorded sometimes after TimeStamp::ProcessCreation()
+ * or at best should be equal to it.
+ *
+ * @returns The first tiemstamp that was taken by this process
+ */
+ static MFBT_API TimeStamp FirstTimeStamp();
+
+ /**
+ * Records a process restart. After this call ProcessCreation() will return
+ * the time when the browser was restarted instead of the actual time when
+ * the process was created.
+ */
+ static MFBT_API void RecordProcessRestart();
+
+#ifdef XP_LINUX
+ uint64_t RawClockMonotonicNanosecondsSinceBoot() {
+ return static_cast<uint64_t>(mValue);
+ }
+#endif
+
+#ifdef XP_DARWIN
+ // Returns the number of nanoseconds since the mach_absolute_time origin.
+ MFBT_API uint64_t RawMachAbsoluteTimeNanoseconds() const;
+#endif
+
+#ifdef XP_WIN
+ Maybe<uint64_t> RawQueryPerformanceCounterValue() const {
+ // mQPC is stored in `mt` i.e. QueryPerformanceCounter * 1000
+ // so divide out the 1000
+ return mValue.mHasQPC ? Some(mValue.mQPC / 1000ULL) : Nothing();
+ }
+#endif
+
+ /**
+ * Compute the difference between two timestamps. Both must be non-null.
+ */
+ TimeDuration operator-(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ static_assert(-INT64_MAX > INT64_MIN, "int64_t sanity check");
+ int64_t ticks = int64_t(mValue - aOther.mValue);
+ // Check for overflow.
+ if (mValue > aOther.mValue) {
+ if (ticks < 0) {
+ ticks = INT64_MAX;
+ }
+ } else {
+ if (ticks > 0) {
+ ticks = INT64_MIN;
+ }
+ }
+ return TimeDuration::FromTicks(ticks);
+ }
+
+ TimeStamp operator+(const TimeDuration& aOther) const {
+ TimeStamp result = *this;
+ result += aOther;
+ return result;
+ }
+ TimeStamp operator-(const TimeDuration& aOther) const {
+ TimeStamp result = *this;
+ result -= aOther;
+ return result;
+ }
+ TimeStamp& operator+=(const TimeDuration& aOther) {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ TimeStampValue value = mValue + aOther.mValue;
+ // Check for underflow.
+ // (We don't check for overflow because it's not obvious what the error
+ // behavior should be in that case.)
+ if (aOther.mValue < 0 && value > mValue) {
+ value = 0;
+ }
+ mValue = value;
+ return *this;
+ }
+ TimeStamp& operator-=(const TimeDuration& aOther) {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ TimeStampValue value = mValue - aOther.mValue;
+ // Check for underflow.
+ // (We don't check for overflow because it's not obvious what the error
+ // behavior should be in that case.)
+ if (aOther.mValue > 0 && value > mValue) {
+ value = 0;
+ }
+ mValue = value;
+ return *this;
+ }
+
+ constexpr bool operator<(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ return mValue < aOther.mValue;
+ }
+ constexpr bool operator<=(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ return mValue <= aOther.mValue;
+ }
+ constexpr bool operator>=(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ return mValue >= aOther.mValue;
+ }
+ constexpr bool operator>(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ return mValue > aOther.mValue;
+ }
+ bool operator==(const TimeStamp& aOther) const {
+ return IsNull() ? aOther.IsNull()
+ : !aOther.IsNull() && mValue == aOther.mValue;
+ }
+ bool operator!=(const TimeStamp& aOther) const { return !(*this == aOther); }
+
+ // Comparing TimeStamps for equality should be discouraged. Adding
+ // two TimeStamps, or scaling TimeStamps, is nonsense and must never
+ // be allowed.
+
+ static MFBT_API void Startup();
+ static MFBT_API void Shutdown();
+
+#if defined(DEBUG)
+ TimeStampValue GetValue() const { return mValue; }
+#endif
+
+ private:
+ friend struct IPC::ParamTraits<mozilla::TimeStamp>;
+ friend struct TimeStampInitialization;
+ friend class TimeStampTests;
+
+ constexpr MOZ_IMPLICIT TimeStamp(TimeStampValue aValue) : mValue(aValue) {}
+
+ static MFBT_API TimeStamp Now(bool aHighResolution);
+
+ /**
+ * Computes the uptime of the current process in microseconds. The result
+ * is platform-dependent and needs to be checked against existing timestamps
+ * for consistency.
+ *
+ * @returns The number of microseconds since the calling process was started
+ * or 0 if an error was encountered while computing the uptime
+ */
+ static MFBT_API uint64_t ComputeProcessUptime();
+
+ /**
+ * When built with PRIntervalTime, a value of 0 means this instance
+ * is "null". Otherwise, the low 32 bits represent a PRIntervalTime,
+ * and the high 32 bits represent a counter of the number of
+ * rollovers of PRIntervalTime that we've seen. This counter starts
+ * at 1 to avoid a real time colliding with the "null" value.
+ *
+ * PR_INTERVAL_MAX is set at 100,000 ticks per second. So the minimum
+ * time to wrap around is about 2^64/100000 seconds, i.e. about
+ * 5,849,424 years.
+ *
+ * When using a system clock, a value is system dependent.
+ */
+ TimeStampValue mValue;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_TimeStamp_h */
diff --git a/mozglue/misc/TimeStamp_darwin.cpp b/mozglue/misc/TimeStamp_darwin.cpp
new file mode 100644
index 0000000000..ec29917985
--- /dev/null
+++ b/mozglue/misc/TimeStamp_darwin.cpp
@@ -0,0 +1,189 @@
+/* -*- 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/. */
+
+//
+// Implement TimeStamp::Now() with mach_absolute_time
+//
+// The "tick" unit for mach_absolute_time is defined using mach_timebase_info()
+// which gives a conversion ratio to nanoseconds. For more information see
+// Apple's QA1398.
+//
+// This code is inspired by Chromium's time_mac.cc. The biggest
+// differences are that we explicitly initialize using
+// TimeStamp::Initialize() instead of lazily in Now() and that
+// we store the time value in ticks and convert when needed instead
+// of storing the time value in nanoseconds.
+
+#include <mach/mach_time.h>
+#include <sys/time.h>
+#include <sys/sysctl.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Uptime.h"
+
+// Estimate of the smallest duration of time we can measure.
+static uint64_t sResolution;
+static uint64_t sResolutionSigDigs;
+
+static const uint64_t kNsPerMs = 1000000;
+static const uint64_t kUsPerSec = 1000000;
+static const double kNsPerMsd = 1000000.0;
+static const double kNsPerSecd = 1000000000.0;
+
+static bool gInitialized = false;
+static double sNsPerTick;
+
+static uint64_t ClockTime() {
+ // mach_absolute_time is it when it comes to ticks on the Mac. Other calls
+ // with less precision (such as TickCount) just call through to
+ // mach_absolute_time.
+ //
+ // At the time of writing mach_absolute_time returns the number of nanoseconds
+ // since boot. This won't overflow 64bits for 500+ years so we aren't going
+ // to worry about that possiblity
+ return mach_absolute_time();
+}
+
+static uint64_t ClockResolutionNs() {
+ uint64_t start = ClockTime();
+ uint64_t end = ClockTime();
+ uint64_t minres = (end - start);
+
+ // 10 total trials is arbitrary: what we're trying to avoid by
+ // looping is getting unlucky and being interrupted by a context
+ // switch or signal, or being bitten by paging/cache effects
+ for (int i = 0; i < 9; ++i) {
+ start = ClockTime();
+ end = ClockTime();
+
+ uint64_t candidate = (start - end);
+ if (candidate < minres) {
+ minres = candidate;
+ }
+ }
+
+ if (0 == minres) {
+ // measurable resolution is either incredibly low, ~1ns, or very
+ // high. fall back on NSPR's resolution assumption
+ minres = 1 * kNsPerMs;
+ }
+
+ return minres;
+}
+
+namespace mozilla {
+
+double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) {
+ MOZ_ASSERT(gInitialized, "calling TimeDuration too early");
+ return (aTicks * sNsPerTick) / kNsPerSecd;
+}
+
+double BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) {
+ MOZ_ASSERT(gInitialized, "calling TimeDuration too early");
+ // don't report a value < mResolution ...
+ int64_t valueSigDigs = sResolution * (aTicks / sResolution);
+ // and chop off insignificant digits
+ valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs);
+ return (valueSigDigs * sNsPerTick) / kNsPerSecd;
+}
+
+int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds(
+ double aMilliseconds) {
+ MOZ_ASSERT(gInitialized, "calling TimeDuration too early");
+ double result = (aMilliseconds * kNsPerMsd) / sNsPerTick;
+ if (result > double(INT64_MAX)) {
+ return INT64_MAX;
+ } else if (result < double(INT64_MIN)) {
+ return INT64_MIN;
+ }
+
+ return result;
+}
+
+int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() {
+ MOZ_ASSERT(gInitialized, "calling TimeDuration too early");
+ return static_cast<int64_t>(sResolution);
+}
+
+void TimeStamp::Startup() {
+ if (gInitialized) {
+ return;
+ }
+
+ mach_timebase_info_data_t timebaseInfo;
+ // Apple's QA1398 suggests that the output from mach_timebase_info
+ // will not change while a program is running, so it should be safe
+ // to cache the result.
+ kern_return_t kr = mach_timebase_info(&timebaseInfo);
+ if (kr != KERN_SUCCESS) {
+ MOZ_RELEASE_ASSERT(false, "mach_timebase_info failed");
+ }
+
+ sNsPerTick = double(timebaseInfo.numer) / timebaseInfo.denom;
+
+ sResolution = ClockResolutionNs();
+
+ // find the number of significant digits in sResolution, for the
+ // sake of ToSecondsSigDigits()
+ for (sResolutionSigDigs = 1; !(sResolutionSigDigs == sResolution ||
+ 10 * sResolutionSigDigs > sResolution);
+ sResolutionSigDigs *= 10)
+ ;
+
+ gInitialized = true;
+}
+
+void TimeStamp::Shutdown() {}
+
+TimeStamp TimeStamp::Now(bool aHighResolution) {
+ return TimeStamp(ClockTime());
+}
+
+uint64_t TimeStamp::RawMachAbsoluteTimeNanoseconds() const {
+ return static_cast<uint64_t>(double(mValue) * sNsPerTick);
+}
+
+// Computes and returns the process uptime in microseconds.
+// Returns 0 if an error was encountered.
+uint64_t TimeStamp::ComputeProcessUptime() {
+ struct timeval tv;
+ int rv = gettimeofday(&tv, nullptr);
+
+ if (rv == -1) {
+ return 0;
+ }
+
+ int mib[] = {
+ CTL_KERN,
+ KERN_PROC,
+ KERN_PROC_PID,
+ getpid(),
+ };
+ u_int mibLen = sizeof(mib) / sizeof(mib[0]);
+
+ struct kinfo_proc proc;
+ size_t bufferSize = sizeof(proc);
+ rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0);
+
+ if (rv == -1) {
+ return 0;
+ }
+
+ uint64_t startTime =
+ ((uint64_t)proc.kp_proc.p_un.__p_starttime.tv_sec * kUsPerSec) +
+ proc.kp_proc.p_un.__p_starttime.tv_usec;
+ uint64_t now = (tv.tv_sec * kUsPerSec) + tv.tv_usec;
+
+ if (startTime > now) {
+ return 0;
+ }
+
+ return now - startTime;
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/TimeStamp_posix.cpp b/mozglue/misc/TimeStamp_posix.cpp
new file mode 100644
index 0000000000..ba32a230eb
--- /dev/null
+++ b/mozglue/misc/TimeStamp_posix.cpp
@@ -0,0 +1,360 @@
+/* -*- 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/. */
+
+//
+// Implement TimeStamp::Now() with POSIX clocks.
+//
+// The "tick" unit for POSIX clocks is simply a nanosecond, as this is
+// the smallest unit of time representable by struct timespec. That
+// doesn't mean that a nanosecond is the resolution of TimeDurations
+// obtained with this API; see TimeDuration::Resolution;
+//
+
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+
+#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__)
+# include <sys/param.h>
+# include <sys/sysctl.h>
+#endif
+
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+# include <sys/user.h>
+#endif
+
+#if defined(__NetBSD__)
+# undef KERN_PROC
+# define KERN_PROC KERN_PROC2
+# define KINFO_PROC struct kinfo_proc2
+#else
+# define KINFO_PROC struct kinfo_proc
+#endif
+
+#if defined(__DragonFly__)
+# define KP_START_SEC kp_start.tv_sec
+# define KP_START_USEC kp_start.tv_usec
+#elif defined(__FreeBSD__)
+# define KP_START_SEC ki_start.tv_sec
+# define KP_START_USEC ki_start.tv_usec
+#else
+# define KP_START_SEC p_ustart_sec
+# define KP_START_USEC p_ustart_usec
+#endif
+
+#include "mozilla/Sprintf.h"
+#include "mozilla/TimeStamp.h"
+
+#if !defined(__wasi__)
+# include <pthread.h>
+#endif
+
+// Estimate of the smallest duration of time we can measure.
+static uint64_t sResolution;
+static uint64_t sResolutionSigDigs;
+
+#ifdef CLOCK_MONOTONIC_COARSE
+static bool sSupportsMonotonicCoarseClock = false;
+#endif
+
+#if !defined(__wasi__)
+static const uint16_t kNsPerUs = 1000;
+#endif
+
+static const uint64_t kNsPerMs = 1000000;
+static const uint64_t kNsPerSec = 1000000000;
+static const double kNsPerMsd = 1000000.0;
+static const double kNsPerSecd = 1000000000.0;
+
+static uint64_t TimespecToNs(const struct timespec& aTs) {
+ uint64_t baseNs = uint64_t(aTs.tv_sec) * kNsPerSec;
+ return baseNs + uint64_t(aTs.tv_nsec);
+}
+
+static uint64_t ClockTimeNs(const clockid_t aClockId = CLOCK_MONOTONIC) {
+ struct timespec ts;
+#ifdef CLOCK_MONOTONIC_COARSE
+ MOZ_RELEASE_ASSERT(
+ aClockId == CLOCK_MONOTONIC ||
+ (sSupportsMonotonicCoarseClock && aClockId == CLOCK_MONOTONIC_COARSE));
+#else
+ MOZ_RELEASE_ASSERT(aClockId == CLOCK_MONOTONIC);
+#endif
+ // this can't fail: we know &ts is valid, and TimeStamp::Startup()
+ // checks that CLOCK_MONOTONIC / CLOCK_MONOTONIC_COARSE are
+ // supported (and aborts if the former is not).
+ clock_gettime(aClockId, &ts);
+
+ // tv_sec is defined to be relative to an arbitrary point in time,
+ // but it would be madness for that point in time to be earlier than
+ // the Epoch. So we can safely assume that even if time_t is 32
+ // bits, tv_sec won't overflow while the browser is open. Revisit
+ // this argument if we're still building with 32-bit time_t around
+ // the year 2037.
+ return TimespecToNs(ts);
+}
+
+static uint64_t ClockResolutionNs() {
+ // NB: why not rely on clock_getres()? Two reasons: (i) it might
+ // lie, and (ii) it might return an "ideal" resolution that while
+ // theoretically true, could never be measured in practice. Since
+ // clock_gettime() likely involves a system call on your platform,
+ // the "actual" timing resolution shouldn't be lower than syscall
+ // overhead.
+
+ uint64_t start = ClockTimeNs();
+ uint64_t end = ClockTimeNs();
+ uint64_t minres = (end - start);
+
+ // 10 total trials is arbitrary: what we're trying to avoid by
+ // looping is getting unlucky and being interrupted by a context
+ // switch or signal, or being bitten by paging/cache effects
+ for (int i = 0; i < 9; ++i) {
+ start = ClockTimeNs();
+ end = ClockTimeNs();
+
+ uint64_t candidate = (start - end);
+ if (candidate < minres) {
+ minres = candidate;
+ }
+ }
+
+ if (0 == minres) {
+ // measurable resolution is either incredibly low, ~1ns, or very
+ // high. fall back on clock_getres()
+ struct timespec ts;
+ if (0 == clock_getres(CLOCK_MONOTONIC, &ts)) {
+ minres = TimespecToNs(ts);
+ }
+ }
+
+ if (0 == minres) {
+ // clock_getres probably failed. fall back on NSPR's resolution
+ // assumption
+ minres = 1 * kNsPerMs;
+ }
+
+ return minres;
+}
+
+namespace mozilla {
+
+double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) {
+ return double(aTicks) / kNsPerSecd;
+}
+
+double BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) {
+ // don't report a value < mResolution ...
+ int64_t valueSigDigs = sResolution * (aTicks / sResolution);
+ // and chop off insignificant digits
+ valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs);
+ return double(valueSigDigs) / kNsPerSecd;
+}
+
+int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds(
+ double aMilliseconds) {
+ double result = aMilliseconds * kNsPerMsd;
+ if (result > double(INT64_MAX)) {
+ return INT64_MAX;
+ }
+ if (result < INT64_MIN) {
+ return INT64_MIN;
+ }
+
+ return result;
+}
+
+int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() {
+ return static_cast<int64_t>(sResolution);
+}
+
+static bool gInitialized = false;
+
+void TimeStamp::Startup() {
+ if (gInitialized) {
+ return;
+ }
+
+ struct timespec dummy;
+ if (clock_gettime(CLOCK_MONOTONIC, &dummy) != 0) {
+ MOZ_CRASH("CLOCK_MONOTONIC is absent!");
+ }
+
+#ifdef CLOCK_MONOTONIC_COARSE
+ if (clock_gettime(CLOCK_MONOTONIC_COARSE, &dummy) == 0) {
+ sSupportsMonotonicCoarseClock = true;
+ }
+#endif
+
+ sResolution = ClockResolutionNs();
+
+ // find the number of significant digits in sResolution, for the
+ // sake of ToSecondsSigDigits()
+ for (sResolutionSigDigs = 1; !(sResolutionSigDigs == sResolution ||
+ 10 * sResolutionSigDigs > sResolution);
+ sResolutionSigDigs *= 10)
+ ;
+
+ gInitialized = true;
+}
+
+void TimeStamp::Shutdown() {}
+
+TimeStamp TimeStamp::Now(bool aHighResolution) {
+#ifdef CLOCK_MONOTONIC_COARSE
+ if (!aHighResolution && sSupportsMonotonicCoarseClock) {
+ return TimeStamp(ClockTimeNs(CLOCK_MONOTONIC_COARSE));
+ }
+#endif
+ return TimeStamp(ClockTimeNs(CLOCK_MONOTONIC));
+}
+
+#if defined(XP_LINUX) || defined(ANDROID)
+
+// Calculates the amount of jiffies that have elapsed since boot and up to the
+// starttime value of a specific process as found in its /proc/*/stat file.
+// Returns 0 if an error occurred.
+
+static uint64_t JiffiesSinceBoot(const char* aFile) {
+ char stat[512];
+
+ FILE* f = fopen(aFile, "r");
+ if (!f) {
+ return 0;
+ }
+
+ int n = fread(&stat, 1, sizeof(stat) - 1, f);
+
+ fclose(f);
+
+ if (n <= 0) {
+ return 0;
+ }
+
+ stat[n] = 0;
+
+ long long unsigned startTime = 0; // instead of uint64_t to keep GCC quiet
+ char* s = strrchr(stat, ')');
+
+ if (!s) {
+ return 0;
+ }
+
+ int rv = sscanf(s + 2,
+ "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u "
+ "%*u %*u %*u %*d %*d %*d %*d %*d %*d %llu",
+ &startTime);
+
+ if (rv != 1 || !startTime) {
+ return 0;
+ }
+
+ return startTime;
+}
+
+// Computes the interval that has elapsed between the thread creation and the
+// process creation by comparing the starttime fields in the respective
+// /proc/*/stat files. The resulting value will be a good approximation of the
+// process uptime. This value will be stored at the address pointed by aTime;
+// if an error occurred 0 will be stored instead.
+
+static void* ComputeProcessUptimeThread(void* aTime) {
+ uint64_t* uptime = static_cast<uint64_t*>(aTime);
+ long hz = sysconf(_SC_CLK_TCK);
+
+ *uptime = 0;
+
+ if (!hz) {
+ return nullptr;
+ }
+
+ char threadStat[40];
+ SprintfLiteral(threadStat, "/proc/self/task/%d/stat",
+ (pid_t)syscall(__NR_gettid));
+
+ uint64_t threadJiffies = JiffiesSinceBoot(threadStat);
+ uint64_t selfJiffies = JiffiesSinceBoot("/proc/self/stat");
+
+ if (!threadJiffies || !selfJiffies) {
+ return nullptr;
+ }
+
+ *uptime = ((threadJiffies - selfJiffies) * kNsPerSec) / hz;
+ return nullptr;
+}
+
+// Computes and returns the process uptime in us on Linux & its derivatives.
+// Returns 0 if an error was encountered.
+
+uint64_t TimeStamp::ComputeProcessUptime() {
+ uint64_t uptime = 0;
+ pthread_t uptime_pthread;
+
+ if (pthread_create(&uptime_pthread, nullptr, ComputeProcessUptimeThread,
+ &uptime)) {
+ MOZ_CRASH("Failed to create process uptime thread.");
+ return 0;
+ }
+
+ pthread_join(uptime_pthread, NULL);
+
+ return uptime / kNsPerUs;
+}
+
+#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__)
+
+// Computes and returns the process uptime in us on various BSD flavors.
+// Returns 0 if an error was encountered.
+
+uint64_t TimeStamp::ComputeProcessUptime() {
+ struct timespec ts;
+ int rv = clock_gettime(CLOCK_REALTIME, &ts);
+
+ if (rv == -1) {
+ return 0;
+ }
+
+ int mib[] = {
+ CTL_KERN,
+ KERN_PROC,
+ KERN_PROC_PID,
+ getpid(),
+# if defined(__NetBSD__) || defined(__OpenBSD__)
+ sizeof(KINFO_PROC),
+ 1,
+# endif
+ };
+ u_int mibLen = sizeof(mib) / sizeof(mib[0]);
+
+ KINFO_PROC proc;
+ size_t bufferSize = sizeof(proc);
+ rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0);
+
+ if (rv == -1) {
+ return 0;
+ }
+
+ uint64_t startTime = ((uint64_t)proc.KP_START_SEC * kNsPerSec) +
+ (proc.KP_START_USEC * kNsPerUs);
+ uint64_t now = ((uint64_t)ts.tv_sec * kNsPerSec) + ts.tv_nsec;
+
+ if (startTime > now) {
+ return 0;
+ }
+
+ return (now - startTime) / kNsPerUs;
+}
+
+#else
+
+uint64_t TimeStamp::ComputeProcessUptime() { return 0; }
+
+#endif
+
+} // namespace mozilla
diff --git a/mozglue/misc/TimeStamp_windows.cpp b/mozglue/misc/TimeStamp_windows.cpp
new file mode 100644
index 0000000000..81da34409d
--- /dev/null
+++ b/mozglue/misc/TimeStamp_windows.cpp
@@ -0,0 +1,577 @@
+/* -*- 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/. */
+
+// Implement TimeStamp::Now() with QueryPerformanceCounter() controlled with
+// values of GetTickCount64().
+
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Uptime.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <intrin.h>
+#include <windows.h>
+
+// To enable logging define to your favorite logging API
+#define LOG(x)
+
+class AutoCriticalSection {
+ public:
+ explicit AutoCriticalSection(LPCRITICAL_SECTION aSection)
+ : mSection(aSection) {
+ ::EnterCriticalSection(mSection);
+ }
+ ~AutoCriticalSection() { ::LeaveCriticalSection(mSection); }
+
+ private:
+ LPCRITICAL_SECTION mSection;
+};
+
+// Estimate of the smallest duration of time we can measure.
+static volatile ULONGLONG sResolution;
+static volatile ULONGLONG sResolutionSigDigs;
+static const double kNsPerSecd = 1000000000.0;
+static const LONGLONG kNsPerMillisec = 1000000;
+
+// ----------------------------------------------------------------------------
+// Global constants
+// ----------------------------------------------------------------------------
+
+// Tolerance to failures settings.
+//
+// What is the interval we want to have failure free.
+// in [ms]
+static const uint32_t kFailureFreeInterval = 5000;
+// How many failures we are willing to tolerate in the interval.
+static const uint32_t kMaxFailuresPerInterval = 4;
+// What is the threshold to treat fluctuations as actual failures.
+// in [ms]
+static const uint32_t kFailureThreshold = 50;
+
+// If we are not able to get the value of GTC time increment, use this value
+// which is the most usual increment.
+static const DWORD kDefaultTimeIncrement = 156001;
+
+// ----------------------------------------------------------------------------
+// Global variables, not changing at runtime
+// ----------------------------------------------------------------------------
+
+// Result of QueryPerformanceFrequency
+// We use default of 1 for the case we can't use QueryPerformanceCounter
+// to make mt/ms conversions work despite that.
+static uint64_t sFrequencyPerSec = 1;
+
+namespace mozilla {
+
+MFBT_API uint64_t GetQueryPerformanceFrequencyPerSec() {
+ return sFrequencyPerSec;
+}
+
+} // namespace mozilla
+
+// How much we are tolerant to GTC occasional loose of resoltion.
+// This number says how many multiples of the minimal GTC resolution
+// detected on the system are acceptable. This number is empirical.
+static const LONGLONG kGTCTickLeapTolerance = 4;
+
+// Base tolerance (more: "inability of detection" range) threshold is calculated
+// dynamically, and kept in sGTCResolutionThreshold.
+//
+// Schematically, QPC worked "100%" correctly if ((GTC_now - GTC_epoch) -
+// (QPC_now - QPC_epoch)) was in [-sGTCResolutionThreshold,
+// sGTCResolutionThreshold] interval every time we'd compared two time stamps.
+// If not, then we check the overflow behind this basic threshold
+// is in kFailureThreshold. If not, we condider it as a QPC failure. If too
+// many failures in short time are detected, QPC is considered faulty and
+// disabled.
+//
+// Kept in [mt]
+static LONGLONG sGTCResolutionThreshold;
+
+// If QPC is found faulty for two stamps in this interval, we engage
+// the fault detection algorithm. For duration larger then this limit
+// we bypass using durations calculated from QPC when jitter is detected,
+// but don't touch the sUseQPC flag.
+//
+// Value is in [ms].
+static const uint32_t kHardFailureLimit = 2000;
+// Conversion to [mt]
+static LONGLONG sHardFailureLimit;
+
+// Conversion of kFailureFreeInterval and kFailureThreshold to [mt]
+static LONGLONG sFailureFreeInterval;
+static LONGLONG sFailureThreshold;
+
+// ----------------------------------------------------------------------------
+// Systemm status flags
+// ----------------------------------------------------------------------------
+
+// Flag for stable TSC that indicates platform where QPC is stable.
+static bool sHasStableTSC = false;
+
+// ----------------------------------------------------------------------------
+// Global state variables, changing at runtime
+// ----------------------------------------------------------------------------
+
+// Initially true, set to false when QPC is found unstable and never
+// returns back to true since that time.
+static bool volatile sUseQPC = true;
+
+// ----------------------------------------------------------------------------
+// Global lock
+// ----------------------------------------------------------------------------
+
+// Thread spin count before entering the full wait state for sTimeStampLock.
+// Inspired by Rob Arnold's work on PRMJ_Now().
+static const DWORD kLockSpinCount = 4096;
+
+// Common mutex (thanks the relative complexity of the logic, this is better
+// then using CMPXCHG8B.)
+// It is protecting the globals bellow.
+static CRITICAL_SECTION sTimeStampLock;
+
+// ----------------------------------------------------------------------------
+// Global lock protected variables
+// ----------------------------------------------------------------------------
+
+// Timestamp in future until QPC must behave correctly.
+// Set to now + kFailureFreeInterval on first QPC failure detection.
+// Set to now + E * kFailureFreeInterval on following errors,
+// where E is number of errors detected during last kFailureFreeInterval
+// milliseconds, calculated simply as:
+// E = (sFaultIntoleranceCheckpoint - now) / kFailureFreeInterval + 1.
+// When E > kMaxFailuresPerInterval -> disable QPC.
+//
+// Kept in [mt]
+static ULONGLONG sFaultIntoleranceCheckpoint = 0;
+
+namespace mozilla {
+
+// Result is in [mt]
+static inline ULONGLONG PerformanceCounter() {
+ LARGE_INTEGER pc;
+ ::QueryPerformanceCounter(&pc);
+
+ // QueryPerformanceCounter may slightly jitter (not be 100% monotonic.)
+ // This is a simple go-backward protection for such a faulty hardware.
+ AutoCriticalSection lock(&sTimeStampLock);
+
+ static decltype(LARGE_INTEGER::QuadPart) last;
+ if (last > pc.QuadPart) {
+ return last * 1000ULL;
+ }
+ last = pc.QuadPart;
+ return pc.QuadPart * 1000ULL;
+}
+
+static void InitThresholds() {
+ DWORD timeAdjustment = 0, timeIncrement = 0;
+ BOOL timeAdjustmentDisabled;
+ GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement,
+ &timeAdjustmentDisabled);
+
+ LOG(("TimeStamp: timeIncrement=%d [100ns]", timeIncrement));
+
+ if (!timeIncrement) {
+ timeIncrement = kDefaultTimeIncrement;
+ }
+
+ // Ceiling to a millisecond
+ // Example values: 156001, 210000
+ DWORD timeIncrementCeil = timeIncrement;
+ // Don't want to round up if already rounded, values will be: 156000, 209999
+ timeIncrementCeil -= 1;
+ // Convert to ms, values will be: 15, 20
+ timeIncrementCeil /= 10000;
+ // Round up, values will be: 16, 21
+ timeIncrementCeil += 1;
+ // Convert back to 100ns, values will be: 160000, 210000
+ timeIncrementCeil *= 10000;
+
+ // How many milli-ticks has the interval rounded up
+ LONGLONG ticksPerGetTickCountResolutionCeiling =
+ (int64_t(timeIncrementCeil) * sFrequencyPerSec) / 10000LL;
+
+ // GTC may jump by 32 (2*16) ms in two steps, therefor use the ceiling value.
+ sGTCResolutionThreshold =
+ LONGLONG(kGTCTickLeapTolerance * ticksPerGetTickCountResolutionCeiling);
+
+ sHardFailureLimit = ms2mt(kHardFailureLimit);
+ sFailureFreeInterval = ms2mt(kFailureFreeInterval);
+ sFailureThreshold = ms2mt(kFailureThreshold);
+}
+
+static void InitResolution() {
+ // 10 total trials is arbitrary: what we're trying to avoid by
+ // looping is getting unlucky and being interrupted by a context
+ // switch or signal, or being bitten by paging/cache effects
+
+ ULONGLONG minres = ~0ULL;
+ if (sUseQPC) {
+ int loops = 10;
+ do {
+ ULONGLONG start = PerformanceCounter();
+ ULONGLONG end = PerformanceCounter();
+
+ ULONGLONG candidate = (end - start);
+ if (candidate < minres) {
+ minres = candidate;
+ }
+ } while (--loops && minres);
+
+ if (0 == minres) {
+ minres = 1;
+ }
+ } else {
+ // GetTickCount has only ~16ms known resolution
+ minres = ms2mt(16);
+ }
+
+ // Converting minres that is in [mt] to nanosecods, multiplicating
+ // the argument to preserve resolution.
+ ULONGLONG result = mt2ms(minres * kNsPerMillisec);
+ if (0 == result) {
+ result = 1;
+ }
+
+ sResolution = result;
+
+ // find the number of significant digits in mResolution, for the
+ // sake of ToSecondsSigDigits()
+ ULONGLONG sigDigs;
+ for (sigDigs = 1; !(sigDigs == result || 10 * sigDigs > result);
+ sigDigs *= 10)
+ ;
+
+ sResolutionSigDigs = sigDigs;
+}
+
+// ----------------------------------------------------------------------------
+// TimeStampValue implementation
+// ----------------------------------------------------------------------------
+MFBT_API TimeStampValue& TimeStampValue::operator+=(const int64_t aOther) {
+ mGTC += aOther;
+ mQPC += aOther;
+ return *this;
+}
+
+MFBT_API TimeStampValue& TimeStampValue::operator-=(const int64_t aOther) {
+ mGTC -= aOther;
+ mQPC -= aOther;
+ return *this;
+}
+
+// If the duration is less then two seconds, perform check of QPC stability
+// by comparing both GTC and QPC calculated durations of this and aOther.
+MFBT_API uint64_t TimeStampValue::CheckQPC(const TimeStampValue& aOther) const {
+ uint64_t deltaGTC = mGTC - aOther.mGTC;
+
+ if (!mHasQPC || !aOther.mHasQPC) { // Both not holding QPC
+ return deltaGTC;
+ }
+
+ uint64_t deltaQPC = mQPC - aOther.mQPC;
+
+ if (sHasStableTSC) { // For stable TSC there is no need to check
+ return deltaQPC;
+ }
+
+ // Check QPC is sane before using it.
+ int64_t diff = DeprecatedAbs(int64_t(deltaQPC) - int64_t(deltaGTC));
+ if (diff <= sGTCResolutionThreshold) {
+ return deltaQPC;
+ }
+
+ // Treat absolutely for calibration purposes
+ int64_t duration = DeprecatedAbs(int64_t(deltaGTC));
+ int64_t overflow = diff - sGTCResolutionThreshold;
+
+ LOG(("TimeStamp: QPC check after %llums with overflow %1.4fms",
+ mt2ms(duration), mt2ms_f(overflow)));
+
+ if (overflow <= sFailureThreshold) { // We are in the limit, let go.
+ return deltaQPC;
+ }
+
+ // QPC deviates, don't use it, since now this method may only return deltaGTC.
+
+ if (!sUseQPC) { // QPC already disabled, no need to run the fault tolerance
+ // algorithm.
+ return deltaGTC;
+ }
+
+ LOG(("TimeStamp: QPC jittered over failure threshold"));
+
+ if (duration < sHardFailureLimit) {
+ // Interval between the two time stamps is very short, consider
+ // QPC as unstable and record a failure.
+ uint64_t now = ms2mt(GetTickCount64());
+
+ AutoCriticalSection lock(&sTimeStampLock);
+
+ if (sFaultIntoleranceCheckpoint && sFaultIntoleranceCheckpoint > now) {
+ // There's already been an error in the last fault intollerant interval.
+ // Time since now to the checkpoint actually holds information on how many
+ // failures there were in the failure free interval we have defined.
+ uint64_t failureCount =
+ (sFaultIntoleranceCheckpoint - now + sFailureFreeInterval - 1) /
+ sFailureFreeInterval;
+ if (failureCount > kMaxFailuresPerInterval) {
+ sUseQPC = false;
+ LOG(("TimeStamp: QPC disabled"));
+ } else {
+ // Move the fault intolerance checkpoint more to the future, prolong it
+ // to reflect the number of detected failures.
+ ++failureCount;
+ sFaultIntoleranceCheckpoint = now + failureCount * sFailureFreeInterval;
+ LOG(("TimeStamp: recording %dth QPC failure", failureCount));
+ }
+ } else {
+ // Setup fault intolerance checkpoint in the future for first detected
+ // error.
+ sFaultIntoleranceCheckpoint = now + sFailureFreeInterval;
+ LOG(("TimeStamp: recording 1st QPC failure"));
+ }
+ }
+
+ return deltaGTC;
+}
+
+MFBT_API uint64_t
+TimeStampValue::operator-(const TimeStampValue& aOther) const {
+ if (IsNull() && aOther.IsNull()) {
+ return uint64_t(0);
+ }
+
+ return CheckQPC(aOther);
+}
+
+class TimeStampValueTests {
+ // Check that nullity is set/not set correctly.
+ static_assert(TimeStampValue{0}.IsNull());
+ static_assert(!TimeStampValue{1}.IsNull());
+
+ // Check that we ignore GTC when both TimeStampValues have QPC. (In each of
+ // these tests, looking at GTC would give a different result.)
+ static_assert(TimeStampValue{1, 2, true} < TimeStampValue{1, 3, true});
+ static_assert(!(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, true}));
+
+ static_assert(TimeStampValue{2, 2, true} < TimeStampValue{1, 3, true});
+ static_assert(TimeStampValue{2, 2, true} <= TimeStampValue{1, 3, true});
+ static_assert(!(TimeStampValue{2, 2, true} > TimeStampValue{1, 3, true}));
+
+ static_assert(TimeStampValue{1, 3, true} > TimeStampValue{1, 2, true});
+ static_assert(!(TimeStampValue{1, 3, true} == TimeStampValue{1, 2, true}));
+
+ static_assert(TimeStampValue{1, 3, true} > TimeStampValue{2, 2, true});
+ static_assert(TimeStampValue{1, 3, true} >= TimeStampValue{2, 2, true});
+ static_assert(!(TimeStampValue{1, 3, true} < TimeStampValue{2, 2, true}));
+
+ static_assert(TimeStampValue{1, 3, true} == TimeStampValue{2, 3, true});
+ static_assert(!(TimeStampValue{1, 3, true} < TimeStampValue{2, 3, true}));
+
+ static_assert(TimeStampValue{1, 2, true} != TimeStampValue{1, 3, true});
+ static_assert(!(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, true}));
+
+ // Check that, if either TimeStampValue doesn't have QPC, we only look at the
+ // GTC values. These are the same cases as above, except that we accept the
+ // opposite results because we turn off QPC on one or both of the
+ // TimeStampValue's.
+ static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, true});
+ static_assert(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, false});
+ static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, false});
+
+ static_assert(TimeStampValue{2, 2, false} > TimeStampValue{1, 3, true});
+ static_assert(TimeStampValue{2, 2, true} > TimeStampValue{1, 3, false});
+ static_assert(TimeStampValue{2, 2, false} > TimeStampValue{1, 3, false});
+
+ static_assert(TimeStampValue{1, 3, false} == TimeStampValue{1, 2, true});
+ static_assert(TimeStampValue{1, 3, true} == TimeStampValue{1, 2, false});
+ static_assert(TimeStampValue{1, 3, false} == TimeStampValue{1, 2, false});
+
+ static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 2, true});
+ static_assert(TimeStampValue{1, 3, true} < TimeStampValue{2, 2, false});
+ static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 2, false});
+
+ static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 3, true});
+ static_assert(TimeStampValue{1, 3, true} < TimeStampValue{2, 3, false});
+ static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 3, false});
+
+ static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, true});
+ static_assert(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, false});
+ static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, false});
+};
+
+// ----------------------------------------------------------------------------
+// TimeDuration and TimeStamp implementation
+// ----------------------------------------------------------------------------
+
+MFBT_API double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) {
+ // Converting before arithmetic avoids blocked store forward
+ return double(aTicks) / (double(sFrequencyPerSec) * 1000.0);
+}
+
+MFBT_API double BaseTimeDurationPlatformUtils::ToSecondsSigDigits(
+ int64_t aTicks) {
+ // don't report a value < mResolution ...
+ LONGLONG resolution = sResolution;
+ LONGLONG resolutionSigDigs = sResolutionSigDigs;
+ LONGLONG valueSigDigs = resolution * (aTicks / resolution);
+ // and chop off insignificant digits
+ valueSigDigs = resolutionSigDigs * (valueSigDigs / resolutionSigDigs);
+ return double(valueSigDigs) / kNsPerSecd;
+}
+
+MFBT_API int64_t
+BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) {
+ double result = ms2mt(aMilliseconds);
+ if (result > double(INT64_MAX)) {
+ return INT64_MAX;
+ } else if (result < double(INT64_MIN)) {
+ return INT64_MIN;
+ }
+
+ return result;
+}
+
+MFBT_API int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() {
+ return static_cast<int64_t>(sResolution);
+}
+
+static bool HasStableTSC() {
+#if defined(_M_ARM64)
+ // AArch64 defines that its system counter run at a constant rate
+ // regardless of the current clock frequency of the system. See "The
+ // Generic Timer", section D7, in the ARMARM for ARMv8.
+ return true;
+#else
+ union {
+ int regs[4];
+ struct {
+ int nIds;
+ char cpuString[12];
+ };
+ } cpuInfo;
+
+ __cpuid(cpuInfo.regs, 0);
+ // Only allow Intel or AMD CPUs for now.
+ // The order of the registers is reg[1], reg[3], reg[2]. We just adjust the
+ // string so that we can compare in one go.
+ if (_strnicmp(cpuInfo.cpuString, "GenuntelineI", sizeof(cpuInfo.cpuString)) &&
+ _strnicmp(cpuInfo.cpuString, "AuthcAMDenti", sizeof(cpuInfo.cpuString))) {
+ return false;
+ }
+
+ int regs[4];
+
+ // detect if the Advanced Power Management feature is supported
+ __cpuid(regs, 0x80000000);
+ if ((unsigned int)regs[0] < 0x80000007) {
+ // XXX should we return true here? If there is no APM there may be
+ // no way how TSC can run out of sync among cores.
+ return false;
+ }
+
+ __cpuid(regs, 0x80000007);
+ // if bit 8 is set than TSC will run at a constant rate
+ // in all ACPI P-states, C-states and T-states
+ return regs[3] & (1 << 8);
+#endif
+}
+
+static bool gInitialized = false;
+
+MFBT_API void TimeStamp::Startup() {
+ if (gInitialized) {
+ return;
+ }
+
+ gInitialized = true;
+
+ // Decide which implementation to use for the high-performance timer.
+
+ InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount);
+
+ bool forceGTC = false;
+ bool forceQPC = false;
+
+ char* modevar = getenv("MOZ_TIMESTAMP_MODE");
+ if (modevar) {
+ if (!strcmp(modevar, "QPC")) {
+ forceQPC = true;
+ } else if (!strcmp(modevar, "GTC")) {
+ forceGTC = true;
+ }
+ }
+
+ LARGE_INTEGER freq;
+ sUseQPC = !forceGTC && ::QueryPerformanceFrequency(&freq);
+ if (!sUseQPC) {
+ // No Performance Counter. Fall back to use GetTickCount64.
+ InitResolution();
+
+ LOG(("TimeStamp: using GetTickCount64"));
+ return;
+ }
+
+ sHasStableTSC = forceQPC || HasStableTSC();
+ LOG(("TimeStamp: HasStableTSC=%d", sHasStableTSC));
+
+ sFrequencyPerSec = freq.QuadPart;
+ LOG(("TimeStamp: QPC frequency=%llu", sFrequencyPerSec));
+
+ InitThresholds();
+ InitResolution();
+
+ return;
+}
+
+MFBT_API void TimeStamp::Shutdown() { DeleteCriticalSection(&sTimeStampLock); }
+
+TimeStampValue NowInternal(bool aHighResolution) {
+ // sUseQPC is volatile
+ bool useQPC = (aHighResolution && sUseQPC);
+
+ // Both values are in [mt] units.
+ ULONGLONG QPC = useQPC ? PerformanceCounter() : uint64_t(0);
+ ULONGLONG GTC = ms2mt(GetTickCount64());
+ return TimeStampValue(GTC, QPC, useQPC);
+}
+
+MFBT_API TimeStamp TimeStamp::Now(bool aHighResolution) {
+ return TimeStamp(NowInternal(aHighResolution));
+}
+
+// Computes and returns the process uptime in microseconds.
+// Returns 0 if an error was encountered.
+
+MFBT_API uint64_t TimeStamp::ComputeProcessUptime() {
+ FILETIME start, foo, bar, baz;
+ bool success = GetProcessTimes(GetCurrentProcess(), &start, &foo, &bar, &baz);
+ if (!success) {
+ return 0;
+ }
+
+ static const StaticDynamicallyLinkedFunctionPtr<void(WINAPI*)(LPFILETIME)>
+ pGetSystemTimePreciseAsFileTime(L"kernel32.dll",
+ "GetSystemTimePreciseAsFileTime");
+
+ FILETIME now;
+ if (pGetSystemTimePreciseAsFileTime) {
+ pGetSystemTimePreciseAsFileTime(&now);
+ } else {
+ GetSystemTimeAsFileTime(&now);
+ }
+
+ ULARGE_INTEGER startUsec = {{start.dwLowDateTime, start.dwHighDateTime}};
+ ULARGE_INTEGER nowUsec = {{now.dwLowDateTime, now.dwHighDateTime}};
+
+ return (nowUsec.QuadPart - startUsec.QuadPart) / 10ULL;
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/TimeStamp_windows.h b/mozglue/misc/TimeStamp_windows.h
new file mode 100644
index 0000000000..97264bf769
--- /dev/null
+++ b/mozglue/misc/TimeStamp_windows.h
@@ -0,0 +1,113 @@
+/* -*- 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 mozilla_TimeStamp_windows_h
+#define mozilla_TimeStamp_windows_h
+
+#include "mozilla/Types.h"
+
+namespace mozilla {
+
+/**
+ * The [mt] unit:
+ *
+ * Many values are kept in ticks of the Performance Counter x 1000,
+ * further just referred as [mt], meaning milli-ticks.
+ *
+ * This is needed to preserve maximum precision of the performance frequency
+ * representation. GetTickCount64 values in milliseconds are multiplied with
+ * frequency per second. Therefore we need to multiply QPC value by 1000 to
+ * have the same units to allow simple arithmentic with both QPC and GTC.
+ */
+#define ms2mt(x) ((x) * mozilla::GetQueryPerformanceFrequencyPerSec())
+#define mt2ms(x) ((x) / mozilla::GetQueryPerformanceFrequencyPerSec())
+#define mt2ms_f(x) (double(x) / mozilla::GetQueryPerformanceFrequencyPerSec())
+
+MFBT_API uint64_t GetQueryPerformanceFrequencyPerSec();
+
+class TimeStamp;
+class TimeStampValue;
+class TimeStampValueTests;
+class TimeStampTests;
+
+TimeStampValue NowInternal(bool aHighResolution);
+
+class TimeStampValue {
+ friend TimeStampValue NowInternal(bool);
+ friend bool IsCanonicalTimeStamp(TimeStampValue);
+ friend struct IPC::ParamTraits<mozilla::TimeStampValue>;
+ friend class TimeStamp;
+ friend class TimeStampValueTests;
+ friend class TimeStampTests;
+
+ // Both QPC and GTC are kept in [mt] units.
+ uint64_t mGTC;
+ uint64_t mQPC;
+
+ bool mIsNull;
+ bool mHasQPC;
+
+ constexpr MFBT_API TimeStampValue(uint64_t aGTC, uint64_t aQPC, bool aHasQPC)
+ : mGTC(aGTC),
+ mQPC(aQPC),
+ mIsNull(aGTC == 0 && aQPC == 0),
+ mHasQPC(aHasQPC) {}
+
+ // This constructor should be explicit but it is replacing a constructor that
+ // was MOZ_IMPLICIT and there are many locations that are using the automatic
+ // conversion.
+ constexpr MOZ_IMPLICIT MFBT_API TimeStampValue(uint64_t aGTCAndQPC)
+ : TimeStampValue(aGTCAndQPC, aGTCAndQPC, true) {}
+
+ MFBT_API uint64_t CheckQPC(const TimeStampValue& aOther) const;
+
+ public:
+ MFBT_API uint64_t operator-(const TimeStampValue& aOther) const;
+
+ TimeStampValue operator+(const int64_t aOther) const {
+ return TimeStampValue(mGTC + aOther, mQPC + aOther, mHasQPC);
+ }
+ TimeStampValue operator-(const int64_t aOther) const {
+ return TimeStampValue(mGTC - aOther, mQPC - aOther, mHasQPC);
+ }
+ MFBT_API TimeStampValue& operator+=(const int64_t aOther);
+ MFBT_API TimeStampValue& operator-=(const int64_t aOther);
+
+ constexpr bool operator<(const TimeStampValue& aOther) const {
+ return mHasQPC && aOther.mHasQPC ? mQPC < aOther.mQPC : mGTC < aOther.mGTC;
+ }
+ constexpr bool operator>(const TimeStampValue& aOther) const {
+ return mHasQPC && aOther.mHasQPC ? mQPC > aOther.mQPC : mGTC > aOther.mGTC;
+ }
+ constexpr bool operator<=(const TimeStampValue& aOther) const {
+ return mHasQPC && aOther.mHasQPC ? mQPC <= aOther.mQPC
+ : mGTC <= aOther.mGTC;
+ }
+ constexpr bool operator>=(const TimeStampValue& aOther) const {
+ return mHasQPC && aOther.mHasQPC ? mQPC >= aOther.mQPC
+ : mGTC >= aOther.mGTC;
+ }
+ constexpr bool operator==(const TimeStampValue& aOther) const {
+ return mHasQPC && aOther.mHasQPC ? mQPC == aOther.mQPC
+ : mGTC == aOther.mGTC;
+ }
+ constexpr bool operator!=(const TimeStampValue& aOther) const {
+ return mHasQPC && aOther.mHasQPC ? mQPC != aOther.mQPC
+ : mGTC != aOther.mGTC;
+ }
+ constexpr bool IsNull() const { return mIsNull; }
+
+#if defined(DEBUG)
+ uint64_t GTC() const { return mGTC; }
+ uint64_t QPC() const { return mQPC; }
+
+ bool HasQPC() const { return mHasQPC; }
+#endif
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_TimeStamp_h */
diff --git a/mozglue/misc/Uptime.cpp b/mozglue/misc/Uptime.cpp
new file mode 100644
index 0000000000..924b154359
--- /dev/null
+++ b/mozglue/misc/Uptime.cpp
@@ -0,0 +1,155 @@
+/* -*- 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 "Uptime.h"
+
+#ifdef XP_WIN
+# include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#endif // XP_WIN
+
+#include <stdint.h>
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Assertions.h"
+
+using namespace mozilla;
+
+namespace {
+
+Maybe<uint64_t> NowIncludingSuspendMs();
+Maybe<uint64_t> NowExcludingSuspendMs();
+static Maybe<uint64_t> mStartExcludingSuspendMs;
+static Maybe<uint64_t> mStartIncludingSuspendMs;
+
+// Apple things
+#if defined(__APPLE__) && defined(__MACH__)
+# include <time.h>
+# include <sys/time.h>
+# include <sys/types.h>
+# include <mach/mach_time.h>
+
+const uint64_t kNSperMS = 1000000;
+
+Maybe<uint64_t> NowExcludingSuspendMs() {
+ return Some(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / kNSperMS);
+}
+
+Maybe<uint64_t> NowIncludingSuspendMs() {
+ return Some(clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / kNSperMS);
+}
+
+#elif defined(XP_WIN)
+
+// Number of hundreds of nanoseconds in a millisecond
+static constexpr uint64_t kHNSperMS = 10000;
+
+Maybe<uint64_t> NowExcludingSuspendMs() {
+ ULONGLONG interrupt_time;
+ if (!QueryUnbiasedInterruptTime(&interrupt_time)) {
+ return Nothing();
+ }
+ return Some(interrupt_time / kHNSperMS);
+}
+
+Maybe<uint64_t> NowIncludingSuspendMs() {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<void(WINAPI*)(
+ PULONGLONG)>
+ pQueryInterruptTime(L"KernelBase.dll", "QueryInterruptTime");
+ if (!pQueryInterruptTime) {
+ // On Windows, this does include the time the computer was suspended so it's
+ // an adequate fallback.
+ TimeStamp processCreation = TimeStamp::ProcessCreation();
+ TimeStamp now = TimeStamp::Now();
+ if (!processCreation.IsNull() && !now.IsNull()) {
+ return Some(uint64_t((now - processCreation).ToMilliseconds()));
+ } else {
+ return Nothing();
+ }
+ }
+ ULONGLONG interrupt_time;
+ pQueryInterruptTime(&interrupt_time);
+ return Some(interrupt_time / kHNSperMS);
+}
+
+#elif defined(XP_UNIX) // including BSDs and Android
+# include <time.h>
+
+// Number of nanoseconds in a millisecond.
+static constexpr uint64_t kNSperMS = 1000000;
+
+uint64_t TimespecToMilliseconds(struct timespec aTs) {
+ return aTs.tv_sec * 1000 + aTs.tv_nsec / kNSperMS;
+}
+
+Maybe<uint64_t> NowExcludingSuspendMs() {
+ struct timespec ts = {0};
+
+# ifdef XP_OPENBSD
+ if (clock_gettime(CLOCK_UPTIME, &ts)) {
+# else
+ if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
+# endif
+ return Nothing();
+ }
+ return Some(TimespecToMilliseconds(ts));
+}
+
+Maybe<uint64_t> NowIncludingSuspendMs() {
+# ifndef CLOCK_BOOTTIME
+ return Nothing();
+# else
+ struct timespec ts = {0};
+
+ if (clock_gettime(CLOCK_BOOTTIME, &ts)) {
+ return Nothing();
+ }
+ return Some(TimespecToMilliseconds(ts));
+# endif
+}
+
+#else // catch all
+
+Maybe<uint64_t> NowExcludingSuspendMs() { return Nothing(); }
+Maybe<uint64_t> NowIncludingSuspendMs() { return Nothing(); }
+
+#endif
+
+}; // anonymous namespace
+
+namespace mozilla {
+
+void InitializeUptime() {
+ MOZ_RELEASE_ASSERT(mStartIncludingSuspendMs.isNothing() &&
+ mStartExcludingSuspendMs.isNothing(),
+ "Must not be called more than once");
+ mStartIncludingSuspendMs = NowIncludingSuspendMs();
+ mStartExcludingSuspendMs = NowExcludingSuspendMs();
+}
+
+Maybe<uint64_t> ProcessUptimeMs() {
+ if (!mStartIncludingSuspendMs) {
+ return Nothing();
+ }
+ Maybe<uint64_t> maybeNow = NowIncludingSuspendMs();
+ if (!maybeNow) {
+ return Nothing();
+ }
+ return Some(maybeNow.value() - mStartIncludingSuspendMs.value());
+}
+
+Maybe<uint64_t> ProcessUptimeExcludingSuspendMs() {
+ if (!mStartExcludingSuspendMs) {
+ return Nothing();
+ }
+ Maybe<uint64_t> maybeNow = NowExcludingSuspendMs();
+ if (!maybeNow) {
+ return Nothing();
+ }
+ return Some(maybeNow.value() - mStartExcludingSuspendMs.value());
+}
+
+}; // namespace mozilla
diff --git a/mozglue/misc/Uptime.h b/mozglue/misc/Uptime.h
new file mode 100644
index 0000000000..4438e0d6d1
--- /dev/null
+++ b/mozglue/misc/Uptime.h
@@ -0,0 +1,26 @@
+/* -*- 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 mozilla_Uptime_h
+#define mozilla_Uptime_h
+
+#include <stdint.h>
+
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+
+// Called at the beginning of the process from TimeStamp::Startup.
+MFBT_API void InitializeUptime();
+// Returns the number of milliseconds the calling process has lived for.
+MFBT_API Maybe<uint64_t> ProcessUptimeMs();
+// Returns the number of milliseconds the calling process has lived for,
+// excluding the time period the system was suspended.
+MFBT_API Maybe<uint64_t> ProcessUptimeExcludingSuspendMs();
+
+}; // namespace mozilla
+
+#endif // mozilla_Uptime_h
diff --git a/mozglue/misc/WinUtils.h b/mozglue/misc/WinUtils.h
new file mode 100644
index 0000000000..2291a352a5
--- /dev/null
+++ b/mozglue/misc/WinUtils.h
@@ -0,0 +1,140 @@
+/* -*- 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 mozilla_glue_MozglueUtils_h
+#define mozilla_glue_MozglueUtils_h
+
+#include <windows.h>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace glue {
+
+#ifdef DEBUG
+
+class MOZ_STATIC_CLASS Win32SRWLock final {
+ public:
+ // Microsoft guarantees that '0' is never a valid thread id
+ // https://docs.microsoft.com/en-ca/windows/desktop/ProcThread/thread-handles-and-identifiers
+ static const DWORD kInvalidThreadId = 0;
+
+ constexpr Win32SRWLock()
+ : mExclusiveThreadId(kInvalidThreadId), mLock(SRWLOCK_INIT) {}
+
+ ~Win32SRWLock() { MOZ_ASSERT(mExclusiveThreadId == kInvalidThreadId); }
+
+ void LockShared() {
+ MOZ_ASSERT(
+ mExclusiveThreadId != GetCurrentThreadId(),
+ "Deadlock detected - A thread attempted to acquire a shared lock on "
+ "a SRWLOCK when it already owns the exclusive lock on it.");
+
+ ::AcquireSRWLockShared(&mLock);
+ }
+
+ void UnlockShared() { ::ReleaseSRWLockShared(&mLock); }
+
+ void LockExclusive() {
+ MOZ_ASSERT(
+ mExclusiveThreadId != GetCurrentThreadId(),
+ "Deadlock detected - A thread attempted to acquire an exclusive lock "
+ "on a SRWLOCK when it already owns the exclusive lock on it.");
+
+ ::AcquireSRWLockExclusive(&mLock);
+ mExclusiveThreadId = GetCurrentThreadId();
+ }
+
+ void UnlockExclusive() {
+ MOZ_ASSERT(mExclusiveThreadId == GetCurrentThreadId());
+
+ mExclusiveThreadId = kInvalidThreadId;
+ ::ReleaseSRWLockExclusive(&mLock);
+ }
+
+ Win32SRWLock(const Win32SRWLock&) = delete;
+ Win32SRWLock(Win32SRWLock&&) = delete;
+ Win32SRWLock& operator=(const Win32SRWLock&) = delete;
+ Win32SRWLock& operator=(Win32SRWLock&&) = delete;
+
+ private:
+ // "Relaxed" memory ordering is fine. Threads will see other thread IDs
+ // appear here in some non-deterministic ordering (or not at all) and simply
+ // ignore them.
+ //
+ // But a thread will only read its own ID if it previously wrote it, and a
+ // single thread doesn't need a memory barrier to read its own write.
+
+ Atomic<DWORD, Relaxed> mExclusiveThreadId;
+ SRWLOCK mLock;
+};
+
+#else // DEBUG
+
+class MOZ_STATIC_CLASS Win32SRWLock final {
+ public:
+ constexpr Win32SRWLock() : mLock(SRWLOCK_INIT) {}
+
+ void LockShared() { ::AcquireSRWLockShared(&mLock); }
+
+ void UnlockShared() { ::ReleaseSRWLockShared(&mLock); }
+
+ void LockExclusive() { ::AcquireSRWLockExclusive(&mLock); }
+
+ void UnlockExclusive() { ::ReleaseSRWLockExclusive(&mLock); }
+
+ ~Win32SRWLock() = default;
+
+ Win32SRWLock(const Win32SRWLock&) = delete;
+ Win32SRWLock(Win32SRWLock&&) = delete;
+ Win32SRWLock& operator=(const Win32SRWLock&) = delete;
+ Win32SRWLock& operator=(Win32SRWLock&&) = delete;
+
+ private:
+ SRWLOCK mLock;
+};
+
+#endif
+
+class MOZ_RAII AutoSharedLock final {
+ public:
+ explicit AutoSharedLock(Win32SRWLock& aLock) : mLock(aLock) {
+ mLock.LockShared();
+ }
+
+ ~AutoSharedLock() { mLock.UnlockShared(); }
+
+ AutoSharedLock(const AutoSharedLock&) = delete;
+ AutoSharedLock(AutoSharedLock&&) = delete;
+ AutoSharedLock& operator=(const AutoSharedLock&) = delete;
+ AutoSharedLock& operator=(AutoSharedLock&&) = delete;
+
+ private:
+ Win32SRWLock& mLock;
+};
+
+class MOZ_RAII AutoExclusiveLock final {
+ public:
+ explicit AutoExclusiveLock(Win32SRWLock& aLock) : mLock(aLock) {
+ mLock.LockExclusive();
+ }
+
+ ~AutoExclusiveLock() { mLock.UnlockExclusive(); }
+
+ AutoExclusiveLock(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock(AutoExclusiveLock&&) = delete;
+ AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete;
+
+ private:
+ Win32SRWLock& mLock;
+};
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_MozglueUtils_h
diff --git a/mozglue/misc/WindowsDllMain.cpp b/mozglue/misc/WindowsDllMain.cpp
new file mode 100644
index 0000000000..1baec678db
--- /dev/null
+++ b/mozglue/misc/WindowsDllMain.cpp
@@ -0,0 +1,20 @@
+/* -*- 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 <libloaderapi.h>
+
+BOOL WINAPI DllMain(HINSTANCE aInstDll, DWORD aReason, LPVOID) {
+ if (aReason == DLL_PROCESS_ATTACH) {
+ ::DisableThreadLibraryCalls(aInstDll);
+
+ // mozglue.dll imports RtlGenRandom from advapi32.dll as SystemFunction036,
+ // but the actual function is implemented in cryptbase.dll. To avoid
+ // loading a fake cryptbase.dll from the installation directory, we preload
+ // cryptbase.dll from the system directory.
+ ::LoadLibraryExW(L"cryptbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ }
+ return TRUE;
+}
diff --git a/mozglue/misc/WindowsDpiAwareness.h b/mozglue/misc/WindowsDpiAwareness.h
new file mode 100644
index 0000000000..589415da6d
--- /dev/null
+++ b/mozglue/misc/WindowsDpiAwareness.h
@@ -0,0 +1,36 @@
+/* -*- 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 WindowsDpiAwareness_h_
+#define WindowsDpiAwareness_h_
+
+#include <windows.h>
+
+#if !defined(DPI_AWARENESS_CONTEXT_DECLARED) && \
+ !defined(DPI_AWARENESS_CONTEXT_UNAWARE)
+
+DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
+
+typedef enum DPI_AWARENESS {
+ DPI_AWARENESS_INVALID = -1,
+ DPI_AWARENESS_UNAWARE = 0,
+ DPI_AWARENESS_SYSTEM_AWARE = 1,
+ DPI_AWARENESS_PER_MONITOR_AWARE = 2
+} DPI_AWARENESS;
+
+# define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
+# define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
+# define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
+
+# define DPI_AWARENESS_CONTEXT_DECLARED
+#endif // (DPI_AWARENESS_CONTEXT_DECLARED)
+
+typedef DPI_AWARENESS_CONTEXT(WINAPI* SetThreadDpiAwarenessContextProc)(
+ DPI_AWARENESS_CONTEXT);
+typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND);
+typedef int(WINAPI* GetSystemMetricsForDpiProc)(int, UINT);
+
+#endif
diff --git a/mozglue/misc/WindowsDpiInitialization.cpp b/mozglue/misc/WindowsDpiInitialization.cpp
new file mode 100644
index 0000000000..972d577651
--- /dev/null
+++ b/mozglue/misc/WindowsDpiInitialization.cpp
@@ -0,0 +1,67 @@
+/* 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 "mozilla/WindowsDpiInitialization.h"
+
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "mozilla/WindowsVersion.h"
+
+#include <shellscalingapi.h>
+#include <windows.h>
+
+namespace mozilla {
+
+typedef HRESULT(WINAPI* SetProcessDpiAwarenessType)(PROCESS_DPI_AWARENESS);
+typedef BOOL(WINAPI* SetProcessDpiAwarenessContextType)(DPI_AWARENESS_CONTEXT);
+
+WindowsDpiInitializationResult WindowsDpiInitialization() {
+ // DPI Awareness can't be used in a Win32k Lockdown process, so there's
+ // nothing to do
+ if (IsWin32kLockedDown()) {
+ return WindowsDpiInitializationResult::Success;
+ }
+
+ // From MSDN:
+ // SetProcessDpiAwarenessContext() was added in the Win10 Anniversary Update
+ // SetProcessDpiAwareness() was added in Windows 8.1
+ // SetProcessDpiAware() was added in Windows Vista
+ //
+ // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 wasn't added later until
+ // the Creators Update, so if it fails we just fall back to
+ // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE
+ if (IsWin10AnniversaryUpdateOrLater()) {
+ DynamicallyLinkedFunctionPtr<SetProcessDpiAwarenessContextType>
+ setProcessDpiAwarenessContext(L"user32.dll",
+ "SetProcessDpiAwarenessContext");
+ if (!setProcessDpiAwarenessContext) {
+ return WindowsDpiInitializationResult::
+ FindSetProcessDpiAwarenessContextFailed;
+ }
+
+ if (!setProcessDpiAwarenessContext(
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) &&
+ !setProcessDpiAwarenessContext(
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
+ return WindowsDpiInitializationResult::
+ SetProcessDpiAwarenessContextFailed;
+ }
+
+ return WindowsDpiInitializationResult::Success;
+ } else {
+ DynamicallyLinkedFunctionPtr<SetProcessDpiAwarenessType>
+ setProcessDpiAwareness(L"Shcore.dll", "SetProcessDpiAwareness");
+ if (!setProcessDpiAwareness) {
+ return WindowsDpiInitializationResult::FindSetProcessDpiAwarenessFailed;
+ }
+
+ if (FAILED(setProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE))) {
+ return WindowsDpiInitializationResult::SetProcessDpiAwarenessFailed;
+ }
+
+ return WindowsDpiInitializationResult::Success;
+ }
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/WindowsDpiInitialization.h b/mozglue/misc/WindowsDpiInitialization.h
new file mode 100644
index 0000000000..7379bd53e1
--- /dev/null
+++ b/mozglue/misc/WindowsDpiInitialization.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; 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 MOZILLA_MOZGLUE_MISC_WINDOWSDPIINITIALIZATION_H_
+#define MOZILLA_MOZGLUE_MISC_WINDOWSDPIINITIALIZATION_H_
+#include "mozilla/Types.h"
+
+namespace mozilla {
+
+// The result codes that may be returned from WindowsDpiInitialization()
+enum class WindowsDpiInitializationResult : uint32_t {
+ Success,
+ FindSetProcessDpiAwarenessContextFailed,
+ SetProcessDpiAwarenessContextFailed,
+ FindSetProcessDpiAwarenessFailed,
+ SetProcessDpiAwarenessFailed,
+};
+
+// Get a string representation of any WindowsDpiInitializationResult value
+inline const char* WindowsDpiInitializationResultString(
+ WindowsDpiInitializationResult result) {
+ switch (result) {
+ case WindowsDpiInitializationResult::Success:
+ return "Success";
+ case WindowsDpiInitializationResult::
+ FindSetProcessDpiAwarenessContextFailed:
+ return "Failed to find SetProcessDpiAwarenessContext";
+ case WindowsDpiInitializationResult::SetProcessDpiAwarenessContextFailed:
+ return "SetProcessDpiAwarenessContext failed";
+ case WindowsDpiInitializationResult::FindSetProcessDpiAwarenessFailed:
+ return "Failed to find SetProcessDpiAwareness";
+ case WindowsDpiInitializationResult::SetProcessDpiAwarenessFailed:
+ return "SetProcessDpiAwareness failed";
+ default:
+ return "Unknown result";
+ }
+}
+
+// Initialize DPI awareness to the best available for the current OS
+// According to MSDN, this will be:
+// Per-Monitor V2 for Windows 10 Creators Update (1703) and later
+// Per-Monitor V1 for Windows 8.1 and later
+// System DPI for Vista and later (we don't support anything older)
+// https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
+MFBT_API WindowsDpiInitializationResult WindowsDpiInitialization();
+
+} // namespace mozilla
+
+#endif // MOZILLA_MOZGLUE_MISC_WINDOWSDPIINITIALIZATION_H_
diff --git a/mozglue/misc/WindowsEnumProcessModules.h b/mozglue/misc/WindowsEnumProcessModules.h
new file mode 100644
index 0000000000..573b0dbdfa
--- /dev/null
+++ b/mozglue/misc/WindowsEnumProcessModules.h
@@ -0,0 +1,61 @@
+/* -*- 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 mozilla_WindowsEnumProcessModules_h
+#define mozilla_WindowsEnumProcessModules_h
+
+#include <windows.h>
+#include <psapi.h>
+
+#include "mozilla/FunctionRef.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+namespace mozilla {
+
+// Why don't we use CreateToolhelp32Snapshot instead of EnumProcessModules?
+// CreateToolhelp32Snapshot gets the ANSI versions of module path strings
+// via ntdll!RtlQueryProcessDebugInformation and stores them into a snapshot.
+// Module32FirstW/Module32NextW re-converts ANSI into Unicode, but it cannot
+// restore lost information. This means we still need GetModuleFileNameEx
+// even when we use CreateToolhelp32Snapshot, but EnumProcessModules is faster.
+inline bool EnumerateProcessModules(
+ const FunctionRef<void(const wchar_t*, HMODULE)>& aCallback) {
+ DWORD modulesSize;
+ if (!::EnumProcessModules(nt::kCurrentProcess, nullptr, 0, &modulesSize)) {
+ return false;
+ }
+
+ DWORD modulesNum = modulesSize / sizeof(HMODULE);
+ UniquePtr<HMODULE[]> modules = MakeUnique<HMODULE[]>(modulesNum);
+ if (!::EnumProcessModules(nt::kCurrentProcess, modules.get(),
+ modulesNum * sizeof(HMODULE), &modulesSize)) {
+ return false;
+ }
+
+ // The list may have shrunk between calls
+ if (modulesSize / sizeof(HMODULE) < modulesNum) {
+ modulesNum = modulesSize / sizeof(HMODULE);
+ }
+
+ for (DWORD i = 0; i < modulesNum; ++i) {
+ UniquePtr<wchar_t[]> modulePath = GetFullModulePath(modules[i]);
+ if (!modulePath) {
+ continue;
+ }
+
+ // Please note that modules[i] could be invalid if the module
+ // was unloaded after GetFullModulePath succeeded.
+ aCallback(modulePath.get(), modules[i]);
+ }
+
+ return true;
+}
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsEnumProcessModules_h
diff --git a/mozglue/misc/WindowsMapRemoteView.cpp b/mozglue/misc/WindowsMapRemoteView.cpp
new file mode 100644
index 0000000000..aca9979ab6
--- /dev/null
+++ b/mozglue/misc/WindowsMapRemoteView.cpp
@@ -0,0 +1,124 @@
+/* -*- 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 "mozilla/WindowsMapRemoteView.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+
+#include <winternl.h>
+
+#if (NTDDI_VERSION < NTDDI_WIN10_RS2)
+
+// MapViewOfFile2 is just an inline function that calls MapViewOfFileNuma2 with
+// its preferred node set to NUMA_NO_PREFERRED_NODE
+WINBASEAPI PVOID WINAPI MapViewOfFileNuma2(HANDLE aFileMapping, HANDLE aProcess,
+ ULONG64 aOffset, PVOID aBaseAddress,
+ SIZE_T aViewSize,
+ ULONG aAllocationType,
+ ULONG aPageProtection,
+ ULONG aPreferredNode);
+
+WINBASEAPI BOOL WINAPI UnmapViewOfFile2(HANDLE aProcess, PVOID aBaseAddress,
+ ULONG aUnmapFlags);
+
+#endif // (NTDDI_VERSION < NTDDI_WIN10_RS2)
+
+enum SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 };
+
+NTSTATUS NTAPI NtMapViewOfSection(
+ HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits,
+ SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize,
+ SECTION_INHERIT aInheritDisposition, ULONG aAllocationType,
+ ULONG aProtectionFlags);
+
+NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE aProcess, PVOID aBaseAddress);
+
+static DWORD GetWin32ErrorCode(NTSTATUS aNtStatus) {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&RtlNtStatusToDosError)>
+ pRtlNtStatusToDosError(L"ntdll.dll", "RtlNtStatusToDosError");
+
+ MOZ_ASSERT(!!pRtlNtStatusToDosError);
+ if (!pRtlNtStatusToDosError) {
+ return ERROR_GEN_FAILURE;
+ }
+
+ return pRtlNtStatusToDosError(aNtStatus);
+}
+
+namespace mozilla {
+
+MFBT_API void* MapRemoteViewOfFile(HANDLE aFileMapping, HANDLE aProcess,
+ ULONG64 aOffset, PVOID aBaseAddress,
+ SIZE_T aViewSize, ULONG aAllocationType,
+ ULONG aProtectionFlags) {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&MapViewOfFileNuma2)>
+ pMapViewOfFileNuma2(L"Api-ms-win-core-memory-l1-1-5.dll",
+ "MapViewOfFileNuma2");
+
+ if (!!pMapViewOfFileNuma2) {
+ return pMapViewOfFileNuma2(aFileMapping, aProcess, aOffset, aBaseAddress,
+ aViewSize, aAllocationType, aProtectionFlags,
+ NUMA_NO_PREFERRED_NODE);
+ }
+
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&NtMapViewOfSection)>
+ pNtMapViewOfSection(L"ntdll.dll", "NtMapViewOfSection");
+
+ MOZ_ASSERT(!!pNtMapViewOfSection);
+ if (!pNtMapViewOfSection) {
+ return nullptr;
+ }
+
+ // For the sake of consistency, we only permit the same flags that
+ // MapViewOfFileNuma2 allows
+ if (aAllocationType != 0 && aAllocationType != MEM_RESERVE &&
+ aAllocationType != MEM_LARGE_PAGES) {
+ ::SetLastError(ERROR_INVALID_PARAMETER);
+ return nullptr;
+ }
+
+ NTSTATUS ntStatus;
+
+ LARGE_INTEGER offset;
+ offset.QuadPart = aOffset;
+
+ ntStatus = pNtMapViewOfSection(aFileMapping, aProcess, &aBaseAddress, 0, 0,
+ &offset, &aViewSize, ViewUnmap,
+ aAllocationType, aProtectionFlags);
+ if (NT_SUCCESS(ntStatus)) {
+ ::SetLastError(ERROR_SUCCESS);
+ return aBaseAddress;
+ }
+
+ ::SetLastError(GetWin32ErrorCode(ntStatus));
+ return nullptr;
+}
+
+MFBT_API bool UnmapRemoteViewOfFile(HANDLE aProcess, PVOID aBaseAddress) {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&UnmapViewOfFile2)>
+ pUnmapViewOfFile2(L"kernel32.dll", "UnmapViewOfFile2");
+
+ if (!!pUnmapViewOfFile2) {
+ return !!pUnmapViewOfFile2(aProcess, aBaseAddress, 0);
+ }
+
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&NtUnmapViewOfSection)>
+ pNtUnmapViewOfSection(L"ntdll.dll", "NtUnmapViewOfSection");
+
+ MOZ_ASSERT(!!pNtUnmapViewOfSection);
+ if (!pNtUnmapViewOfSection) {
+ return false;
+ }
+
+ NTSTATUS ntStatus = pNtUnmapViewOfSection(aProcess, aBaseAddress);
+ ::SetLastError(GetWin32ErrorCode(ntStatus));
+ return NT_SUCCESS(ntStatus);
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/WindowsMapRemoteView.h b/mozglue/misc/WindowsMapRemoteView.h
new file mode 100644
index 0000000000..6ab88074b5
--- /dev/null
+++ b/mozglue/misc/WindowsMapRemoteView.h
@@ -0,0 +1,25 @@
+/* -*- 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_WindowsMapRemoteView_h
+#define mozilla_WindowsMapRemoteView_h
+
+#include "mozilla/Types.h"
+
+#include <windows.h>
+
+namespace mozilla {
+
+MFBT_API PVOID MapRemoteViewOfFile(HANDLE aFileMapping, HANDLE aProcess,
+ ULONG64 aOffset, PVOID aBaseAddress,
+ SIZE_T aViewSize, ULONG aAllocationType,
+ ULONG aProtectionFlags);
+
+MFBT_API bool UnmapRemoteViewOfFile(HANDLE aProcess, PVOID aBaseAddress);
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsMapRemoteView_h
diff --git a/mozglue/misc/WindowsProcessMitigations.cpp b/mozglue/misc/WindowsProcessMitigations.cpp
new file mode 100644
index 0000000000..c439253533
--- /dev/null
+++ b/mozglue/misc/WindowsProcessMitigations.cpp
@@ -0,0 +1,101 @@
+/* -*- 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 "mozilla/WindowsProcessMitigations.h"
+
+#include <processthreadsapi.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+
+static_assert(sizeof(PROCESS_MITIGATION_DYNAMIC_CODE_POLICY) == 4);
+
+namespace mozilla {
+
+static decltype(&::GetProcessMitigationPolicy)
+FetchGetProcessMitigationPolicyFunc() {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::GetProcessMitigationPolicy)>
+ pGetProcessMitigationPolicy(L"kernel32.dll",
+ "GetProcessMitigationPolicy");
+ return pGetProcessMitigationPolicy;
+}
+
+static bool sWin32kLockedDownInPolicy = false;
+
+MFBT_API bool IsWin32kLockedDown() {
+ static bool sWin32kLockedDown = []() {
+ auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc();
+
+ PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY polInfo;
+ if (!pGetProcessMitigationPolicy ||
+ !pGetProcessMitigationPolicy(::GetCurrentProcess(),
+ ProcessSystemCallDisablePolicy, &polInfo,
+ sizeof(polInfo))) {
+ // We failed to get pointer to GetProcessMitigationPolicy or the call
+ // to it failed, so just return what the sandbox policy says.
+ return sWin32kLockedDownInPolicy;
+ }
+
+ return !!polInfo.DisallowWin32kSystemCalls;
+ }();
+
+ return sWin32kLockedDown;
+}
+
+MFBT_API void SetWin32kLockedDownInPolicy() {
+ sWin32kLockedDownInPolicy = true;
+}
+
+MFBT_API bool IsDynamicCodeDisabled() {
+ auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc();
+ if (!pGetProcessMitigationPolicy) {
+ return false;
+ }
+
+ PROCESS_MITIGATION_DYNAMIC_CODE_POLICY polInfo;
+ if (!pGetProcessMitigationPolicy(::GetCurrentProcess(),
+ ProcessDynamicCodePolicy, &polInfo,
+ sizeof(polInfo))) {
+ return false;
+ }
+
+ return polInfo.ProhibitDynamicCode;
+}
+
+MFBT_API bool IsEafPlusEnabled() {
+ auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc();
+ if (!pGetProcessMitigationPolicy) {
+ return false;
+ }
+
+ PROCESS_MITIGATION_PAYLOAD_RESTRICTION_POLICY polInfo;
+ if (!pGetProcessMitigationPolicy(::GetCurrentProcess(),
+ ProcessPayloadRestrictionPolicy, &polInfo,
+ sizeof(polInfo))) {
+ return false;
+ }
+
+ return polInfo.EnableExportAddressFilterPlus;
+}
+
+MFBT_API bool IsUserShadowStackEnabled() {
+ auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc();
+ if (!pGetProcessMitigationPolicy) {
+ return false;
+ }
+
+ PROCESS_MITIGATION_USER_SHADOW_STACK_POLICY polInfo;
+ if (!pGetProcessMitigationPolicy(::GetCurrentProcess(),
+ ProcessUserShadowStackPolicy, &polInfo,
+ sizeof(polInfo))) {
+ return false;
+ }
+
+ return polInfo.EnableUserShadowStack;
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/WindowsProcessMitigations.h b/mozglue/misc/WindowsProcessMitigations.h
new file mode 100644
index 0000000000..95d27d2c3f
--- /dev/null
+++ b/mozglue/misc/WindowsProcessMitigations.h
@@ -0,0 +1,22 @@
+/* -*- 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 mozilla_WindowsProcessMitigations_h
+#define mozilla_WindowsProcessMitigations_h
+
+#include "mozilla/Types.h"
+
+namespace mozilla {
+
+MFBT_API bool IsWin32kLockedDown();
+MFBT_API void SetWin32kLockedDownInPolicy();
+MFBT_API bool IsDynamicCodeDisabled();
+MFBT_API bool IsEafPlusEnabled();
+MFBT_API bool IsUserShadowStackEnabled();
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsProcessMitigations_h
diff --git a/mozglue/misc/WindowsStackCookie.h b/mozglue/misc/WindowsStackCookie.h
new file mode 100644
index 0000000000..c4196b3853
--- /dev/null
+++ b/mozglue/misc/WindowsStackCookie.h
@@ -0,0 +1,63 @@
+/* -*- 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_WindowsStackCookie_h
+#define mozilla_WindowsStackCookie_h
+
+#if defined(DEBUG) && defined(_M_X64) && !defined(__MINGW64__)
+
+# include <windows.h>
+# include <winnt.h>
+
+# include <cstdint>
+
+# include "mozilla/Types.h"
+
+namespace mozilla {
+
+// This function does pattern matching on the instructions generated for a
+// given function, to detect whether it uses stack buffers. More specifically,
+// it looks for instructions that characterize the presence of stack cookie
+// checks. When this function returns true, it can be a false positive, but we
+// use a rather long pattern to make false positives very unlikely.
+// Note: Do not use this function inside the function that lives at
+// aFunctionAddress, as that could introduce stack buffers.
+// Note: The pattern we use does not work for MinGW builds.
+inline bool HasStackCookieCheck(uintptr_t aFunctionAddress) {
+ DWORD64 imageBase{};
+ auto entry = ::RtlLookupFunctionEntry(
+ reinterpret_cast<DWORD64>(aFunctionAddress), &imageBase, nullptr);
+ if (entry && entry->EndAddress > entry->BeginAddress + 14) {
+ auto begin = reinterpret_cast<uint8_t*>(imageBase + entry->BeginAddress);
+ auto end = reinterpret_cast<uint8_t*>(imageBase + entry->EndAddress - 14);
+ for (auto pc = begin; pc != end; ++pc) {
+ // 48 8b 05 XX XX XX XX: mov rax, qword ptr [rip + XXXXXXXX]
+ if ((pc[0] == 0x48 && pc[1] == 0x8b && pc[2] == 0x05) &&
+ // 48 31 e0: xor rax, rsp
+ (pc[7] == 0x48 && pc[8] == 0x31 && pc[9] == 0xe0) &&
+ // 48 89 (8|4)4 24 ...: mov qword ptr [rsp + ...], rax
+ (pc[10] == 0x48 && pc[11] == 0x89 &&
+ (pc[12] == 0x44 || pc[12] == 0x84) && pc[13] == 0x24)) {
+ return true;
+ }
+ }
+ }
+ // In x64, if there is no entry, then there is no stack allocation, hence
+ // there is no stack cookie check: "Table-based exception handling requires a
+ // table entry for all functions that allocate stack space or call another
+ // function (for example, nonleaf functions)."
+ // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64
+ // Similarly, if the gap between begin and end is less than 14 bytes, then
+ // the function cannot contain the pattern we are looking for, therefore it
+ // has no cookie check either.
+ return false;
+}
+
+} // namespace mozilla
+
+#endif // defined(DEBUG) && defined(_M_X64) && !defined(__MINGW64__)
+
+#endif // mozilla_WindowsStackCookie_h
diff --git a/mozglue/misc/WindowsUnicode.cpp b/mozglue/misc/WindowsUnicode.cpp
new file mode 100644
index 0000000000..464380b6da
--- /dev/null
+++ b/mozglue/misc/WindowsUnicode.cpp
@@ -0,0 +1,59 @@
+/* -*- 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 "WindowsUnicode.h"
+
+#include <windows.h>
+// For UNICODE_STRING
+#include <winternl.h>
+
+#include <string.h>
+
+namespace mozilla {
+namespace glue {
+
+mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr,
+ const size_t aStrLenExclNul) {
+ int numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, aStrLenExclNul, nullptr,
+ 0, nullptr, nullptr);
+ if (!numConv) {
+ return nullptr;
+ }
+
+ // Include room for the null terminator by adding one
+ auto buf = mozilla::MakeUnique<char[]>(numConv + 1);
+
+ numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, aStrLenExclNul, buf.get(),
+ numConv, nullptr, nullptr);
+ if (!numConv) {
+ return nullptr;
+ }
+
+ // Add null termination. numConv does not include the terminator, so we don't
+ // subtract 1 when indexing into buf.
+ buf[numConv] = 0;
+
+ return buf;
+}
+
+mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr) {
+ return WideToUTF8(aStr, wcslen(aStr));
+}
+
+mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr) {
+ return WideToUTF8(aStr.data(), aStr.length());
+}
+
+mozilla::UniquePtr<char[]> WideToUTF8(PCUNICODE_STRING aStr) {
+ if (!aStr) {
+ return nullptr;
+ }
+
+ return WideToUTF8(aStr->Buffer, aStr->Length / sizeof(WCHAR));
+}
+
+} // namespace glue
+} // namespace mozilla
diff --git a/mozglue/misc/WindowsUnicode.h b/mozglue/misc/WindowsUnicode.h
new file mode 100644
index 0000000000..77fc376b92
--- /dev/null
+++ b/mozglue/misc/WindowsUnicode.h
@@ -0,0 +1,35 @@
+/* -*- 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_glue_WindowsUnicode_h
+#define mozilla_glue_WindowsUnicode_h
+
+#include "mozilla/UniquePtr.h"
+
+#include <string>
+
+struct _UNICODE_STRING;
+
+namespace mozilla {
+namespace glue {
+
+mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr,
+ const size_t aStrLenExclNul);
+
+mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr);
+mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr);
+mozilla::UniquePtr<char[]> WideToUTF8(const _UNICODE_STRING* aStr);
+
+#if defined(bstr_t)
+inline mozilla::UniquePtr<char[]> WideToUTF8(const _bstr_t& aStr) {
+ return WideToUTF8(static_cast<const wchar_t*>(aStr), aStr.length());
+}
+#endif // defined(bstr_t)
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_WindowsUnicode_h
diff --git a/mozglue/misc/WindowsUnwindInfo.h b/mozglue/misc/WindowsUnwindInfo.h
new file mode 100644
index 0000000000..3667b5f2bc
--- /dev/null
+++ b/mozglue/misc/WindowsUnwindInfo.h
@@ -0,0 +1,329 @@
+/* -*- 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_WindowsUnwindInfo_h
+#define mozilla_WindowsUnwindInfo_h
+
+#ifdef _M_X64
+
+# include <cstdint>
+
+# include "mozilla/Assertions.h"
+# include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+// On Windows x64, there is no standard function prologue, hence extra
+// information that describes the prologue must be added for each non-leaf
+// function in order to properly unwind the stack. This extra information is
+// grouped into so-called function tables.
+//
+// A function table is a contiguous array of one or more RUNTIME_FUNCTION
+// entries. Each RUNTIME_FUNCTION entry associates a start and end offset in
+// code with specific unwind information. The function table is present in the
+// .pdata section of binaries for static code, and added dynamically with
+// RtlAddFunctionTable or RtlInstallFunctionTableCallback for dynamic code.
+// RUNTIME_FUNCTION entries point to the unwind information, which can thus
+// live at a different location in memory, for example it lives in the .xdata
+// section for static code.
+//
+// Contrary to RUNTIME_FUNCTION, Microsoft provides no standard structure
+// definition to map the unwind information. This file thus provides some
+// helpers to read this data, originally based on breakpad code. The unwind
+// information is partially documented at:
+// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64.
+
+// The unwind information stores a bytecode in UnwindInfo.unwind_code[] that
+// describes how the instructions in the function prologue interact with the
+// stack. An instruction in this bytecode is called an unwind code.
+// UnwindCodeOperationCodes enumerates all opcodes used by this bytecode.
+// Unwind codes are stored in contiguous slots of 16 bits, where each unwind
+// code can span either 1, 2, or 3 slots depending on the opcode it uses.
+enum UnwindOperationCodes : uint8_t {
+ // UnwindCode.operation_info == register number
+ UWOP_PUSH_NONVOL = 0,
+ // UnwindCode.operation_info == 0 or 1,
+ // alloc size in next slot (if 0) or next 2 slots (if 1)
+ UWOP_ALLOC_LARGE = 1,
+ // UnwindCode.operation_info == size of allocation / 8 - 1
+ UWOP_ALLOC_SMALL = 2,
+ // no UnwindCode.operation_info; register number UnwindInfo.frame_register
+ // receives (rsp + UnwindInfo.frame_offset*16)
+ UWOP_SET_FPREG = 3,
+ // UnwindCode.operation_info == register number, offset in next slot
+ UWOP_SAVE_NONVOL = 4,
+ // UnwindCode.operation_info == register number, offset in next 2 slots
+ UWOP_SAVE_NONVOL_FAR = 5,
+ // Version 1; undocumented; not meant for x64
+ UWOP_SAVE_XMM = 6,
+ // Version 2; undocumented
+ UWOP_EPILOG = 6,
+ // Version 1; undocumented; not meant for x64
+ UWOP_SAVE_XMM_FAR = 7,
+ // Version 2; undocumented
+ UWOP_SPARE = 7,
+ // UnwindCode.operation_info == XMM reg number, offset in next slot
+ UWOP_SAVE_XMM128 = 8,
+ // UnwindCode.operation_info == XMM reg number, offset in next 2 slots
+ UWOP_SAVE_XMM128_FAR = 9,
+ // UnwindCode.operation_info == 0: no error-code, 1: error-code
+ UWOP_PUSH_MACHFRAME = 10
+};
+
+// Strictly speaking, UnwindCode represents a slot -- not a full unwind code.
+union UnwindCode {
+ struct {
+ uint8_t offset_in_prolog;
+ UnwindOperationCodes unwind_operation_code : 4;
+ uint8_t operation_info : 4;
+ };
+ uint16_t frame_offset;
+};
+
+// UnwindInfo is a variable-sized struct meant for C-style direct access to the
+// unwind information. Be careful:
+// - prefer using the size() helper method to using sizeof;
+// - don't construct objects of this type, cast pointers instead;
+// - consider using the IterableUnwindInfo helpers to iterate over unwind
+// codes.
+struct UnwindInfo {
+ uint8_t version : 3;
+ uint8_t flags : 5; // either 0, UNW_FLAG_CHAININFO, or a combination of
+ // UNW_FLAG_EHANDLER and UNW_FLAG_UHANDLER
+ uint8_t size_of_prolog;
+ uint8_t count_of_codes; // contains the length of the unwind_code[] array
+ uint8_t frame_register : 4;
+ uint8_t frame_offset : 4;
+ UnwindCode unwind_code[1]; // variable length
+ // Note: There is extra data after the variable length array if using flags
+ // UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER, or UNW_FLAG_CHAININFO. We
+ // ignore the extra data at the moment. For more details, see:
+ // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64.
+ //
+ // When using UNW_FLAG_EHANDLER or UNW_FLAG_UHANDLER, the extra data
+ // includes handler data of unspecificied size: only the handler knows
+ // the correct size for this data. This makes it difficult to know the
+ // size of the full unwind information or to copy it in this particular
+ // case.
+
+ UnwindInfo(const UnwindInfo&) = delete;
+ UnwindInfo& operator=(const UnwindInfo&) = delete;
+ UnwindInfo(UnwindInfo&&) = delete;
+ UnwindInfo& operator=(UnwindInfo&&) = delete;
+ ~UnwindInfo() = delete;
+
+ // Size of this structure, including the variable length unwind_code array
+ // but NOT including the extra data related to flags UNW_FLAG_EHANDLER,
+ // UNW_FLAG_UHANDLER, and UNW_FLAG_CHAININFO.
+ //
+ // The places where we currently use these helpers read unwind information at
+ // function entry points; as such we expect that they may encounter
+ // UNW_FLAG_EHANDLER and/or UNW_FLAG_UHANDLER but won't need to use the
+ // associated extra data, and it is expected that they should not encounter
+ // UNW_FLAG_CHAININFO. UNW_FLAG_CHAININFO is typically used for code that
+ // lives separately from the entry point of the function to which it belongs,
+ // this code then has chained unwind info pointing to the entry point.
+ inline size_t Size() const {
+ return offsetof(UnwindInfo, unwind_code) +
+ count_of_codes * sizeof(UnwindCode);
+ }
+
+ // Note: We currently do not copy the extra data related to flags
+ // UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER, and UNW_FLAG_CHAININFO.
+ UniquePtr<uint8_t[]> Copy() const {
+ auto s = Size();
+ auto result = MakeUnique<uint8_t[]>(s);
+ std::memcpy(result.get(), reinterpret_cast<const void*>(this), s);
+ return result;
+ }
+
+ // An unwind code spans a number of slots in the unwind_code array that can
+ // vary from 1 to 3. This method assumes that the index parameter points to
+ // a slot that is the start of an unwind code. If the unwind code is
+ // well-formed, it returns true and sets its second parameter to the number
+ // of slots that the unwind code occupies.
+ //
+ // This function returns false if the unwind code is ill-formed, i.e.:
+ // - either the index points out of bounds;
+ // - or the opcode is invalid, or unexpected (e.g. UWOP_SAVE_XMM and
+ // UWOP_SAVE_XMM_FAR in version 1);
+ // - or using the correct slots count for the opcode would go out of bounds.
+ bool GetSlotsCountForCodeAt(uint8_t aIndex, uint8_t* aSlotsCount) const {
+ if (aIndex >= count_of_codes) {
+ MOZ_ASSERT_UNREACHABLE("The index is out of bounds");
+ return false;
+ }
+
+ const UnwindCode& unwindCode = unwind_code[aIndex];
+ uint8_t slotsCount = 0;
+
+ // See https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64
+ switch (unwindCode.unwind_operation_code) {
+ // Start with fixed-size opcodes common to versions 1 and 2
+ case UWOP_SAVE_NONVOL_FAR:
+ case UWOP_SAVE_XMM128_FAR:
+ slotsCount = 3;
+ break;
+
+ case UWOP_SAVE_NONVOL:
+ case UWOP_SAVE_XMM128:
+ slotsCount = 2;
+ break;
+
+ case UWOP_PUSH_NONVOL:
+ case UWOP_ALLOC_SMALL:
+ case UWOP_SET_FPREG:
+ case UWOP_PUSH_MACHFRAME:
+ slotsCount = 1;
+ break;
+
+ // UWOP_ALLOC_LARGE is the only variable-sized opcode. It is common to
+ // versions 1 and 2. It is ill-formed if the info is not 0 or 1.
+ case UWOP_ALLOC_LARGE:
+ if (unwindCode.operation_info > 1) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Operation UWOP_ALLOC_LARGE is used, but operation_info "
+ "is not 0 or 1");
+ return false;
+ }
+ slotsCount = 2 + unwindCode.operation_info;
+ break;
+
+ case UWOP_SPARE:
+ if (version != 2) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Operation code UWOP_SPARE is used, but version is not 2");
+ return false;
+ }
+ slotsCount = 3;
+ break;
+
+ case UWOP_EPILOG:
+ if (version != 2) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Operation code UWOP_EPILOG is used, but version is not 2");
+ return false;
+ }
+ slotsCount = 2;
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("An unknown operation code is used");
+ return false;
+ }
+
+ // The unwind code is ill-formed if using the correct number of slots for
+ // the opcode would go out of bounds.
+ if (count_of_codes - aIndex < slotsCount) {
+ MOZ_ASSERT_UNREACHABLE(
+ "A valid operation code is used, but it spans too many slots");
+ return false;
+ }
+
+ *aSlotsCount = slotsCount;
+ return true;
+ }
+};
+
+class IterableUnwindInfo {
+ class Iterator {
+ public:
+ UnwindInfo& Info() { return mInfo; }
+
+ uint8_t Index() const {
+ MOZ_ASSERT(IsValid());
+ return mIndex;
+ }
+
+ uint8_t SlotsCount() const {
+ MOZ_ASSERT(IsValid());
+ return mSlotsCount;
+ }
+
+ // An iterator is valid if it points to a well-formed unwind code.
+ // The end iterator is invalid as it does not point to any unwind code.
+ // All invalid iterators compare equal, which allows comparison with the
+ // end iterator to exit loops as soon as an ill-formed unwind code is met.
+ bool IsValid() const { return mIsValid; }
+
+ bool IsAtEnd() const { return mIndex >= mInfo.count_of_codes; }
+
+ bool operator==(const Iterator& aOther) const {
+ if (mIsValid != aOther.mIsValid) {
+ return false;
+ }
+ // Comparing two invalid iterators.
+ if (!mIsValid) {
+ return true;
+ }
+ // Comparing two valid iterators.
+ return mIndex == aOther.mIndex;
+ }
+
+ bool operator!=(const Iterator& aOther) const { return !(*this == aOther); }
+
+ Iterator& operator++() {
+ MOZ_ASSERT(IsValid());
+ mIndex += mSlotsCount;
+ if (mIndex < mInfo.count_of_codes) {
+ mIsValid = mInfo.GetSlotsCountForCodeAt(mIndex, &mSlotsCount);
+ MOZ_ASSERT(IsValid());
+ } else {
+ mIsValid = false;
+ }
+ return *this;
+ }
+
+ const UnwindCode& operator*() {
+ MOZ_ASSERT(IsValid());
+ return mInfo.unwind_code[mIndex];
+ }
+
+ private:
+ friend class IterableUnwindInfo;
+
+ Iterator(UnwindInfo& aInfo, uint8_t aIndex, uint8_t aSlotsCount,
+ bool aIsValid)
+ : mInfo(aInfo),
+ mIndex(aIndex),
+ mSlotsCount(aSlotsCount),
+ mIsValid(aIsValid){};
+
+ UnwindInfo& mInfo;
+ uint8_t mIndex;
+ uint8_t mSlotsCount;
+ bool mIsValid;
+ };
+
+ public:
+ explicit IterableUnwindInfo(UnwindInfo& aInfo)
+ : mBegin(aInfo, 0, 0, false),
+ mEnd(aInfo, aInfo.count_of_codes, 0, false) {
+ if (aInfo.count_of_codes) {
+ mBegin.mIsValid = aInfo.GetSlotsCountForCodeAt(0, &mBegin.mSlotsCount);
+ MOZ_ASSERT(mBegin.mIsValid);
+ }
+ }
+
+ explicit IterableUnwindInfo(uint8_t* aInfo)
+ : IterableUnwindInfo(*reinterpret_cast<UnwindInfo*>(aInfo)) {}
+
+ UnwindInfo& Info() { return mBegin.Info(); }
+
+ const Iterator& begin() { return mBegin; }
+
+ const Iterator& end() { return mEnd; }
+
+ private:
+ Iterator mBegin;
+ Iterator mEnd;
+};
+
+} // namespace mozilla
+
+#endif // _M_X64
+
+#endif // mozilla_WindowsUnwindInfo_h
diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
new file mode 100644
index 0000000000..7d2bcfa712
--- /dev/null
+++ b/mozglue/misc/decimal/Decimal.cpp
@@ -0,0 +1,1017 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Decimal.h"
+#include "moz-decimal-utils.h"
+#include "DoubleConversion.h"
+
+using namespace moz_decimal_utils;
+
+#include <algorithm>
+#include <float.h>
+
+namespace blink {
+
+namespace DecimalPrivate {
+
+// This class handles Decimal special values.
+class SpecialValueHandler {
+ STACK_ALLOCATED();
+ WTF_MAKE_NONCOPYABLE(SpecialValueHandler);
+public:
+ enum HandleResult {
+ BothFinite,
+ BothInfinity,
+ EitherNaN,
+ LHSIsInfinity,
+ RHSIsInfinity,
+ };
+
+ SpecialValueHandler(const Decimal& lhs, const Decimal& rhs);
+ HandleResult handle();
+ Decimal value() const;
+
+private:
+ enum Result {
+ ResultIsLHS,
+ ResultIsRHS,
+ ResultIsUnknown,
+ };
+
+ const Decimal& m_lhs;
+ const Decimal& m_rhs;
+ Result m_result;
+};
+
+SpecialValueHandler::SpecialValueHandler(const Decimal& lhs, const Decimal& rhs)
+ : m_lhs(lhs), m_rhs(rhs), m_result(ResultIsUnknown)
+{
+}
+
+SpecialValueHandler::HandleResult SpecialValueHandler::handle()
+{
+ if (m_lhs.isFinite() && m_rhs.isFinite())
+ return BothFinite;
+
+ const Decimal::EncodedData::FormatClass lhsClass = m_lhs.value().formatClass();
+ const Decimal::EncodedData::FormatClass rhsClass = m_rhs.value().formatClass();
+ if (lhsClass == Decimal::EncodedData::ClassNaN) {
+ m_result = ResultIsLHS;
+ return EitherNaN;
+ }
+
+ if (rhsClass == Decimal::EncodedData::ClassNaN) {
+ m_result = ResultIsRHS;
+ return EitherNaN;
+ }
+
+ if (lhsClass == Decimal::EncodedData::ClassInfinity)
+ return rhsClass == Decimal::EncodedData::ClassInfinity ? BothInfinity : LHSIsInfinity;
+
+ if (rhsClass == Decimal::EncodedData::ClassInfinity)
+ return RHSIsInfinity;
+
+ ASSERT_NOT_REACHED();
+ return BothFinite;
+}
+
+Decimal SpecialValueHandler::value() const
+{
+ switch (m_result) {
+ case ResultIsLHS:
+ return m_lhs;
+ case ResultIsRHS:
+ return m_rhs;
+ case ResultIsUnknown:
+ default:
+ ASSERT_NOT_REACHED();
+ return m_lhs;
+ }
+}
+
+// This class is used for 128 bit unsigned integer arithmetic.
+class UInt128 {
+public:
+ UInt128(uint64_t low, uint64_t high)
+ : m_high(high), m_low(low)
+ {
+ }
+
+ UInt128& operator/=(uint32_t);
+
+ uint64_t high() const { return m_high; }
+ uint64_t low() const { return m_low; }
+
+ static UInt128 multiply(uint64_t u, uint64_t v) { return UInt128(u * v, multiplyHigh(u, v)); }
+
+private:
+ static uint32_t highUInt32(uint64_t x) { return static_cast<uint32_t>(x >> 32); }
+ static uint32_t lowUInt32(uint64_t x) { return static_cast<uint32_t>(x & ((static_cast<uint64_t>(1) << 32) - 1)); }
+ static uint64_t makeUInt64(uint32_t low, uint32_t high) { return low | (static_cast<uint64_t>(high) << 32); }
+
+ static uint64_t multiplyHigh(uint64_t, uint64_t);
+
+ uint64_t m_high;
+ uint64_t m_low;
+};
+
+UInt128& UInt128::operator/=(const uint32_t divisor)
+{
+ ASSERT(divisor);
+
+ if (!m_high) {
+ m_low /= divisor;
+ return *this;
+ }
+
+ uint32_t dividend[4];
+ dividend[0] = lowUInt32(m_low);
+ dividend[1] = highUInt32(m_low);
+ dividend[2] = lowUInt32(m_high);
+ dividend[3] = highUInt32(m_high);
+
+ uint32_t quotient[4];
+ uint32_t remainder = 0;
+ for (int i = 3; i >= 0; --i) {
+ const uint64_t work = makeUInt64(dividend[i], remainder);
+ remainder = static_cast<uint32_t>(work % divisor);
+ quotient[i] = static_cast<uint32_t>(work / divisor);
+ }
+ m_low = makeUInt64(quotient[0], quotient[1]);
+ m_high = makeUInt64(quotient[2], quotient[3]);
+ return *this;
+}
+
+// Returns high 64bit of 128bit product.
+uint64_t UInt128::multiplyHigh(uint64_t u, uint64_t v)
+{
+ const uint64_t uLow = lowUInt32(u);
+ const uint64_t uHigh = highUInt32(u);
+ const uint64_t vLow = lowUInt32(v);
+ const uint64_t vHigh = highUInt32(v);
+ const uint64_t partialProduct = uHigh * vLow + highUInt32(uLow * vLow);
+ return uHigh * vHigh + highUInt32(partialProduct) + highUInt32(uLow * vHigh + lowUInt32(partialProduct));
+}
+
+static int countDigits(uint64_t x)
+{
+ int numberOfDigits = 0;
+ for (uint64_t powerOfTen = 1; x >= powerOfTen; powerOfTen *= 10) {
+ ++numberOfDigits;
+ if (powerOfTen >= std::numeric_limits<uint64_t>::max() / 10)
+ break;
+ }
+ return numberOfDigits;
+}
+
+static uint64_t scaleDown(uint64_t x, int n)
+{
+ ASSERT(n >= 0);
+ while (n > 0 && x) {
+ x /= 10;
+ --n;
+ }
+ return x;
+}
+
+static uint64_t scaleUp(uint64_t x, int n)
+{
+ ASSERT(n >= 0);
+ ASSERT(n <= Precision);
+
+ uint64_t y = 1;
+ uint64_t z = 10;
+ for (;;) {
+ if (n & 1)
+ y = y * z;
+
+ n >>= 1;
+ if (!n)
+ return x * y;
+
+ z = z * z;
+ }
+}
+
+} // namespace DecimalPrivate
+
+using namespace DecimalPrivate;
+
+bool Decimal::EncodedData::operator==(const EncodedData& another) const
+{
+ return m_sign == another.m_sign
+ && m_formatClass == another.m_formatClass
+ && m_exponent == another.m_exponent
+ && m_coefficient == another.m_coefficient;
+}
+
+
+Decimal::Decimal(int32_t i32)
+ : Decimal(DecimalLiteral{i32}) {}
+
+Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient)
+ : m_data(sign, coefficient ? exponent : 0, coefficient) {}
+
+Decimal::Decimal(const EncodedData& data)
+ : m_data(data)
+{
+}
+
+Decimal::Decimal(const Decimal& other)
+ : m_data(other.m_data)
+{
+}
+
+Decimal& Decimal::operator=(const Decimal& other)
+{
+ m_data = other.m_data;
+ return *this;
+}
+
+Decimal& Decimal::operator+=(const Decimal& other)
+{
+ m_data = (*this + other).m_data;
+ return *this;
+}
+
+Decimal& Decimal::operator-=(const Decimal& other)
+{
+ m_data = (*this - other).m_data;
+ return *this;
+}
+
+Decimal& Decimal::operator*=(const Decimal& other)
+{
+ m_data = (*this * other).m_data;
+ return *this;
+}
+
+Decimal& Decimal::operator/=(const Decimal& other)
+{
+ m_data = (*this / other).m_data;
+ return *this;
+}
+
+Decimal Decimal::operator-() const
+{
+ if (isNaN())
+ return *this;
+
+ Decimal result(*this);
+ result.m_data.setSign(invertSign(m_data.sign()));
+ return result;
+}
+
+Decimal Decimal::operator+(const Decimal& rhs) const
+{
+ const Decimal& lhs = *this;
+ const Sign lhsSign = lhs.sign();
+ const Sign rhsSign = rhs.sign();
+
+ SpecialValueHandler handler(lhs, rhs);
+ switch (handler.handle()) {
+ case SpecialValueHandler::BothFinite:
+ break;
+
+ case SpecialValueHandler::BothInfinity:
+ return lhsSign == rhsSign ? lhs : nan();
+
+ case SpecialValueHandler::EitherNaN:
+ return handler.value();
+
+ case SpecialValueHandler::LHSIsInfinity:
+ return lhs;
+
+ case SpecialValueHandler::RHSIsInfinity:
+ return rhs;
+ }
+
+ const AlignedOperands alignedOperands = alignOperands(lhs, rhs);
+
+ const uint64_t result = lhsSign == rhsSign
+ ? alignedOperands.lhsCoefficient + alignedOperands.rhsCoefficient
+ : alignedOperands.lhsCoefficient - alignedOperands.rhsCoefficient;
+
+ if (lhsSign == Negative && rhsSign == Positive && !result)
+ return Decimal(Positive, alignedOperands.exponent, 0);
+
+ return static_cast<int64_t>(result) >= 0
+ ? Decimal(lhsSign, alignedOperands.exponent, result)
+ : Decimal(invertSign(lhsSign), alignedOperands.exponent, -static_cast<int64_t>(result));
+}
+
+Decimal Decimal::operator-(const Decimal& rhs) const
+{
+ const Decimal& lhs = *this;
+ const Sign lhsSign = lhs.sign();
+ const Sign rhsSign = rhs.sign();
+
+ SpecialValueHandler handler(lhs, rhs);
+ switch (handler.handle()) {
+ case SpecialValueHandler::BothFinite:
+ break;
+
+ case SpecialValueHandler::BothInfinity:
+ return lhsSign == rhsSign ? nan() : lhs;
+
+ case SpecialValueHandler::EitherNaN:
+ return handler.value();
+
+ case SpecialValueHandler::LHSIsInfinity:
+ return lhs;
+
+ case SpecialValueHandler::RHSIsInfinity:
+ return infinity(invertSign(rhsSign));
+ }
+
+ const AlignedOperands alignedOperands = alignOperands(lhs, rhs);
+
+ const uint64_t result = lhsSign == rhsSign
+ ? alignedOperands.lhsCoefficient - alignedOperands.rhsCoefficient
+ : alignedOperands.lhsCoefficient + alignedOperands.rhsCoefficient;
+
+ if (lhsSign == Negative && rhsSign == Negative && !result)
+ return Decimal(Positive, alignedOperands.exponent, 0);
+
+ return static_cast<int64_t>(result) >= 0
+ ? Decimal(lhsSign, alignedOperands.exponent, result)
+ : Decimal(invertSign(lhsSign), alignedOperands.exponent, -static_cast<int64_t>(result));
+}
+
+Decimal Decimal::operator*(const Decimal& rhs) const
+{
+ const Decimal& lhs = *this;
+ const Sign lhsSign = lhs.sign();
+ const Sign rhsSign = rhs.sign();
+ const Sign resultSign = lhsSign == rhsSign ? Positive : Negative;
+
+ SpecialValueHandler handler(lhs, rhs);
+ switch (handler.handle()) {
+ case SpecialValueHandler::BothFinite: {
+ const uint64_t lhsCoefficient = lhs.m_data.coefficient();
+ const uint64_t rhsCoefficient = rhs.m_data.coefficient();
+ int resultExponent = lhs.exponent() + rhs.exponent();
+ UInt128 work(UInt128::multiply(lhsCoefficient, rhsCoefficient));
+ while (work.high()) {
+ work /= 10;
+ ++resultExponent;
+ }
+ return Decimal(resultSign, resultExponent, work.low());
+ }
+
+ case SpecialValueHandler::BothInfinity:
+ return infinity(resultSign);
+
+ case SpecialValueHandler::EitherNaN:
+ return handler.value();
+
+ case SpecialValueHandler::LHSIsInfinity:
+ return rhs.isZero() ? nan() : infinity(resultSign);
+
+ case SpecialValueHandler::RHSIsInfinity:
+ return lhs.isZero() ? nan() : infinity(resultSign);
+ }
+
+ ASSERT_NOT_REACHED();
+ return nan();
+}
+
+Decimal Decimal::operator/(const Decimal& rhs) const
+{
+ const Decimal& lhs = *this;
+ const Sign lhsSign = lhs.sign();
+ const Sign rhsSign = rhs.sign();
+ const Sign resultSign = lhsSign == rhsSign ? Positive : Negative;
+
+ SpecialValueHandler handler(lhs, rhs);
+ switch (handler.handle()) {
+ case SpecialValueHandler::BothFinite:
+ break;
+
+ case SpecialValueHandler::BothInfinity:
+ return nan();
+
+ case SpecialValueHandler::EitherNaN:
+ return handler.value();
+
+ case SpecialValueHandler::LHSIsInfinity:
+ return infinity(resultSign);
+
+ case SpecialValueHandler::RHSIsInfinity:
+ return zero(resultSign);
+ }
+
+ ASSERT(lhs.isFinite());
+ ASSERT(rhs.isFinite());
+
+ if (rhs.isZero())
+ return lhs.isZero() ? nan() : infinity(resultSign);
+
+ int resultExponent = lhs.exponent() - rhs.exponent();
+
+ if (lhs.isZero())
+ return Decimal(resultSign, resultExponent, 0);
+
+ uint64_t remainder = lhs.m_data.coefficient();
+ const uint64_t divisor = rhs.m_data.coefficient();
+ uint64_t result = 0;
+ for (;;) {
+ while (remainder < divisor && result < MaxCoefficient / 10) {
+ remainder *= 10;
+ result *= 10;
+ --resultExponent;
+ }
+ if (remainder < divisor)
+ break;
+ uint64_t quotient = remainder / divisor;
+ if (result > MaxCoefficient - quotient)
+ break;
+ result += quotient;
+ remainder %= divisor;
+ if (!remainder)
+ break;
+ }
+
+ if (remainder > divisor / 2)
+ ++result;
+
+ return Decimal(resultSign, resultExponent, result);
+}
+
+bool Decimal::operator==(const Decimal& rhs) const
+{
+ if (isNaN() || rhs.isNaN())
+ return false;
+ return m_data == rhs.m_data || compareTo(rhs).isZero();
+}
+
+bool Decimal::operator!=(const Decimal& rhs) const
+{
+ if (isNaN() || rhs.isNaN())
+ return true;
+ if (m_data == rhs.m_data)
+ return false;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero();
+}
+
+bool Decimal::operator<(const Decimal& rhs) const
+{
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero() && result.isNegative();
+}
+
+bool Decimal::operator<=(const Decimal& rhs) const
+{
+ if (isNaN() || rhs.isNaN())
+ return false;
+ if (m_data == rhs.m_data)
+ return true;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return result.isZero() || result.isNegative();
+}
+
+bool Decimal::operator>(const Decimal& rhs) const
+{
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero() && result.isPositive();
+}
+
+bool Decimal::operator>=(const Decimal& rhs) const
+{
+ if (isNaN() || rhs.isNaN())
+ return false;
+ if (m_data == rhs.m_data)
+ return true;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return result.isZero() || !result.isNegative();
+}
+
+Decimal Decimal::abs() const
+{
+ Decimal result(*this);
+ result.m_data.setSign(Positive);
+ return result;
+}
+
+Decimal::AlignedOperands Decimal::alignOperands(const Decimal& lhs, const Decimal& rhs)
+{
+ ASSERT(lhs.isFinite());
+ ASSERT(rhs.isFinite());
+
+ const int lhsExponent = lhs.exponent();
+ const int rhsExponent = rhs.exponent();
+ int exponent = std::min(lhsExponent, rhsExponent);
+ uint64_t lhsCoefficient = lhs.m_data.coefficient();
+ uint64_t rhsCoefficient = rhs.m_data.coefficient();
+
+ if (lhsExponent > rhsExponent) {
+ const int numberOfLHSDigits = countDigits(lhsCoefficient);
+ if (numberOfLHSDigits) {
+ const int lhsShiftAmount = lhsExponent - rhsExponent;
+ const int overflow = numberOfLHSDigits + lhsShiftAmount - Precision;
+ if (overflow <= 0) {
+ lhsCoefficient = scaleUp(lhsCoefficient, lhsShiftAmount);
+ } else {
+ lhsCoefficient = scaleUp(lhsCoefficient, lhsShiftAmount - overflow);
+ rhsCoefficient = scaleDown(rhsCoefficient, overflow);
+ exponent += overflow;
+ }
+ }
+
+ } else if (lhsExponent < rhsExponent) {
+ const int numberOfRHSDigits = countDigits(rhsCoefficient);
+ if (numberOfRHSDigits) {
+ const int rhsShiftAmount = rhsExponent - lhsExponent;
+ const int overflow = numberOfRHSDigits + rhsShiftAmount - Precision;
+ if (overflow <= 0) {
+ rhsCoefficient = scaleUp(rhsCoefficient, rhsShiftAmount);
+ } else {
+ rhsCoefficient = scaleUp(rhsCoefficient, rhsShiftAmount - overflow);
+ lhsCoefficient = scaleDown(lhsCoefficient, overflow);
+ exponent += overflow;
+ }
+ }
+ }
+
+ AlignedOperands alignedOperands;
+ alignedOperands.exponent = exponent;
+ alignedOperands.lhsCoefficient = lhsCoefficient;
+ alignedOperands.rhsCoefficient = rhsCoefficient;
+ return alignedOperands;
+}
+
+static bool isMultiplePowersOfTen(uint64_t coefficient, int n)
+{
+ return !coefficient || !(coefficient % scaleUp(1, n));
+}
+
+// Round toward positive infinity.
+Decimal Decimal::ceil() const
+{
+ if (isSpecial())
+ return *this;
+
+ if (exponent() >= 0)
+ return *this;
+
+ uint64_t result = m_data.coefficient();
+ const int numberOfDigits = countDigits(result);
+ const int numberOfDropDigits = -exponent();
+ if (numberOfDigits <= numberOfDropDigits)
+ return isPositive() ? Decimal(1) : zero(Positive);
+
+ result = scaleDown(result, numberOfDropDigits);
+ if (isPositive() && !isMultiplePowersOfTen(m_data.coefficient(), numberOfDropDigits))
+ ++result;
+ return Decimal(sign(), 0, result);
+}
+
+Decimal Decimal::compareTo(const Decimal& rhs) const
+{
+ const Decimal result(*this - rhs);
+ switch (result.m_data.formatClass()) {
+ case EncodedData::ClassInfinity:
+ return result.isNegative() ? Decimal(-1) : Decimal(1);
+
+ case EncodedData::ClassNaN:
+ case EncodedData::ClassNormal:
+ return result;
+
+ case EncodedData::ClassZero:
+ return zero(Positive);
+
+ default:
+ ASSERT_NOT_REACHED();
+ return nan();
+ }
+}
+
+// Round toward negative infinity.
+Decimal Decimal::floor() const
+{
+ if (isSpecial())
+ return *this;
+
+ if (exponent() >= 0)
+ return *this;
+
+ uint64_t result = m_data.coefficient();
+ const int numberOfDigits = countDigits(result);
+ const int numberOfDropDigits = -exponent();
+ if (numberOfDigits < numberOfDropDigits)
+ return isPositive() ? zero(Positive) : Decimal(-1);
+
+ result = scaleDown(result, numberOfDropDigits);
+ if (isNegative() && !isMultiplePowersOfTen(m_data.coefficient(), numberOfDropDigits))
+ ++result;
+ return Decimal(sign(), 0, result);
+}
+
+Decimal Decimal::fromDouble(double doubleValue)
+{
+ if (std::isfinite(doubleValue))
+ return fromString(mozToString(doubleValue));
+
+ if (std::isinf(doubleValue))
+ return infinity(doubleValue < 0 ? Negative : Positive);
+
+ return nan();
+}
+
+Decimal Decimal::fromString(const String& str)
+{
+ int exponent = 0;
+ Sign exponentSign = Positive;
+ int numberOfDigits = 0;
+ int numberOfDigitsAfterDot = 0;
+ int numberOfExtraDigits = 0;
+ Sign sign = Positive;
+
+ enum {
+ StateDigit,
+ StateDot,
+ StateDotDigit,
+ StateE,
+ StateEDigit,
+ StateESign,
+ StateSign,
+ StateStart,
+ StateZero,
+ } state = StateStart;
+
+#define HandleCharAndBreak(expected, nextState) \
+ if (ch == expected) { \
+ state = nextState; \
+ break; \
+ }
+
+#define HandleTwoCharsAndBreak(expected1, expected2, nextState) \
+ if (ch == expected1 || ch == expected2) { \
+ state = nextState; \
+ break; \
+ }
+
+ uint64_t accumulator = 0;
+ for (unsigned index = 0; index < str.length(); ++index) {
+ const int ch = str[index];
+ switch (state) {
+ case StateDigit:
+ if (ch >= '0' && ch <= '9') {
+ if (numberOfDigits < Precision) {
+ ++numberOfDigits;
+ accumulator *= 10;
+ accumulator += ch - '0';
+ } else {
+ ++numberOfExtraDigits;
+ }
+ break;
+ }
+
+ HandleCharAndBreak('.', StateDot);
+ HandleTwoCharsAndBreak('E', 'e', StateE);
+ return nan();
+
+ case StateDot:
+ case StateDotDigit:
+ if (ch >= '0' && ch <= '9') {
+ if (numberOfDigits < Precision) {
+ ++numberOfDigits;
+ ++numberOfDigitsAfterDot;
+ accumulator *= 10;
+ accumulator += ch - '0';
+ }
+ state = StateDotDigit;
+ break;
+ }
+
+ HandleTwoCharsAndBreak('E', 'e', StateE);
+ return nan();
+
+ case StateE:
+ if (ch == '+') {
+ exponentSign = Positive;
+ state = StateESign;
+ break;
+ }
+
+ if (ch == '-') {
+ exponentSign = Negative;
+ state = StateESign;
+ break;
+ }
+
+ if (ch >= '0' && ch <= '9') {
+ exponent = ch - '0';
+ state = StateEDigit;
+ break;
+ }
+
+ return nan();
+
+ case StateEDigit:
+ if (ch >= '0' && ch <= '9') {
+ exponent *= 10;
+ exponent += ch - '0';
+ if (exponent > ExponentMax + Precision) {
+ if (accumulator)
+ return exponentSign == Negative ? zero(Positive) : infinity(sign);
+ return zero(sign);
+ }
+ state = StateEDigit;
+ break;
+ }
+
+ return nan();
+
+ case StateESign:
+ if (ch >= '0' && ch <= '9') {
+ exponent = ch - '0';
+ state = StateEDigit;
+ break;
+ }
+
+ return nan();
+
+ case StateSign:
+ if (ch >= '1' && ch <= '9') {
+ accumulator = ch - '0';
+ numberOfDigits = 1;
+ state = StateDigit;
+ break;
+ }
+
+ HandleCharAndBreak('0', StateZero);
+ return nan();
+
+ case StateStart:
+ if (ch >= '1' && ch <= '9') {
+ accumulator = ch - '0';
+ numberOfDigits = 1;
+ state = StateDigit;
+ break;
+ }
+
+ if (ch == '-') {
+ sign = Negative;
+ state = StateSign;
+ break;
+ }
+
+ if (ch == '+') {
+ sign = Positive;
+ state = StateSign;
+ break;
+ }
+
+ HandleCharAndBreak('0', StateZero);
+ HandleCharAndBreak('.', StateDot);
+ return nan();
+
+ case StateZero:
+ if (ch == '0')
+ break;
+
+ if (ch >= '1' && ch <= '9') {
+ accumulator = ch - '0';
+ numberOfDigits = 1;
+ state = StateDigit;
+ break;
+ }
+
+ HandleCharAndBreak('.', StateDot);
+ HandleTwoCharsAndBreak('E', 'e', StateE);
+ return nan();
+
+ default:
+ ASSERT_NOT_REACHED();
+ return nan();
+ }
+ }
+
+ if (state == StateZero)
+ return zero(sign);
+
+ if (state == StateDigit || state == StateEDigit || state == StateDotDigit) {
+ int resultExponent = exponent * (exponentSign == Negative ? -1 : 1) - numberOfDigitsAfterDot + numberOfExtraDigits;
+ if (resultExponent < ExponentMin)
+ return zero(Positive);
+
+ const int overflow = resultExponent - ExponentMax + 1;
+ if (overflow > 0) {
+ if (overflow + numberOfDigits - numberOfDigitsAfterDot > Precision)
+ return infinity(sign);
+ accumulator = scaleUp(accumulator, overflow);
+ resultExponent -= overflow;
+ }
+
+ return Decimal(sign, resultExponent, accumulator);
+ }
+
+ return nan();
+}
+
+Decimal Decimal::infinity(const Sign sign)
+{
+ return Decimal(EncodedData(sign, EncodedData::ClassInfinity));
+}
+
+Decimal Decimal::nan()
+{
+ return Decimal(EncodedData(Positive, EncodedData::ClassNaN));
+}
+
+Decimal Decimal::remainder(const Decimal& rhs) const
+{
+ const Decimal quotient = *this / rhs;
+ return quotient.isSpecial() ? quotient : *this - (quotient.isNegative() ? quotient.ceil() : quotient.floor()) * rhs;
+}
+
+Decimal Decimal::round() const
+{
+ if (isSpecial())
+ return *this;
+
+ if (exponent() >= 0)
+ return *this;
+
+ uint64_t result = m_data.coefficient();
+ const int numberOfDigits = countDigits(result);
+ const int numberOfDropDigits = -exponent();
+ if (numberOfDigits < numberOfDropDigits)
+ return zero(Positive);
+
+ result = scaleDown(result, numberOfDropDigits - 1);
+ if (result % 10 >= 5)
+ result += 10;
+ result /= 10;
+ return Decimal(sign(), 0, result);
+}
+
+double Decimal::toDouble() const
+{
+ if (isFinite()) {
+ bool valid;
+ const double doubleValue = mozToDouble(toString(), &valid);
+ return valid ? doubleValue : std::numeric_limits<double>::quiet_NaN();
+ }
+
+ if (isInfinity())
+ return isNegative() ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
+
+ return std::numeric_limits<double>::quiet_NaN();
+}
+
+String Decimal::toString() const
+{
+ switch (m_data.formatClass()) {
+ case EncodedData::ClassInfinity:
+ return sign() ? "-Infinity" : "Infinity";
+
+ case EncodedData::ClassNaN:
+ return "NaN";
+
+ case EncodedData::ClassNormal:
+ case EncodedData::ClassZero:
+ break;
+
+ default:
+ ASSERT_NOT_REACHED();
+ return "";
+ }
+
+ StringBuilder builder;
+ if (sign())
+ builder.append('-');
+
+ int originalExponent = exponent();
+ uint64_t coefficient = m_data.coefficient();
+
+ if (originalExponent < 0) {
+ const int maxDigits = DBL_DIG;
+ uint64_t lastDigit = 0;
+ while (countDigits(coefficient) > maxDigits) {
+ lastDigit = coefficient % 10;
+ coefficient /= 10;
+ ++originalExponent;
+ }
+
+ if (lastDigit >= 5)
+ ++coefficient;
+
+ while (originalExponent < 0 && coefficient && !(coefficient % 10)) {
+ coefficient /= 10;
+ ++originalExponent;
+ }
+ }
+
+ const String digits = mozToString(coefficient);
+ int coefficientLength = static_cast<int>(digits.length());
+ const int adjustedExponent = originalExponent + coefficientLength - 1;
+ if (originalExponent <= 0 && adjustedExponent >= -6) {
+ if (!originalExponent) {
+ builder.append(digits);
+ return builder.toString();
+ }
+
+ if (adjustedExponent >= 0) {
+ for (int i = 0; i < coefficientLength; ++i) {
+ builder.append(digits[i]);
+ if (i == adjustedExponent)
+ builder.append('.');
+ }
+ return builder.toString();
+ }
+
+ builder.appendLiteral("0.");
+ for (int i = adjustedExponent + 1; i < 0; ++i)
+ builder.append('0');
+
+ builder.append(digits);
+
+ } else {
+ builder.append(digits[0]);
+ while (coefficientLength >= 2 && digits[coefficientLength - 1] == '0')
+ --coefficientLength;
+ if (coefficientLength >= 2) {
+ builder.append('.');
+ for (int i = 1; i < coefficientLength; ++i)
+ builder.append(digits[i]);
+ }
+
+ if (adjustedExponent) {
+ builder.append(adjustedExponent < 0 ? "e" : "e+");
+ builder.appendNumber(adjustedExponent);
+ }
+ }
+ return builder.toString();
+}
+
+bool Decimal::toString(char* strBuf, size_t bufLength) const
+{
+ ASSERT(bufLength > 0);
+ String str = toString();
+ size_t length = str.copy(strBuf, bufLength);
+ if (length < bufLength) {
+ strBuf[length] = '\0';
+ return true;
+ }
+ strBuf[bufLength - 1] = '\0';
+ return false;
+}
+
+Decimal Decimal::zero(Sign sign)
+{
+ return Decimal(EncodedData(sign, EncodedData::ClassZero));
+}
+
+} // namespace blink
+
+// Implementation of DoubleConversion.h:
+
+namespace mozilla {
+
+Maybe<double> StringToDouble(Span<const char> aStringSpan) {
+ bool valid = false;
+ double result = mozToDouble(aStringSpan, &valid);
+ return valid ? Some(result) : Nothing();
+}
+
+}
diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h
new file mode 100644
index 0000000000..4bb9a841e5
--- /dev/null
+++ b/mozglue/misc/decimal/Decimal.h
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Imported from:
+ * https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/platform/Decimal.h
+ * Check UPSTREAM-GIT-SHA for the commit ID of the last update from Blink core.
+ */
+
+#ifndef Decimal_h
+#define Decimal_h
+
+#include "mozilla/Assertions.h"
+#include <stdint.h>
+#include "mozilla/Types.h"
+
+#include <string>
+
+#ifndef ASSERT
+#define DEFINED_ASSERT_FOR_DECIMAL_H 1
+#define ASSERT MOZ_ASSERT
+#endif
+
+#define PLATFORM_EXPORT
+
+// To use USING_FAST_MALLOC we'd need:
+// https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/wtf/Allocator.h
+// Since we don't allocate Decimal objects, no need.
+#define USING_FAST_MALLOC(type) \
+ void ignore_this_dummy_method() = delete
+
+#define DISALLOW_NEW() \
+ private: \
+ void* operator new(size_t) = delete; \
+ void* operator new(size_t, void*) = delete; \
+ public:
+
+namespace blink {
+
+namespace DecimalPrivate {
+constexpr int ExponentMax = 1023;
+constexpr int ExponentMin = -1023;
+constexpr int Precision = 18;
+
+static const uint64_t MaxCoefficient = UINT64_C(0xDE0B6B3A763FFFF); // 999999999999999999 == 18 9's
+class SpecialValueHandler;
+}
+
+struct DecimalLiteral {
+ int32_t value;
+ friend constexpr DecimalLiteral operator*(int32_t lhs, DecimalLiteral rhs) {
+ return {lhs * rhs.value};
+ }
+ constexpr DecimalLiteral operator-() {
+ return {-value};
+ }
+};
+
+constexpr DecimalLiteral operator""_d(unsigned long long value) {
+ return {static_cast<int32_t>(value)};
+}
+
+// This class represents decimal base floating point number.
+//
+// FIXME: Once all C++ compiler support decimal type, we should replace this
+// class to compiler supported one. See below URI for current status of decimal
+// type for C++: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1977.html
+class PLATFORM_EXPORT Decimal {
+ USING_FAST_MALLOC(Decimal);
+public:
+ enum Sign {
+ Positive,
+ Negative,
+ };
+
+ // You should not use EncodedData other than unit testing.
+ class EncodedData {
+ DISALLOW_NEW();
+ // For accessing FormatClass.
+ friend class Decimal;
+ friend class DecimalPrivate::SpecialValueHandler;
+ public:
+ constexpr EncodedData(Sign sign, int exponent, uint64_t coefficient)
+ : m_coefficient(0),
+ m_exponent(0),
+ m_formatClass(coefficient ? ClassNormal : ClassZero),
+ m_sign(sign) {
+ if (exponent >= DecimalPrivate::ExponentMin &&
+ exponent <= DecimalPrivate::ExponentMax) {
+ while (coefficient > DecimalPrivate::MaxCoefficient) {
+ coefficient /= 10;
+ ++exponent;
+ }
+ }
+
+ if (exponent > DecimalPrivate::ExponentMax) {
+ m_formatClass = ClassInfinity;
+ return;
+ }
+
+ if (exponent < DecimalPrivate::ExponentMin) {
+ m_formatClass = ClassZero;
+ return;
+ }
+
+ m_coefficient = coefficient;
+ m_exponent = static_cast<int16_t>(exponent);
+ }
+
+ bool operator==(const EncodedData&) const;
+ bool operator!=(const EncodedData& another) const { return !operator==(another); }
+
+ uint64_t coefficient() const { return m_coefficient; }
+ int countDigits() const;
+ int exponent() const { return m_exponent; }
+ bool isFinite() const { return !isSpecial(); }
+ bool isInfinity() const { return m_formatClass == ClassInfinity; }
+ bool isNaN() const { return m_formatClass == ClassNaN; }
+ bool isSpecial() const { return m_formatClass == ClassInfinity || m_formatClass == ClassNaN; }
+ bool isZero() const { return m_formatClass == ClassZero; }
+ Sign sign() const { return m_sign; }
+ void setSign(Sign sign) { m_sign = sign; }
+
+ private:
+ enum FormatClass {
+ ClassInfinity,
+ ClassNormal,
+ ClassNaN,
+ ClassZero,
+ };
+
+ constexpr EncodedData(Sign sign, FormatClass formatClass)
+ : m_coefficient(0),
+ m_exponent(0),
+ m_formatClass(formatClass),
+ m_sign(sign) {}
+
+ FormatClass formatClass() const { return m_formatClass; }
+
+ uint64_t m_coefficient;
+ int16_t m_exponent;
+ FormatClass m_formatClass;
+ Sign m_sign;
+ };
+
+ constexpr explicit Decimal(DecimalLiteral i32)
+ : m_data(i32.value < 0 ? Negative : Positive, 0,
+ i32.value < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32.value))
+ : static_cast<uint64_t>(i32.value)) {}
+
+ MFBT_API explicit Decimal(int32_t i32 = 0);
+ MFBT_API Decimal(Sign sign, int exponent, uint64_t coefficient);
+ MFBT_API Decimal(const Decimal&);
+
+ MFBT_API Decimal& operator=(const Decimal&);
+ MFBT_API Decimal& operator+=(const Decimal&);
+ MFBT_API Decimal& operator-=(const Decimal&);
+ MFBT_API Decimal& operator*=(const Decimal&);
+ MFBT_API Decimal& operator/=(const Decimal&);
+
+ MFBT_API Decimal operator-() const;
+
+ MFBT_API bool operator==(const Decimal&) const;
+ MFBT_API bool operator!=(const Decimal&) const;
+ MFBT_API bool operator<(const Decimal&) const;
+ MFBT_API bool operator<=(const Decimal&) const;
+ MFBT_API bool operator>(const Decimal&) const;
+ MFBT_API bool operator>=(const Decimal&) const;
+
+ MFBT_API Decimal operator+(const Decimal&) const;
+ MFBT_API Decimal operator-(const Decimal&) const;
+ MFBT_API Decimal operator*(const Decimal&) const;
+ MFBT_API Decimal operator/(const Decimal&) const;
+
+ int exponent() const
+ {
+ ASSERT(isFinite());
+ return m_data.exponent();
+ }
+
+ bool isFinite() const { return m_data.isFinite(); }
+ bool isInfinity() const { return m_data.isInfinity(); }
+ bool isNaN() const { return m_data.isNaN(); }
+ bool isNegative() const { return sign() == Negative; }
+ bool isPositive() const { return sign() == Positive; }
+ bool isSpecial() const { return m_data.isSpecial(); }
+ bool isZero() const { return m_data.isZero(); }
+
+ MFBT_API Decimal abs() const;
+ MFBT_API Decimal ceil() const;
+ MFBT_API Decimal floor() const;
+ MFBT_API Decimal remainder(const Decimal&) const;
+ MFBT_API Decimal round() const;
+
+ MFBT_API double toDouble() const;
+ // Note: toString method supports infinity and nan but fromString not.
+ MFBT_API std::string toString() const;
+ MFBT_API bool toString(char* strBuf, size_t bufLength) const;
+
+ static MFBT_API Decimal fromDouble(double);
+ // fromString supports following syntax EBNF:
+ // number ::= sign? digit+ ('.' digit*) (exponent-marker sign? digit+)?
+ // | sign? '.' digit+ (exponent-marker sign? digit+)?
+ // sign ::= '+' | '-'
+ // exponent-marker ::= 'e' | 'E'
+ // digit ::= '0' | '1' | ... | '9'
+ // Note: fromString doesn't support "infinity" and "nan".
+ static MFBT_API Decimal fromString(const std::string& aValue);
+ static MFBT_API Decimal infinity(Sign);
+ static MFBT_API Decimal nan();
+ static MFBT_API Decimal zero(Sign);
+
+ // You should not use below methods. We expose them for unit testing.
+ MFBT_API explicit Decimal(const EncodedData&);
+ const EncodedData& value() const { return m_data; }
+
+private:
+ struct AlignedOperands {
+ uint64_t lhsCoefficient;
+ uint64_t rhsCoefficient;
+ int exponent;
+ };
+
+ MFBT_API explicit Decimal(double);
+ MFBT_API Decimal compareTo(const Decimal&) const;
+
+ static MFBT_API AlignedOperands alignOperands(const Decimal& lhs, const Decimal& rhs);
+ static inline Sign invertSign(Sign sign) { return sign == Negative ? Positive : Negative; }
+
+ Sign sign() const { return m_data.sign(); }
+
+ EncodedData m_data;
+};
+
+} // namespace blink
+
+namespace mozilla {
+typedef blink::Decimal Decimal;
+using blink::operator""_d;
+} // namespace mozilla
+
+#undef USING_FAST_MALLOC
+
+#ifdef DEFINED_ASSERT_FOR_DECIMAL_H
+#undef DEFINED_ASSERT_FOR_DECIMAL_H
+#undef ASSERT
+#endif
+
+#endif // Decimal_h
diff --git a/mozglue/misc/decimal/DoubleConversion.h b/mozglue/misc/decimal/DoubleConversion.h
new file mode 100644
index 0000000000..14c19e2540
--- /dev/null
+++ b/mozglue/misc/decimal/DoubleConversion.h
@@ -0,0 +1,27 @@
+/* -*- 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/. */
+
+/* A utility function that converts a string to a double independent of OS locale. */
+
+#ifndef MOZILLA_DOUBLECONVERSION_H
+#define MOZILLA_DOUBLECONVERSION_H
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+
+#include <string>
+
+namespace mozilla {
+
+// Parses aStringSpan into a double floating point value. Always treats . as the
+// decimal separator, regardless of OS locale. Consumes the entire string;
+// trailing garbage is invalid. Returns Nothing() for invalid input.
+// The implementation uses double_conversion::StringToDoubleConverter with
+// NO_FLAGS, see double-conversion/string-to-double.h for more documentation.
+Maybe<double> StringToDouble(Span<const char> aStringSpan);
+
+}
+
+#endif // MOZILLA_DOUBLECONVERSION_H
diff --git a/mozglue/misc/decimal/UPSTREAM-GIT-SHA b/mozglue/misc/decimal/UPSTREAM-GIT-SHA
new file mode 100644
index 0000000000..ed86150b28
--- /dev/null
+++ b/mozglue/misc/decimal/UPSTREAM-GIT-SHA
@@ -0,0 +1 @@
+cad4c9e3b3c9e80bb189059373db528272bca96f
diff --git a/mozglue/misc/decimal/add-doubleconversion-impl.patch b/mozglue/misc/decimal/add-doubleconversion-impl.patch
new file mode 100644
index 0000000000..1cf0fb6ff1
--- /dev/null
+++ b/mozglue/misc/decimal/add-doubleconversion-impl.patch
@@ -0,0 +1,42 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -25,16 +25,17 @@
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ #include "Decimal.h"
+ #include "moz-decimal-utils.h"
++#include "DoubleConversion.h"
+
+ using namespace moz_decimal_utils;
+
+ #include <algorithm>
+ #include <float.h>
+
+ namespace blink {
+
+@@ -1043,8 +1044,20 @@ bool Decimal::toString(char* strBuf, siz
+ }
+
+ Decimal Decimal::zero(Sign sign)
+ {
+ return Decimal(EncodedData(sign, EncodedData::ClassZero));
+ }
+
+ } // namespace blink
++
++// Implementation of DoubleConversion.h:
++
++namespace mozilla {
++
++Maybe<double> StringToDouble(Span<const char> aStringSpan) {
++ bool valid = false;
++ double result = mozToDouble(aStringSpan, &valid);
++ return valid ? Some(result) : Nothing();
++}
++
++}
diff --git a/mozglue/misc/decimal/comparison-with-nan.patch b/mozglue/misc/decimal/comparison-with-nan.patch
new file mode 100644
index 0000000000..0e274ce033
--- /dev/null
+++ b/mozglue/misc/decimal/comparison-with-nan.patch
@@ -0,0 +1,67 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -509,21 +509,25 @@ Decimal Decimal::operator/(const Decimal
+ if (remainder > divisor / 2)
+ ++result;
+
+ return Decimal(resultSign, resultExponent, result);
+ }
+
+ bool Decimal::operator==(const Decimal& rhs) const
+ {
++ if (isNaN() || rhs.isNaN())
++ return false;
+ return m_data == rhs.m_data || compareTo(rhs).isZero();
+ }
+
+ bool Decimal::operator!=(const Decimal& rhs) const
+ {
++ if (isNaN() || rhs.isNaN())
++ return true;
+ if (m_data == rhs.m_data)
+ return false;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero();
+ }
+
+@@ -532,16 +536,18 @@ bool Decimal::operator<(const Decimal& r
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero() && result.isNegative();
+ }
+
+ bool Decimal::operator<=(const Decimal& rhs) const
+ {
++ if (isNaN() || rhs.isNaN())
++ return false;
+ if (m_data == rhs.m_data)
+ return true;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return result.isZero() || result.isNegative();
+ }
+
+@@ -550,16 +556,18 @@ bool Decimal::operator>(const Decimal& r
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero() && result.isPositive();
+ }
+
+ bool Decimal::operator>=(const Decimal& rhs) const
+ {
++ if (isNaN() || rhs.isNaN())
++ return false;
+ if (m_data == rhs.m_data)
+ return true;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return result.isZero() || !result.isNegative();
+ }
+
diff --git a/mozglue/misc/decimal/fix-wshadow-warnings.patch b/mozglue/misc/decimal/fix-wshadow-warnings.patch
new file mode 100644
index 0000000000..465c61a223
--- /dev/null
+++ b/mozglue/misc/decimal/fix-wshadow-warnings.patch
@@ -0,0 +1,171 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -118,18 +118,18 @@ Decimal SpecialValueHandler::value() con
+ ASSERT_NOT_REACHED();
+ return m_lhs;
+ }
+ }
+
+ // This class is used for 128 bit unsigned integer arithmetic.
+ class UInt128 {
+ public:
+- UInt128(uint64_t low, uint64_t high)
+- : m_high(high), m_low(low)
++ UInt128(uint64_t aLow, uint64_t aHigh)
++ : m_high(aHigh), m_low(aLow)
+ {
+ }
+
+ UInt128& operator/=(uint32_t);
+
+ uint64_t high() const { return m_high; }
+ uint64_t low() const { return m_low; }
+
+@@ -224,68 +224,68 @@ static uint64_t scaleUp(uint64_t x, int
+ z = z * z;
+ }
+ }
+
+ } // namespace DecimalPrivate
+
+ using namespace DecimalPrivate;
+
+-Decimal::EncodedData::EncodedData(Sign sign, FormatClass formatClass)
++Decimal::EncodedData::EncodedData(Sign aSign, FormatClass aFormatClass)
+ : m_coefficient(0)
+ , m_exponent(0)
+- , m_formatClass(formatClass)
+- , m_sign(sign)
++ , m_formatClass(aFormatClass)
++ , m_sign(aSign)
+ {
+ }
+
+-Decimal::EncodedData::EncodedData(Sign sign, int exponent, uint64_t coefficient)
+- : m_formatClass(coefficient ? ClassNormal : ClassZero)
+- , m_sign(sign)
++Decimal::EncodedData::EncodedData(Sign aSign, int aExponent, uint64_t aCoefficient)
++ : m_formatClass(aCoefficient ? ClassNormal : ClassZero)
++ , m_sign(aSign)
+ {
+- if (exponent >= ExponentMin && exponent <= ExponentMax) {
+- while (coefficient > MaxCoefficient) {
+- coefficient /= 10;
+- ++exponent;
++ if (aExponent >= ExponentMin && aExponent <= ExponentMax) {
++ while (aCoefficient > MaxCoefficient) {
++ aCoefficient /= 10;
++ ++aExponent;
+ }
+ }
+
+- if (exponent > ExponentMax) {
++ if (aExponent > ExponentMax) {
+ m_coefficient = 0;
+ m_exponent = 0;
+ m_formatClass = ClassInfinity;
+ return;
+ }
+
+- if (exponent < ExponentMin) {
++ if (aExponent < ExponentMin) {
+ m_coefficient = 0;
+ m_exponent = 0;
+ m_formatClass = ClassZero;
+ return;
+ }
+
+- m_coefficient = coefficient;
+- m_exponent = static_cast<int16_t>(exponent);
++ m_coefficient = aCoefficient;
++ m_exponent = static_cast<int16_t>(aExponent);
+ }
+
+ bool Decimal::EncodedData::operator==(const EncodedData& another) const
+ {
+ return m_sign == another.m_sign
+ && m_formatClass == another.m_formatClass
+ && m_exponent == another.m_exponent
+ && m_coefficient == another.m_coefficient;
+ }
+
+ Decimal::Decimal(int32_t i32)
+ : m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32))
+ {
+ }
+
+-Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient)
+- : m_data(sign, coefficient ? exponent : 0, coefficient)
++Decimal::Decimal(Sign aSign, int aExponent, uint64_t aCoefficient)
++ : m_data(aSign, aCoefficient ? aExponent : 0, aCoefficient)
+ {
+ }
+
+ Decimal::Decimal(const EncodedData& data)
+ : m_data(data)
+ {
+ }
+
+@@ -479,32 +479,32 @@ Decimal Decimal::operator/(const Decimal
+ if (rhs.isZero())
+ return lhs.isZero() ? nan() : infinity(resultSign);
+
+ int resultExponent = lhs.exponent() - rhs.exponent();
+
+ if (lhs.isZero())
+ return Decimal(resultSign, resultExponent, 0);
+
+- uint64_t remainder = lhs.m_data.coefficient();
++ uint64_t lhsRemainder = lhs.m_data.coefficient();
+ const uint64_t divisor = rhs.m_data.coefficient();
+ uint64_t result = 0;
+ while (result < MaxCoefficient / 100) {
+- while (remainder < divisor) {
+- remainder *= 10;
++ while (lhsRemainder < divisor) {
++ lhsRemainder *= 10;
+ result *= 10;
+ --resultExponent;
+ }
+- result += remainder / divisor;
+- remainder %= divisor;
+- if (!remainder)
++ result += lhsRemainder / divisor;
++ lhsRemainder %= divisor;
++ if (!lhsRemainder)
+ break;
+ }
+
+- if (remainder > divisor / 2)
++ if (lhsRemainder > divisor / 2)
+ ++result;
+
+ return Decimal(resultSign, resultExponent, result);
+ }
+
+ bool Decimal::operator==(const Decimal& rhs) const
+ {
+ if (isNaN() || rhs.isNaN())
+diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h
+--- a/mozglue/misc/decimal/Decimal.h
++++ b/mozglue/misc/decimal/Decimal.h
+@@ -88,17 +88,17 @@ public:
+ int countDigits() const;
+ int exponent() const { return m_exponent; }
+ bool isFinite() const { return !isSpecial(); }
+ bool isInfinity() const { return m_formatClass == ClassInfinity; }
+ bool isNaN() const { return m_formatClass == ClassNaN; }
+ bool isSpecial() const { return m_formatClass == ClassInfinity || m_formatClass == ClassNaN; }
+ bool isZero() const { return m_formatClass == ClassZero; }
+ Sign sign() const { return m_sign; }
+- void setSign(Sign sign) { m_sign = sign; }
++ void setSign(Sign aSign) { m_sign = aSign; }
+
+ private:
+ enum FormatClass {
+ ClassInfinity,
+ ClassNormal,
+ ClassNaN,
+ ClassZero,
+ };
diff --git a/mozglue/misc/decimal/mfbt-abi-markers.patch b/mozglue/misc/decimal/mfbt-abi-markers.patch
new file mode 100644
index 0000000000..1d50d3d643
--- /dev/null
+++ b/mozglue/misc/decimal/mfbt-abi-markers.patch
@@ -0,0 +1,150 @@
+diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h
+--- a/mozglue/misc/decimal/Decimal.h
++++ b/mozglue/misc/decimal/Decimal.h
+@@ -26,16 +26,18 @@
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ #ifndef Decimal_h
+ #define Decimal_h
+
++#include "mozilla/Types.h"
++
+ #include "platform/PlatformExport.h"
+ #include "wtf/Allocator.h"
+ #include "wtf/Assertions.h"
+ #include "wtf/text/WTFString.h"
+ #include <stdint.h>
+
+ namespace blink {
+
+@@ -91,92 +93,92 @@ public:
+ FormatClass formatClass() const { return m_formatClass; }
+
+ uint64_t m_coefficient;
+ int16_t m_exponent;
+ FormatClass m_formatClass;
+ Sign m_sign;
+ };
+
+- Decimal(int32_t = 0);
+- Decimal(Sign, int exponent, uint64_t coefficient);
+- Decimal(const Decimal&);
++ MFBT_API explicit Decimal(int32_t = 0);
++ MFBT_API Decimal(Sign, int exponent, uint64_t coefficient);
++ MFBT_API Decimal(const Decimal&);
+
+- Decimal& operator=(const Decimal&);
+- Decimal& operator+=(const Decimal&);
+- Decimal& operator-=(const Decimal&);
+- Decimal& operator*=(const Decimal&);
+- Decimal& operator/=(const Decimal&);
++ MFBT_API Decimal& operator=(const Decimal&);
++ MFBT_API Decimal& operator+=(const Decimal&);
++ MFBT_API Decimal& operator-=(const Decimal&);
++ MFBT_API Decimal& operator*=(const Decimal&);
++ MFBT_API Decimal& operator/=(const Decimal&);
+
+- Decimal operator-() const;
++ MFBT_API Decimal operator-() const;
+
+- bool operator==(const Decimal&) const;
+- bool operator!=(const Decimal&) const;
+- bool operator<(const Decimal&) const;
+- bool operator<=(const Decimal&) const;
+- bool operator>(const Decimal&) const;
+- bool operator>=(const Decimal&) const;
++ MFBT_API bool operator==(const Decimal&) const;
++ MFBT_API bool operator!=(const Decimal&) const;
++ MFBT_API bool operator<(const Decimal&) const;
++ MFBT_API bool operator<=(const Decimal&) const;
++ MFBT_API bool operator>(const Decimal&) const;
++ MFBT_API bool operator>=(const Decimal&) const;
+
+- Decimal operator+(const Decimal&) const;
+- Decimal operator-(const Decimal&) const;
+- Decimal operator*(const Decimal&) const;
+- Decimal operator/(const Decimal&) const;
++ MFBT_API Decimal operator+(const Decimal&) const;
++ MFBT_API Decimal operator-(const Decimal&) const;
++ MFBT_API Decimal operator*(const Decimal&) const;
++ MFBT_API Decimal operator/(const Decimal&) const;
+
+ int exponent() const
+ {
+ ASSERT(isFinite());
+ return m_data.exponent();
+ }
+
+ bool isFinite() const { return m_data.isFinite(); }
+ bool isInfinity() const { return m_data.isInfinity(); }
+ bool isNaN() const { return m_data.isNaN(); }
+ bool isNegative() const { return sign() == Negative; }
+ bool isPositive() const { return sign() == Positive; }
+ bool isSpecial() const { return m_data.isSpecial(); }
+ bool isZero() const { return m_data.isZero(); }
+
+- Decimal abs() const;
+- Decimal ceil() const;
+- Decimal floor() const;
+- Decimal remainder(const Decimal&) const;
+- Decimal round() const;
++ MFBT_API Decimal abs() const;
++ MFBT_API Decimal ceil() const;
++ MFBT_API Decimal floor() const;
++ MFBT_API Decimal remainder(const Decimal&) const;
++ MFBT_API Decimal round() const;
+
+- double toDouble() const;
++ MFBT_API double toDouble() const;
+ // Note: toString method supports infinity and nan but fromString not.
+- String toString() const;
++ MFBT_API String toString() const;
+
+- static Decimal fromDouble(double);
++ static MFBT_API Decimal fromDouble(double);
+ // fromString supports following syntax EBNF:
+ // number ::= sign? digit+ ('.' digit*) (exponent-marker sign? digit+)?
+ // | sign? '.' digit+ (exponent-marker sign? digit+)?
+ // sign ::= '+' | '-'
+ // exponent-marker ::= 'e' | 'E'
+ // digit ::= '0' | '1' | ... | '9'
+ // Note: fromString doesn't support "infinity" and "nan".
+- static Decimal fromString(const String&);
+- static Decimal infinity(Sign);
+- static Decimal nan();
+- static Decimal zero(Sign);
++ static MFBT_API Decimal fromString(const String&);
++ static MFBT_API Decimal infinity(Sign);
++ static MFBT_API Decimal nan();
++ static MFBT_API Decimal zero(Sign);
+
+ // You should not use below methods. We expose them for unit testing.
+- explicit Decimal(const EncodedData&);
++ MFBT_API explicit Decimal(const EncodedData&);
+ const EncodedData& value() const { return m_data; }
+
+ private:
+ struct AlignedOperands {
+ uint64_t lhsCoefficient;
+ uint64_t rhsCoefficient;
+ int exponent;
+ };
+
+- Decimal(double);
+- Decimal compareTo(const Decimal&) const;
++ MFBT_API explicit Decimal(double);
++ MFBT_API Decimal compareTo(const Decimal&) const;
+
+- static AlignedOperands alignOperands(const Decimal& lhs, const Decimal& rhs);
++ static MFBT_API AlignedOperands alignOperands(const Decimal& lhs, const Decimal& rhs);
+ static inline Sign invertSign(Sign sign) { return sign == Negative ? Positive : Negative; }
+
+ Sign sign() const { return m_data.sign(); }
+
+ EncodedData m_data;
+ };
+
+ } // namespace blink
diff --git a/mozglue/misc/decimal/moz-constexpr-decimal.patch b/mozglue/misc/decimal/moz-constexpr-decimal.patch
new file mode 100644
index 0000000000..845352bd05
--- /dev/null
+++ b/mozglue/misc/decimal/moz-constexpr-decimal.patch
@@ -0,0 +1,218 @@
+diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
+index 7fdf38c5cb7c3..8bce5c3312f55 100644
+--- a/dom/html/HTMLInputElement.cpp
++++ b/dom/html/HTMLInputElement.cpp
+@@ -187,16 +187,18 @@ static const nsAttrValue::EnumTable kCaptureTable[] = {
+
+ static const nsAttrValue::EnumTable* kCaptureDefault = &kCaptureTable[2];
+
+-const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
+-const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
+-const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
+-const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
+-const Decimal HTMLInputElement::kStepScaleFactorWeek = Decimal(7 * 86400000);
+-const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
+-const Decimal HTMLInputElement::kDefaultStepBaseWeek = Decimal(-259200000);
+-const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
+-const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
+-const Decimal HTMLInputElement::kStepAny = Decimal(0);
++using namespace blink;
++
++constexpr Decimal HTMLInputElement::kStepScaleFactorDate(86400000_d);
++constexpr Decimal HTMLInputElement::kStepScaleFactorNumberRange(1_d);
++constexpr Decimal HTMLInputElement::kStepScaleFactorTime(1000_d);
++constexpr Decimal HTMLInputElement::kStepScaleFactorMonth(1_d);
++constexpr Decimal HTMLInputElement::kStepScaleFactorWeek(7 * 86400000_d);
++constexpr Decimal HTMLInputElement::kDefaultStepBase(0_d);
++constexpr Decimal HTMLInputElement::kDefaultStepBaseWeek(-259200000_d);
++constexpr Decimal HTMLInputElement::kDefaultStep(1_d);
++constexpr Decimal HTMLInputElement::kDefaultStepTime(60_d);
++constexpr Decimal HTMLInputElement::kStepAny(0_d);
+
+ const double HTMLInputElement::kMinimumYear = 1;
+ const double HTMLInputElement::kMaximumYear = 275760;
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+index cc828e28439f5..7d2bcfa712c5d 100644
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -41,12 +41,6 @@ namespace blink {
+
+ namespace DecimalPrivate {
+
+-static int const ExponentMax = 1023;
+-static int const ExponentMin = -1023;
+-static int const Precision = 18;
+-
+-static const uint64_t MaxCoefficient = UINT64_C(0xDE0B6B3A763FFFF); // 999999999999999999 == 18 9's
+-
+ // This class handles Decimal special values.
+ class SpecialValueHandler {
+ STACK_ALLOCATED();
+@@ -230,43 +224,6 @@ static uint64_t scaleUp(uint64_t x, int n)
+
+ using namespace DecimalPrivate;
+
+-Decimal::EncodedData::EncodedData(Sign sign, FormatClass formatClass)
+- : m_coefficient(0)
+- , m_exponent(0)
+- , m_formatClass(formatClass)
+- , m_sign(sign)
+-{
+-}
+-
+-Decimal::EncodedData::EncodedData(Sign sign, int exponent, uint64_t coefficient)
+- : m_formatClass(coefficient ? ClassNormal : ClassZero)
+- , m_sign(sign)
+-{
+- if (exponent >= ExponentMin && exponent <= ExponentMax) {
+- while (coefficient > MaxCoefficient) {
+- coefficient /= 10;
+- ++exponent;
+- }
+- }
+-
+- if (exponent > ExponentMax) {
+- m_coefficient = 0;
+- m_exponent = 0;
+- m_formatClass = ClassInfinity;
+- return;
+- }
+-
+- if (exponent < ExponentMin) {
+- m_coefficient = 0;
+- m_exponent = 0;
+- m_formatClass = ClassZero;
+- return;
+- }
+-
+- m_coefficient = coefficient;
+- m_exponent = static_cast<int16_t>(exponent);
+-}
+-
+ bool Decimal::EncodedData::operator==(const EncodedData& another) const
+ {
+ return m_sign == another.m_sign
+@@ -275,15 +232,12 @@ bool Decimal::EncodedData::operator==(const EncodedData& another) const
+ && m_coefficient == another.m_coefficient;
+ }
+
++
+ Decimal::Decimal(int32_t i32)
+- : m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32))
+-{
+-}
++ : Decimal(DecimalLiteral{i32}) {}
+
+ Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient)
+- : m_data(sign, coefficient ? exponent : 0, coefficient)
+-{
+-}
++ : m_data(sign, coefficient ? exponent : 0, coefficient) {}
+
+ Decimal::Decimal(const EncodedData& data)
+ : m_data(data)
+diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h
+index 10d0e2c7cefa3..4bb9a841e585f 100644
+--- a/mozglue/misc/decimal/Decimal.h
++++ b/mozglue/misc/decimal/Decimal.h
+@@ -65,9 +65,28 @@
+ namespace blink {
+
+ namespace DecimalPrivate {
++constexpr int ExponentMax = 1023;
++constexpr int ExponentMin = -1023;
++constexpr int Precision = 18;
++
++static const uint64_t MaxCoefficient = UINT64_C(0xDE0B6B3A763FFFF); // 999999999999999999 == 18 9's
+ class SpecialValueHandler;
+ }
+
++struct DecimalLiteral {
++ int32_t value;
++ friend constexpr DecimalLiteral operator*(int32_t lhs, DecimalLiteral rhs) {
++ return {lhs * rhs.value};
++ }
++ constexpr DecimalLiteral operator-() {
++ return {-value};
++ }
++};
++
++constexpr DecimalLiteral operator""_d(unsigned long long value) {
++ return {static_cast<int32_t>(value)};
++}
++
+ // This class represents decimal base floating point number.
+ //
+ // FIXME: Once all C++ compiler support decimal type, we should replace this
+@@ -88,7 +107,32 @@ public:
+ friend class Decimal;
+ friend class DecimalPrivate::SpecialValueHandler;
+ public:
+- EncodedData(Sign, int exponent, uint64_t coefficient);
++ constexpr EncodedData(Sign sign, int exponent, uint64_t coefficient)
++ : m_coefficient(0),
++ m_exponent(0),
++ m_formatClass(coefficient ? ClassNormal : ClassZero),
++ m_sign(sign) {
++ if (exponent >= DecimalPrivate::ExponentMin &&
++ exponent <= DecimalPrivate::ExponentMax) {
++ while (coefficient > DecimalPrivate::MaxCoefficient) {
++ coefficient /= 10;
++ ++exponent;
++ }
++ }
++
++ if (exponent > DecimalPrivate::ExponentMax) {
++ m_formatClass = ClassInfinity;
++ return;
++ }
++
++ if (exponent < DecimalPrivate::ExponentMin) {
++ m_formatClass = ClassZero;
++ return;
++ }
++
++ m_coefficient = coefficient;
++ m_exponent = static_cast<int16_t>(exponent);
++ }
+
+ bool operator==(const EncodedData&) const;
+ bool operator!=(const EncodedData& another) const { return !operator==(another); }
+@@ -112,7 +156,12 @@ public:
+ ClassZero,
+ };
+
+- EncodedData(Sign, FormatClass);
++ constexpr EncodedData(Sign sign, FormatClass formatClass)
++ : m_coefficient(0),
++ m_exponent(0),
++ m_formatClass(formatClass),
++ m_sign(sign) {}
++
+ FormatClass formatClass() const { return m_formatClass; }
+
+ uint64_t m_coefficient;
+@@ -121,8 +170,13 @@ public:
+ Sign m_sign;
+ };
+
+- MFBT_API explicit Decimal(int32_t = 0);
+- MFBT_API Decimal(Sign, int exponent, uint64_t coefficient);
++ constexpr explicit Decimal(DecimalLiteral i32)
++ : m_data(i32.value < 0 ? Negative : Positive, 0,
++ i32.value < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32.value))
++ : static_cast<uint64_t>(i32.value)) {}
++
++ MFBT_API explicit Decimal(int32_t i32 = 0);
++ MFBT_API Decimal(Sign sign, int exponent, uint64_t coefficient);
+ MFBT_API Decimal(const Decimal&);
+
+ MFBT_API Decimal& operator=(const Decimal&);
+@@ -209,6 +263,7 @@ private:
+
+ namespace mozilla {
+ typedef blink::Decimal Decimal;
++using blink::operator""_d;
+ } // namespace mozilla
+
+ #undef USING_FAST_MALLOC
diff --git a/mozglue/misc/decimal/moz-decimal-utils.h b/mozglue/misc/decimal/moz-decimal-utils.h
new file mode 100644
index 0000000000..05ea61e4bd
--- /dev/null
+++ b/mozglue/misc/decimal/moz-decimal-utils.h
@@ -0,0 +1,110 @@
+/* -*- 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 MOZ_DECIMAL_UTILS_H
+#define MOZ_DECIMAL_UTILS_H
+
+// This file contains extra includes, defines and typedefs to allow compilation
+// of Decimal.cpp under the Mozilla source without blink core dependencies. Do
+// not include it into any file other than Decimal.cpp.
+
+#include "double-conversion/double-conversion.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Casting.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Span.h"
+
+#include <cmath>
+#include <cstring>
+#include <iomanip>
+#include <limits>
+#include <sstream>
+
+#ifndef UINT64_C
+// For Android toolchain
+#define UINT64_C(c) (c ## ULL)
+#endif
+
+#ifdef ASSERT
+#undef ASSERT
+#endif
+#define ASSERT MOZ_ASSERT
+
+#define ASSERT_NOT_REACHED() MOZ_ASSERT_UNREACHABLE("moz-decimal-utils.h")
+
+#define STACK_ALLOCATED() DISALLOW_NEW()
+
+#define WTF_MAKE_NONCOPYABLE(ClassName) \
+ private: \
+ ClassName(const ClassName&) = delete; \
+ void operator=(const ClassName&) = delete;
+
+typedef std::string String;
+
+double mozToDouble(mozilla::Span<const char> aStr, bool *valid) {
+ double_conversion::StringToDoubleConverter converter(
+ double_conversion::StringToDoubleConverter::NO_FLAGS,
+ mozilla::UnspecifiedNaN<double>(), mozilla::UnspecifiedNaN<double>(), nullptr, nullptr);
+ const char* str = aStr.Elements();
+ int length = mozilla::AssertedCast<int>(aStr.Length());
+ int processed_char_count; // unused - NO_FLAGS requires the whole string to parse
+ double result = converter.StringToDouble(str, length, &processed_char_count);
+ *valid = std::isfinite(result);
+ return result;
+}
+
+double mozToDouble(const String &aStr, bool *valid) {
+ return mozToDouble(mozilla::MakeStringSpan(aStr.c_str()), valid);
+}
+
+String mozToString(double aNum) {
+ char buffer[64];
+ int buffer_length = mozilla::ArrayLength(buffer);
+ const double_conversion::DoubleToStringConverter& converter =
+ double_conversion::DoubleToStringConverter::EcmaScriptConverter();
+ double_conversion::StringBuilder builder(buffer, buffer_length);
+ converter.ToShortest(aNum, &builder);
+ return String(builder.Finalize());
+}
+
+String mozToString(int64_t aNum) {
+ std::ostringstream o;
+ o << std::setprecision(std::numeric_limits<int64_t>::digits10) << aNum;
+ return o.str();
+}
+
+String mozToString(uint64_t aNum) {
+ std::ostringstream o;
+ o << std::setprecision(std::numeric_limits<uint64_t>::digits10) << aNum;
+ return o.str();
+}
+
+namespace moz_decimal_utils {
+
+class StringBuilder
+{
+public:
+ void append(char c) {
+ mStr += c;
+ }
+ void appendLiteral(const char *aStr) {
+ mStr += aStr;
+ }
+ void appendNumber(int aNum) {
+ mStr += mozToString(int64_t(aNum));
+ }
+ void append(const String& aStr) {
+ mStr += aStr;
+ }
+ std::string toString() const {
+ return mStr;
+ }
+private:
+ std::string mStr;
+};
+
+} // namespace moz_decimal_utils
+
+#endif
diff --git a/mozglue/misc/decimal/to-moz-dependencies.patch b/mozglue/misc/decimal/to-moz-dependencies.patch
new file mode 100644
index 0000000000..bf19a6da96
--- /dev/null
+++ b/mozglue/misc/decimal/to-moz-dependencies.patch
@@ -0,0 +1,224 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -23,22 +23,20 @@
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+-#include "platform/Decimal.h"
++#include "Decimal.h"
++#include "moz-decimal-utils.h"
+
+-#include "wtf/Allocator.h"
+-#include "wtf/MathExtras.h"
+-#include "wtf/Noncopyable.h"
+-#include "wtf/text/StringBuilder.h"
++using namespace moz_decimal_utils;
+
+ #include <algorithm>
+ #include <float.h>
+
+ namespace blink {
+
+ namespace DecimalPrivate {
+
+@@ -690,17 +688,17 @@ Decimal Decimal::floor() const
+ if (isNegative() && !isMultiplePowersOfTen(m_data.coefficient(), numberOfDropDigits))
+ ++result;
+ return Decimal(sign(), 0, result);
+ }
+
+ Decimal Decimal::fromDouble(double doubleValue)
+ {
+ if (std::isfinite(doubleValue))
+- return fromString(String::numberToStringECMAScript(doubleValue));
++ return fromString(mozToString(doubleValue));
+
+ if (std::isinf(doubleValue))
+ return infinity(doubleValue < 0 ? Negative : Positive);
+
+ return nan();
+ }
+
+ Decimal Decimal::fromString(const String& str)
+@@ -931,17 +929,17 @@ Decimal Decimal::round() const
+ result /= 10;
+ return Decimal(sign(), 0, result);
+ }
+
+ double Decimal::toDouble() const
+ {
+ if (isFinite()) {
+ bool valid;
+- const double doubleValue = toString().toDouble(&valid);
++ const double doubleValue = mozToDouble(toString(), &valid);
+ return valid ? doubleValue : std::numeric_limits<double>::quiet_NaN();
+ }
+
+ if (isInfinity())
+ return isNegative() ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
+
+ return std::numeric_limits<double>::quiet_NaN();
+ }
+@@ -984,17 +982,17 @@ String Decimal::toString() const
+ ++coefficient;
+
+ while (originalExponent < 0 && coefficient && !(coefficient % 10)) {
+ coefficient /= 10;
+ ++originalExponent;
+ }
+ }
+
+- const String digits = String::number(coefficient);
++ const String digits = mozToString(coefficient);
+ int coefficientLength = static_cast<int>(digits.length());
+ const int adjustedExponent = originalExponent + coefficientLength - 1;
+ if (originalExponent <= 0 && adjustedExponent >= -6) {
+ if (!originalExponent) {
+ builder.append(digits);
+ return builder.toString();
+ }
+
+@@ -1026,14 +1024,27 @@ String Decimal::toString() const
+ if (adjustedExponent) {
+ builder.append(adjustedExponent < 0 ? "e" : "e+");
+ builder.appendNumber(adjustedExponent);
+ }
+ }
+ return builder.toString();
+ }
+
++bool Decimal::toString(char* strBuf, size_t bufLength) const
++{
++ ASSERT(bufLength > 0);
++ String str = toString();
++ size_t length = str.copy(strBuf, bufLength);
++ if (length < bufLength) {
++ strBuf[length] = '\0';
++ return true;
++ }
++ strBuf[bufLength - 1] = '\0';
++ return false;
++}
++
+ Decimal Decimal::zero(Sign sign)
+ {
+ return Decimal(EncodedData(sign, EncodedData::ClassZero));
+ }
+
+ } // namespace blink
+diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h
+--- a/mozglue/misc/decimal/Decimal.h
++++ b/mozglue/misc/decimal/Decimal.h
+@@ -23,26 +23,49 @@
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
++/**
++ * Imported from:
++ * https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/platform/Decimal.h
++ * Check UPSTREAM-GIT-SHA for the commit ID of the last update from Blink core.
++ */
++
+ #ifndef Decimal_h
+ #define Decimal_h
+
++#include "mozilla/Assertions.h"
++#include <stdint.h>
+ #include "mozilla/Types.h"
+
+-#include "platform/PlatformExport.h"
+-#include "wtf/Allocator.h"
+-#include "wtf/Assertions.h"
+-#include "wtf/text/WTFString.h"
+-#include <stdint.h>
++#include <string>
++
++#ifndef ASSERT
++#define DEFINED_ASSERT_FOR_DECIMAL_H 1
++#define ASSERT MOZ_ASSERT
++#endif
++
++#define PLATFORM_EXPORT
++
++// To use USING_FAST_MALLOC we'd need:
++// https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/wtf/Allocator.h
++// Since we don't allocate Decimal objects, no need.
++#define USING_FAST_MALLOC(type) \
++ void ignore_this_dummy_method() = delete
++
++#define DISALLOW_NEW() \
++ private: \
++ void* operator new(size_t) = delete; \
++ void* operator new(size_t, void*) = delete; \
++ public:
+
+ namespace blink {
+
+ namespace DecimalPrivate {
+ class SpecialValueHandler;
+ }
+
+ // This class represents decimal base floating point number.
+@@ -139,27 +162,28 @@ public:
+ MFBT_API Decimal abs() const;
+ MFBT_API Decimal ceil() const;
+ MFBT_API Decimal floor() const;
+ MFBT_API Decimal remainder(const Decimal&) const;
+ MFBT_API Decimal round() const;
+
+ MFBT_API double toDouble() const;
+ // Note: toString method supports infinity and nan but fromString not.
+- MFBT_API String toString() const;
++ MFBT_API std::string toString() const;
++ MFBT_API bool toString(char* strBuf, size_t bufLength) const;
+
+ static MFBT_API Decimal fromDouble(double);
+ // fromString supports following syntax EBNF:
+ // number ::= sign? digit+ ('.' digit*) (exponent-marker sign? digit+)?
+ // | sign? '.' digit+ (exponent-marker sign? digit+)?
+ // sign ::= '+' | '-'
+ // exponent-marker ::= 'e' | 'E'
+ // digit ::= '0' | '1' | ... | '9'
+ // Note: fromString doesn't support "infinity" and "nan".
+- static MFBT_API Decimal fromString(const String&);
++ static MFBT_API Decimal fromString(const std::string& aValue);
+ static MFBT_API Decimal infinity(Sign);
+ static MFBT_API Decimal nan();
+ static MFBT_API Decimal zero(Sign);
+
+ // You should not use below methods. We expose them for unit testing.
+ MFBT_API explicit Decimal(const EncodedData&);
+ const EncodedData& value() const { return m_data; }
+
+@@ -178,9 +202,20 @@ private:
+
+ Sign sign() const { return m_data.sign(); }
+
+ EncodedData m_data;
+ };
+
+ } // namespace blink
+
++namespace mozilla {
++typedef blink::Decimal Decimal;
++} // namespace mozilla
++
++#undef USING_FAST_MALLOC
++
++#ifdef DEFINED_ASSERT_FOR_DECIMAL_H
++#undef DEFINED_ASSERT_FOR_DECIMAL_H
++#undef ASSERT
++#endif
++
+ #endif // Decimal_h
diff --git a/mozglue/misc/decimal/update.sh b/mozglue/misc/decimal/update.sh
new file mode 100755
index 0000000000..696685f9ba
--- /dev/null
+++ b/mozglue/misc/decimal/update.sh
@@ -0,0 +1,61 @@
+# Usage: ./update.sh [blink-core-source-directory]
+#
+# Copies the needed files from a directory containing the original
+# Decimal.h and Decimal.cpp source that we need.
+# If [blink-core-source-directory] is not specified, this script will
+# attempt to download the latest versions using git.
+
+set -e
+
+FILES=(
+ "Decimal.h"
+ "Decimal.cpp"
+)
+
+OWN_NAME=`basename $0`
+
+if [ $# -gt 1 ]; then
+ echo "$OWN_NAME: Too many arguments">&2
+ exit 1
+fi
+
+if [ $# -eq 1 ]; then
+ BLINK_CORE_DIR="$1"
+ for F in "${FILES[@]}"
+ do
+ P="$BLINK_CORE_DIR/$F"
+ if [ ! -f "$P" ]; then
+ echo "$OWN_NAME: Couldn't find file: $P">&2
+ exit 1
+ fi
+ done
+ for F in "${FILES[@]}"
+ do
+ P="$BLINK_CORE_DIR/$F"
+ cp "$P" .
+ done
+else
+ #LATEST_SHA=$(cat UPSTREAM-GIT-SHA)
+ LATEST_SHA=$(git ls-remote https://chromium.googlesource.com/chromium/src.git/ | awk "/refs\/heads\/master/ {print \$1}")
+ REPO_PATH="https://chromium.googlesource.com/chromium/src.git/+/$LATEST_SHA/third_party/WebKit/Source/platform"
+ #REPO_PATH="https://github.com/WebKit/webkit/tree/master/Source/WebCore/platform"
+ for F in "${FILES[@]}"
+ do
+ printf "Downloading `basename $F`..."
+ curl "$REPO_PATH/${F}?format=TEXT" | base64 -D > "$F"
+ echo done.
+ done
+ echo $LATEST_SHA > UPSTREAM-GIT-SHA
+fi
+
+# Apply patches:
+
+patch -p4 < zero-serialization.patch
+patch -p4 < comparison-with-nan.patch
+patch -p4 < mfbt-abi-markers.patch
+patch -p4 < to-moz-dependencies.patch
+patch -p4 < add-doubleconversion-impl.patch
+patch -p4 < moz-constexpr-decimal.patch
+# The following is disabled. See
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1208357#c7
+#patch -p4 < fix-wshadow-warnings.patch
diff --git a/mozglue/misc/decimal/zero-serialization.patch b/mozglue/misc/decimal/zero-serialization.patch
new file mode 100644
index 0000000000..b8de9241bd
--- /dev/null
+++ b/mozglue/misc/decimal/zero-serialization.patch
@@ -0,0 +1,22 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -277,17 +277,17 @@ bool Decimal::EncodedData::operator==(co
+ }
+
+ Decimal::Decimal(int32_t i32)
+ : m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32))
+ {
+ }
+
+ Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient)
+- : m_data(sign, exponent, coefficient)
++ : m_data(sign, coefficient ? exponent : 0, coefficient)
+ {
+ }
+
+ Decimal::Decimal(const EncodedData& data)
+ : m_data(data)
+ {
+ }
+
diff --git a/mozglue/misc/moz.build b/mozglue/misc/moz.build
new file mode 100644
index 0000000000..016506f16a
--- /dev/null
+++ b/mozglue/misc/moz.build
@@ -0,0 +1,157 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+FINAL_LIBRARY = "mozglue"
+
+EXPORTS.mozilla += [
+ "AutoProfilerLabel.h",
+ "AwakeTimeStamp.h",
+ "decimal/Decimal.h",
+ "decimal/DoubleConversion.h",
+ "IntegerPrintfMacros.h",
+ "MmapFaultHandler.h",
+ "PlatformConditionVariable.h",
+ "PlatformMutex.h",
+ "PlatformRWLock.h",
+ "Printf.h",
+ "SIMD.h",
+ "Sprintf.h",
+ "SSE.h",
+ "StackWalk.h",
+ "TimeStamp.h",
+ "Uptime.h",
+]
+
+EXPORTS.mozilla.glue += [
+ "Debug.h",
+ "WinUtils.h",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ EXPORTS.mozilla += [
+ "GetKnownFolderPath.h",
+ "PreXULSkeletonUI.h",
+ "StackWalk_windows.h",
+ "StackWalkThread.h",
+ "TimeStamp_windows.h",
+ "WindowsDpiAwareness.h",
+ ]
+
+SOURCES += [
+ "AutoProfilerLabel.cpp",
+ "AwakeTimeStamp.cpp",
+ "Debug.cpp",
+ "MmapFaultHandler.cpp",
+ "Printf.cpp",
+ "SIMD.cpp",
+ "StackWalk.cpp",
+ "TimeStamp.cpp",
+ "Uptime.cpp",
+]
+
+if CONFIG["TARGET_CPU"].startswith("x86"):
+ SOURCES += [
+ "SIMD_avx2.cpp",
+ "SSE.cpp",
+ ]
+ SOURCES["SIMD_avx2.cpp"].flags += ["-mavx2"]
+
+if not CONFIG["JS_STANDALONE"]:
+ EXPORTS.mozilla += [
+ "ProcessType.h",
+ "RuntimeExceptionModule.h",
+ ]
+
+ SOURCES += [
+ "ProcessType.cpp",
+ "RuntimeExceptionModule.cpp",
+ ]
+
+OS_LIBS += CONFIG["REALTIME_LIBS"]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ EXPORTS.mozilla += [
+ "DynamicallyLinkedFunctionPtr.h",
+ "ImportDir.h",
+ "NativeNt.h",
+ "WindowsDpiInitialization.h",
+ "WindowsEnumProcessModules.h",
+ "WindowsMapRemoteView.h",
+ "WindowsProcessMitigations.h",
+ "WindowsStackCookie.h",
+ "WindowsUnwindInfo.h",
+ ]
+ EXPORTS.mozilla.glue += [
+ "WindowsUnicode.h",
+ ]
+ SOURCES += [
+ "GetKnownFolderPath.cpp",
+ "TimeStamp_windows.cpp",
+ "WindowsDllMain.cpp",
+ "WindowsDpiInitialization.cpp",
+ "WindowsMapRemoteView.cpp",
+ "WindowsProcessMitigations.cpp",
+ "WindowsUnicode.cpp",
+ ]
+
+ OS_LIBS += [
+ "dbghelp",
+ "oleaut32",
+ "ole32",
+ ]
+
+ if not CONFIG["JS_STANDALONE"]:
+ SOURCES += [
+ "/ipc/mscom/COMWrappers.cpp",
+ "/ipc/mscom/ProcessRuntime.cpp",
+ "PreXULSkeletonUI.cpp",
+ ]
+
+elif CONFIG["OS_ARCH"] == "Darwin":
+ SOURCES += [
+ "TimeStamp_darwin.cpp",
+ ]
+elif CONFIG["HAVE_CLOCK_MONOTONIC"]:
+ SOURCES += [
+ "TimeStamp_posix.cpp",
+ ]
+elif CONFIG["COMPILE_ENVIRONMENT"]:
+ error("No TimeStamp implementation on this platform. Build will not succeed")
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "ConditionVariable_windows.cpp",
+ "Mutex_windows.cpp",
+ "RWLock_windows.cpp",
+ ]
+# WASI hasn't supported cond vars and mutexes yet so noop implementation is used.
+elif CONFIG["OS_ARCH"] == "WASI":
+ SOURCES += [
+ "ConditionVariable_noop.cpp",
+ "Mutex_noop.cpp",
+ ]
+else:
+ SOURCES += [
+ "ConditionVariable_posix.cpp",
+ "Mutex_posix.cpp",
+ "RWLock_posix.cpp",
+ ]
+
+if CONFIG["MOZ_LINKER"] and CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ LOCAL_INCLUDES += [
+ "/mozglue/linker",
+ ]
+
+SOURCES += [
+ "decimal/Decimal.cpp",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "clang-cl"):
+ # Suppress warnings from third-party V8 Decimal code.
+ SOURCES["decimal/Decimal.cpp"].flags += ["-Wno-implicit-fallthrough"]
+
+for var in ("MOZ_APP_BASENAME", "MOZ_APP_VENDOR"):
+ DEFINES[var] = '"%s"' % CONFIG[var]