From c16381316f89274fc536f522c75e72af5d407023 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 | 41 ++++++++++++++++++++++++++++ 2 files changed, 59 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..e5108944c810 100644 --- a/test/parallel/test-urlpattern.js +++ b/test/parallel/test-urlpattern.js @@ -50,3 +50,44 @@ 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', + }); +} +