#![allow(clippy::needless_pass_by_value)]
#[cfg(test)]
mod tests;
use boa_engine::class::Class;
use boa_engine::realm::Realm;
use boa_engine::value::Convert;
use boa_engine::{
Context, Finalize, JsData, JsResult, JsString, JsValue, Trace, boa_class, boa_module, js_error,
};
use std::fmt::Display;
#[derive(Debug, Clone, JsData, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
pub struct Url(#[unsafe_ignore_trace] url::Url);
impl Url {
pub fn register(realm: Option<Realm>, context: &mut Context) -> JsResult<()> {
js_module::boa_register(realm, context)
}
}
impl Display for Url {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<url::Url> for Url {
fn from(url: url::Url) -> Self {
Self(url)
}
}
impl From<Url> for url::Url {
fn from(url: Url) -> url::Url {
url.0
}
}
#[boa_class(rename = "URL")]
#[boa(rename_all = "camelCase")]
impl Url {
#[boa(constructor)]
pub fn new(Convert(ref url): Convert<String>, base: Option<Convert<String>>) -> JsResult<Self> {
if let Some(Convert(ref base)) = base {
let base_url = url::Url::parse(base)
.map_err(|e| js_error!(TypeError: "Failed to parse base URL: {}", e))?;
if base_url.cannot_be_a_base() {
return Err(js_error!(TypeError: "Base URL {} cannot be a base", base));
}
let url = base_url
.join(url)
.map_err(|e| js_error!(TypeError: "Failed to parse URL: {}", e))?;
Ok(Self(url))
} else {
let url = url::Url::parse(url)
.map_err(|e| js_error!(TypeError: "Failed to parse URL: {}", e))?;
Ok(Self(url))
}
}
#[boa(getter)]
fn hash(&self) -> JsString {
JsString::from(url::quirks::hash(&self.0))
}
#[boa(setter)]
#[boa(rename = "hash")]
fn set_hash(&mut self, value: Convert<String>) {
url::quirks::set_hash(&mut self.0, &value.0);
}
#[boa(getter)]
fn hostname(&self) -> JsString {
JsString::from(url::quirks::hostname(&self.0))
}
#[boa(setter)]
#[boa(rename = "hostname")]
fn set_hostname(&mut self, value: Convert<String>) {
let _ = url::quirks::set_hostname(&mut self.0, &value.0);
}
#[boa(getter)]
fn host(&self) -> JsString {
JsString::from(url::quirks::host(&self.0))
}
#[boa(setter)]
#[boa(rename = "host")]
fn set_host(&mut self, value: Convert<String>) {
let _ = url::quirks::set_host(&mut self.0, &value.0);
}
#[boa(getter)]
fn href(&self) -> JsString {
JsString::from(url::quirks::href(&self.0))
}
#[boa(setter)]
#[boa(rename = "href")]
fn set_href(&mut self, value: Convert<String>) -> JsResult<()> {
url::quirks::set_href(&mut self.0, &value.0)
.map_err(|e| js_error!(TypeError: "Failed to set href: {}", e))
}
#[boa(getter)]
fn origin(&self) -> JsString {
JsString::from(url::quirks::origin(&self.0))
}
#[boa(getter)]
fn password(&self) -> JsString {
JsString::from(url::quirks::password(&self.0))
}
#[boa(setter)]
#[boa(rename = "password")]
fn set_password(&mut self, value: Convert<String>) {
let _ = url::quirks::set_password(&mut self.0, &value.0);
}
#[boa(getter)]
fn pathname(&self) -> JsString {
JsString::from(url::quirks::pathname(&self.0))
}
#[boa(setter)]
#[boa(rename = "pathname")]
fn set_pathname(&mut self, value: Convert<String>) {
let () = url::quirks::set_pathname(&mut self.0, &value.0);
}
#[boa(getter)]
fn port(&self) -> JsString {
JsString::from(url::quirks::port(&self.0))
}
#[boa(setter)]
#[boa(rename = "port")]
fn set_port(&mut self, value: Convert<JsString>) {
let _ = url::quirks::set_port(&mut self.0, &value.0.to_std_string_lossy());
}
#[boa(getter)]
fn protocol(&self) -> JsString {
JsString::from(url::quirks::protocol(&self.0))
}
#[boa(setter)]
#[boa(rename = "protocol")]
fn set_protocol(&mut self, value: Convert<String>) {
let _ = url::quirks::set_protocol(&mut self.0, &value.0);
}
#[boa(getter)]
fn search(&self) -> JsString {
JsString::from(url::quirks::search(&self.0))
}
#[boa(setter)]
#[boa(rename = "search")]
fn set_search(&mut self, value: Convert<String>) {
url::quirks::set_search(&mut self.0, &value.0);
}
#[boa(getter)]
fn search_params() -> JsResult<()> {
Err(js_error!(Error: "URL.searchParams is not implemented"))
}
#[boa(getter)]
fn username(&self) -> JsString {
JsString::from(self.0.username())
}
#[boa(setter)]
#[boa(rename = "username")]
fn set_username(&mut self, value: Convert<String>) {
let _ = self.0.set_username(&value.0);
}
fn to_string(&self) -> JsString {
JsString::from(format!("{}", self.0))
}
#[boa(rename = "toJSON")]
fn to_json(&self) -> JsString {
JsString::from(format!("{}", self.0))
}
#[boa(static)]
fn create_object_url() -> JsResult<()> {
Err(js_error!(Error: "URL.createObjectURL is not implemented"))
}
#[boa(static)]
fn can_parse(url: Convert<String>, base: Option<Convert<String>>) -> bool {
Url::new(url, base).is_ok()
}
#[boa(static)]
fn parse(
url: Convert<String>,
base: Option<Convert<String>>,
context: &mut Context,
) -> JsResult<JsValue> {
Url::new(url, base).map_or(Ok(JsValue::null()), |u| {
Url::from_data(u, context).map(JsValue::from)
})
}
#[boa(static)]
fn revoke_object_url() -> JsResult<()> {
Err(js_error!(Error: "URL.revokeObjectURL is not implemented"))
}
}
#[boa_module]
pub mod js_module {
type Url = super::Url;
}