Commit 464cbbda8a8 for nodejs

commit 464cbbda8a81f2b2b007eedafb4a34ab4d0cfb38
Author: Romain Lanz <2793951+RomainLanz@users.noreply.github.com>
Date:   Fri Sep 25 15:49:56 2026 +0200

    url: create URLSearchParams collision map lazily

    Object property keys are already unique unless USVString normalization
    changes a key. Avoid allocating and updating a SafeMap for ordinary
    record keys. Create and backfill it when normalization first changes a
    key.

    This improves object construction by 10% to 25% in the measured cases
    and reduces allocation by about 31% for a ten-property record.

    Assisted-by: Amp
    Signed-off-by: Romain Lanz <romain.lanz@pm.me>
    PR-URL: https://github.com/nodejs/node/pull/65801
    Reviewed-By: James M Snell <jasnell@gmail.com>

diff --git a/lib/internal/url.js b/lib/internal/url.js
index be6413eee86..471df4db439 100644
--- a/lib/internal/url.js
+++ b/lib/internal/url.js
@@ -447,7 +447,9 @@ class URLSearchParams {
       } else {
         // Record<USVString, USVString>
         // Need to use reflection APIs for full spec compliance.
-        const visited = new SafeMap();
+        // Object keys are unique, but USVString normalization may merge
+        // distinct keys. Lazily track normalized keys if a key changes.
+        let visited;
         const keys = ReflectOwnKeys(init);
         for (let i = 0; i < keys.length; i++) {
           const key = keys[i];
@@ -456,8 +458,18 @@ class URLSearchParams {
             const typedKey = toUSVString(key);
             const typedValue = toUSVString(init[key]);

-            // Two different keys may become the same USVString after normalization.
-            // In that case, we retain the later one. Refer to WPT.
+            if (visited === undefined && typedKey === key) {
+              ArrayPrototypePush(this.#searchParams, typedKey, typedValue);
+              continue;
+            }
+
+            if (visited === undefined) {
+              visited = new SafeMap();
+              for (let j = 0; j < this.#searchParams.length; j += 2) {
+                visited.set(this.#searchParams[j], j + 1);
+              }
+            }
+
             const keyIdx = visited.get(typedKey);
             if (keyIdx !== undefined) {
               this.#searchParams[keyIdx] = typedValue;
diff --git a/test/parallel/test-whatwg-url-custom-searchparams-constructor.js b/test/parallel/test-whatwg-url-custom-searchparams-constructor.js
index 9cfbd03a973..6caae46666e 100644
--- a/test/parallel/test-whatwg-url-custom-searchparams-constructor.js
+++ b/test/parallel/test-whatwg-url-custom-searchparams-constructor.js
@@ -47,6 +47,19 @@ function makeIterableFunc(array) {
   params = new URLSearchParams({ hasOwnProperty: 1 });
   assert.strictEqual(params.get('hasOwnProperty'), '1');
   assert.strictEqual(params.toString(), 'hasOwnProperty=1');
+  // A malformed key can collide with a valid key collected before the map
+  // exists.
+  params = new URLSearchParams({
+    'before': '0',
+    '\uFFFDx': 'first',
+    '\uD835x': 'last',
+    'after': '3'
+  });
+  assert.deepStrictEqual([...params], [
+    ['before', '0'],
+    ['\uFFFDx', 'last'],
+    ['after', '3'],
+  ]);
   assert.throws(() => new URLSearchParams([[1]]), tupleError);
   assert.throws(() => new URLSearchParams([[1, 2, 3]]), tupleError);
   assert.throws(() => new URLSearchParams({ [Symbol('test')]: 42 }),