From d3687d9bb7dd9072f5b8e65179982161aea9c8ab Mon Sep 17 00:00:00 2001 From: piyushrajyadav Date: Sun, 23 Aug 2026 11:37:11 +0530 Subject: [PATCH] url: align URLPatternInit dictionary conversion with WebIDL Read URLPatternInit dictionary members in lexicographical order so getters and proxy traps observe the access sequence required by WebIDL. Use sparse dictionary template values so URLPatternResult inputs only expose members present after WebIDL conversion. Fixes: https://github.com/nodejs/node/issues/64780 Signed-off-by: piyushrajyadav --- src/node_url_pattern.cc | 47 ++++++++++++-------------------- test/parallel/test-urlpattern.js | 40 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/node_url_pattern.cc b/src/node_url_pattern.cc index bc007db47dac..358a102749ca 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,28 +335,28 @@ 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( 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 42bdd114f43d..0ba84cae3f81 100644 --- a/test/parallel/test-urlpattern.js +++ b/test/parallel/test-urlpattern.js @@ -50,3 +50,43 @@ assert.throws(() => { assert.strictEqual(result.pathname.input, '/test'); assert.strictEqual(result.pathname.groups.value, 'test'); } + +{ + 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); +} + +{ + 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', + }); +}