diff --git a/moli-renderer-v8/src/native_bridge/element/resource_elements.rs b/moli-renderer-v8/src/native_bridge/element/resource_elements.rs index 13240c24bd..052a99a777 100644 --- a/moli-renderer-v8/src/native_bridge/element/resource_elements.rs +++ b/moli-renderer-v8/src/native_bridge/element/resource_elements.rs @@ -3,6 +3,7 @@ use super::*; fn anchor_url_string_function_getter<'s>( scope: &mut v8::PinScope<'s, '_>, receiver: v8::Local<'s, v8::Object>, + parse_failure_value: &'static str, project: impl FnOnce(&url::Url) -> String, mut rv: v8::ReturnValue<'_, v8::Value>, ) { @@ -14,7 +15,7 @@ fn anchor_url_string_function_getter<'s>( }; let value = parsed_url_like_attribute(unsafe { &*runtime_ptr }, handle, "href") .map(|url| project(&url)) - .unwrap_or_default(); + .unwrap_or_else(|| parse_failure_value.to_owned()); if let Some(value) = v8_string(scope, &value) { rv.set(value.into()); } else { @@ -280,6 +281,7 @@ fn anchor_host_getter_function<'s>( anchor_url_string_function_getter( scope, args.this(), + "", |url| { url.host_str() .map(|host| { @@ -336,6 +338,7 @@ fn anchor_hostname_getter_function<'s>( anchor_url_string_function_getter( scope, args.this(), + "", |url| url.host_str().unwrap_or_default().to_owned(), rv, ); @@ -368,6 +371,7 @@ fn anchor_port_getter_function<'s>( anchor_url_string_function_getter( scope, args.this(), + "", |url| url.port().map(|port| port.to_string()).unwrap_or_default(), rv, ); @@ -397,7 +401,7 @@ fn anchor_pathname_getter_function<'s>( args: v8::FunctionCallbackArguments<'s>, rv: v8::ReturnValue<'_, v8::Value>, ) { - anchor_url_string_function_getter(scope, args.this(), |url| url.path().to_owned(), rv); + anchor_url_string_function_getter(scope, args.this(), "", |url| url.path().to_owned(), rv); } fn anchor_pathname_setter_function<'s>( @@ -426,6 +430,7 @@ fn anchor_search_getter_function<'s>( anchor_url_string_function_getter( scope, args.this(), + "", |url| url::quirks::search(url).to_owned(), rv, ); @@ -454,6 +459,7 @@ fn anchor_hash_getter_function<'s>( anchor_url_string_function_getter( scope, args.this(), + "", |url| url::quirks::hash(url).to_owned(), rv, ); @@ -479,7 +485,13 @@ fn anchor_origin_getter_function<'s>( args: v8::FunctionCallbackArguments<'s>, rv: v8::ReturnValue<'_, v8::Value>, ) { - anchor_url_string_function_getter(scope, args.this(), moli_url::origin_ascii_serialization, rv); + anchor_url_string_function_getter( + scope, + args.this(), + "", + moli_url::origin_ascii_serialization, + rv, + ); } fn anchor_protocol_getter_function<'s>( @@ -487,7 +499,13 @@ fn anchor_protocol_getter_function<'s>( args: v8::FunctionCallbackArguments<'s>, rv: v8::ReturnValue<'_, v8::Value>, ) { - anchor_url_string_function_getter(scope, args.this(), |url| format!("{}:", url.scheme()), rv); + anchor_url_string_function_getter( + scope, + args.this(), + ":", + |url| format!("{}:", url.scheme()), + rv, + ); } fn anchor_protocol_setter_function<'s>( @@ -511,7 +529,7 @@ fn anchor_username_getter_function<'s>( args: v8::FunctionCallbackArguments<'s>, rv: v8::ReturnValue<'_, v8::Value>, ) { - anchor_url_string_function_getter(scope, args.this(), |url| url.username().to_owned(), rv); + anchor_url_string_function_getter(scope, args.this(), "", |url| url.username().to_owned(), rv); } fn anchor_username_setter_function<'s>( @@ -540,6 +558,7 @@ fn anchor_password_getter_function<'s>( anchor_url_string_function_getter( scope, args.this(), + "", |url| url.password().unwrap_or("").to_owned(), rv, ); diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/legacy_resources_and_document_mutations.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/legacy_resources_and_document_mutations.rs index 26d541d308..85c77569a1 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/legacy_resources_and_document_mutations.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/detached/legacy_resources_and_document_mutations.rs @@ -1648,3 +1648,45 @@ fn detached_document_creation_brand_checks_accept_standard_prototype_methods() { r#"{"htmlElement":"true,SECTION,true,false","htmlElementNs":"true,x,article,http://www.w3.org/1999/xhtml","xmlElement":"true,Mixed,null","xmlElementNs":"true,p,item,urn:test","characterNodes":"true,txt,true,note,true,2,true,pi","attrs":"true,data-x,true,p,flag,urn:test","importAdopt":"true,p,item,true,true","documentOwn":"false,false,false,false,false"}"# ); } + +#[test] +fn hyperlink_protocol_is_colon_when_href_cannot_be_parsed() { + let mut vm = new_storage_test_vm("https://hyperlink-invalid-url.test/page.html"); + + let result = vm + .eval( + r#" +(() => { + const base = document.createElement("base"); + base.href = "about:blank"; + (document.head || document.documentElement || document).appendChild(base); + const inputs = [ + "", + "javascript://:443", + "javascript://test:test", + "javascript://[:1]", + "mailto://:443", + "mailto://test:test", + "mailto://[:1]" + ]; + + for (const tag of ["a", "area"]) { + const element = document.createElement(tag); + for (const input of inputs) { + element.setAttribute("href", input); + if (element.href !== input) { + throw new Error(`${tag} should preserve the unparsable href ${input}`); + } + if (element.protocol !== ":") { + throw new Error(`${tag} should expose ':' for the unparsable href ${input}`); + } + } + } + return "ok"; +})() +"#, + ) + .expect("unparsable hyperlink protocol probe should evaluate"); + + assert_eq!(result, "ok"); +}