script/dom/
url.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::default::Default;
6
7use base::generic_channel::GenericSend;
8use dom_struct::dom_struct;
9use js::rust::HandleObject;
10use net_traits::CoreResourceMsg;
11use net_traits::blob_url_store::parse_blob_url;
12use net_traits::filemanager_thread::FileManagerThreadMsg;
13use profile_traits::ipc;
14use servo_url::{ImmutableOrigin, ServoUrl};
15use uuid::Uuid;
16
17use crate::dom::bindings::cell::DomRefCell;
18use crate::dom::bindings::codegen::Bindings::URLBinding::URLMethods;
19use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
20use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
21use crate::dom::bindings::root::{DomRoot, MutNullableDom};
22use crate::dom::bindings::str::{DOMString, USVString};
23use crate::dom::blob::Blob;
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::urlhelper::UrlHelper;
26use crate::dom::urlsearchparams::URLSearchParams;
27use crate::script_runtime::CanGc;
28
29/// <https://url.spec.whatwg.org/#url>
30#[dom_struct]
31#[expect(clippy::upper_case_acronyms)]
32pub(crate) struct URL {
33    reflector_: Reflector,
34
35    /// <https://url.spec.whatwg.org/#concept-url-url>
36    #[no_trace]
37    url: DomRefCell<ServoUrl>,
38
39    /// <https://url.spec.whatwg.org/#dom-url-searchparams>
40    search_params: MutNullableDom<URLSearchParams>,
41}
42
43impl URL {
44    fn new_inherited(url: ServoUrl) -> URL {
45        URL {
46            reflector_: Reflector::new(),
47            url: DomRefCell::new(url),
48            search_params: Default::default(),
49        }
50    }
51
52    fn new(
53        global: &GlobalScope,
54        proto: Option<HandleObject>,
55        url: ServoUrl,
56        can_gc: CanGc,
57    ) -> DomRoot<URL> {
58        reflect_dom_object_with_proto(Box::new(URL::new_inherited(url)), global, proto, can_gc)
59    }
60
61    pub(crate) fn query_pairs(&self) -> Vec<(String, String)> {
62        self.url
63            .borrow()
64            .as_url()
65            .query_pairs()
66            .into_owned()
67            .collect()
68    }
69
70    pub(crate) fn origin(&self) -> ImmutableOrigin {
71        self.url.borrow().origin()
72    }
73
74    pub(crate) fn set_query_pairs(&self, pairs: &[(String, String)]) {
75        let mut url = self.url.borrow_mut();
76
77        if pairs.is_empty() {
78            url.as_mut_url().set_query(None);
79        } else {
80            url.as_mut_url()
81                .query_pairs_mut()
82                .clear()
83                .extend_pairs(pairs);
84        }
85    }
86
87    /// <https://w3c.github.io/FileAPI/#unicodeBlobURL>
88    fn unicode_serialization_blob_url(origin: &ImmutableOrigin, id: &Uuid) -> String {
89        // Step 1. Let result be the empty string.
90        // Step 2. Append the string "blob:" to result.
91        let mut result = "blob:".to_string();
92
93        // Step 3. Let settings be the current settings object.
94        // Step 4. Let origin be settings’s origin.
95        // Step 5. Let serialized be the ASCII serialization of origin.
96        // Step 6. If serialized is "null", set it to an implementation-defined value.
97        // Step 7. Append serialized to result.
98        // N.B. We leave it as "null" right now.
99        result.push_str(&origin.ascii_serialization());
100
101        // Step 8. Append U+0024 SOLIDUS (/) to result.
102        result.push('/');
103
104        // Step 9. Generate a UUID [RFC4122] as a string and append it to result.
105        result.push_str(&id.to_string());
106
107        // Step 10. Return result.
108        result
109    }
110}
111
112impl URLMethods<crate::DomTypeHolder> for URL {
113    /// <https://url.spec.whatwg.org/#constructors>
114    fn Constructor(
115        global: &GlobalScope,
116        proto: Option<HandleObject>,
117        can_gc: CanGc,
118        url: USVString,
119        base: Option<USVString>,
120    ) -> Fallible<DomRoot<URL>> {
121        // Step 1. Parse url with base.
122        let parsed_base = match base {
123            None => None,
124            Some(base) => {
125                match ServoUrl::parse(&base.0) {
126                    Ok(base) => Some(base),
127                    Err(error) => {
128                        // Step 2. Throw a TypeError if URL parsing fails.
129                        return Err(Error::Type(format!("could not parse base: {}", error)));
130                    },
131                }
132            },
133        };
134        let parsed_url = match ServoUrl::parse_with_base(parsed_base.as_ref(), &url.0) {
135            Ok(url) => url,
136            Err(error) => {
137                // Step 2. Throw a TypeError if URL parsing fails.
138                return Err(Error::Type(format!("could not parse URL: {}", error)));
139            },
140        };
141
142        // Skip the steps below.
143        // Instead of constructing a new `URLSearchParams` object here, construct it
144        // on-demand inside `URL::SearchParams`.
145        //
146        // Step 3. Let query be parsedURL’s query.
147        // Step 5. Set this’s query object to a new URLSearchParams object.
148        // Step 6. Initialize this’s query object with query.
149        // Step 7. Set this’s query object’s URL object to this.
150
151        // Step 4. Set this’s URL to parsedURL.
152        Ok(URL::new(global, proto, parsed_url, can_gc))
153    }
154
155    /// <https://url.spec.whatwg.org/#dom-url-canparse>
156    fn CanParse(_global: &GlobalScope, url: USVString, base: Option<USVString>) -> bool {
157        // Step 1.
158        let parsed_base = match base {
159            None => None,
160            Some(base) => match ServoUrl::parse(&base.0) {
161                Ok(base) => Some(base),
162                Err(_) => {
163                    // Step 2.1
164                    return false;
165                },
166            },
167        };
168        // Step 2.2, 3
169        ServoUrl::parse_with_base(parsed_base.as_ref(), &url.0).is_ok()
170    }
171
172    /// <https://url.spec.whatwg.org/#dom-url-parse>
173    fn Parse(
174        global: &GlobalScope,
175        url: USVString,
176        base: Option<USVString>,
177        can_gc: CanGc,
178    ) -> Option<DomRoot<URL>> {
179        // Step 1: Let parsedURL be the result of running the API URL parser on url with base,
180        // if given.
181        let parsed_base = base.and_then(|base| ServoUrl::parse(base.0.as_str()).ok());
182        let parsed_url = ServoUrl::parse_with_base(parsed_base.as_ref(), &url.0);
183
184        // Step 2: If parsedURL is failure, then return null.
185        // Step 3: Let url be a new URL object.
186        // Step 4: Initialize url with parsedURL.
187        // Step 5: Return url.
188
189        // These steps are all handled while mapping the Result to an Option<ServoUrl>.
190        // Regarding initialization, the same condition should apply here as stated in the comments
191        // in Self::Constructor above - construct it on-demand inside `URL::SearchParams`.
192        Some(URL::new(global, None, parsed_url.ok()?, can_gc))
193    }
194
195    /// <https://w3c.github.io/FileAPI/#dfn-createObjectURL>
196    fn CreateObjectURL(global: &GlobalScope, blob: &Blob) -> DOMString {
197        // XXX: Second field is an unicode-serialized Origin, it is a temporary workaround
198        //      and should not be trusted. See issue https://github.com/servo/servo/issues/11722
199        let origin = global.origin().immutable();
200
201        let id = blob.get_blob_url_id();
202
203        DOMString::from(URL::unicode_serialization_blob_url(origin, &id))
204    }
205
206    /// <https://w3c.github.io/FileAPI/#dfn-revokeObjectURL>
207    fn RevokeObjectURL(global: &GlobalScope, url: DOMString) {
208        // If the value provided for the url argument is not a Blob URL OR
209        // if the value provided for the url argument does not have an entry in the Blob URL Store,
210        // this method call does nothing. User agents may display a message on the error console.
211        let origin = global.origin().immutable();
212
213        if let Ok(url) = ServoUrl::parse(&url.str()) {
214            if url.fragment().is_none() && *origin == url.origin() {
215                if let Ok((id, _)) = parse_blob_url(&url) {
216                    let resource_threads = global.resource_threads();
217                    let (tx, rx) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
218                    let msg = FileManagerThreadMsg::RevokeBlobURL(id, origin.clone(), tx);
219                    let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));
220
221                    let _ = rx.recv().unwrap();
222                }
223            }
224        }
225    }
226
227    /// <https://url.spec.whatwg.org/#dom-url-hash>
228    fn Hash(&self) -> USVString {
229        UrlHelper::Hash(&self.url.borrow())
230    }
231
232    /// <https://url.spec.whatwg.org/#dom-url-hash>
233    fn SetHash(&self, value: USVString) {
234        UrlHelper::SetHash(&mut self.url.borrow_mut(), value);
235    }
236
237    /// <https://url.spec.whatwg.org/#dom-url-host>
238    fn Host(&self) -> USVString {
239        UrlHelper::Host(&self.url.borrow())
240    }
241
242    /// <https://url.spec.whatwg.org/#dom-url-host>
243    fn SetHost(&self, value: USVString) {
244        UrlHelper::SetHost(&mut self.url.borrow_mut(), value);
245    }
246
247    /// <https://url.spec.whatwg.org/#dom-url-hostname>
248    fn Hostname(&self) -> USVString {
249        UrlHelper::Hostname(&self.url.borrow())
250    }
251
252    /// <https://url.spec.whatwg.org/#dom-url-hostname>
253    fn SetHostname(&self, value: USVString) {
254        UrlHelper::SetHostname(&mut self.url.borrow_mut(), value);
255    }
256
257    /// <https://url.spec.whatwg.org/#dom-url-href>
258    fn Href(&self) -> USVString {
259        UrlHelper::Href(&self.url.borrow())
260    }
261
262    /// <https://url.spec.whatwg.org/#dom-url-href>
263    fn SetHref(&self, value: USVString) -> ErrorResult {
264        match ServoUrl::parse(&value.0) {
265            Ok(url) => {
266                *self.url.borrow_mut() = url;
267                self.search_params.set(None); // To be re-initialized in the SearchParams getter.
268                Ok(())
269            },
270            Err(error) => Err(Error::Type(format!("could not parse URL: {}", error))),
271        }
272    }
273
274    /// <https://url.spec.whatwg.org/#dom-url-password>
275    fn Password(&self) -> USVString {
276        UrlHelper::Password(&self.url.borrow())
277    }
278
279    /// <https://url.spec.whatwg.org/#dom-url-password>
280    fn SetPassword(&self, value: USVString) {
281        UrlHelper::SetPassword(&mut self.url.borrow_mut(), value);
282    }
283
284    /// <https://url.spec.whatwg.org/#dom-url-pathname>
285    fn Pathname(&self) -> USVString {
286        UrlHelper::Pathname(&self.url.borrow())
287    }
288
289    /// <https://url.spec.whatwg.org/#dom-url-pathname>
290    fn SetPathname(&self, value: USVString) {
291        UrlHelper::SetPathname(&mut self.url.borrow_mut(), value);
292    }
293
294    /// <https://url.spec.whatwg.org/#dom-url-port>
295    fn Port(&self) -> USVString {
296        UrlHelper::Port(&self.url.borrow())
297    }
298
299    /// <https://url.spec.whatwg.org/#dom-url-port>
300    fn SetPort(&self, value: USVString) {
301        UrlHelper::SetPort(&mut self.url.borrow_mut(), value);
302    }
303
304    /// <https://url.spec.whatwg.org/#dom-url-protocol>
305    fn Protocol(&self) -> USVString {
306        UrlHelper::Protocol(&self.url.borrow())
307    }
308
309    /// <https://url.spec.whatwg.org/#dom-url-protocol>
310    fn SetProtocol(&self, value: USVString) {
311        UrlHelper::SetProtocol(&mut self.url.borrow_mut(), value);
312    }
313
314    /// <https://url.spec.whatwg.org/#dom-url-origin>
315    fn Origin(&self) -> USVString {
316        UrlHelper::Origin(&self.url.borrow())
317    }
318
319    /// <https://url.spec.whatwg.org/#dom-url-search>
320    fn Search(&self) -> USVString {
321        UrlHelper::Search(&self.url.borrow())
322    }
323
324    /// <https://url.spec.whatwg.org/#dom-url-search>
325    fn SetSearch(&self, value: USVString) {
326        UrlHelper::SetSearch(&mut self.url.borrow_mut(), value);
327        if let Some(search_params) = self.search_params.get() {
328            search_params.set_list(self.query_pairs());
329        }
330    }
331
332    /// <https://url.spec.whatwg.org/#dom-url-searchparams>
333    fn SearchParams(&self, can_gc: CanGc) -> DomRoot<URLSearchParams> {
334        self.search_params
335            .or_init(|| URLSearchParams::new(&self.global(), Some(self), can_gc))
336    }
337
338    /// <https://url.spec.whatwg.org/#dom-url-username>
339    fn Username(&self) -> USVString {
340        UrlHelper::Username(&self.url.borrow())
341    }
342
343    /// <https://url.spec.whatwg.org/#dom-url-username>
344    fn SetUsername(&self, value: USVString) {
345        UrlHelper::SetUsername(&mut self.url.borrow_mut(), value);
346    }
347
348    /// <https://url.spec.whatwg.org/#dom-url-tojson>
349    fn ToJSON(&self) -> USVString {
350        self.Href()
351    }
352}