tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit a091e7c4c132680be00a5021386dcad16f6b45df
parent 2633fd83c25fe178b19aac2cd7649d3745ffd54a
Author: Henrik Skupin <mail@hskupin.info>
Date:   Mon,  1 Dec 2025 20:43:58 +0000

Bug 2000801 - [remote] Add class for bi-directional maps (BiMap). r=jdescottes

Differential Revision: https://phabricator.services.mozilla.com/D273538

Diffstat:
Mremote/jar.mn | 1+
Aremote/shared/BiMap.sys.mjs | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aremote/shared/test/xpcshell/test_BiMap.js | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mremote/shared/test/xpcshell/xpcshell.toml | 2++
4 files changed, 304 insertions(+), 0 deletions(-)

diff --git a/remote/jar.mn b/remote/jar.mn @@ -16,6 +16,7 @@ remote.jar: content/shared/Addon.sys.mjs (shared/Addon.sys.mjs) content/shared/AppInfo.sys.mjs (shared/AppInfo.sys.mjs) content/shared/AsyncQueue.sys.mjs (shared/AsyncQueue.sys.mjs) + content/shared/BiMap.sys.mjs (shared/BiMap.sys.mjs) content/shared/Browser.sys.mjs (shared/Browser.sys.mjs) content/shared/Capture.sys.mjs (shared/Capture.sys.mjs) content/shared/ChallengeHeaderParser.sys.mjs (shared/ChallengeHeaderParser.sys.mjs) diff --git a/remote/shared/BiMap.sys.mjs b/remote/shared/BiMap.sys.mjs @@ -0,0 +1,139 @@ +/* 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/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", +}); + +/** + * A bidirectional map that maintains two-way mappings between UUIDs and objects. + * + * This class ensures that each object maps to exactly one UUID + * and vice versa. Also it allows efficient lookup in both directions: + * + * - from a UUID to an object + * - from an object to a UUID + */ +export class BiMap { + #idToObject; + #objectToId; + + constructor() { + this.#idToObject = new Map(); + this.#objectToId = new Map(); + } + + /** + * Clears all the mappings. + */ + clear() { + this.#idToObject = new Map(); + this.#objectToId = new Map(); + } + + /** + * Deletes a mapping by the given object. + * + * @param {object} object + * The object to remove from the BiMap. + */ + deleteByObject(object) { + const id = this.#objectToId.get(object); + + if (id !== undefined) { + this.#objectToId.delete(object); + this.#idToObject.delete(id); + } + } + + /** + * Deletes a mapping by the given id. + * + * @param {string} id + * The id to remove from the BiMap. + */ + deleteById(id) { + const object = this.#idToObject.get(id); + + if (object !== undefined) { + this.#idToObject.delete(id); + this.#objectToId.delete(object); + } + } + + /** + * Retrieves the id for the given object, or inserts a new mapping if not found. + * + * @param {object} object + * The object to look up or insert. + * + * @returns {string} + * The id associated with the object. + */ + getOrInsert(object) { + if (this.hasObject(object)) { + return this.getId(object); + } + + const id = lazy.generateUUID(); + this.#objectToId.set(object, id); + this.#idToObject.set(id, object); + + return id; + } + + /** + * Retrieves the id associated with the given object. + * + * @param {object} object + * The object to look up. + * + * @returns {string} + * The id associated with the object, or undefined if not found. + */ + getId(object) { + return this.#objectToId.get(object); + } + + /** + * Retrieves the object associated with the given id. + * + * @param {string} id + * The id to look up. + * + * @returns {object} + * The object associated with the id, or undefined if not found. + */ + getObject(id) { + return this.#idToObject.get(id); + } + + /** + * Checks whether the BiMap contains the given id. + * + * @param {string} id + * The id to check for. + * + * @returns {boolean} + * True if the id exists in the BiMap, false otherwise. + */ + hasId(id) { + return this.#idToObject.has(id); + } + + /** + * Checks whether the BiMap contains the given object. + * + * @param {object} object + * The object to check for. + * + * @returns {boolean} + * True if the object exists in the BiMap, false otherwise. + */ + hasObject(object) { + return this.#objectToId.has(object); + } +} diff --git a/remote/shared/test/xpcshell/test_BiMap.js b/remote/shared/test/xpcshell/test_BiMap.js @@ -0,0 +1,162 @@ +/* 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/. */ + +const { BiMap } = ChromeUtils.importESModule( + "chrome://remote/content/shared/BiMap.sys.mjs" +); + +add_task(function test_BiMap_constructor() { + const bimap = new BiMap(); + ok(bimap, "BiMap instance created"); +}); + +add_task(function test_BiMap_clear() { + const bimap = new BiMap(); + const obj1 = { foo: "bar" }; + const obj2 = { baz: "qux" }; + + const id1 = bimap.getOrInsert(obj1); + const id2 = bimap.getOrInsert(obj2); + + ok(bimap.hasId(id1), "First id exists before clear"); + ok(bimap.hasId(id2), "Second id exists before clear"); + + bimap.clear(); + + ok(!bimap.hasId(id1), "First id removed after clear"); + ok(!bimap.hasId(id2), "Second id removed after clear"); + ok(!bimap.hasObject(obj1), "First object removed after clear"); + ok(!bimap.hasObject(obj2), "Second object removed after clear"); +}); + +add_task(function test_BiMap_getOrInsert() { + const bimap = new BiMap(); + const obj = { foo: "bar" }; + + const regExpUUID = new RegExp( + /^[a-f|0-9]{8}-[a-f|0-9]{4}-[a-f|0-9]{4}-[a-f|0-9]{4}-[a-f|0-9]{12}$/g + ); + + const id = bimap.getOrInsert(obj); + + ok(regExpUUID.test(id), "Returned a valid UUID"); + equal(bimap.getId(obj), id, "Object is mapped to the id"); + equal(bimap.getObject(id), obj, "Id is mapped to the object"); +}); + +add_task(function test_BiMap_multipleMappings() { + const bimap = new BiMap(); + const obj1 = { name: "first" }; + const obj2 = { name: "second" }; + const obj3 = { name: "third" }; + + const id1 = bimap.getOrInsert(obj1); + const id2 = bimap.getOrInsert(obj2); + const id3 = bimap.getOrInsert(obj3); + + equal(bimap.getId(obj1), id1, "First mapping correct"); + equal(bimap.getId(obj2), id2, "Second mapping correct"); + equal(bimap.getId(obj3), id3, "Third mapping correct"); + + equal(bimap.getObject(id1), obj1, "First reverse mapping correct"); + equal(bimap.getObject(id2), obj2, "Second reverse mapping correct"); + equal(bimap.getObject(id3), obj3, "Third reverse mapping correct"); +}); + +add_task(function test_BiMap_getId() { + const bimap = new BiMap(); + const obj = { foo: "bar" }; + + equal(bimap.getId(obj), undefined, "Returns undefined for unknown object"); + + const id = bimap.getOrInsert(obj); + + equal(bimap.getId(obj), id, "Retrieved correct id for object"); +}); + +add_task(function test_BiMap_getObject() { + const bimap = new BiMap(); + const obj = { foo: "bar" }; + + equal( + bimap.getObject("unknown-id"), + undefined, + "Returns undefined for unknown id" + ); + + const id = bimap.getOrInsert(obj); + + equal(bimap.getObject(id), obj, "Retrieved correct object for id"); +}); + +add_task(function test_BiMap_hasId() { + const bimap = new BiMap(); + const obj = { foo: "bar" }; + + const id = bimap.getOrInsert(obj); + + ok(!bimap.hasId("unknown-id"), "Returns false for unknown id"); + ok(bimap.hasId(id), "Returns true for existing id"); +}); + +add_task(function test_BiMap_hasObject() { + const bimap = new BiMap(); + const obj = { foo: "bar" }; + const otherObj = { baz: "qux" }; + + ok(!bimap.hasObject(otherObj), "Returns false for non-existing object"); + + bimap.getOrInsert(obj); + + ok(bimap.hasObject(obj), "Returns true for existing object"); +}); + +add_task(function test_BiMap_deleteById() { + const bimap = new BiMap(); + const obj = { foo: "bar" }; + + const id = bimap.getOrInsert(obj); + + ok(bimap.hasId(id), "Id exists before deletion"); + ok(bimap.hasObject(obj), "Object exists before deletion"); + + // Deleting non-existing id should not affect existing mappings. + bimap.deleteById("unknown-id"); + + ok(bimap.hasId(id), "Existing id still present"); + ok(bimap.hasObject(obj), "Existing object still present"); + + // Delete existing id. + bimap.deleteById(id); + + ok(!bimap.hasId(id), "Id removed after deletion"); + ok(!bimap.hasObject(obj), "Object removed after deletion"); + equal(bimap.getId(obj), undefined, "Object no longer maps to id"); + equal(bimap.getObject(id), undefined, "Id no longer maps to object"); +}); + +add_task(function test_BiMap_deleteByObject() { + const bimap = new BiMap(); + const obj = { foo: "bar" }; + const otherObj = { baz: "qux" }; + + const id = bimap.getOrInsert(obj); + + ok(bimap.hasId(id), "Id exists before deletion"); + ok(bimap.hasObject(obj), "Object exists before deletion"); + + // Deleting non-existing object should not affect existing mappings. + bimap.deleteByObject(otherObj); + + ok(bimap.hasId(id), "Existing id still present"); + ok(bimap.hasObject(obj), "Existing object still present"); + + // Delete existing object. + bimap.deleteByObject(obj); + + ok(!bimap.hasId(id), "Id removed after deletion"); + ok(!bimap.hasObject(obj), "Object removed after deletion"); + equal(bimap.getId(obj), undefined, "Object no longer maps to id"); + equal(bimap.getObject(id), undefined, "Id no longer maps to object"); +}); diff --git a/remote/shared/test/xpcshell/xpcshell.toml b/remote/shared/test/xpcshell/xpcshell.toml @@ -5,6 +5,8 @@ head = "head.js" ["test_AsyncQueue.js"] +["test_BiMap.js"] + ["test_ChallengeHeaderParser.js"] ["test_DOM.js"]