88#include " node_metadata.h"
99#include " node_process-inl.h"
1010#include " path.h"
11+ #include " simdutf.h"
1112#include " util-inl.h"
1213#include " v8-fast-api-calls.h"
1314#include " v8-local-handle.h"
@@ -33,6 +34,38 @@ using v8::SnapshotCreator;
3334using v8::String;
3435using v8::Value;
3536
37+ namespace {
38+
39+ // Parse a V8 string as a URL. One-byte ASCII inputs are parsed in place
40+ // without allocating a UTF-8 copy. `reuse_input` is set when the serialized
41+ // href is identical to that ASCII input so the caller can return the original
42+ // V8 string. Non-ASCII inputs are never reused: UTF-8 conversion may replace
43+ // unpaired surrogates, so the original string may not match href.
44+ ada::result<ada::url_aggregator> ParseUrlFromV8String (
45+ Isolate* isolate,
46+ Local<String> input,
47+ const ada::url_aggregator* base_url,
48+ bool * reuse_input) {
49+ {
50+ String::ValueView view (isolate, input);
51+ if (view.is_one_byte ()) {
52+ const char * data = reinterpret_cast <const char *>(view.data8 ());
53+ const size_t length = static_cast <size_t >(view.length ());
54+ if (simdutf::validate_ascii (data, length)) [[likely]] {
55+ const std::string_view input_view (data, length);
56+ auto out = ada::parse<ada::url_aggregator>(input_view, base_url);
57+ *reuse_input = out.has_value () && out->get_href () == input_view;
58+ return out;
59+ }
60+ }
61+ }
62+ *reuse_input = false ;
63+ Utf8Value utf8 (isolate, input);
64+ return ada::parse<ada::url_aggregator>(utf8.ToStringView (), base_url);
65+ }
66+
67+ } // namespace
68+
3669void BindingData::MemoryInfo (MemoryTracker* tracker) const {
3770 tracker->TrackField (" url_components_buffer" , url_components_buffer_);
3871}
@@ -392,32 +425,51 @@ void BindingData::Parse(const FunctionCallbackInfo<Value>& args) {
392425 Realm* realm = Realm::GetCurrent (args);
393426 BindingData* binding_data = realm->GetBindingData <BindingData>();
394427 Isolate* isolate = realm->isolate ();
395- std::optional<std::string> base_{} ;
428+ Local<String> input_string = args[ 0 ]. As <String>() ;
396429
397- Utf8Value input (isolate, args[0 ]);
398430 ada::result<ada::url_aggregator> base;
399431 ada::url_aggregator* base_pointer = nullptr ;
400432 if (args[1 ]->IsString ()) {
401- base_ = Utf8Value (isolate, args[1 ]).ToString ();
402- base = ada::parse<ada::url_aggregator>(*base_);
403- if (!base && raise_exception) {
404- return ThrowInvalidURL (realm->env (), input.ToStringView (), base_);
405- } else if (!base) {
433+ bool unused_reuse = false ;
434+ base = ParseUrlFromV8String (
435+ isolate, args[1 ].As <String>(), nullptr , &unused_reuse);
436+ if (!base) {
437+ if (raise_exception) {
438+ Utf8Value input (isolate, input_string);
439+ Utf8Value base_utf8 (isolate, args[1 ]);
440+ return ThrowInvalidURL (
441+ realm->env (), input.ToStringView (), base_utf8.ToString ());
442+ }
406443 return ;
407444 }
408445 base_pointer = &base.value ();
409446 }
410- auto out =
411- ada::parse<ada::url_aggregator>(input.ToStringView (), base_pointer);
412447
413- if (!out && raise_exception) {
414- return ThrowInvalidURL (realm->env (), input.ToStringView (), base_);
415- } else if (!out) {
448+ bool reuse_input = false ;
449+ auto out =
450+ ParseUrlFromV8String (isolate, input_string, base_pointer, &reuse_input);
451+ if (!out) {
452+ if (raise_exception) {
453+ Utf8Value input (isolate, input_string);
454+ std::optional<std::string> base_error;
455+ if (args[1 ]->IsString ()) {
456+ base_error = Utf8Value (isolate, args[1 ]).ToString ();
457+ }
458+ return ThrowInvalidURL (
459+ realm->env (), input.ToStringView (), std::move (base_error));
460+ }
416461 return ;
417462 }
418463
419464 binding_data->UpdateComponents (out->get_components (), out->type );
420465
466+ // Already-serialized ASCII URLs are the common case. Reuse the input
467+ // string instead of allocating an identical V8 string from href.
468+ if (reuse_input) {
469+ args.GetReturnValue ().Set (args[0 ]);
470+ return ;
471+ }
472+
421473 Local<Value> ret;
422474 if (ToV8Value (realm->context (), out->get_href (), isolate).ToLocal (&ret))
423475 [[likely]] {
@@ -439,13 +491,15 @@ void BindingData::Update(const FunctionCallbackInfo<Value>& args) {
439491 return ;
440492 }
441493 enum url_update_action action = static_cast <enum url_update_action>(val);
442- Utf8Value input (isolate, args[0 ].As <String>());
443494 Utf8Value new_value (isolate, args[2 ].As <String>());
444495
445496 std::string_view new_value_view = new_value.ToStringView ();
446497 // A serialized URL is not always reparsable: the IDNA encoder can emit a
447498 // host label that the decoder rejects. Fail the update instead of crashing.
448- auto out = ada::parse<ada::url_aggregator>(input.ToStringView ());
499+ // Existing hrefs are typically already-serialized ASCII, so parse in place.
500+ bool unused_reuse = false ;
501+ auto out = ParseUrlFromV8String (
502+ isolate, args[0 ].As <String>(), nullptr , &unused_reuse);
449503 if (!out) {
450504 return args.GetReturnValue ().Set (false );
451505 }
0 commit comments