Skip to main content

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