Bug 1629796: Replace finalization iterator with multiple callback calls. r=jonco
☠☠ backed out by 1e2ce9981292 ☠ ☠
authorAndré Bargull <andre.bargull@gmail.com>
Wed, 13 May 2020 11:47:05 +0000
changeset 529599 c0c9d8211967c0a3a3dd8e21efb2c66ca7cdc9f3
parent 529598 532a07b71c30c20366f77f134153ed1ea5a1bb04
child 529600 d0283f2ceef9d6daf5dbe5b184e69e75dec8d5fa
push id115798
push userncsoregi@mozilla.com
push dateWed, 13 May 2020 12:02:41 +0000
treeherderautoland@c0c9d8211967 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1629796
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1629796: Replace finalization iterator with multiple callback calls. r=jonco Implements the spec changes from: https://github.com/tc39/proposal-weakrefs/pull/187 The spec change removes the `FinalizationRegistryCleanupIterator` in favour of calling the clean-up callback for each finalised value. It also allows to call `cleanupSome()` within the callback function. `FinalizationRegistryObject::cleanupQueuedRecords()` has been changed to iterate from back to front, because this allows us to call `GCVector::popCopy()`, which makes it more efficient to remove entries from the `records` vector. Differential Revision: https://phabricator.services.mozilla.com/D70821
js/public/Class.h
js/src/builtin/FinalizationRegistryObject.cpp
js/src/builtin/FinalizationRegistryObject.h
js/src/jit-test/tests/gc/bug-1620196.js
js/src/jit-test/tests/gc/bug1600488-1.js
js/src/jit-test/tests/gc/finalizationRegistry-ccw.js
js/src/jit-test/tests/gc/finalizationRegistry-cleanupSome-recursive.js
js/src/jit-test/tests/gc/finalizationRegistry-gray.js
js/src/jit-test/tests/gc/finalizationRegistry.js
js/src/tests/jstests.list
js/src/vm/GlobalObject.h
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -695,17 +695,17 @@ static const uint32_t JSCLASS_FOREGROUND
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
 static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
-    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 26;
+    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 25;
 
 static constexpr uint32_t JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(uint32_t n) {
   return JSCLASS_IS_GLOBAL |
          JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + n);
 }
 
 static constexpr uint32_t JSCLASS_GLOBAL_FLAGS =
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0);
--- a/js/src/builtin/FinalizationRegistryObject.cpp
+++ b/js/src/builtin/FinalizationRegistryObject.cpp
@@ -321,17 +321,16 @@ bool FinalizationRegistryObject::constru
   InitReservedSlot(registry, RegistrationsSlot, registrations.release(),
                    MemoryUse::FinalizationRegistryRegistrations);
   InitReservedSlot(registry, ActiveRecords, activeRecords.release(),
                    MemoryUse::FinalizationRegistryRecordSet);
   InitReservedSlot(registry, RecordsToBeCleanedUpSlot,
                    recordsToBeCleanedUp.release(),
                    MemoryUse::FinalizationRegistryRecordVector);
   registry->initReservedSlot(IsQueuedForCleanupSlot, BooleanValue(false));
-  registry->initReservedSlot(IsCleanupJobActiveSlot, BooleanValue(false));
 
   if (!cx->runtime()->gc.addFinalizationRegistry(cx, registry)) {
     return false;
   }
 
   args.rval().setObject(*registry);
   return true;
 }
@@ -434,38 +433,29 @@ FinalizationRecordVector* FinalizationRe
   }
   return static_cast<FinalizationRecordVector*>(value.toPrivate());
 }
 
 bool FinalizationRegistryObject::isQueuedForCleanup() const {
   return getReservedSlot(IsQueuedForCleanupSlot).toBoolean();
 }
 
-bool FinalizationRegistryObject::isCleanupJobActive() const {
-  return getReservedSlot(IsCleanupJobActiveSlot).toBoolean();
-}
-
 void FinalizationRegistryObject::queueRecordToBeCleanedUp(
     FinalizationRecordObject* record) {
   AutoEnterOOMUnsafeRegion oomUnsafe;
   if (!recordsToBeCleanedUp()->append(record)) {
     oomUnsafe.crash("FinalizationRegistryObject::queueRecordsToBeCleanedUp");
   }
 }
 
 void FinalizationRegistryObject::setQueuedForCleanup(bool value) {
   MOZ_ASSERT(value != isQueuedForCleanup());
   setReservedSlot(IsQueuedForCleanupSlot, BooleanValue(value));
 }
 
-void FinalizationRegistryObject::setCleanupJobActive(bool value) {
-  MOZ_ASSERT(value != isCleanupJobActive());
-  setReservedSlot(IsCleanupJobActiveSlot, BooleanValue(value));
-}
-
 // FinalizationRegistry.prototype.register(target, heldValue [, unregisterToken
 // ])
 // https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.register
 /* static */
 bool FinalizationRegistryObject::register_(JSContext* cx, unsigned argc,
                                            Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -700,40 +690,29 @@ bool FinalizationRegistryObject::unregis
 
 // FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
 // https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.cleanupSome
 bool FinalizationRegistryObject::cleanupSome(JSContext* cx, unsigned argc,
                                              Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   // 1. Let finalizationRegistry be the this value.
-  // 2. If Type(finalizationRegistry) is not Object, throw a TypeError
-  //    exception.
-  // 3. If finalizationRegistry does not have [[Cells]] and
-  //    [[IsFinalizationRegistryCleanupJobActive]] internal slots, throw a
-  //    TypeError exception.
+  // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
   if (!args.thisv().isObject() ||
       !args.thisv().toObject().is<FinalizationRegistryObject>()) {
     JS_ReportErrorNumberASCII(
         cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_REGISTRY,
         "Receiver of FinalizationRegistry.cleanupSome call");
     return false;
   }
 
-  // 4. If finalizationRegistry.[[IsFinalizationRegistryCleanupJobActive]] is
-  //    true, throw a TypeError exception.
   RootedFinalizationRegistryObject registry(
       cx, &args.thisv().toObject().as<FinalizationRegistryObject>());
-  if (registry->isCleanupJobActive()) {
-    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_BAD_CLEANUP_STATE);
-    return false;
-  }
 
-  // 5. If callback is not undefined and IsCallable(callback) is false, throw a
+  // 3. If callback is not undefined and IsCallable(callback) is false, throw a
   //    TypeError exception.
   RootedObject cleanupCallback(cx);
   if (!args.get(0).isUndefined()) {
     cleanupCallback = ValueToCallable(cx, args.get(0), -1, NO_CONSTRUCT);
     if (!cleanupCallback) {
       return false;
     }
   }
@@ -741,248 +720,58 @@ bool FinalizationRegistryObject::cleanup
   if (!cleanupQueuedRecords(cx, registry, cleanupCallback)) {
     return false;
   }
 
   args.rval().setUndefined();
   return true;
 }
 
-/* static */
-bool FinalizationRegistryObject::hasRegisteredRecordsToBeCleanedUp(
-    HandleFinalizationRegistryObject registry) {
-  FinalizationRecordVector* records = registry->recordsToBeCleanedUp();
-  size_t initialLength = records->length();
-  if (initialLength == 0) {
-    return false;
-  }
-
-  for (FinalizationRecordObject* record : *records) {
-    if (record->isActive()) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
 // CleanupFinalizationRegistry ( finalizationRegistry [ , callback ] )
 // https://tc39.es/proposal-weakrefs/#sec-cleanup-finalization-registry
 /* static */
 bool FinalizationRegistryObject::cleanupQueuedRecords(
     JSContext* cx, HandleFinalizationRegistryObject registry,
     HandleObject callbackArg) {
   MOZ_ASSERT(cx->compartment() == registry->compartment());
 
-  // 2. If CheckForEmptyCells(finalizationRegistry) is false, return.
-  if (!hasRegisteredRecordsToBeCleanedUp(registry)) {
-    return true;
-  }
-
-  // 3. Let iterator be
-  //    !CreateFinalizationRegistryCleanupIterator(finalizationRegistry).
-  Rooted<FinalizationIteratorObject*> iterator(
-      cx, FinalizationIteratorObject::create(cx, registry));
-  if (!iterator) {
-    return false;
-  }
-
-  // 4. If callback is undefined, set callback to
+  // 2. If callback is undefined, set callback to
   //    finalizationRegistry.[[CleanupCallback]].
   RootedValue callback(cx);
   if (callbackArg) {
     callback.setObject(*callbackArg);
   } else {
     JSObject* cleanupCallback = registry->cleanupCallback();
     MOZ_ASSERT(cleanupCallback);
     callback.setObject(*cleanupCallback);
   }
 
-  // 5. Set finalizationRegistry.[[IsFinalizationRegistryCleanupJobActive]] to
-  //    true.
-  registry->setCleanupJobActive(true);
-
-  FinalizationRecordVector* records = registry->recordsToBeCleanedUp();
-#ifdef DEBUG
-  size_t initialLength = records->length();
-#endif
-
-  // 6. Let result be Call(callback, undefined, iterator).
-  RootedValue iteratorVal(cx, ObjectValue(*iterator));
-  RootedValue rval(cx);
-  bool ok = Call(cx, callback, UndefinedHandleValue, iteratorVal, &rval);
+  // 3. While finalizationRegistry.[[Cells]] contains a Record cell such that
+  //    cell.[[WeakRefTarget]] is empty, then an implementation may perform the
+  //    following steps,
+  //    a. Choose any such cell.
+  //    b. Remove cell from finalizationRegistry.[[Cells]].
+  //    c. Perform ? Call(callback, undefined, « cell.[[HeldValue]] »).
 
-  // Remove records that were iterated over.
-  size_t index = iterator->index();
-  MOZ_ASSERT(index <= records->length());
-  MOZ_ASSERT(initialLength <= records->length());
-  if (index > 0) {
-    records->erase(records->begin(), records->begin() + index);
-  }
-
-  // 7. Set finalizationRegistry.[[IsFinalizationRegistryCleanupJobActive]] to
-  //    false.
-  registry->setCleanupJobActive(false);
-
-  // 8. Set iterator.[[FinalizationRegistry]] to empty.
-  iterator->clearFinalizationRegistry();
-
-  return ok;
-}
-
-///////////////////////////////////////////////////////////////////////////
-// FinalizationIteratorObject
-
-const JSClass FinalizationIteratorObject::class_ = {
-    "FinalizationRegistryCleanupIterator",
-    JSCLASS_HAS_RESERVED_SLOTS(SlotCount), JS_NULL_CLASS_OPS,
-    JS_NULL_CLASS_SPEC};
+  RootedValue heldValue(cx);
+  RootedValue rval(cx);
+  FinalizationRecordVector* records = registry->recordsToBeCleanedUp();
+  FinalizationRecordSet* activeRecords = registry->activeRecords();
+  while (!records->empty()) {
+    FinalizationRecordObject* record = records->popCopy();
 
-const JSFunctionSpec FinalizationIteratorObject::methods_[] = {
-    JS_FN(js_next_str, next, 0, 0), JS_FS_END};
+    // Skip over records that have been unregistered.
+    if (!record->isActive()) {
+      continue;
+    }
 
-const JSPropertySpec FinalizationIteratorObject::properties_[] = {
-    JS_STRING_SYM_PS(toStringTag, "FinalizationRegistry Cleanup Iterator",
-                     JSPROP_READONLY),
-    JS_PS_END};
+    heldValue.set(record->heldValue());
 
-/* static */
-bool GlobalObject::initFinalizationIteratorProto(JSContext* cx,
-                                                 Handle<GlobalObject*> global) {
-  Rooted<JSObject*> base(
-      cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
-  if (!base) {
-    return false;
-  }
-  RootedPlainObject proto(
-      cx, GlobalObject::createBlankPrototypeInheriting<PlainObject>(cx, base));
-  if (!proto) {
-    return false;
-  }
-  if (!JS_DefineFunctions(cx, proto, FinalizationIteratorObject::methods_) ||
-      !JS_DefineProperties(cx, proto,
-                           FinalizationIteratorObject::properties_)) {
-    return false;
-  }
-  global->setReservedSlot(FINALIZATION_ITERATOR_PROTO, ObjectValue(*proto));
-  return true;
-}
+    activeRecords->remove(record);
+    record->clear();
 
-/* static */ FinalizationIteratorObject* FinalizationIteratorObject::create(
-    JSContext* cx, HandleFinalizationRegistryObject registry) {
-  MOZ_ASSERT(registry);
-
-  RootedObject proto(cx, GlobalObject::getOrCreateFinalizationIteratorPrototype(
-                             cx, cx->global()));
-  if (!proto) {
-    return nullptr;
-  }
-
-  FinalizationIteratorObject* iterator =
-      NewObjectWithGivenProto<FinalizationIteratorObject>(cx, proto);
-  if (!iterator) {
-    return nullptr;
+    if (!Call(cx, callback, UndefinedHandleValue, heldValue, &rval)) {
+      return false;
+    }
   }
 
-  iterator->initReservedSlot(FinalizationRegistrySlot, ObjectValue(*registry));
-  iterator->initReservedSlot(IndexSlot, Int32Value(0));
-
-  return iterator;
-}
-
-FinalizationRegistryObject* FinalizationIteratorObject::finalizationRegistry()
-    const {
-  Value value = getReservedSlot(FinalizationRegistrySlot);
-  if (value.isUndefined()) {
-    return nullptr;
-  }
-  return &value.toObject().as<FinalizationRegistryObject>();
-}
-
-size_t FinalizationIteratorObject::index() const {
-  int32_t i = getReservedSlot(IndexSlot).toInt32();
-  MOZ_ASSERT(i >= 0);
-  return size_t(i);
-}
-
-void FinalizationIteratorObject::setIndex(size_t i) {
-  MOZ_ASSERT(i <= INT32_MAX);
-  setReservedSlot(IndexSlot, Int32Value(int32_t(i)));
-}
-
-void FinalizationIteratorObject::clearFinalizationRegistry() {
-  MOZ_ASSERT(finalizationRegistry());
-  setReservedSlot(FinalizationRegistrySlot, UndefinedValue());
-}
-
-// %FinalizationRegistryCleanupIteratorPrototype%.next()
-// https://tc39.es/proposal-weakrefs/#sec-%finalizationregistrycleanupiterator%.next
-/* static */
-bool FinalizationIteratorObject::next(JSContext* cx, unsigned argc, Value* vp) {
-  CallArgs args = CallArgsFromVp(argc, vp);
-
-  // 1. Let iterator be the this value.
-  // 2. If Type(iterator) is not Object, throw a TypeError exception.
-  // 3. If iterator does not have a [[FinalizationRegistry]] internal slot,
-  //    throw a TypeError exception.
-  if (!args.thisv().isObject() ||
-      !args.thisv().toObject().is<FinalizationIteratorObject>()) {
-    JS_ReportErrorNumberASCII(
-        cx, GetErrorMessage, nullptr, JSMSG_NOT_A_FINALIZATION_ITERATOR,
-        "Receiver of FinalizationRegistryCleanupIterator.next call");
-    return false;
-  }
-
-  RootedFinalizationIteratorObject iterator(
-      cx, &args.thisv().toObject().as<FinalizationIteratorObject>());
-
-  // 4. If iterator.[[FinalizationRegistry]] is empty, throw a TypeError
-  //    exception.
-  // 5. Let finalizationRegistry be iterator.[[FinalizationRegistry]].
-  RootedFinalizationRegistryObject registry(cx,
-                                            iterator->finalizationRegistry());
-  if (!registry) {
-    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_STALE_FINALIZATION_REGISTRY_ITERATOR);
-    return false;
-  }
-
-  // 8. If finalizationRegistry.[[Cells]] contains a Record cell such that
-  //    cell.[[Target]] is empty,
-  //    a. Choose any such cell.
-  //    b. Remove cell from finalizationRegistry.[[Cells]].
-  //    c. Return CreateIterResultObject(cell.[[HeldValue]], false).
-  FinalizationRecordVector* records = registry->recordsToBeCleanedUp();
-  size_t index = iterator->index();
-  MOZ_ASSERT(index <= records->length());
-
-  // Advance until we find a record that hasn't been unregistered.
-  while (index < records->length() && index < INT32_MAX &&
-         !(*records)[index]->isActive()) {
-    index++;
-    iterator->setIndex(index);
-  }
-
-  if (index < records->length() && index < INT32_MAX) {
-    RootedFinalizationRecordObject record(cx, (*records)[index]);
-    RootedValue heldValue(cx, record->heldValue());
-    PlainObject* result = CreateIterResultObject(cx, heldValue, false);
-    if (!result) {
-      return false;
-    }
-
-    registry->activeRecords()->remove(record);
-    record->clear();
-    iterator->setIndex(index + 1);
-
-    args.rval().setObject(*result);
-    return true;
-  }
-
-  // 9. Otherwise, return CreateIterResultObject(undefined, true).
-  PlainObject* result = CreateIterResultObject(cx, UndefinedHandleValue, true);
-  if (!result) {
-    return false;
-  }
-
-  args.rval().setObject(*result);
   return true;
 }
--- a/js/src/builtin/FinalizationRegistryObject.h
+++ b/js/src/builtin/FinalizationRegistryObject.h
@@ -176,34 +176,31 @@ using FinalizationRecordSet =
 // The FinalizationRegistry object itself.
 class FinalizationRegistryObject : public NativeObject {
   enum {
     CleanupCallbackSlot = 0,
     RegistrationsSlot,
     ActiveRecords,
     RecordsToBeCleanedUpSlot,
     IsQueuedForCleanupSlot,
-    IsCleanupJobActiveSlot,
     SlotCount
   };
 
  public:
   static const JSClass class_;
   static const JSClass protoClass_;
 
   JSObject* cleanupCallback() const;
   ObjectWeakMap* registrations() const;
   FinalizationRecordSet* activeRecords() const;
   FinalizationRecordVector* recordsToBeCleanedUp() const;
   bool isQueuedForCleanup() const;
-  bool isCleanupJobActive() const;
 
   void queueRecordToBeCleanedUp(FinalizationRecordObject* record);
   void setQueuedForCleanup(bool value);
-  void setCleanupJobActive(bool value);
 
   void sweep();
 
   static bool cleanupQueuedRecords(JSContext* cx,
                                    HandleFinalizationRegistryObject registry,
                                    HandleObject callback = nullptr);
 
  private:
@@ -222,19 +219,16 @@ class FinalizationRegistryObject : publi
                               HandleObject unregisterToken,
                               HandleFinalizationRecordObject record);
   static void removeRegistrationOnError(
       HandleFinalizationRegistryObject registry, HandleObject unregisterToken,
       HandleFinalizationRecordObject record);
 
   static void trace(JSTracer* trc, JSObject* obj);
   static void finalize(JSFreeOp* fop, JSObject* obj);
-
-  static bool hasRegisteredRecordsToBeCleanedUp(
-      HandleFinalizationRegistryObject registry);
 };
 
 // An iterator over a finalization registry's queued held values. In the spec
 // this is called FinalizationRegistryCleanupIterator.
 class FinalizationIteratorObject : public NativeObject {
   enum { FinalizationRegistrySlot = 0, IndexSlot, SlotCount };
 
  public:
--- a/js/src/jit-test/tests/gc/bug-1620196.js
+++ b/js/src/jit-test/tests/gc/bug-1620196.js
@@ -1,9 +1,9 @@
 // |jit-test| --enable-weak-refs
 
 gczeal(4);
 let heldValues = [];
-registry = new FinalizationRegistry(iterator => {
-    heldValues.push(...iterator);
+registry = new FinalizationRegistry(value => {
+    heldValues.push(value);
 });
 registry.register({}, 42);
 gc();
--- a/js/src/jit-test/tests/gc/bug1600488-1.js
+++ b/js/src/jit-test/tests/gc/bug1600488-1.js
@@ -1,16 +1,16 @@
 // |jit-test| --enable-weak-refs
 
 const token = {};
-let iterated;
-const finalizationRegistry = new FinalizationRegistry(items => {
-    iterated = items.next().value;
+let cleanedUpValue;
+const finalizationRegistry = new FinalizationRegistry(value => {
+  cleanedUpValue = value;
 });
 {
     let object = {};
     finalizationRegistry.register(object, token, token);
     object = undefined;
 }
 gc();
 finalizationRegistry.cleanupSome();
-assertEq(iterated, token);
+assertEq(cleanedUpValue, token);
 assertEq(finalizationRegistry.unregister(token), false);
--- a/js/src/jit-test/tests/gc/finalizationRegistry-ccw.js
+++ b/js/src/jit-test/tests/gc/finalizationRegistry-ccw.js
@@ -6,26 +6,26 @@ gczeal(0);
 
 let heldValues = [];
 
 function ccwToObject() {
     return evalcx('({})', newGlobal({newCompartment: true}));
 }
 
 function newRegistry() {
-  return new FinalizationRegistry(iterator => {
-    heldValues.push(...iterator);
+  return new FinalizationRegistry(value => {
+    heldValues.push(value);
   });
 }
 
 function ccwToRegistry() {
   let global = newGlobal({newCompartment: true});
   global.heldValues = heldValues;
   return global.eval(
-    `new FinalizationRegistry(iterator => heldValues.push(...iterator))`);
+    `new FinalizationRegistry(value => heldValues.push(value))`);
 }
 
 function incrementalGC() {
   startgc(1);
   while (gcstate() !== "NotActive") {
     gcslice(1000);
   }
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/finalizationRegistry-cleanupSome-recursive.js
@@ -0,0 +1,51 @@
+// |jit-test| --enable-weak-refs
+
+// Test trying to call cleanupSome recursively in callback.
+
+// 0: Initial state.
+// 1: Attempt recursive calls.
+// 2: After recursive calls.
+let state = 0;
+
+let registry = new FinalizationRegistry(x => {
+  if (state === 0) {
+    state = 1;
+    try {
+      registry.cleanupSome();
+    } catch (e) {
+      // Pass the test if any error was thrown.
+      return;
+    } finally {
+      state = 2;
+    }
+    throw new Error("expected stack overflow error");
+  }
+
+  if (state === 1) {
+    registry.cleanupSome();
+  }
+});
+
+// Attempt to find the maximum supported stack depth.
+function findStackSize(i) {
+  try {
+    return findStackSize(i + 1);
+  } catch {
+    return i;
+  }
+}
+const stackSize = findStackSize(0);
+
+// Multiply the calculated stack size by some factor just to be on the safe side.
+const exceedStackDepthLimit = stackSize * 5;
+
+let values = [];
+for (let i = 0; i < exceedStackDepthLimit; ++i) {
+  let v = {};
+  registry.register(v, i);
+  values.push(v);
+}
+values.length = 0;
+
+gc();
+drainJobQueue();
--- a/js/src/jit-test/tests/gc/finalizationRegistry-gray.js
+++ b/js/src/jit-test/tests/gc/finalizationRegistry-gray.js
@@ -1,12 +1,12 @@
 // |jit-test| --enable-weak-refs
 
 // Test gray finalization registry is correctly barrired.
 target = {};
-registry = new FinalizationRegistry(iterator => undefined);
+registry = new FinalizationRegistry(value => undefined);
 registry.register(target, 1);
 grayRoot()[0] = registry;
 registry = undefined;
 gc(); // Registry is now marked gray.
 target = undefined;
 gc(); // Target dies, registry is queued.
 drainJobQueue();
--- a/js/src/jit-test/tests/gc/finalizationRegistry.js
+++ b/js/src/jit-test/tests/gc/finalizationRegistry.js
@@ -56,46 +56,19 @@ assertEq(typeof proto.cleanupSome, 'func
 assertEq(proto[Symbol.toStringTag], "FinalizationRegistry");
 checkPropertyDescriptor(proto, Symbol.toStringTag, false, false, true);
 
 // 3.4 Properties of FinalizationRegistry Instances
 let registry = new FinalizationRegistry(x => 0);
 assertEq(Object.getPrototypeOf(registry), proto);
 assertEq(Object.getOwnPropertyNames(registry).length, 0);
 
-// Get a cleanup iterator.
-let iterator;
-registry = new FinalizationRegistry(it => iterator = it);
-registry.register({}, 0);
-gc();
-drainJobQueue();
-assertEq(typeof registry, 'object');
-assertEq(typeof iterator, 'object');
-
-// 3.5.2 The %FinalizationRegistryCleanupIteratorPrototype% Object
-let arrayIterator = [][Symbol.iterator]();
-let iteratorProto = arrayIterator.__proto__.__proto__;
-proto = iterator.__proto__;
-assertEq(typeof proto, "object");
-assertEq(proto.__proto__, iteratorProto);
-
-// 3.5.2.1 %FinalizationRegistryCleanupIteratorPrototype%.next()
-assertEq(proto.hasOwnProperty("next"), true);
-assertEq(typeof proto.next, "function");
-
-// 3.5.2.2 %FinalizationRegistryCleanupIteratorPrototype% [ @@toStringTag ]
-assertEq(proto[Symbol.toStringTag], "FinalizationRegistry Cleanup Iterator");
-checkPropertyDescriptor(proto, Symbol.toStringTag, false, false, true);
-
-// 3.5.3 Properties of FinalizationRegistry Cleanup Iterator Instances
-assertEq(Object.getOwnPropertyNames(iterator).length, 0);
-
 let heldValues = [];
-registry = new FinalizationRegistry(iterator => {
-  heldValues.push(...iterator);
+registry = new FinalizationRegistry(value => {
+  heldValues.push(value);
 });
 
 // Test a single target.
 heldValues = [];
 registry.register({}, 42);
 gc();
 drainJobQueue();
 assertEq(heldValues.length, 1);
@@ -112,18 +85,18 @@ assertEq(heldValues.length, 100);
 heldValues = heldValues.sort((a, b) => a - b);
 for (let i = 0; i < 100; i++) {
   assertEq(heldValues[i], i);
 }
 
 // Test a single object in multiple registries
 heldValues = [];
 let heldValues2 = [];
-let registry2 = new FinalizationRegistry(iterator => {
-  heldValues2.push(...iterator);
+let registry2 = new FinalizationRegistry(value => {
+  heldValues2.push(value);
 });
 {
   let object = {};
   registry.register(object, 1);
   registry2.register(object, 2);
   object = null;
 }
 gc();
@@ -191,89 +164,86 @@ drainJobQueue();
 assertEq(heldValues.length, 0);
 
 // FinalizationRegistry is designed to be subclassable.
 class MyRegistry extends FinalizationRegistry {
   constructor(callback) {
     super(callback);
   }
 }
-let r2 = new MyRegistry(iterator => {
-  heldValues.push(...iterator);
+let r2 = new MyRegistry(value => {
+  heldValues.push(value);
 });
 heldValues = [];
 r2.register({}, 42);
 gc();
 drainJobQueue();
 assertEq(heldValues.length, 1);
 assertEq(heldValues[0], 42);
 
-// Test trying to use iterator after the callback.
-iterator = undefined;
-let r3 = new FinalizationRegistry(i => iterator = i);
-r3.register({}, 1);
-gc();
-drainJobQueue();
-assertEq(typeof iterator, 'object');
-assertThrowsTypeError(() => iterator.next());
-
-// Test trying to use the wrong iterator inside the callback.
-let r4 = new FinalizationRegistry(x => {
-  assertThrowsTypeError(() => iterator.next());
-});
-r4.register({}, 1);
-gc();
-drainJobQueue();
-
 // Test cleanupSome.
 heldValues = [];
-let r5 = new FinalizationRegistry(i => heldValues = [...i]);
+let r5 = new FinalizationRegistry(v => heldValues.push(v));
 r5.register({}, 1);
 r5.register({}, 2);
 r5.register({}, 3);
 gc();
 r5.cleanupSome();
 assertEq(heldValues.length, 3);
 heldValues = heldValues.sort((a, b) => a - b);
 assertEq(heldValues[0], 1);
 assertEq(heldValues[1], 2);
 assertEq(heldValues[2], 3);
 
 // Test trying to call cleanupSome in callback.
 let r6 = new FinalizationRegistry(x => {
-  assertThrowsTypeError(() => r6.cleanupSome());
+  r6.cleanupSome();
 });
 r6.register({}, 1);
 gc();
 drainJobQueue();
 
+// Test trying to call cleanupSome in callback with multiple values.
+let callbackCounter7 = 0;
+let r7 = new FinalizationRegistry(x => {
+  callbackCounter7++;
+  r7.cleanupSome();
+});
+r7.register({}, 1);
+r7.register({}, 2);
+r7.register({}, 3);
+r7.register({}, 4);
+gc();
+drainJobQueue();
+assertEq(callbackCounter7, 4);
+
 // Test that targets don't keep the finalization registry alive.
 let target = {};
-registry = new FinalizationRegistry(iterator => undefined);
+registry = new FinalizationRegistry(value => undefined);
 registry.register(target, 1);
 let weakRef = new WeakRef(registry);
 registry = undefined;
 assertEq(typeof weakRef.deref(), 'object');
 drainJobQueue();
 gc();
 assertEq(weakRef.deref(), undefined);
 assertEq(typeof target, 'object');
 
 // Test that targets don't keep the finalization registry alive when also
 // used as the unregister token.
-registry = new FinalizationRegistry(iterator => undefined);
+registry = new FinalizationRegistry(value => undefined);
 registry.register(target, 1, target);
 weakRef = new WeakRef(registry);
 registry = undefined;
 assertEq(typeof weakRef.deref(), 'object');
 drainJobQueue();
 gc();
 assertEq(weakRef.deref(), undefined);
 assertEq(typeof target, 'object');
 
 // Test that cleanup doesn't happen if the finalization registry dies.
 heldValues = [];
-new FinalizationRegistry(iterator => {
-  heldValues.push(...iterator);
+new FinalizationRegistry(value => {
+  heldValues.push(value);
 }).register({}, 1);
 gc();
 drainJobQueue();
 assertEq(heldValues.length, 0);
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -407,24 +407,16 @@ ignore-flag(--more-compartments) script 
 skip script test262/built-ins/Object/prototype/toString/symbol-tag-non-str-builtin.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1591099
 skip script test262/intl402/ListFormat/constructor/constructor/proto-from-ctor-realm.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1362154
 skip script test262/built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js
 
-# https://github.com/tc39/proposal-weakrefs/pull/187
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1629796
-skip script test262/built-ins/FinalizationRegistry/gc-has-one-chance-to-call-cleanupCallback.js
-skip script test262/built-ins/FinalizationRegistry/prototype/cleanupSome/holdings-multiple-values.js
-skip script test262/built-ins/FinalizationRegistry/prototype/cleanupSome/cleanup-prevented-with-reference.js
-skip script test262/built-ins/FinalizationRegistry/prototype/cleanupSome/reentrancy.js
-skip script test262/built-ins/FinalizationRegistry/prototype/unregister/unregister-cleaned-up-cell.js
-
 # Depends upon the SharedArrayBuffer constructor being defined as a global
 # property -- and right now, it's only defined for cross-site-isolated pages
 # that request it using COOP/COEP.
 fails-if(!xulRuntime.shell) script test262/built-ins/ArrayBuffer/prototype/byteLength/this-is-sharedarraybuffer.js
 fails-if(!xulRuntime.shell) script test262/built-ins/ArrayBuffer/prototype/slice/this-is-sharedarraybuffer.js
 fails-if(!xulRuntime.shell) script test262/built-ins/Atomics/add/bigint/bad-range.js
 fails-if(!xulRuntime.shell) script test262/built-ins/Atomics/add/bigint/good-views.js
 fails-if(!xulRuntime.shell) script test262/built-ins/Atomics/add/validate-arraytype-before-index-coercion.js
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -110,17 +110,16 @@ class GlobalObject : public NativeObject
     ASYNC_FROM_SYNC_ITERATOR_PROTO,
     ASYNC_GENERATOR_PROTO,
     MAP_ITERATOR_PROTO,
     SET_ITERATOR_PROTO,
     MODULE_PROTO,
     IMPORT_ENTRY_PROTO,
     EXPORT_ENTRY_PROTO,
     REQUESTED_MODULE_PROTO,
-    FINALIZATION_ITERATOR_PROTO,
     REGEXP_STATICS,
     RUNTIME_CODEGEN_ENABLED,
     INTRINSICS,
     FOR_OF_PIC_CHAIN,
     WINDOW_PROXY,
     GLOBAL_THIS_RESOLVED,
     INSTRUMENTATION,
     SOURCE_URLS,
@@ -543,22 +542,16 @@ class GlobalObject : public NativeObject
   static JSObject* getOrCreateTypedArrayPrototype(
       JSContext* cx, Handle<GlobalObject*> global) {
     if (!ensureConstructor(cx, global, JSProto_TypedArray)) {
       return nullptr;
     }
     return &global->getPrototype(JSProto_TypedArray).toObject();
   }
 
-  static JSObject* getOrCreateFinalizationIteratorPrototype(
-      JSContext* cx, Handle<GlobalObject*> global) {
-    return getOrCreateObject(cx, global, FINALIZATION_ITERATOR_PROTO,
-                             initFinalizationIteratorProto);
-  }
-
  private:
   using ObjectInitOp = bool (*)(JSContext*, Handle<GlobalObject*>);
 
   static JSObject* getOrCreateObject(JSContext* cx,
                                      Handle<GlobalObject*> global,
                                      unsigned slot, ObjectInitOp init) {
     Value v = global->getSlotRef(slot);
     if (v.isObject()) {
@@ -848,20 +841,16 @@ class GlobalObject : public NativeObject
   static bool initExportEntryProto(JSContext* cx, Handle<GlobalObject*> global);
   static bool initRequestedModuleProto(JSContext* cx,
                                        Handle<GlobalObject*> global);
 
   // Implemented in builtin/TypedObject.cpp
   static bool initTypedObjectModule(JSContext* cx,
                                     Handle<GlobalObject*> global);
 
-  // Implemented in builtin/FinalizationRegistry.cpp
-  static bool initFinalizationIteratorProto(JSContext* cx,
-                                            Handle<GlobalObject*> global);
-
   static bool initStandardClasses(JSContext* cx, Handle<GlobalObject*> global);
   static bool initSelfHostingBuiltins(JSContext* cx,
                                       Handle<GlobalObject*> global,
                                       const JSFunctionSpec* builtins);
 
   Realm::DebuggerVector& getDebuggers() const {
     return realm()->getDebuggers();
   }