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