Added: trunk/LayoutTests/inspector/unit-tests/iterableweakset-expected.txt (0 => 290612)
--- trunk/LayoutTests/inspector/unit-tests/iterableweakset-expected.txt (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/iterableweakset-expected.txt 2022-02-28 21:00:47 UTC (rev 290612)
@@ -0,0 +1,86 @@
+Testing all methods of IterableWeakSet.
+
+
+== Running test suite: IterableWeakSet
+-- Running test case: IterableWeakSet.prototype.constructor.Empty
+[]
+
+-- Running test case: IterableWeakSet.prototype.constructor.NonEmpty
+[{"value":1},{"value":2},{"value":3}]
+
+-- Running test case: IterableWeakSet.prototype.has
+[{"value":1},{"value":2},{"value":3}]
+PASS: 'has' should return true if a key exists.
+PASS: 'has' should return true if a key exists (more than once).
+PASS: 'has' should return true if a key exists.
+PASS: 'has' should return false if a key doesn't exist.
+
+-- Running test case: IterableWeakSet.prototype.add
+[]
+[{"value":1}]
+[{"value":1},{"value":2}]
+[{"value":1},{"value":2},{"value":3}]
+[{"value":1},{"value":2},{"value":3}]
+
+-- Running test case: IterableWeakSet.prototype.delete
+[{"value":1},{"value":2},{"value":3}]
+PASS: 'delete' should return true for a known key.
+[{"value":2},{"value":3}]
+PASS: 'delete' should return true for a known key.
+[{"value":3}]
+PASS: 'delete' should return false for an already deleted key.
+[{"value":3}]
+PASS: 'delete' should return true for a known key.
+[]
+PASS: 'delete' should return false for an unknown key.
+[]
+
+-- Running test case: IterableWeakSet.prototype.take
+[{"value":1},{"value":2},{"value":3}]
+PASS: 'take' should return the key for a known key.
+[{"value":2},{"value":3}]
+PASS: 'take' should return the key for a known key.
+[{"value":3}]
+PASS: 'take' should return undefined for an already deleted key.
+[{"value":3}]
+PASS: 'take' should return the key for a known key.
+[]
+PASS: 'take' should return undefined for an unknown key.
+[]
+
+-- Running test case: IterableWeakSet.prototype.clear
+[{"value":1},{"value":2},{"value":3}]
+[]
+
+-- Running test case: IterableWeakSet.prototype.keys
+[{"value":1},{"value":2},{"value":3}]
+
+-- Running test case: IterableWeakSet.prototype.values
+[{"value":1},{"value":2},{"value":3}]
+
+-- Running test case: IterableWeakSet.prototype.copy
+[{"value":1},{"value":2},{"value":3}]
+PASS: Copy should not return the same object.
+PASS: Copy should return a deep copy.
+PASS: Modifying the original should not modify the copy.
+
+-- Running test case: IterableWeakSet.DoesNotKeepObjectsAlive
+Evaluating `IterableWeakSet` source in the inspected page...
+Testing `IterableWeakSet` in the inspected page...
+[{"value":1},{"value":2},{"value":3}]
+PASS: 'has' should return true for '{"value":1}' after construction.
+PASS: 'has' should return true for '{"value":2}' after construction.
+PASS: 'has' should return true for '{"value":3}' after construction.
+[{"value":2},{"value":3}]
+PASS: Should not contain '{"value": 1}' after `_one_ = null`.
+PASS: 'has' should return true for '{"value":2}' after `_one_ = null`.
+PASS: 'has' should return true for '{"value":3}' after `_one_ = null`.
+[{"value":3}]
+PASS: Should not contain '{"value": 1}' after `two = null`.
+PASS: Should not contain '{"value": 2}' after `two = null`.
+PASS: 'has' should return true for '{"value":3}' after `two = null`.
+[]
+PASS: Should not contain '{"value": 1}' after `three = null`.
+PASS: Should not contain '{"value": 2}' after `three = null`.
+PASS: Should not contain '{"value": 3}' after `three = null`.
+
Added: trunk/LayoutTests/inspector/unit-tests/iterableweakset.html (0 => 290612)
--- trunk/LayoutTests/inspector/unit-tests/iterableweakset.html (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/iterableweakset.html 2022-02-28 21:00:47 UTC (rev 290612)
@@ -0,0 +1,281 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script src=""
+<script>
+async function checkDoesNotKeepObjectsAlive()
+{
+ let _one_ = [{value: 1}];
+ let two = [{value: 2}];
+ let three = [{value: 3}];
+
+ let iterableWeakSet = new IterableWeakSet([one[0], two[0], three[0], two[0]]);
+
+ function check(description, itemOrValue, present) {
+ if (typeof itemOrValue === "object")
+ TestPage.dispatchEventToFrontend("TestPage-checkDoesNotKeepObjectsAlive-log", {message: `${iterableWeakSet.has(itemOrValue) === present ? "PASS" : "FAIL"}: 'has' should return ${present ? "true" : "false"} for '${JSON.stringify(itemOrValue)}' after ${description}.`});
+
+ if (typeof itemOrValue === "number") {
+ let found = false;
+ for (let item of iterableWeakSet) {
+ if (item.value !== itemOrValue)
+ continue;
+
+ if (found) {
+ TestPage.dispatchEventToFrontend("TestPage-checkDoesNotKeepObjectsAlive-log", {message: `FAIL: Should not find '${JSON.stringify(itemOrValue)}' more than once after ${description}.`});
+ continue;
+ }
+
+ found = true;
+ }
+
+ TestPage.dispatchEventToFrontend("TestPage-checkDoesNotKeepObjectsAlive-log", {message: `${found === present ? "PASS" : "FAIL"}: Should ${present ? "" : "not"} contain '{"value": ${itemOrValue}}' after ${description}.`});
+ }
+ }
+
+ TestPage.dispatchEventToFrontend("TestPage-checkDoesNotKeepObjectsAlive-log", {message: JSON.stringify(iterableWeakSet)});
+ check("construction", one[0], true);
+ check("construction", two[0], true);
+ check("construction", three[0], true);
+
+ nukeArray(one);
+ await new Promise(setTimeout);
+ gc();
+ await new Promise(setTimeout);
+
+ TestPage.dispatchEventToFrontend("TestPage-checkDoesNotKeepObjectsAlive-log", {message: JSON.stringify(iterableWeakSet)});
+ check("`_one_ = null`", 1, false);
+ check("`_one_ = null`", two[0], true);
+ check("`_one_ = null`", three[0], true);
+
+ nukeArray(two);
+ await new Promise(setTimeout);
+ gc();
+ await new Promise(setTimeout);
+
+ TestPage.dispatchEventToFrontend("TestPage-checkDoesNotKeepObjectsAlive-log", {message: JSON.stringify(iterableWeakSet)});
+ check("`two = null`", 1, false);
+ check("`two = null`", 2, false);
+ check("`two = null`", three[0], true);
+
+ nukeArray(three);
+ await new Promise(setTimeout);
+ gc();
+ await new Promise(setTimeout);
+
+ TestPage.dispatchEventToFrontend("TestPage-checkDoesNotKeepObjectsAlive-log", {message: JSON.stringify(iterableWeakSet)});
+ check("`three = null`", 1, false);
+ check("`three = null`", 2, false);
+ check("`three = null`", 3, false);
+
+ TestPage.dispatchEventToFrontend("TestPage-checkDoesNotKeepObjectsAlive-done");
+}
+
+function test()
+{
+ let suite = InspectorTest.createAsyncSuite("IterableWeakSet");
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.constructor.Empty",
+ async test() {
+ let iterableWeakSet = new IterableWeakSet;
+ InspectorTest.log(iterableWeakSet);
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.constructor.NonEmpty",
+ async test() {
+ let _one_ = {value: 1};
+ let two = {value: 2};
+ let three = {value: 3};
+
+ let iterableWeakSet = new IterableWeakSet([one, two, three, two]);
+ InspectorTest.log(iterableWeakSet);
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.has",
+ async test() {
+ let _one_ = {value: 1};
+ let two = {value: 2};
+ let three = {value: 3};
+ let four = {value: 4};
+
+ let iterableWeakSet = new IterableWeakSet([one, two, three, two]);
+ InspectorTest.log(iterableWeakSet);
+ InspectorTest.expectTrue(iterableWeakSet.has(one), "'has' should return true if a key exists.");
+ InspectorTest.expectTrue(iterableWeakSet.has(two), "'has' should return true if a key exists (more than once).");
+ InspectorTest.expectTrue(iterableWeakSet.has(three), "'has' should return true if a key exists.");
+ InspectorTest.expectFalse(iterableWeakSet.has(four), "'has' should return false if a key doesn't exist.");
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.add",
+ async test() {
+ let _one_ = {value: 1};
+ let two = {value: 2};
+ let three = {value: 3};
+
+ let iterableWeakSet = new IterableWeakSet;
+ InspectorTest.log(iterableWeakSet);
+
+ iterableWeakSet.add(one);
+ InspectorTest.log(iterableWeakSet);
+
+ iterableWeakSet.add(two);
+ InspectorTest.log(iterableWeakSet);
+
+ iterableWeakSet.add(three);
+ InspectorTest.log(iterableWeakSet);
+
+ iterableWeakSet.add(two);
+ InspectorTest.log(iterableWeakSet);
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.delete",
+ async test() {
+ let _one_ = {value: 1};
+ let two = {value: 2};
+ let three = {value: 3};
+ let four = {value: 4};
+
+ let iterableWeakSet = new IterableWeakSet([one, two, three, two]);
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectTrue(iterableWeakSet.delete(one), "'delete' should return true for a known key.");
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectTrue(iterableWeakSet.delete(two), "'delete' should return true for a known key.");
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectFalse(iterableWeakSet.delete(two), "'delete' should return false for an already deleted key.");
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectTrue(iterableWeakSet.delete(three), "'delete' should return true for a known key.");
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectFalse(iterableWeakSet.delete(four), "'delete' should return false for an unknown key.");
+ InspectorTest.log(iterableWeakSet);
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.take",
+ async test() {
+ let _one_ = {value: 1};
+ let two = {value: 2};
+ let three = {value: 3};
+ let four = {value: 4};
+
+ let iterableWeakSet = new IterableWeakSet([one, two, three, two]);
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectEqual(iterableWeakSet.take(one)?.value, 1, "'take' should return the key for a known key.");
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectEqual(iterableWeakSet.take(two)?.value, 2, "'take' should return the key for a known key.");
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectEqual(iterableWeakSet.take(two), undefined, "'take' should return undefined for an already deleted key.");
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectEqual(iterableWeakSet.take(three)?.value, 3, "'take' should return the key for a known key.");
+ InspectorTest.log(iterableWeakSet);
+
+ InspectorTest.expectEqual(iterableWeakSet.take(four), undefined, "'take' should return undefined for an unknown key.");
+ InspectorTest.log(iterableWeakSet);
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.clear",
+ async test() {
+ let _one_ = {value: 1};
+ let two = {value: 2};
+ let three = {value: 3};
+
+ let iterableWeakSet = new IterableWeakSet([one, two, three, two]);
+ InspectorTest.log(iterableWeakSet);
+
+ iterableWeakSet.clear();
+ InspectorTest.log(iterableWeakSet);
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.keys",
+ async test() {
+ let _one_ = {value: 1};
+ let two = {value: 2};
+ let three = {value: 3};
+
+ let iterableWeakSet = new IterableWeakSet([one, two, three, two]);
+ InspectorTest.log(Array.from(iterableWeakSet.keys()));
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.values",
+ async test() {
+ let _one_ = {value: 1};
+ let two = {value: 2};
+ let three = {value: 3};
+
+ let iterableWeakSet = new IterableWeakSet([one, two, three, two]);
+ InspectorTest.log(Array.from(iterableWeakSet.values()));
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.prototype.copy",
+ async test() {
+ let _one_ = {value: 1};
+ let two = {value: 2};
+ let three = {value: 3};
+ let four = {value: 4};
+
+ let iterableWeakSet = new IterableWeakSet([one, two, three, two]);
+ InspectorTest.log(iterableWeakSet);
+
+ let copy = iterableWeakSet.copy();
+ InspectorTest.expectNotEqual(iterableWeakSet, copy, "Copy should not return the same object.")
+ InspectorTest.expectEqual(JSON.stringify(iterableWeakSet), JSON.stringify(copy), "Copy should return a deep copy.");
+
+ iterableWeakSet.add(four);
+ InspectorTest.expectNotEqual(JSON.stringify(iterableWeakSet), JSON.stringify(copy), "Modifying the original should not modify the copy.");
+ },
+ });
+
+ suite.addTestCase({
+ name: "IterableWeakSet.DoesNotKeepObjectsAlive",
+ async test() {
+ // Send `IterableWeakSet` to the page so that `GCController` can be used.
+ InspectorTest.log("Evaluating `IterableWeakSet` source in the inspected page...");
+ await InspectorTest.evaluateInPage(String(IterableWeakSet));
+
+ InspectorTest.addEventListener("TestPage-checkDoesNotKeepObjectsAlive-log", (event) => {
+ InspectorTest.log(event.data.message);
+ });
+
+ InspectorTest.log("Testing `IterableWeakSet` in the inspected page...");
+ await Promise.all([
+ InspectorTest.awaitEvent("TestPage-checkDoesNotKeepObjectsAlive-done"),
+ InspectorTest.evaluateInPage(`checkDoesNotKeepObjectsAlive()`),
+ ]);
+ },
+ });
+
+ suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+ <p>Testing all methods of IterableWeakSet.</p>
+</body>
+</html>
Added: trunk/Source/WebInspectorUI/UserInterface/Base/IterableWeakSet.js (0 => 290612)
--- trunk/Source/WebInspectorUI/UserInterface/Base/IterableWeakSet.js (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/IterableWeakSet.js 2022-02-28 21:00:47 UTC (rev 290612)
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 Apple 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:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
+ */
+
+class IterableWeakSet
+{
+ constructor(items = [])
+ {
+ this._wrappers = new Set;
+ this._wrapperForItem = new WeakMap;
+
+ for (let item of items)
+ this.add(item);
+ }
+
+ // Public
+
+ get size()
+ {
+ let size = 0;
+ for (let wrapper of this._wrappers) {
+ if (wrapper.deref())
+ ++size;
+ }
+ return size;
+ }
+
+ has(item)
+ {
+ let result = this._wrapperForItem.has(item);
+ console.assert(Array.from(this._wrappers).some((wrapper) => wrapper.deref() === item) === result, this, item);
+ return result;
+ }
+
+ add(item)
+ {
+ console.assert(typeof item === "object", item);
+ console.assert(item !== null, item);
+
+ if (this.has(item))
+ return;
+
+ let wrapper = new WeakRef(item);
+ this._wrappers.add(wrapper);
+ this._wrapperForItem.set(item, wrapper);
+ this._finalizationRegistry.register(item, {weakThis: new WeakRef(this), wrapper}, wrapper);
+ }
+
+ delete(item)
+ {
+ return !!this.take(item);
+ }
+
+ take(item)
+ {
+ let wrapper = this._wrapperForItem.get(item);
+ if (!wrapper)
+ return undefined;
+
+ let itemDeleted = this._wrapperForItem.delete(item);
+ console.assert(itemDeleted, this, item);
+
+ let wrapperDeleted = this._wrappers.delete(wrapper);
+ console.assert(wrapperDeleted, this, item);
+
+ this._finalizationRegistry.unregister(wrapper);
+
+ console.assert(wrapper.deref() === item, this, item);
+ return item;
+ }
+
+ clear()
+ {
+ for (let wrapper of this._wrappers) {
+ this._wrapperForItem.delete(wrapper);
+ this._finalizationRegistry.unregister(wrapper);
+ }
+
+ this._wrappers.clear();
+ }
+
+ keys()
+ {
+ return this.values();
+ }
+
+ *values()
+ {
+ for (let wrapper of this._wrappers) {
+ let item = wrapper.deref();
+ console.assert(!item === !this._wrapperForItem.has(item), this, item);
+ if (item)
+ yield item;
+ }
+ }
+
+ [Symbol.iterator]()
+ {
+ return this.values();
+ }
+
+ copy()
+ {
+ return new IterableWeakSet(this.toJSON());
+ }
+
+ toJSON()
+ {
+ return Array.from(this);
+ }
+
+ // Private
+
+ get _finalizationRegistry()
+ {
+ return IterableWeakSet._finalizationRegistry ??= new FinalizationRegistry(function(heldValue) {
+ heldValue.weakThis.deref()?._wrappers.delete(heldValue.wrapper);
+ });
+ }
+}