script/dom/html/
htmlhyperlinkelementutils.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 html5ever::local_name;
6use js::context::JSContext;
7use servo_url::ServoUrl;
8
9use crate::dom::bindings::cell::DomRefCell;
10use crate::dom::bindings::conversions::DerivedFrom;
11use crate::dom::bindings::inheritance::Castable;
12use crate::dom::bindings::str::{DOMString, USVString};
13use crate::dom::element::Element;
14use crate::dom::node::NodeTraits;
15use crate::dom::urlhelper::UrlHelper;
16use crate::script_runtime::CanGc;
17
18pub(crate) trait HyperlinkElement {
19    fn get_url(&self) -> &DomRefCell<Option<ServoUrl>>;
20}
21
22/// <https://html.spec.whatwg.org/multipage/#htmlhyperlinkelementutils>
23pub(crate) trait HyperlinkElementTraits {
24    fn get_hash(&self) -> USVString;
25    fn set_hash(&self, cx: &mut JSContext, value: USVString);
26    fn get_host(&self) -> USVString;
27    fn set_host(&self, cx: &mut JSContext, value: USVString);
28    fn get_hostname(&self) -> USVString;
29    fn set_hostname(&self, cx: &mut JSContext, value: USVString);
30    fn get_href(&self) -> USVString;
31    fn set_href(&self, cx: &mut JSContext, value: USVString);
32    fn get_origin(&self) -> USVString;
33    fn get_password(&self) -> USVString;
34    fn set_password(&self, cx: &mut JSContext, value: USVString);
35    fn get_pathname(&self) -> USVString;
36    fn set_pathname(&self, cx: &mut JSContext, value: USVString);
37    fn get_port(&self) -> USVString;
38    fn set_port(&self, cx: &mut JSContext, value: USVString);
39    fn get_protocol(&self) -> USVString;
40    fn set_protocol(&self, cx: &mut JSContext, value: USVString);
41    fn get_search(&self) -> USVString;
42    fn set_search(&self, cx: &mut JSContext, value: USVString);
43    fn get_username(&self) -> USVString;
44    fn set_url(&self);
45    fn set_username(&self, cx: &mut JSContext, value: USVString);
46    fn update_href(&self, cx: &mut JSContext, url: &ServoUrl);
47    fn reinitialize_url(&self);
48}
49
50impl<T: HyperlinkElement + DerivedFrom<Element> + Castable + NodeTraits> HyperlinkElementTraits
51    for T
52{
53    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
54    fn get_hash(&self) -> USVString {
55        // Step 1. Reinitialize url.
56        self.reinitialize_url();
57
58        // Step 2. Let url be this's url.
59        match *self.get_url().borrow() {
60            // Step 3. If url is null, or url's fragment is either null or the empty string, return
61            // the empty string.
62            None => USVString(String::new()),
63            Some(ref url) if url.fragment().is_none() || url.fragment() == Some("") => {
64                USVString(String::new())
65            },
66            Some(ref url) => {
67                // Step 4. Return "#", followed by url's fragment.
68                UrlHelper::Hash(url)
69            },
70        }
71    }
72
73    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
74    fn set_hash(&self, cx: &mut JSContext, value: USVString) {
75        // Step 1. Reinitialize url.
76        self.reinitialize_url();
77
78        // Step 2. Let url be this's url.
79        // Step 3. If url is null, then return.
80        let mut url = self.get_url().borrow_mut();
81        let Some(url) = url.as_mut() else {
82            return;
83        };
84
85        // Step 4. If the given value is the empty string, set url's fragment to null.
86        // Note this step is taken care of by UrlHelper::SetHash when the value is Some
87        // Steps 5. Otherwise:
88        // Step 5.1. Let input be the given value with a single leading "#" removed, if any.
89        // Step 5.2. Set url's fragment to the empty string.
90        // Note these steps are taken care of by UrlHelper::SetHash
91        // Step 5.4.  Basic URL parse input, with url as url and fragment state as state
92        // override.
93        UrlHelper::SetHash(url, value);
94
95        // Step 6. Update href.
96        self.update_href(cx, url);
97    }
98
99    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
100    fn get_host(&self) -> USVString {
101        // Step 1. Reinitialize url.
102        self.reinitialize_url();
103
104        // Step 2. Let url be this's url.
105        match *self.get_url().borrow() {
106            // Step 3. If url or url's host is null, return the empty string.
107            None => USVString(String::new()),
108            Some(ref url) => {
109                if url.host().is_none() {
110                    USVString(String::new())
111                } else {
112                    // Step 4. If url's port is null, return url's host, serialized.
113                    // Step 5. Return url's host, serialized, followed by ":" and url's port,
114                    // serialized.
115                    UrlHelper::Host(url)
116                }
117            },
118        }
119    }
120
121    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
122    fn set_host(&self, cx: &mut JSContext, value: USVString) {
123        // Step 1. Reinitialize url.
124        self.reinitialize_url();
125
126        // Step 2. Let url be this's url.
127        let mut url = self.get_url().borrow_mut();
128        let url = match url.as_mut() {
129            // Step 3. If url or url's host is null, return the empty string.
130            Some(ref url) if url.cannot_be_a_base() => return,
131            None => return,
132            Some(url) => url,
133        };
134
135        // Step 4. Basic URL parse the given value, with url as url and host state as state
136        // override.
137        UrlHelper::SetHost(url, value);
138
139        // Step 5. Update href.
140        self.update_href(cx, url);
141    }
142
143    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
144    fn get_hostname(&self) -> USVString {
145        // Step 1. Reinitialize url.
146        self.reinitialize_url();
147
148        // Step 2. Let url be this's url.
149        match *self.get_url().borrow() {
150            // Step 3. If url or url's host is null, return the empty string.
151            None => USVString(String::new()),
152            Some(ref url) => {
153                // Step 4. Return url's host, serialized.
154                UrlHelper::Hostname(url)
155            },
156        }
157    }
158
159    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
160    fn set_hostname(&self, cx: &mut JSContext, value: USVString) {
161        // Step 1. Reinitialize url.
162        self.reinitialize_url();
163
164        // Step 2. Let url be this's url.
165        let mut url = self.get_url().borrow_mut();
166        let url = match url.as_mut() {
167            // Step 3. If url is null or url has an opaque path, then return.
168            None => return,
169            Some(ref url) if url.cannot_be_a_base() => return,
170            Some(url) => url,
171        };
172
173        // Step 4. Basic URL parse the given value, with url as url and hostname state as state
174        // override.
175        UrlHelper::SetHostname(url, value);
176
177        // Step 5. Update href.
178        self.update_href(cx, url);
179    }
180
181    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href>
182    fn get_href(&self) -> USVString {
183        // Step 1. Reinitialize url.
184        self.reinitialize_url();
185
186        // Step 2. Let url be this's url.
187        USVString(match *self.get_url().borrow() {
188            None => {
189                match self.upcast::<Element>().get_attribute(&local_name!("href")) {
190                    // Step 3. If url is null and this has no href content attribute, return the
191                    // empty string.
192                    None => String::new(),
193
194                    // Step 4. Otherwise, if url is null, return this's href content attribute's value.
195                    Some(attribute) => (**attribute.value()).to_owned(),
196                }
197            },
198            // Step 5. Return url, serialized.
199            Some(ref url) => url.as_str().to_owned(),
200        })
201    }
202
203    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href
204    fn set_href(&self, cx: &mut JSContext, value: USVString) {
205        self.upcast::<Element>().set_string_attribute(
206            &local_name!("href"),
207            value.into(),
208            CanGc::from_cx(cx),
209        );
210
211        self.set_url();
212    }
213
214    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-origin>
215    fn get_origin(&self) -> USVString {
216        // Step 1. Reinitialize url.
217        self.reinitialize_url();
218
219        USVString(match *self.get_url().borrow() {
220            // Step 2. If this's url is null, return the empty string.
221            None => "".to_owned(),
222            // Step 3. Return the serialization of this's url's origin.
223            Some(ref url) => url.origin().ascii_serialization(),
224        })
225    }
226
227    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
228    fn get_password(&self) -> USVString {
229        // Step 1. Reinitialize url.
230        self.reinitialize_url();
231
232        // Step 2. Let url be this's url.
233        match *self.get_url().borrow() {
234            // Step 3. If url is null, then return the empty string.
235            None => USVString(String::new()),
236            // Steps 4. Return url's password.
237            Some(ref url) => UrlHelper::Password(url),
238        }
239    }
240
241    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
242    fn set_password(&self, cx: &mut JSContext, value: USVString) {
243        // Step 1. Reinitialize url.
244        self.reinitialize_url();
245
246        // Step 2. Let url be this's url.
247        let mut url = self.get_url().borrow_mut();
248        let url = match url.as_mut() {
249            // Step 3. If url is null or url cannot have a username/password/port, then return.
250            None => return,
251            Some(ref url) if url.host().is_none() || url.cannot_be_a_base() => return,
252            Some(url) => url,
253        };
254
255        // Step 4. Set the password, given url and the given value.
256        UrlHelper::SetPassword(url, value);
257
258        // Step 5. Update href.
259        self.update_href(cx, url);
260    }
261
262    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
263    fn get_pathname(&self) -> USVString {
264        // Step 1. Reinitialize url.
265        self.reinitialize_url();
266
267        // Step 2. Let url be this's url.
268        match *self.get_url().borrow() {
269            // Step 3. If url is null, then return the empty string.
270            None => USVString(String::new()),
271            // Steps 4. Return the result of URL path serializing url.
272            Some(ref url) => UrlHelper::Pathname(url),
273        }
274    }
275
276    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
277    fn set_pathname(&self, cx: &mut JSContext, value: USVString) {
278        // Step 1. Reinitialize url.
279        self.reinitialize_url();
280
281        // Step 2. Let url be this's url.
282        let mut url = self.get_url().borrow_mut();
283        let url = match url.as_mut() {
284            // Step 3. If url is null or url has an opaque path, then return.
285            None => return,
286            Some(ref url) if url.cannot_be_a_base() => return,
287            Some(url) => url,
288        };
289
290        // Step 4. Set url's path to the empty list.
291        // Step 5. Basic URL parse the given value, with url as url and path start state as state override.
292        UrlHelper::SetPathname(url, value);
293
294        // Step 6. Update href.
295        self.update_href(cx, url);
296    }
297
298    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
299    fn get_port(&self) -> USVString {
300        // Step 1. Reinitialize url.
301        self.reinitialize_url();
302
303        // Step 2. Let url be this's url.
304        match *self.get_url().borrow() {
305            // Step 3. If url or url's port is null, return the empty string.
306            None => USVString(String::new()),
307            // Step 4. Return url's port, serialized.
308            Some(ref url) => UrlHelper::Port(url),
309        }
310    }
311
312    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
313    fn set_port(&self, cx: &mut JSContext, value: USVString) {
314        // Step 1. Reinitialize url.
315        self.reinitialize_url();
316
317        // Step 2. Let url be this's url.
318        let mut url = self.get_url().borrow_mut();
319        let url = match url.as_mut() {
320            // Step 3. If url is null or url cannot have a username/password/port, then return.
321            None => return,
322            Some(ref url)
323                // https://url.spec.whatwg.org/#cannot-have-a-username-password-port
324                if url.host().is_none() || url.cannot_be_a_base() || url.scheme() == "file" =>
325            {
326                return;
327            },
328            Some(url) => url,
329        };
330
331        // Step 4. If the given value is the empty string, then set url's port to null.
332        // Step 5. Otherwise, basic URL parse the given value, with url as url and port state as
333        // state override.
334        UrlHelper::SetPort(url, value);
335
336        // Step 6. Update href.
337        self.update_href(cx, url);
338    }
339
340    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
341    fn get_protocol(&self) -> USVString {
342        // Step 1. Reinitialize url.
343        self.reinitialize_url();
344
345        match *self.get_url().borrow() {
346            // Step 2. If this's url is null, return ":".
347            None => USVString(":".to_owned()),
348            // Step 3. Return this's url's scheme, followed by ":".
349            Some(ref url) => UrlHelper::Protocol(url),
350        }
351    }
352
353    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
354    fn set_protocol(&self, cx: &mut JSContext, value: USVString) {
355        // Step 1. Reinitialize url.
356        self.reinitialize_url();
357
358        let mut url = self.get_url().borrow_mut();
359        let url = match url.as_mut() {
360            // Step 2. If this's url is null, then return.
361            None => return,
362            Some(url) => url,
363        };
364
365        // Step 3. Basic URL parse the given value, followed by ":", with this's url as url and
366        // scheme start state as state override.
367        UrlHelper::SetProtocol(url, value);
368
369        // Step 4. Update href.
370        self.update_href(cx, url);
371    }
372
373    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
374    fn get_search(&self) -> USVString {
375        // Step 1. Reinitialize url.
376        self.reinitialize_url();
377
378        // Step 2. Let url be this's url.
379        match *self.get_url().borrow() {
380            // Step 3. If url is null, or url's query is either null or the empty string, return the
381            // empty string.
382            // Step 4. Return "?", followed by url's query.
383            // Note: This is handled in UrlHelper::Search
384            None => USVString(String::new()),
385            Some(ref url) => UrlHelper::Search(url),
386        }
387    }
388
389    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
390    fn set_search(&self, cx: &mut JSContext, value: USVString) {
391        // Step 1. Reinitialize url.
392        self.reinitialize_url();
393
394        // Step 2. Let url be this's url.
395        let mut url = self.get_url().borrow_mut();
396        let url = match url.as_mut() {
397            // Step 3. If url is null, terminate these steps.
398            None => return,
399            Some(url) => url,
400        };
401
402        // Step 4. If the given value is the empty string, set url's query to null.
403        // Step 5. Otherwise:
404        // Note: Inner steps are handled by UrlHelper::SetSearch
405        UrlHelper::SetSearch(url, value);
406
407        // Step 6. Update href.
408        self.update_href(cx, url);
409    }
410
411    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
412    fn get_username(&self) -> USVString {
413        // Step 1. Reinitialize url.
414        self.reinitialize_url();
415
416        match *self.get_url().borrow() {
417            // Step 2. If this's url is null, return the empty string.
418            None => USVString(String::new()),
419            // Step 3. Return this's url's username.
420            Some(ref url) => UrlHelper::Username(url),
421        }
422    }
423
424    /// <https://html.spec.whatwg.org/multipage/#concept-hyperlink-url-set>
425    fn set_url(&self) {
426        // Step 1. Set this element's url to null.
427        *self.get_url().borrow_mut() = None;
428
429        let attribute = self.upcast::<Element>().get_attribute(&local_name!("href"));
430
431        // Step 2. If this element's href content attribute is absent, then return.
432        let Some(attribute) = attribute else {
433            return;
434        };
435
436        let document = self.owner_document();
437
438        // Step 3. Let url be the result of encoding-parsing a URL given this element's href content
439        // attribute's value, relative to this element's node document.
440        let url = document.encoding_parse_a_url(&attribute.value());
441
442        // Step 4. If url is not failure, then set this element's url to url.
443        if let Ok(url) = url {
444            *self.get_url().borrow_mut() = Some(url);
445        }
446    }
447
448    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
449    fn set_username(&self, cx: &mut JSContext, value: USVString) {
450        // Step 1. Reinitialize url.
451        self.reinitialize_url();
452
453        // Step 2. Let url be this's url.
454        let mut url = self.get_url().borrow_mut();
455        let url = match url.as_mut() {
456            // Step 3. If url is null or url cannot have a username/password/port, then return.
457            None => return,
458            Some(ref url) if url.host().is_none() || url.cannot_be_a_base() => return,
459            Some(url) => url,
460        };
461
462        // Step 4. Set the username, given url and the given value.
463        UrlHelper::SetUsername(url, value);
464
465        // Step 5. Update href.
466        self.update_href(cx, url);
467    }
468
469    /// <https://html.spec.whatwg.org/multipage/#update-href>
470    fn update_href(&self, cx: &mut JSContext, url: &ServoUrl) {
471        self.upcast::<Element>().set_string_attribute(
472            &local_name!("href"),
473            DOMString::from(url.as_str()),
474            CanGc::from_cx(cx),
475        );
476    }
477
478    /// <https://html.spec.whatwg.org/multipage/#reinitialise-url>
479    fn reinitialize_url(&self) {
480        // Step 1. If the element's url is non-null, its scheme is "blob", and it has an opaque
481        // path, then terminate these steps.
482        match *self.get_url().borrow() {
483            Some(ref url) if url.scheme() == "blob" && url.cannot_be_a_base() => return,
484            _ => (),
485        }
486
487        // Step 2. Set the url.
488        self.set_url();
489    }
490}