use ada_url::Url;
use rustc_hash::FxHashMap;
use std::cell::RefCell;
thread_local! {
static URL_STORAGE: RefCell<FxHashMap<u32, Url>> = RefCell::new(FxHashMap::default());
static NEXT_URL_ID: RefCell<u32> = const { RefCell::new(1) };
}
fn allocate_url_id() -> u32 {
NEXT_URL_ID.with(|id| {
let mut current = id.borrow_mut();
let new_id = *current;
*current += 1;
new_id
})
}
fn store_url(url: Url) -> u32 {
let id = allocate_url_id();
URL_STORAGE.with(|storage| {
storage.borrow_mut().insert(id, url);
});
id
}
#[inline]
fn to_v8_string<'s>(scope: &mut v8::PinScope<'s, '_>, s: &str) -> v8::Local<'s, v8::String> {
v8::String::new(scope, s).unwrap()
}
#[inline]
fn to_rust_string(scope: &mut v8::PinScope, val: v8::Local<v8::Value>) -> String {
val.to_string(scope).unwrap().to_rust_string_lossy(scope)
}
fn url_parse(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
if args.length() == 0 {
rv.set(v8::null(scope).into());
return;
}
let url_str = to_rust_string(scope, args.get(0));
let base = if args.length() > 1 && !args.get(1).is_undefined() {
Some(to_rust_string(scope, args.get(1)))
} else {
None
};
let parsed = if let Some(base_str) = base {
Url::parse(&url_str, Some(&base_str))
} else {
Url::parse(&url_str, None)
};
match parsed {
Ok(url) => {
let url_id = store_url(url);
let id_value = v8::Number::new(scope, url_id as f64);
rv.set(id_value.into());
}
Err(_) => {
rv.set(v8::null(scope).into());
}
}
}
fn url_get_property(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
if args.length() < 2 {
rv.set(v8::undefined(scope).into());
return;
}
let id = args.get(0).number_value(scope).unwrap() as u32;
let prop = to_rust_string(scope, args.get(1));
URL_STORAGE.with(|storage| {
let storage_ref = storage.borrow();
if let Some(url) = storage_ref.get(&id) {
let value = match prop.as_str() {
"href" => url.as_str(),
"origin" => &url.origin(),
"protocol" => url.protocol(),
"username" => url.username(),
"password" => url.password(),
"host" => url.host(),
"hostname" => url.hostname(),
"port" => url.port(),
"pathname" => url.pathname(),
"search" => url.search(),
"hash" => url.hash(),
_ => "",
};
let v8_str = to_v8_string(scope, value);
rv.set(v8_str.into());
} else {
rv.set(v8::undefined(scope).into());
}
});
}
fn url_set_property(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
if args.length() < 3 {
rv.set(v8::Boolean::new(scope, false).into());
return;
}
let id = args.get(0).number_value(scope).unwrap() as u32;
let prop = to_rust_string(scope, args.get(1));
let value = to_rust_string(scope, args.get(2));
URL_STORAGE.with(|storage| {
let mut storage_mut = storage.borrow_mut();
if let Some(url) = storage_mut.get_mut(&id) {
match prop.as_str() {
"href" => {
if let Ok(new_url) = Url::parse(&value, None) {
*url = new_url;
rv.set(v8::Boolean::new(scope, true).into());
return;
}
}
"protocol" => {
let _ = url.set_protocol(&value);
rv.set(v8::Boolean::new(scope, true).into());
return;
}
"pathname" => {
let _ = url.set_pathname(Some(&value));
rv.set(v8::Boolean::new(scope, true).into());
return;
}
"search" => {
if value.is_empty() || value == "?" {
url.set_search(None);
} else {
url.set_search(Some(&value));
}
rv.set(v8::Boolean::new(scope, true).into());
return;
}
"hash" => {
if value.is_empty() || value == "#" {
url.set_hash(None);
} else {
url.set_hash(Some(&value));
}
rv.set(v8::Boolean::new(scope, true).into());
return;
}
_ => {}
}
}
rv.set(v8::Boolean::new(scope, false).into());
});
}
pub(crate) fn get_external_references() -> Vec<v8::ExternalReference> {
vec![
v8::ExternalReference {
function: v8::MapFnTo::map_fn_to(url_parse),
},
v8::ExternalReference {
function: v8::MapFnTo::map_fn_to(url_get_property),
},
v8::ExternalReference {
function: v8::MapFnTo::map_fn_to(url_set_property),
},
]
}
pub(crate) fn register_bindings(scope: &mut v8::PinScope, bindings: v8::Local<v8::Object>) {
macro_rules! binding {
($name:expr, $func:expr) => {
let key = to_v8_string(scope, $name);
let val = v8::Function::new(scope, $func).unwrap();
bindings.set(scope, key.into(), val.into());
};
}
binding!("urlParse", url_parse);
binding!("urlGetProperty", url_get_property);
binding!("urlSetProperty", url_set_property);
}