Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
2026-02-07 21:40:07 +08:00
11 changed files with 299 additions and 33 deletions
+78 -19
View File
@@ -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<imgCacheEntry> 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<imgCacheEntry>
imgCacheQueue::Pop()
{
if (mQueue.empty()) {
if (mQueue.IsEmpty()) {
return nullptr;
}
if (IsDirty()) {
Refresh();
}
RefPtr<imgCacheEntry> entry = mQueue[0];
std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
mQueue.pop_back();
RefPtr<imgCacheEntry> 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,13 @@ imgCacheQueue::IsDirty()
uint32_t
imgCacheQueue::GetNumElements() const
{
return mQueue.size();
return mQueue.Length();
}
bool
imgCacheQueue::Contains(imgCacheEntry* aEntry) const
{
return mQueue.Contains(aEntry);
}
imgCacheQueue::iterator
@@ -1540,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);
}
@@ -1569,7 +1609,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 +1912,7 @@ imgLoader::RemoveFromCache(const ImageCacheKey& aKey)
}
bool
imgLoader::RemoveFromCache(imgCacheEntry* entry)
imgLoader::RemoveFromCache(imgCacheEntry* entry, QueueState aQueueState)
{
LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry");
@@ -1886,13 +1928,24 @@ imgLoader::RemoveFromCache(imgCacheEntry* entry)
cache.Remove(key);
if (queue.IsDirty()) {
queue.Refresh();
}
if (entry->HasNoProxies()) {
LOG_STATIC_FUNC(gImgLog,
"imgLoader::RemoveFromCache removing from tracker");
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);
}
}
entry->SetEvicted(true);
@@ -1937,13 +1990,13 @@ imgLoader::EvictEntries(imgCacheQueue& aQueueToClear)
// We have to make a temporary, since RemoveFromCache removes the element
// from the queue, invalidating iterators.
nsTArray<RefPtr<imgCacheEntry> > 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;
}
}
@@ -2732,6 +2785,12 @@ imgCacheValidator::AddProxy(imgRequestProxy* aProxy)
mProxies.AppendObject(aProxy);
}
void
imgCacheValidator::RemoveProxy(imgRequestProxy* aProxy)
{
mProxies.RemoveObject(aProxy);
}
/** nsIRequestObserver methods **/
NS_IMETHODIMP
+13 -2
View File
@@ -177,7 +177,8 @@ public:
uint32_t GetSize() const;
void UpdateSize(int32_t diff);
uint32_t GetNumElements() const;
typedef std::vector<RefPtr<imgCacheEntry> > queueContainer;
bool Contains(imgCacheEntry* aEntry) const;
typedef nsTArray<RefPtr<imgCacheEntry> > queueContainer;
typedef queueContainer::iterator iterator;
typedef queueContainer::const_iterator const_iterator;
@@ -315,7 +316,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);
@@ -515,6 +525,7 @@ public:
bool forcePrincipalCheckForCacheEntry);
void AddProxy(imgRequestProxy* aProxy);
void RemoveProxy(imgRequestProxy* aProxy);
NS_DECL_ISUPPORTS
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+8 -2
View File
@@ -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;
+61
View File
@@ -253,6 +253,67 @@ 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));
}
}
// 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) {
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;
};
}
// 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]);
return A;
}
/* ES5 15.4.4.18. */
function ArrayForEach(callbackfn/*, thisArg*/) {
/* Step 1. */
+3 -1
View File
@@ -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;
}
+59
View File
@@ -0,0 +1,59 @@
<!doctype html>
<meta charset="utf-8">
<title>Array.prototype.toSorted test</title>
<style>
body { font: 14px/1.4 sans-serif; padding: 16px; }
.pass { color: #0a0; }
.fail { color: #c00; }
pre { white-space: pre-wrap; }
</style>
<pre id="log"></pre>
<script>
(function() {
const log = document.getElementById("log");
function write(msg, cls) {
const line = document.createElement("div");
if (cls) line.className = cls;
line.textContent = msg;
log.appendChild(line);
}
window.assertEq = function(actual, expected, msg) {
if (actual !== expected) {
throw new Error((msg ? msg + ": " : "") +
"expected " + expected + ", got " + actual);
}
};
window.assertThrowsInstanceOf = function(fn, ctor, msg) {
let threw = false;
try {
fn();
} catch (e) {
if (e instanceof ctor) {
threw = true;
} else {
throw new Error((msg ? msg + ": " : "") +
"threw " + e + ", expected " + ctor.name);
}
}
if (!threw) {
throw new Error((msg ? msg + ": " : "") + "did not throw");
}
};
window.reportCompare = function() {};
let hadError = false;
window.addEventListener("error", function(e) {
hadError = true;
write("FAIL: " + e.message, "fail");
});
window.addEventListener("load", function() {
write("PASS: toSorted.js loaded", "pass");
if (!hadError)
write("PASS: all toSorted tests passed", "pass");
});
})();
</script>
<script src="toSorted.js"></script>
+57
View File
@@ -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);
+1
View File
@@ -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") \
-4
View File
@@ -206,11 +206,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);
+1 -2
View File
@@ -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']
+18 -3
View File
@@ -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<T*>& 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<T*>& 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;
}