From 432378b3762cd00718bda2eea11ed308d069e585 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 1 Feb 2026 19:26:11 +0100 Subject: [PATCH 01/12] No issue - Enable performance observers by default in the platform. Performance observers have shown to be stable in daily use, so we should enable this by default. Any applications building on UXP can decide for themselves whether to explicitly enable or disable this API. --- modules/libpref/init/all.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 505e670b90..2bbabbaf3a 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -204,11 +204,7 @@ pref("dom.permissions.revoke.enable", false); pref("dom.performance.time_to_non_blank_paint.enabled", false); // Enable Performance Observer API -#ifdef NIGHTLY_BUILD pref("dom.enable_performance_observer", true); -#else -pref("dom.enable_performance_observer", false); -#endif // Enable requestIdleCallback API pref("dom.requestIdleCallback.enabled", true); From 17338528b772dae8fa8043b200bb2d2e446476d1 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Tue, 3 Feb 2026 08:51:06 +0100 Subject: [PATCH 02/12] Issue #2928 - Re-order imgLoader::RemoveFromCache Remove from tracker first before removing from cache, and if the cache queue is dirty, refresh it (causes a re-heap) before manipulating further. --- image/imgLoader.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index a362bc50e6..9bb9880ab9 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -1884,17 +1884,20 @@ imgLoader::RemoveFromCache(imgCacheEntry* entry) "imgLoader::RemoveFromCache", "entry's uri", key.Spec()); - cache.Remove(key); - if (entry->HasNoProxies()) { LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache removing from tracker"); + if (queue.IsDirty()) { + queue.Refresh(); + } if (mCacheTracker) { mCacheTracker->RemoveObject(entry); } queue.Remove(entry); } + cache.Remove(key); + entry->SetEvicted(true); request->SetIsInCache(false); AddToUncachedImages(request); From 15e076130d8238971a5083028b77b315349540a7 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Tue, 3 Feb 2026 10:17:19 +0100 Subject: [PATCH 03/12] Issue #2928 - Always refresh dirty queue. --- image/imgLoader.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index 9bb9880ab9..4ef0617ae4 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -1884,12 +1884,13 @@ imgLoader::RemoveFromCache(imgCacheEntry* entry) "imgLoader::RemoveFromCache", "entry's uri", key.Spec()); + if (queue.IsDirty()) { + queue.Refresh(); + } + if (entry->HasNoProxies()) { LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache removing from tracker"); - if (queue.IsDirty()) { - queue.Refresh(); - } if (mCacheTracker) { mCacheTracker->RemoveObject(entry); } From 21f4ab5e02f6ece0618244f8b08f40db59bf95a1 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 4 Feb 2026 09:07:23 +0100 Subject: [PATCH 04/12] Issue #2928 - Avoid searching the image cache queue for an entry after we just popped it off the queue. --- image/imgLoader.cpp | 25 ++++++++++++++++++++----- image/imgLoader.h | 11 ++++++++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index 4ef0617ae4..5b4c723dd3 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -989,6 +989,12 @@ imgCacheQueue::GetNumElements() const return mQueue.size(); } +bool +imgCacheQueue::Contains(imgCacheEntry* aEntry) const +{ + return mQueue.Contains(aEntry); +} + imgCacheQueue::iterator imgCacheQueue::begin() { @@ -1569,7 +1575,9 @@ imgLoader::CheckCacheLimits(imgCacheTable& cache, imgCacheQueue& queue) } if (entry) { - RemoveFromCache(entry); + // We just popped this entry from the queue, so pass AlreadyRemoved + // to avoid searching the queue again in RemoveFromCache. + RemoveFromCache(entry, QueueState::AlreadyRemoved); } } } @@ -1870,7 +1878,7 @@ imgLoader::RemoveFromCache(const ImageCacheKey& aKey) } bool -imgLoader::RemoveFromCache(imgCacheEntry* entry) +imgLoader::RemoveFromCache(imgCacheEntry* entry, QueueState aQueueState) { LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry"); @@ -1884,6 +1892,8 @@ imgLoader::RemoveFromCache(imgCacheEntry* entry) "imgLoader::RemoveFromCache", "entry's uri", key.Spec()); + cache.Remove(key); + if (queue.IsDirty()) { queue.Refresh(); } @@ -1894,11 +1904,16 @@ imgLoader::RemoveFromCache(imgCacheEntry* entry) if (mCacheTracker) { mCacheTracker->RemoveObject(entry); } - queue.Remove(entry); + // Only search the queue to remove the entry if its possible it might + // be in the queue. If we know its not in the queue this would be + // wasted work. + MOZ_ASSERT_IF(aQueueState == QueueState::AlreadyRemoved, + !queue.Contains(entry)); + if (aQueueState == QueueState::MaybeExists) { + queue.Remove(entry); + } } - cache.Remove(key); - entry->SetEvicted(true); request->SetIsInCache(false); AddToUncachedImages(request); diff --git a/image/imgLoader.h b/image/imgLoader.h index 8e818a2907..dd7a0ba4bb 100644 --- a/image/imgLoader.h +++ b/image/imgLoader.h @@ -341,7 +341,16 @@ public: nsresult InitCache(); bool RemoveFromCache(const ImageCacheKey& aKey); - bool RemoveFromCache(imgCacheEntry* entry); + + // Enumeration describing if a given entry is in the cache queue or not. + // There are some cases we know the entry is definitely not in the queue. + enum class QueueState { + MaybeExists, + AlreadyRemoved + }; + + bool RemoveFromCache(imgCacheEntry* entry, + QueueState aQueueState = QueueState::MaybeExists); bool PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* aEntry); From c5cc9805060a15ea2a1a77bb04e47f806d495a8e Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 4 Feb 2026 09:19:05 +0100 Subject: [PATCH 05/12] Issue #2928 - Improve imgLoader cache queue handling. - Convert the imgCacheQueue to nsTArray from Vector - Avoid marking the queue dirty when element operations are performed that don't upset the sort order. This should improve performance as well. --- image/imgLoader.cpp | 66 ++++++++++++++++++++++++++++++++++----------- image/imgLoader.h | 3 ++- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index 5b4c723dd3..16e5096187 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -928,12 +928,38 @@ using namespace std; void imgCacheQueue::Remove(imgCacheEntry* entry) { - queueContainer::iterator it = find(mQueue.begin(), mQueue.end(), entry); - if (it != mQueue.end()) { - mSize -= (*it)->GetDataSize(); - mQueue.erase(it); - MarkDirty(); + uint64_t index = mQueue.IndexOf(entry); + if (index == queueContainer::NoIndex) { + return; } + + mSize -= mQueue[index]->GetDataSize(); + + // If the queue is clean and this is the first entry, + // then we can efficiently remove the entry without + // dirtying the sort order. + if (!IsDirty() && index == 0) { + std::pop_heap(mQueue.begin(), mQueue.end(), + imgLoader::CompareCacheEntries); + mQueue.RemoveElementAt(mQueue.Length() - 1); + return; + } + + // Remove from the middle of the list. This potentially + // breaks the binary heap sort order. + mQueue.RemoveElementAt(index); + + // If we only have one entry or the queue is empty, though, + // then the sort order is still effectively good. + // Simply refresh the list to clear the dirty flag. + if (mQueue.Length() <= 1) { + Refresh(); + return; + } + + // Otherwise we must mark the queue dirty and potentially + // trigger an expensive sort later. + MarkDirty(); } void @@ -942,23 +968,26 @@ imgCacheQueue::Push(imgCacheEntry* entry) mSize += entry->GetDataSize(); RefPtr refptr(entry); - mQueue.push_back(refptr); - MarkDirty(); + mQueue.AppendElement(Move(refptr)); + // If we're not dirty already, then we can efficiently add this to the binary heap immediately. + if (!IsDirty()) { + std::push_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); + } } already_AddRefed imgCacheQueue::Pop() { - if (mQueue.empty()) { + if (mQueue.IsEmpty()) { return nullptr; } if (IsDirty()) { Refresh(); } - RefPtr entry = mQueue[0]; std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); - mQueue.pop_back(); + RefPtr entry = Move(mQueue.LastElement()); + mQueue.RemoveElementAt(mQueue.Length() - 1); mSize -= entry->GetDataSize(); return entry.forget(); @@ -967,6 +996,7 @@ imgCacheQueue::Pop() void imgCacheQueue::Refresh() { + // Re-heap the list. This is an O(3 * n) operation and best avoided if possible. std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); mDirty = false; } @@ -986,7 +1016,7 @@ imgCacheQueue::IsDirty() uint32_t imgCacheQueue::GetNumElements() const { - return mQueue.size(); + return mQueue.Length(); } bool @@ -1546,7 +1576,11 @@ void imgLoader::CacheEntriesChanged(bool aForChrome, int32_t aSizeDiff /* = 0 */) { imgCacheQueue& queue = GetCacheQueue(aForChrome); - queue.MarkDirty(); + // We only need to dirty the queue if there is any sorting taking place. + // Empty or single-entry lists can't become dirty. + if (queue.GetNumElements() > 1) { + queue.MarkDirty(); + } queue.UpdateSize(aSizeDiff); } @@ -1956,13 +1990,13 @@ imgLoader::EvictEntries(imgCacheQueue& aQueueToClear) // We have to make a temporary, since RemoveFromCache removes the element // from the queue, invalidating iterators. nsTArray > entries(aQueueToClear.GetNumElements()); - for (imgCacheQueue::const_iterator i = aQueueToClear.begin(); - i != aQueueToClear.end(); ++i) { + for (auto i = aQueueToClear.begin(); i != aQueueToClear.end(); ++i) { entries.AppendElement(*i); } - for (uint32_t i = 0; i < entries.Length(); ++i) { - if (!RemoveFromCache(entries[i])) { + // Iterate in reverse order to minimize array copying. + for (auto& entry : entries) { + if (!RemoveFromCache(entry)) { return NS_ERROR_FAILURE; } } diff --git a/image/imgLoader.h b/image/imgLoader.h index dd7a0ba4bb..ca827c1d08 100644 --- a/image/imgLoader.h +++ b/image/imgLoader.h @@ -203,7 +203,8 @@ public: uint32_t GetSize() const; void UpdateSize(int32_t diff); uint32_t GetNumElements() const; - typedef std::vector > queueContainer; + bool Contains(imgCacheEntry* aEntry) const; + typedef nsTArray > queueContainer; typedef queueContainer::iterator iterator; typedef queueContainer::const_iterator const_iterator; From e99ed753820503beb61fd430be6339a0c8990011 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Thu, 5 Feb 2026 13:44:21 +0100 Subject: [PATCH 06/12] Issue #2928 - Add extra checks to nsExpirationTracker. Turn some asserts into early exits and add sanity checks. Also check main thread access for the fake mutex and report. --- xpcom/ds/nsExpirationTracker.h | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/xpcom/ds/nsExpirationTracker.h b/xpcom/ds/nsExpirationTracker.h index 672256d1dc..53b4ceafdc 100644 --- a/xpcom/ds/nsExpirationTracker.h +++ b/xpcom/ds/nsExpirationTracker.h @@ -127,9 +127,15 @@ public: */ nsresult AddObjectLocked(T* aObj, const AutoLock& aAutoLock) { + if (NS_WARN_IF(!aObj)) { + // Invalid object to add + return NS_ERROR_UNEXPECTED; + } nsExpirationState* state = aObj->GetExpirationState(); - NS_ASSERTION(!state->IsTracked(), - "Tried to add an object that's already tracked"); + if (NS_WARN_IF(state->IsTracked())) { + // Tried to add an object that's already tracked. + return NS_ERROR_UNEXPECTED; + } nsTArray& generation = mGenerations[mNewestGeneration]; uint32_t index = generation.Length(); if (index > nsExpirationState::MAX_INDEX_IN_GENERATION) { @@ -156,8 +162,15 @@ public: */ void RemoveObjectLocked(T* aObj, const AutoLock& aAutoLock) { + if (NS_WARN_IF(!aObj)) { + // Invalid object to remove + return; + } nsExpirationState* state = aObj->GetExpirationState(); - NS_ASSERTION(state->IsTracked(), "Tried to remove an object that's not tracked"); + if (NS_WARN_IF(!state->IsTracked())) { + // Tried to remove an object that's not tracked + return; + } nsTArray& generation = mGenerations[state->mGeneration]; uint32_t index = state->mIndexInGeneration; NS_ASSERTION(generation.Length() > index && @@ -458,11 +471,13 @@ class nsExpirationTracker : protected ::detail::SingleThreadedExpirationTracker< Lock mLock; AutoLock FakeLock() { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); return AutoLock(mLock); } Lock& GetMutex() override { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); return mLock; } From 1078e45dd3f72160730f126a6f2af68a3b48bf40 Mon Sep 17 00:00:00 2001 From: mittorn Date: Fri, 6 Feb 2026 23:14:54 +0000 Subject: [PATCH 07/12] Issue #2932 - Ensure that imgRequestProxy::CancelAndForgetObserver removes itself from the cache validator https://hg-edge.mozilla.org/releases/mozilla-beta/rev/af82d0c33427 --- image/imgLoader.cpp | 6 ++++++ image/imgLoader.h | 1 + image/imgRequestProxy.cpp | 10 ++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index 16e5096187..8b1a5f03de 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -2785,6 +2785,12 @@ imgCacheValidator::AddProxy(imgRequestProxy* aProxy) mProxies.AppendObject(aProxy); } +void +imgCacheValidator::RemoveProxy(imgRequestProxy* aProxy) +{ + mProxies.RemoveObject(aProxy); +} + /** nsIRequestObserver methods **/ NS_IMETHODIMP diff --git a/image/imgLoader.h b/image/imgLoader.h index ca827c1d08..8fee429e45 100644 --- a/image/imgLoader.h +++ b/image/imgLoader.h @@ -551,6 +551,7 @@ public: bool forcePrincipalCheckForCacheEntry); void AddProxy(imgRequestProxy* aProxy); + void RemoveProxy(imgRequestProxy* aProxy); NS_DECL_ISUPPORTS NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER diff --git a/image/imgRequestProxy.cpp b/image/imgRequestProxy.cpp index 4ccf1ff1d2..08e8c61903 100644 --- a/image/imgRequestProxy.cpp +++ b/image/imgRequestProxy.cpp @@ -342,8 +342,14 @@ imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) bool oldIsInLoadGroup = mIsInLoadGroup; mIsInLoadGroup = false; - if (GetOwner()) { - GetOwner()->RemoveProxy(this, aStatus); + imgRequest* owner = GetOwner(); + if (owner) { + imgCacheValidator* validator = owner->GetValidator(); + if (validator) { + validator->RemoveProxy(this); + } + + owner->RemoveProxy(this, aStatus); } mIsInLoadGroup = oldIsInLoadGroup; From b8f699692830269b3062c2f10441ad503d840fe1 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Wed, 4 Feb 2026 09:53:46 -0500 Subject: [PATCH 08/12] Issue #2551 - Initial attempt at toSorted implementation --- js/src/builtin/Array.js | 23 +++++++++++++++++++++++ js/src/jsarray.cpp | 4 +++- js/src/vm/CommonPropertyNames.h | 1 + 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index 54446d2578..11a4a7485f 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -253,6 +253,29 @@ function ArraySort(comparefn) { return MergeSort(O, len, comparefn); } +// ES2023 22.1.3.30 Array.prototype.toSorted ( comparefn ) +function ArrayToSorted(comparefn) { + if (comparefn !== undefined) { + if (!IsCallable(comparefn)) { + ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, comparefn)); + } + } + + var O = ToObject(this); + var len = ToLength(O.length); + + var A = ArraySpeciesCreate(O, len); + for (var k = 0; k < len; k++) { + if (k in O) + _DefineDataProperty(A, k, O[k]); + else + delete A[k]; + } + + callFunction(std_Array_sort, A, comparefn); + return A; +} + /* ES5 15.4.4.18. */ function ArrayForEach(callbackfn/*, thisArg*/) { /* Step 1. */ diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 4f5c717be5..6cd57f19a2 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -3208,6 +3208,7 @@ static const JSFunctionSpec array_methods[] = { /* ES2023 proposals */ JS_SELF_HOSTED_FN("findLast", "ArrayFindLast", 1,0), JS_SELF_HOSTED_FN("findLastIndex", "ArrayFindLastIndex", 1,0), + JS_SELF_HOSTED_FN("toSorted", "ArrayToSorted", 1,0), JS_FS_END }; @@ -3379,7 +3380,8 @@ array_proto_finish(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto) !DefineProperty(cx, unscopables, cx->names().flatMap, value) || !DefineProperty(cx, unscopables, cx->names().includes, value) || !DefineProperty(cx, unscopables, cx->names().keys, value) || - !DefineProperty(cx, unscopables, cx->names().values, value)) + !DefineProperty(cx, unscopables, cx->names().values, value) || + !DefineProperty(cx, unscopables, cx->names().toSorted, value)) { return false; } diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 8489648d6b..166bce158a 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -428,6 +428,7 @@ macro(toJSON, toJSON, "toJSON") \ macro(toLocaleString, toLocaleString, "toLocaleString") \ macro(toSource, toSource, "toSource") \ + macro(toSorted, toSorted, "toSorted") \ macro(toString, toString, "toString") \ macro(toUTCString, toUTCString, "toUTCString") \ macro(true, true_, "true") \ From 89e4711761b8f39ed21d37375c19fb8ae19444d2 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Thu, 5 Feb 2026 09:53:39 -0500 Subject: [PATCH 09/12] Issue #2551 - Ensure toSorted is 100% spec compliant per multiple tests --- js/src/builtin/Array.js | 46 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index 11a4a7485f..3efe5c409b 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -264,15 +264,51 @@ function ArrayToSorted(comparefn) { var O = ToObject(this); var len = ToLength(O.length); - var A = ArraySpeciesCreate(O, len); + var items = new List(); + var itemsLen = 0; for (var k = 0; k < len; k++) { if (k in O) - _DefineDataProperty(A, k, O[k]); - else - delete A[k]; + items[itemsLen++] = O[k]; } - callFunction(std_Array_sort, A, comparefn); + var wrappedCompareFn = comparefn; + var sortCompare; + if (wrappedCompareFn === undefined) { + sortCompare = function(x, y) { + if (x === undefined) + return y === undefined ? 0 : 1; + if (y === undefined) + return -1; + + var xString = ToString(x); + var yString = ToString(y); + if (xString < yString) + return -1; + if (xString > yString) + return 1; + return 0; + }; + } else { + sortCompare = function(x, y) { + if (x === undefined) + return y === undefined ? 0 : 1; + if (y === undefined) + return -1; + + var v = ToNumber(wrappedCompareFn(x, y)); + return v !== v ? 0 : v; + }; + } + + if (itemsLen > 1) + MergeSort(items, itemsLen, sortCompare); + + var A = ArraySpeciesCreate(O, 0); + A.length = len; + + for (var j = 0; j < itemsLen; j++) + _DefineDataProperty(A, j, items[j]); + return A; } From 68a5ed17c37c3078489d7330c13422b626843fc9 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Thu, 5 Feb 2026 10:33:42 -0500 Subject: [PATCH 10/12] Issue #2551 - Add toSorted test and do some final tweaks to toSorted to be compliant --- js/src/builtin/Array.js | 9 ++-- js/src/tests/ecma_6/Array/toSorted.html | 59 +++++++++++++++++++++++++ js/src/tests/ecma_6/Array/toSorted.js | 57 ++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 js/src/tests/ecma_6/Array/toSorted.html create mode 100644 js/src/tests/ecma_6/Array/toSorted.js diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index 3efe5c409b..a257a2547d 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -265,10 +265,9 @@ function ArrayToSorted(comparefn) { var len = ToLength(O.length); var items = new List(); - var itemsLen = 0; + var itemsLen = len; for (var k = 0; k < len; k++) { - if (k in O) - items[itemsLen++] = O[k]; + items[k] = O[k]; } var wrappedCompareFn = comparefn; @@ -303,9 +302,7 @@ function ArrayToSorted(comparefn) { if (itemsLen > 1) MergeSort(items, itemsLen, sortCompare); - var A = ArraySpeciesCreate(O, 0); - A.length = len; - + var A = ArraySpeciesCreate(O, len); for (var j = 0; j < itemsLen; j++) _DefineDataProperty(A, j, items[j]); diff --git a/js/src/tests/ecma_6/Array/toSorted.html b/js/src/tests/ecma_6/Array/toSorted.html new file mode 100644 index 0000000000..3738b05c48 --- /dev/null +++ b/js/src/tests/ecma_6/Array/toSorted.html @@ -0,0 +1,59 @@ + + +Array.prototype.toSorted test + +

+
+
diff --git a/js/src/tests/ecma_6/Array/toSorted.js b/js/src/tests/ecma_6/Array/toSorted.js
new file mode 100644
index 0000000000..fbd2540deb
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/toSorted.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+assertEq(typeof Array.prototype.toSorted, "function");
+
+// Non-mutating behavior.
+let original = [3, 1, 2];
+let sorted = original.toSorted();
+assertEq(original !== sorted, true);
+assertEq(original.join(","), "3,1,2");
+assertEq(sorted.join(","), "1,2,3");
+
+// Compare function.
+let nums = [10, 1, 5];
+let desc = nums.toSorted((a, b) => b - a);
+assertEq(desc.join(","), "10,5,1");
+
+// Stable sort.
+let stableInput = [
+    {v: 1, id: "a"},
+    {v: 1, id: "b"},
+    {v: 1, id: "c"}
+];
+let stableSorted = stableInput.toSorted((x, y) => x.v - y.v);
+assertEq(stableSorted.map(o => o.id).join(""), "abc");
+
+// Holes are treated as undefined (properties are created).
+let sparse = [3, , 1];
+let sparseSorted = sparse.toSorted();
+assertEq(sparseSorted.length, 3);
+assertEq(sparseSorted[0], 1);
+assertEq(sparseSorted[1], 3);
+assertEq(2 in sparseSorted, true);
+assertEq(sparseSorted[2], undefined);
+
+// Array-like input.
+let arrayLike = {0: 2, 1: 1, length: 2};
+let arrayLikeSorted = Array.prototype.toSorted.call(arrayLike);
+assertEq(Array.isArray(arrayLikeSorted), true);
+assertEq(arrayLikeSorted.join(","), "1,2");
+
+// Getter access order (ascending indices).
+let accessLog = [];
+let getterArr = {
+    length: 3,
+    get 0() { accessLog.push(0); return 3; },
+    get 1() { accessLog.push(1); return 1; },
+    get 2() { accessLog.push(2); return 2; }
+};
+Array.prototype.toSorted.call(getterArr);
+assertEq(accessLog.join(","), "0,1,2");
+
+// Comparator errors propagate.
+assertThrowsInstanceOf(() => [1, 2].toSorted(1), TypeError);
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);

From 6e0414ddb4e4bc47a29cb253c7280c63487e0d3a Mon Sep 17 00:00:00 2001
From: Basilisk-Dev 
Date: Fri, 6 Feb 2026 06:54:54 -0500
Subject: [PATCH 11/12] Issue #2551 - comment toSorted implementation

---
 js/src/builtin/Array.js | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js
index a257a2547d..9a6022f340 100644
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -261,15 +261,18 @@ function ArrayToSorted(comparefn) {
         }
     }
 
+    // Step 1: Let O be ? ToObject(this). Let len be ? ToLength(O.length).
     var O = ToObject(this);
     var len = ToLength(O.length);
 
+    // Step 2: Snapshot values in ascending index order into a List.
     var items = new List();
     var itemsLen = len;
     for (var k = 0; k < len; k++) {
         items[k] = O[k];
     }
 
+    // Step 3: Create SortCompare per spec.
     var wrappedCompareFn = comparefn;
     var sortCompare;
     if (wrappedCompareFn === undefined) {
@@ -299,9 +302,11 @@ function ArrayToSorted(comparefn) {
         };
     }
 
+    // Step 4: Sort the snapshot List using SortCompare.
     if (itemsLen > 1)
         MergeSort(items, itemsLen, sortCompare);
 
+    // Step 5: Create result array and write sorted values.
     var A = ArraySpeciesCreate(O, len);
     for (var j = 0; j < itemsLen; j++)
         _DefineDataProperty(A, j, items[j]);

From 4f821a5abb8a070d206d267e8d04c8ebe8cfbf14 Mon Sep 17 00:00:00 2001
From: Moonchild 
Date: Fri, 6 Feb 2026 10:44:03 +0100
Subject: [PATCH 12/12] No Issue - Remove redundant OS check for toolkit/fonts

This is already guarded by MOZ_BUNDLED_FONTS one level higher and blocks
--enable-bundled-fonts from working on other OSes
---
 toolkit/fonts/moz.build | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/toolkit/fonts/moz.build b/toolkit/fonts/moz.build
index bfdff9e075..129a7737c6 100644
--- a/toolkit/fonts/moz.build
+++ b/toolkit/fonts/moz.build
@@ -3,5 +3,4 @@
 # 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/.
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'):
-    FINAL_TARGET_FILES.fonts += ['TwemojiMozilla.ttf']
+FINAL_TARGET_FILES.fonts += ['TwemojiMozilla.ttf']