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}