1use std::default::Default;
6use std::fmt::Write as _;
7use std::rc::Rc;
8
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use js::rust::HandleObject;
12use net_traits::CoreResourceMsg;
13use net_traits::blob_url_store::parse_blob_url;
14use net_traits::filemanager_thread::FileManagerThreadMsg;
15use profile_traits::generic_channel;
16use script_bindings::cell::DomRefCell;
17use script_bindings::cformat;
18use script_bindings::reflector::{Reflector, reflect_weak_referenceable_dom_object_with_proto};
19use servo_base::generic_channel::GenericSend;
20use servo_url::{ImmutableOrigin, ServoUrl};
21use url::Url;
22use uuid::Uuid;
23
24use crate::dom::bindings::codegen::Bindings::URLBinding::URLMethods;
25use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
26use crate::dom::bindings::reflector::DomGlobal;
27use crate::dom::bindings::root::{DomRoot, MutNullableDom};
28use crate::dom::bindings::str::{DOMString, USVString};
29use crate::dom::blob::Blob;
30use crate::dom::globalscope::GlobalScope;
31use crate::dom::url::urlhelper::UrlHelper;
32use crate::dom::url::urlsearchparams::URLSearchParams;
33
34#[dom_struct]
36#[expect(clippy::upper_case_acronyms)]
37pub(crate) struct URL {
38 reflector_: Reflector,
39
40 #[no_trace]
42 url: DomRefCell<ServoUrl>,
43
44 search_params: MutNullableDom<URLSearchParams>,
46}
47
48impl URL {
49 fn new_inherited(url: ServoUrl) -> URL {
50 URL {
51 reflector_: Reflector::new(),
52 url: DomRefCell::new(url),
53 search_params: Default::default(),
54 }
55 }
56
57 fn new(
58 cx: &mut JSContext,
59 global: &GlobalScope,
60 proto: Option<HandleObject>,
61 url: ServoUrl,
62 ) -> DomRoot<URL> {
63 reflect_weak_referenceable_dom_object_with_proto(
64 cx,
65 Rc::new(URL::new_inherited(url)),
66 global,
67 proto,
68 )
69 }
70
71 pub(crate) fn query_pairs(&self) -> Vec<(String, String)> {
72 self.url
73 .borrow()
74 .as_url()
75 .query_pairs()
76 .into_owned()
77 .collect()
78 }
79
80 pub(crate) fn origin(&self) -> ImmutableOrigin {
81 self.url.borrow().origin()
82 }
83
84 pub(crate) fn set_query_pairs(&self, pairs: &[(String, String)]) {
85 let mut url = self.url.borrow_mut();
86
87 if pairs.is_empty() {
88 url.as_mut_url().set_query(None);
89 } else {
90 url.as_mut_url()
91 .query_pairs_mut()
92 .clear()
93 .extend_pairs(pairs);
94 }
95 }
96
97 fn unicode_serialization_blob_url(origin: &ImmutableOrigin, id: &Uuid) -> String {
99 let mut result = "blob:".to_string();
102
103 result.push_str(origin.ascii_serialization().as_ref());
110
111 result.push('/');
113
114 write!(result, "{id}").unwrap();
116
117 result
119 }
120}
121
122impl URLMethods<crate::DomTypeHolder> for URL {
123 fn Constructor(
125 cx: &mut JSContext,
126 global: &GlobalScope,
127 proto: Option<HandleObject>,
128 url: USVString,
129 base: Option<USVString>,
130 ) -> Fallible<DomRoot<URL>> {
131 let parsed_base = match base {
133 None => None,
134 Some(base) => {
135 match ServoUrl::parse(&base.0) {
136 Ok(base) => Some(base),
137 Err(error) => {
138 return Err(Error::Type(cformat!("could not parse base: {}", error)));
140 },
141 }
142 },
143 };
144 let parsed_url = match ServoUrl::parse_with_base(parsed_base.as_ref(), &url.0) {
145 Ok(url) => url,
146 Err(error) => {
147 return Err(Error::Type(cformat!("could not parse URL: {}", error)));
149 },
150 };
151
152 Ok(URL::new(cx, global, proto, parsed_url))
163 }
164
165 fn CanParse(_global: &GlobalScope, url: USVString, base: Option<USVString>) -> bool {
167 let parsed_url = run_api_url_parser(url, base);
169
170 parsed_url.is_ok()
173 }
174
175 fn Parse(
177 cx: &mut JSContext,
178 global: &GlobalScope,
179 url: USVString,
180 base: Option<USVString>,
181 ) -> Option<DomRoot<URL>> {
182 let parsed_url = run_api_url_parser(url, base).ok()?;
186
187 Some(URL::new(cx, global, None, ServoUrl::from_url(parsed_url)))
193 }
194
195 fn CreateObjectURL(global: &GlobalScope, blob: &Blob) -> DOMString {
197 let origin = global.origin();
200
201 let id = blob.get_blob_url_id();
202
203 DOMString::from(URL::unicode_serialization_blob_url(origin.immutable(), &id))
204 }
205
206 fn RevokeObjectURL(global: &GlobalScope, url: DOMString) {
208 let origin = global.origin().immutable().clone();
212
213 if let Ok(url) = ServoUrl::parse(&url.str()) &&
214 url.fragment().is_none() &&
215 let Ok(id) = parse_blob_url(&url)
216 {
217 let resource_threads = global.resource_threads();
218 let (tx, rx) = generic_channel::channel(global.time_profiler_chan().clone()).unwrap();
219 let msg = FileManagerThreadMsg::RevokeBlobURL(id, origin, tx);
220 let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));
221
222 let _ = rx.recv().unwrap();
223 }
224 }
225
226 fn Hash(&self) -> USVString {
228 UrlHelper::Hash(&self.url.borrow())
229 }
230
231 fn SetHash(&self, value: USVString) {
233 UrlHelper::SetHash(&mut self.url.borrow_mut(), value);
234 }
235
236 fn Host(&self) -> USVString {
238 UrlHelper::Host(&self.url.borrow())
239 }
240
241 fn SetHost(&self, value: USVString) {
243 UrlHelper::SetHost(&mut self.url.borrow_mut(), value);
244 }
245
246 fn Hostname(&self) -> USVString {
248 UrlHelper::Hostname(&self.url.borrow())
249 }
250
251 fn SetHostname(&self, value: USVString) {
253 UrlHelper::SetHostname(&mut self.url.borrow_mut(), value);
254 }
255
256 fn Href(&self) -> USVString {
258 UrlHelper::Href(&self.url.borrow())
259 }
260
261 fn SetHref(&self, value: USVString) -> ErrorResult {
263 match ServoUrl::parse(&value.0) {
264 Ok(url) => {
265 *self.url.borrow_mut() = url;
266 self.search_params.set(None); Ok(())
268 },
269 Err(error) => Err(Error::Type(cformat!("could not parse URL: {}", error))),
270 }
271 }
272
273 fn Password(&self) -> USVString {
275 UrlHelper::Password(&self.url.borrow())
276 }
277
278 fn SetPassword(&self, value: USVString) {
280 UrlHelper::SetPassword(&mut self.url.borrow_mut(), value);
281 }
282
283 fn Pathname(&self) -> USVString {
285 UrlHelper::Pathname(&self.url.borrow())
286 }
287
288 fn SetPathname(&self, value: USVString) {
290 UrlHelper::SetPathname(&mut self.url.borrow_mut(), value);
291 }
292
293 fn Port(&self) -> USVString {
295 UrlHelper::Port(&self.url.borrow())
296 }
297
298 fn SetPort(&self, value: USVString) {
300 UrlHelper::SetPort(&mut self.url.borrow_mut(), value);
301 }
302
303 fn Protocol(&self) -> USVString {
305 UrlHelper::Protocol(&self.url.borrow())
306 }
307
308 fn SetProtocol(&self, value: USVString) {
310 UrlHelper::SetProtocol(&mut self.url.borrow_mut(), value);
311 }
312
313 fn Origin(&self) -> USVString {
315 UrlHelper::Origin(&self.url.borrow())
316 }
317
318 fn Search(&self) -> USVString {
320 UrlHelper::Search(&self.url.borrow())
321 }
322
323 fn SetSearch(&self, value: USVString) {
325 UrlHelper::SetSearch(&mut self.url.borrow_mut(), value);
326 if let Some(search_params) = self.search_params.get() {
327 search_params.set_list(self.query_pairs());
328 }
329 }
330
331 fn SearchParams(&self, cx: &mut JSContext) -> DomRoot<URLSearchParams> {
333 self.search_params
334 .or_init(|| URLSearchParams::new(cx, &self.global(), Some(self)))
335 }
336
337 fn Username(&self) -> USVString {
339 UrlHelper::Username(&self.url.borrow())
340 }
341
342 fn SetUsername(&self, value: USVString) {
344 UrlHelper::SetUsername(&mut self.url.borrow_mut(), value);
345 }
346
347 fn ToJSON(&self) -> USVString {
349 self.Href()
350 }
351}
352
353fn run_api_url_parser(url: USVString, base: Option<USVString>) -> Result<Url, url::ParseError> {
355 let parsed_base = base.map(|base| Url::parse(&base.0)).transpose()?;
360
361 Url::options().base_url(parsed_base.as_ref()).parse(&url.0)
363}