From 65b8d1d480f6b93b9ad06db2a0b0cb83289cbc5e Mon Sep 17 00:00:00 2001 From: Archkon <180910180+Archkon@users.noreply.github.com> Date: Sat, 25 Jul 2026 19:09:57 +0800 Subject: [PATCH 1/2] url: read URLPatternInit members in WebIDL order Read URLPatternInit dictionary members in lexicographical order so getters and proxy traps observe the access sequence required by WebIDL. Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com> --- src/node_url_pattern.cc | 12 ++++++------ test/parallel/test-urlpattern.js | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/node_url_pattern.cc b/src/node_url_pattern.cc index 571413b47da12d..a625bb1ff8bb64 100644 --- a/src/node_url_pattern.cc +++ b/src/node_url_pattern.cc @@ -359,15 +359,15 @@ std::optional URLPattern::URLPatternInit::FromJsObject( Environment* env, Local obj) { ada::url_pattern_init init{}; Local components[] = { - env->protocol_string(), - env->username_string(), - env->password_string(), + env->base_url_string(), + env->hash_string(), env->hostname_string(), - env->port_string(), + env->password_string(), env->pathname_string(), + env->port_string(), + env->protocol_string(), env->search_string(), - env->hash_string(), - env->base_url_string(), + env->username_string(), }; auto isolate = env->isolate(); const auto set_parameter = [&](std::string_view key, std::string_view value) { diff --git a/test/parallel/test-urlpattern.js b/test/parallel/test-urlpattern.js index 96c431f5b87f69..e1803186329338 100644 --- a/test/parallel/test-urlpattern.js +++ b/test/parallel/test-urlpattern.js @@ -17,6 +17,30 @@ assert.throws(() => { message: 'boom', }); +{ + const accessed = []; + const expected = [ + 'baseURL', + 'hash', + 'hostname', + 'password', + 'pathname', + 'port', + 'protocol', + 'search', + 'username', + ]; + const init = new Proxy({}, { + get(target, name, receiver) { + accessed.push(name); + return Reflect.get(target, name, receiver); + }, + }); + + new URLPattern(init); + assert.deepStrictEqual(accessed, expected); +} + // Verify that if an error is thrown while accessing the ignoreCase // option, the error is appropriately propagated. assert.throws(() => { From 7e619a041437f7b3587b491ef5b77c7aec82ea04 Mon Sep 17 00:00:00 2001 From: Archkon <180910180+Archkon@users.noreply.github.com> Date: Sat, 25 Jul 2026 19:24:00 +0800 Subject: [PATCH 2/2] url: omit absent URLPatternInit members from exec results Use sparse dictionary template values so URLPatternResult inputs only expose members present after WebIDL conversion. Emit present members in WebIDL order. Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com> --- src/node_url_pattern.cc | 35 +++++++++++--------------------- test/parallel/test-urlpattern.js | 16 +++++++++++++++ 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/node_url_pattern.cc b/src/node_url_pattern.cc index a625bb1ff8bb64..65ef09c2247ae8 100644 --- a/src/node_url_pattern.cc +++ b/src/node_url_pattern.cc @@ -307,32 +307,21 @@ MaybeLocal URLPattern::URLPatternInit::ToJsObject( auto tmpl = env->urlpatterninit_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view namesVec[] = { - "protocol", - "username", - "password", + "baseURL", + "hash", "hostname", - "port", + "password", "pathname", + "port", + "protocol", "search", - "hash", - "baseURL", + "username", }; tmpl = DictionaryTemplate::New(isolate, namesVec); env->set_urlpatterninit_template(tmpl); } - MaybeLocal values[] = { - Undefined(isolate), // protocol - Undefined(isolate), // username - Undefined(isolate), // password - Undefined(isolate), // hostname - Undefined(isolate), // port - Undefined(isolate), // pathname - Undefined(isolate), // search - Undefined(isolate), // hash - Undefined(isolate), // baseURL - }; - + MaybeLocal values[9]; int idx = 0; Local temp; const auto trySet = [&](const std::optional& val) { @@ -346,13 +335,13 @@ MaybeLocal URLPattern::URLPatternInit::ToJsObject( return true; }; - if (!trySet(init.protocol) || !trySet(init.username) || - !trySet(init.password) || !trySet(init.hostname) || !trySet(init.port) || - !trySet(init.pathname) || !trySet(init.search) || !trySet(init.hash) || - !trySet(init.base_url)) { + if (!trySet(init.base_url) || !trySet(init.hash) || !trySet(init.hostname) || + !trySet(init.password) || !trySet(init.pathname) || !trySet(init.port) || + !trySet(init.protocol) || !trySet(init.search) || + !trySet(init.username)) { return {}; } - return NewDictionaryInstance(env->context(), tmpl, values); + return tmpl->NewInstance(context, values); } std::optional URLPattern::URLPatternInit::FromJsObject( diff --git a/test/parallel/test-urlpattern.js b/test/parallel/test-urlpattern.js index e1803186329338..920288eb025611 100644 --- a/test/parallel/test-urlpattern.js +++ b/test/parallel/test-urlpattern.js @@ -50,3 +50,19 @@ assert.throws(() => { }, { message: 'boom' }); + +{ + const input = new URLPattern({ pathname: '/x' }) + .exec({ + protocol: 'https', + pathname: '/x', + username: undefined, + }).inputs[0]; + + assert.deepStrictEqual(Object.keys(input), ['pathname', 'protocol']); + assert.strictEqual('username' in input, false); + assert.deepStrictEqual({ ...input }, { + pathname: '/x', + protocol: 'https', + }); +}