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