Serenity Operating System
at master 580 lines 20 kB view raw
1/* 2 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/URLParser.h> 8#include <LibWeb/DOM/Document.h> 9#include <LibWeb/HTML/HTMLHyperlinkElementUtils.h> 10#include <LibWeb/Infra/CharacterTypes.h> 11#include <LibWeb/Infra/Strings.h> 12#include <LibWeb/Loader/FrameLoader.h> 13 14namespace Web::HTML { 15 16HTMLHyperlinkElementUtils::~HTMLHyperlinkElementUtils() = default; 17 18// https://html.spec.whatwg.org/multipage/links.html#reinitialise-url 19void HTMLHyperlinkElementUtils::reinitialize_url() const 20{ 21 // 1. If element's url is non-null, its scheme is "blob", and its cannot-be-a-basFe-URL is true, terminate these steps. 22 if (m_url.has_value() && m_url->scheme() == "blob"sv && m_url->cannot_be_a_base_url()) 23 return; 24 25 // 2. Set the url. 26 const_cast<HTMLHyperlinkElementUtils*>(this)->set_the_url(); 27} 28 29// https://html.spec.whatwg.org/multipage/links.html#concept-hyperlink-url-set 30void HTMLHyperlinkElementUtils::set_the_url() 31{ 32 // 1. If this element's href content attribute is absent, set this element's url to null. 33 auto href_content_attribute = hyperlink_element_utils_href(); 34 if (href_content_attribute.is_null()) { 35 m_url = {}; 36 return; 37 } 38 39 // 2. Otherwise, parse this element's href content attribute value relative to this element's node document. 40 // If parsing is successful, set this element's url to the result; otherwise, set this element's url to null. 41 m_url = hyperlink_element_utils_document().parse_url(href_content_attribute); 42} 43 44// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-origin 45DeprecatedString HTMLHyperlinkElementUtils::origin() const 46{ 47 // 1. Reinitialize url. 48 reinitialize_url(); 49 50 // 2. If this element's url is null, return the empty string. 51 if (!m_url.has_value()) 52 return DeprecatedString::empty(); 53 54 // 3. Return the serialization of this element's url's origin. 55 return m_url->serialize_origin(); 56} 57 58// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-protocol 59DeprecatedString HTMLHyperlinkElementUtils::protocol() const 60{ 61 // 1. Reinitialize url. 62 reinitialize_url(); 63 64 // 2. If this element's url is null, return ":". 65 if (!m_url.has_value()) 66 return ":"sv; 67 68 // 3. Return this element's url's scheme, followed by ":". 69 return DeprecatedString::formatted("{}:", m_url->scheme()); 70} 71 72// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-protocol 73void HTMLHyperlinkElementUtils::set_protocol(DeprecatedString protocol) 74{ 75 // 1. Reinitialize url. 76 reinitialize_url(); 77 78 // 2. If this element's url is null, terminate these steps. 79 if (!m_url.has_value()) 80 return; 81 82 // 3. Basic URL parse the given value, followed by ":", with this element's url as url and scheme start state as state override. 83 auto result_url = URLParser::parse(DeprecatedString::formatted("{}:", protocol), nullptr, m_url, URLParser::State::SchemeStart); 84 if (result_url.is_valid()) 85 m_url = move(result_url); 86 87 // 4. Update href. 88 update_href(); 89} 90 91// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-username 92DeprecatedString HTMLHyperlinkElementUtils::username() const 93{ 94 // 1. Reinitialize url. 95 reinitialize_url(); 96 97 // 2. If this element's url is null, return the empty string. 98 if (!m_url.has_value()) 99 return DeprecatedString::empty(); 100 101 // 3. Return this element's url's username. 102 return m_url->username(); 103} 104 105// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-username 106void HTMLHyperlinkElementUtils::set_username(DeprecatedString username) 107{ 108 // 1. Reinitialize url. 109 reinitialize_url(); 110 111 // 2. Let url be this element's url. 112 auto& url = m_url; 113 114 // 3. If url is null or url cannot have a username/password/port, then return. 115 if (!url.has_value() || url->cannot_have_a_username_or_password_or_port()) 116 return; 117 118 // 4. Set the username given this’s URL and the given value. 119 url->set_username(AK::URL::percent_encode(username, AK::URL::PercentEncodeSet::Userinfo)); 120 121 // 5. Update href. 122 update_href(); 123} 124 125// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-password 126DeprecatedString HTMLHyperlinkElementUtils::password() const 127{ 128 // 1. Reinitialize url. 129 reinitialize_url(); 130 131 // 2. Let url be this element's url. 132 auto& url = m_url; 133 134 // 3. If url is null, then return the empty string. 135 if (!url.has_value()) 136 return DeprecatedString::empty(); 137 138 // 4. Return url's password. 139 return url->password(); 140} 141 142// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-password 143void HTMLHyperlinkElementUtils::set_password(DeprecatedString password) 144{ 145 // 1. Reinitialize url. 146 reinitialize_url(); 147 148 // 2. Let url be this element's url. 149 auto& url = m_url; 150 151 // 3. If url is null or url cannot have a username/password/port, then return. 152 if (!url.has_value() || url->cannot_have_a_username_or_password_or_port()) 153 return; 154 155 // 4. Set the password, given url and the given value. 156 url->set_password(move(password)); 157 158 // 5. Update href. 159 update_href(); 160} 161 162// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-host 163DeprecatedString HTMLHyperlinkElementUtils::host() const 164{ 165 // 1. Reinitialize url. 166 reinitialize_url(); 167 168 // 2. Let url be this element's url. 169 auto& url = m_url; 170 171 // 3. If url or url's host is null, return the empty string. 172 if (!url.has_value() || url->host().is_null()) 173 return DeprecatedString::empty(); 174 175 // 4. If url's port is null, return url's host, serialized. 176 if (!url->port().has_value()) 177 return url->host(); 178 179 // 5. Return url's host, serialized, followed by ":" and url's port, serialized. 180 return DeprecatedString::formatted("{}:{}", url->host(), url->port().value()); 181} 182 183// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-host 184void HTMLHyperlinkElementUtils::set_host(DeprecatedString host) 185{ 186 // 1. Reinitialize url. 187 reinitialize_url(); 188 189 // 2. Let url be this element's url. 190 auto& url = m_url; 191 192 // 3. If url is null or url's cannot-be-a-base-URL is true, then return. 193 if (!url.has_value() || url->cannot_be_a_base_url()) 194 return; 195 196 // 4. Basic URL parse the given value, with url as url and host state as state override. 197 auto result_url = URLParser::parse(host, nullptr, url, URLParser::State::Host); 198 if (result_url.is_valid()) 199 m_url = move(result_url); 200 201 // 5. Update href. 202 update_href(); 203} 204 205DeprecatedString HTMLHyperlinkElementUtils::hostname() const 206{ 207 // 1. Reinitialize url. 208 // 209 // 2. Let url be this element's url. 210 // 211 // 3. If url or url's host is null, return the empty string. 212 // 213 // 4. Return url's host, serialized. 214 return AK::URL(href()).host(); 215} 216 217void HTMLHyperlinkElementUtils::set_hostname(DeprecatedString hostname) 218{ 219 // 1. Reinitialize url. 220 reinitialize_url(); 221 222 // 2. Let url be this element's url. 223 auto& url = m_url; 224 225 // 3. If url is null or url's cannot-be-a-base-URL is true, then return. 226 if (!url.has_value() || url->cannot_be_a_base_url()) 227 return; 228 229 // 4. Basic URL parse the given value, with url as url and hostname state as state override. 230 auto result_url = URLParser::parse(hostname, nullptr, m_url, URLParser::State::Hostname); 231 if (result_url.is_valid()) 232 m_url = move(result_url); 233 234 // 5. Update href. 235 update_href(); 236} 237 238// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-port 239DeprecatedString HTMLHyperlinkElementUtils::port() const 240{ 241 // 1. Reinitialize url. 242 reinitialize_url(); 243 244 // 2. Let url be this element's url. 245 auto& url = m_url; 246 247 // 3. If url or url's port is null, return the empty string. 248 if (!url.has_value() || !url->port().has_value()) 249 return DeprecatedString::empty(); 250 251 // 4. Return url's port, serialized. 252 return DeprecatedString::number(url->port().value()); 253} 254 255// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-port 256void HTMLHyperlinkElementUtils::set_port(DeprecatedString port) 257{ 258 // 1. Reinitialize url. 259 reinitialize_url(); 260 261 // 2. Let url be this element's url. 262 263 // 3. If url is null or url cannot have a username/password/port, then return. 264 if (!m_url.has_value() || m_url->cannot_have_a_username_or_password_or_port()) 265 return; 266 267 // 4. If the given value is the empty string, then set url's port to null. 268 if (port.is_empty()) { 269 m_url->set_port({}); 270 } else { 271 // 5. Otherwise, basic URL parse the given value, with url as url and port state as state override. 272 auto result_url = URLParser::parse(port, nullptr, m_url, URLParser::State::Port); 273 if (result_url.is_valid()) 274 m_url = move(result_url); 275 } 276 277 // 6. Update href. 278 update_href(); 279} 280 281// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-pathname 282DeprecatedString HTMLHyperlinkElementUtils::pathname() const 283{ 284 // 1. Reinitialize url. 285 reinitialize_url(); 286 287 // 2. Let url be this element's url. 288 289 // 3. If url is null, return the empty string. 290 if (!m_url.has_value()) 291 return DeprecatedString::empty(); 292 293 // 4. If url's cannot-be-a-base-URL is true, then return url's path[0]. 294 // 5. If url's path is empty, then return the empty string. 295 // 6. Return "/", followed by the strings in url's path (including empty strings), separated from each other by "/". 296 return m_url->path(); 297} 298 299// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-pathname 300void HTMLHyperlinkElementUtils::set_pathname(DeprecatedString pathname) 301{ 302 // 1. Reinitialize url. 303 reinitialize_url(); 304 305 // 2. Let url be this element's url. 306 307 // 3. If url is null or url's cannot-be-a-base-URL is true, then return. 308 if (!m_url.has_value() || m_url->cannot_be_a_base_url()) 309 return; 310 311 // 4. Set url's path to the empty list. 312 auto url = m_url; // We copy the URL here to follow other browser's behaviour of reverting the path change if the parse failed. 313 url->set_paths({}); 314 315 // 5. Basic URL parse the given value, with url as url and path start state as state override. 316 auto result_url = URLParser::parse(pathname, nullptr, move(url), URLParser::State::PathStart); 317 if (result_url.is_valid()) 318 m_url = move(result_url); 319 320 // 6. Update href. 321 update_href(); 322} 323 324DeprecatedString HTMLHyperlinkElementUtils::search() const 325{ 326 // 1. Reinitialize url. 327 reinitialize_url(); 328 329 // 2. Let url be this element's url. 330 331 // 3. If url is null, or url's query is either null or the empty string, return the empty string. 332 if (!m_url.has_value() || m_url->query().is_null() || m_url->query().is_empty()) 333 return DeprecatedString::empty(); 334 335 // 4. Return "?", followed by url's query. 336 return DeprecatedString::formatted("?{}", m_url->query()); 337} 338 339void HTMLHyperlinkElementUtils::set_search(DeprecatedString search) 340{ 341 // 1. Reinitialize url. 342 reinitialize_url(); 343 344 // 2. Let url be this element's url. 345 346 // 3. If url is null, terminate these steps. 347 if (!m_url.has_value()) 348 return; 349 350 // 4. If the given value is the empty string, set url's query to null. 351 if (search.is_empty()) { 352 m_url->set_query({}); 353 } else { 354 // 5. Otherwise: 355 // 1. Let input be the given value with a single leading "?" removed, if any. 356 auto input = search.substring_view(search.starts_with('?')); 357 358 // 2. Set url's query to the empty string. 359 auto url_copy = m_url; // We copy the URL here to follow other browser's behaviour of reverting the search change if the parse failed. 360 url_copy->set_query(DeprecatedString::empty()); 361 362 // 3. Basic URL parse input, with null, this element's node document's document's character encoding, url as url, and query state as state override. 363 auto result_url = URLParser::parse(input, nullptr, move(url_copy), URLParser::State::Query); 364 if (result_url.is_valid()) 365 m_url = move(result_url); 366 } 367 368 // 6. Update href. 369 update_href(); 370} 371 372DeprecatedString HTMLHyperlinkElementUtils::hash() const 373{ 374 // 1. Reinitialize url. 375 reinitialize_url(); 376 377 // 2. Let url be this element's url. 378 379 // 3. If url is null, or url's fragment is either null or the empty string, return the empty string. 380 if (!m_url.has_value() || m_url->fragment().is_null() || m_url->fragment().is_empty()) 381 return DeprecatedString::empty(); 382 383 // 4. Return "#", followed by url's fragment. 384 return DeprecatedString::formatted("#{}", m_url->fragment()); 385} 386 387void HTMLHyperlinkElementUtils::set_hash(DeprecatedString hash) 388{ 389 // 1. Reinitialize url. 390 reinitialize_url(); 391 392 // 2. Let url be this element's url. 393 394 // 3. If url is null, then return. 395 if (!m_url.has_value()) 396 return; 397 398 // 4. If the given value is the empty string, set url's fragment to null. 399 if (hash.is_empty()) { 400 m_url->set_fragment({}); 401 } else { 402 // 5. Otherwise: 403 // 1. Let input be the given value with a single leading "#" removed, if any. 404 auto input = hash.substring_view(hash.starts_with('#')); 405 406 // 2. Set url's fragment to the empty string. 407 auto url_copy = m_url; // We copy the URL here to follow other browser's behaviour of reverting the hash change if the parse failed. 408 url_copy->set_fragment(DeprecatedString::empty()); 409 410 // 3. Basic URL parse input, with url as url and fragment state as state override. 411 auto result_url = URLParser::parse(input, nullptr, move(url_copy), URLParser::State::Fragment); 412 if (result_url.is_valid()) 413 m_url = move(result_url); 414 } 415 416 // 6. Update href. 417 update_href(); 418} 419 420// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-href 421DeprecatedString HTMLHyperlinkElementUtils::href() const 422{ 423 // 1. Reinitialize url. 424 reinitialize_url(); 425 426 // 2. Let url be this element's url. 427 auto& url = m_url; 428 429 // 3. If url is null and this element has no href content attribute, return the empty string. 430 auto href_content_attribute = hyperlink_element_utils_href(); 431 if (!url.has_value() && href_content_attribute.is_null()) 432 return DeprecatedString::empty(); 433 434 // 4. Otherwise, if url is null, return this element's href content attribute's value. 435 if (!url->is_valid()) 436 return href_content_attribute; 437 438 // 5. Return url, serialized. 439 return url->serialize(); 440} 441 442// https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-href 443void HTMLHyperlinkElementUtils::set_href(DeprecatedString href) 444{ 445 // The href attribute's setter must set this element's href content attribute's value to the given value. 446 set_hyperlink_element_utils_href(move(href)); 447} 448 449// https://html.spec.whatwg.org/multipage/links.html#update-href 450void HTMLHyperlinkElementUtils::update_href() 451{ 452 // To update href, set the element's href content attribute's value to the element's url, serialized. 453} 454 455bool HTMLHyperlinkElementUtils::cannot_navigate() const 456{ 457 // An element element cannot navigate if one of the following is true: 458 459 // 1. element's node document is not fully active 460 auto const& document = const_cast<HTMLHyperlinkElementUtils*>(this)->hyperlink_element_utils_document(); 461 if (!document.is_fully_active()) 462 return true; 463 464 // 2. element is not an a element and is not connected. 465 if (!hyperlink_element_utils_is_html_anchor_element() && !hyperlink_element_utils_is_connected()) 466 return true; 467 468 return false; 469} 470 471// https://html.spec.whatwg.org/multipage/links.html#following-hyperlinks-2 472void HTMLHyperlinkElementUtils::follow_the_hyperlink(Optional<DeprecatedString> hyperlink_suffix) 473{ 474 // To follow the hyperlink created by an element subject, given an optional hyperlinkSuffix (default null): 475 476 // 1. If subject cannot navigate, then return. 477 if (cannot_navigate()) 478 return; 479 480 // FIXME: 2. Let replace be false. 481 482 // 3. Let source be subject's node document's browsing context. 483 auto* source = hyperlink_element_utils_document().browsing_context(); 484 if (!source) 485 return; 486 487 // 4. Let targetAttributeValue be the empty string. 488 // 5. If subject is an a or area element, then set targetAttributeValue to 489 // the result of getting an element's target given subject. 490 DeprecatedString target_attribute_value = get_an_elements_target(); 491 492 // 6. Let noopener be the result of getting an element's noopener with subject and targetAttributeValue. 493 bool noopener = get_an_elements_noopener(target_attribute_value); 494 495 // 7. Let target be the first return value of applying the rules for 496 // choosing a browsing context given targetAttributeValue, source, and 497 // noopener. 498 auto target = source->choose_a_browsing_context(target_attribute_value, noopener).browsing_context; 499 500 // 8. If target is null, then return. 501 if (!target) 502 return; 503 504 // 9. Parse a URL given subject's href attribute, relative to subject's node 505 // document. 506 auto url = source->active_document()->parse_url(href()); 507 508 // 10. If that is successful, let URL be the resulting URL string. 509 auto url_string = url.to_deprecated_string(); 510 511 // 11. Otherwise, if parsing the URL failed, the user agent may report the 512 // error to the user in a user-agent-specific manner, may queue an element 513 // task on the DOM manipulation task source given subject to navigate the 514 // target browsing context to an error page to report the error, or may 515 // ignore the error and do nothing. In any case, the user agent must then 516 // return. 517 518 // 12. If hyperlinkSuffix is non-null, then append it to URL. 519 if (hyperlink_suffix.has_value()) { 520 StringBuilder url_builder; 521 url_builder.append(url_string); 522 url_builder.append(*hyperlink_suffix); 523 524 url_string = url_builder.to_deprecated_string(); 525 } 526 527 // FIXME: 13. Let request be a new request whose URL is URL and whose 528 // referrer policy is the current state of subject's referrerpolicy content 529 // attribute. 530 531 // FIXME: 14. If subject's link types includes the noreferrer keyword, then 532 // set request's referrer to "no-referrer". 533 534 // 15. Queue an element task on the DOM manipulation task source given 535 // subject to navigate target to request with the source browsing context 536 // set to source. 537 // FIXME: "navigate" means implementing the navigation algorithm here: 538 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate 539 hyperlink_element_utils_queue_an_element_task(Task::Source::DOMManipulation, [url_string, target] { 540 target->loader().load(url_string, FrameLoader::Type::Navigation); 541 }); 542} 543 544DeprecatedString HTMLHyperlinkElementUtils::get_an_elements_target() const 545{ 546 // To get an element's target, given an a, area, or form element element, run these steps: 547 548 // 1. If element has a target attribute, then return that attribute's value. 549 if (auto target = hyperlink_element_utils_target(); !target.is_empty()) 550 return target; 551 552 // FIXME: 2. If element's node document contains a base element with a 553 // target attribute, then return the value of the target attribute of the 554 // first such base element. 555 556 // 3. Return the empty string. 557 return ""; 558} 559 560// https://html.spec.whatwg.org/multipage/links.html#get-an-element's-noopener 561bool HTMLHyperlinkElementUtils::get_an_elements_noopener(StringView target) const 562{ 563 // To get an element's noopener, given an a, area, or form element element and a string target: 564 auto rel = hyperlink_element_utils_rel().to_lowercase(); 565 auto link_types = rel.view().split_view_if(Infra::is_ascii_whitespace); 566 567 // 1. If element's link types include the noopener or noreferrer keyword, then return true. 568 if (link_types.contains_slow("noopener"sv) || link_types.contains_slow("noreferrer"sv)) 569 return true; 570 571 // 2. If element's link types do not include the opener keyword and 572 // target is an ASCII case-insensitive match for "_blank", then return true. 573 if (!link_types.contains_slow("opener"sv) && Infra::is_ascii_case_insensitive_match(target, "_blank"sv)) 574 return true; 575 576 // 3. Return false. 577 return false; 578} 579 580}