/src/serenity/Userland/Libraries/LibWeb/HTML/HTMLAnchorElement.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <LibWeb/ARIA/Roles.h> |
9 | | #include <LibWeb/Bindings/HTMLAnchorElementPrototype.h> |
10 | | #include <LibWeb/DOM/DOMTokenList.h> |
11 | | #include <LibWeb/DOM/Event.h> |
12 | | #include <LibWeb/HTML/AttributeNames.h> |
13 | | #include <LibWeb/HTML/HTMLAnchorElement.h> |
14 | | #include <LibWeb/HTML/HTMLImageElement.h> |
15 | | #include <LibWeb/HTML/Window.h> |
16 | | #include <LibWeb/PixelUnits.h> |
17 | | #include <LibWeb/ReferrerPolicy/ReferrerPolicy.h> |
18 | | #include <LibWeb/UIEvents/MouseEvent.h> |
19 | | |
20 | | namespace Web::HTML { |
21 | | |
22 | | JS_DEFINE_ALLOCATOR(HTMLAnchorElement); |
23 | | |
24 | | HTMLAnchorElement::HTMLAnchorElement(DOM::Document& document, DOM::QualifiedName qualified_name) |
25 | 0 | : HTMLElement(document, move(qualified_name)) |
26 | 0 | { |
27 | 0 | } |
28 | | |
29 | 0 | HTMLAnchorElement::~HTMLAnchorElement() = default; |
30 | | |
31 | | void HTMLAnchorElement::initialize(JS::Realm& realm) |
32 | 0 | { |
33 | 0 | Base::initialize(realm); |
34 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLAnchorElement); |
35 | 0 | } |
36 | | |
37 | | void HTMLAnchorElement::visit_edges(Cell::Visitor& visitor) |
38 | 0 | { |
39 | 0 | Base::visit_edges(visitor); |
40 | 0 | visitor.visit(m_rel_list); |
41 | 0 | } |
42 | | |
43 | | void HTMLAnchorElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value) |
44 | 0 | { |
45 | 0 | HTMLElement::attribute_changed(name, old_value, value); |
46 | 0 | if (name == HTML::AttributeNames::href) { |
47 | 0 | set_the_url(); |
48 | 0 | } else if (name == HTML::AttributeNames::rel) { |
49 | 0 | if (m_rel_list) |
50 | 0 | m_rel_list->associated_attribute_changed(value.value_or(String {})); |
51 | 0 | } |
52 | 0 | } |
53 | | |
54 | | Optional<String> HTMLAnchorElement::hyperlink_element_utils_href() const |
55 | 0 | { |
56 | 0 | return attribute(HTML::AttributeNames::href); |
57 | 0 | } |
58 | | |
59 | | WebIDL::ExceptionOr<void> HTMLAnchorElement::set_hyperlink_element_utils_href(String href) |
60 | 0 | { |
61 | 0 | return set_attribute(HTML::AttributeNames::href, move(href)); |
62 | 0 | } |
63 | | |
64 | | Optional<String> HTMLAnchorElement::hyperlink_element_utils_referrerpolicy() const |
65 | 0 | { |
66 | 0 | return attribute(HTML::AttributeNames::referrerpolicy); |
67 | 0 | } |
68 | | |
69 | | bool HTMLAnchorElement::has_activation_behavior() const |
70 | 0 | { |
71 | 0 | return true; |
72 | 0 | } |
73 | | |
74 | | // https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements |
75 | | void HTMLAnchorElement::activation_behavior(Web::DOM::Event const& event) |
76 | 0 | { |
77 | | // The activation behavior of an a or area element element given an event event is: |
78 | | |
79 | | // 1. If element has no href attribute, then return. |
80 | 0 | if (href().is_empty()) |
81 | 0 | return; |
82 | | |
83 | | // AD-HOC: Do not activate the element for clicks with the ctrl/cmd modifier present. This lets |
84 | | // the chrome open the link in a new tab. |
85 | 0 | if (is<UIEvents::MouseEvent>(event)) { |
86 | 0 | auto const& mouse_event = static_cast<UIEvents::MouseEvent const&>(event); |
87 | 0 | if (mouse_event.platform_ctrl_key()) |
88 | 0 | return; |
89 | 0 | } |
90 | | |
91 | | // 2. Let hyperlinkSuffix be null. |
92 | 0 | Optional<String> hyperlink_suffix {}; |
93 | | |
94 | | // 3. If element is an a element, and event's target is an img with an ismap attribute specified, then: |
95 | 0 | if (event.target() && is<HTMLImageElement>(*event.target()) && static_cast<HTMLImageElement const&>(*event.target()).has_attribute(AttributeNames::ismap)) { |
96 | | // 1. Let x and y be 0. |
97 | 0 | CSSPixels x { 0 }; |
98 | 0 | CSSPixels y { 0 }; |
99 | | |
100 | | // 2. If event's isTrusted attribute is initialized to true, then set x to the distance in CSS pixels from the left edge of the image |
101 | | // to the location of the click, and set y to the distance in CSS pixels from the top edge of the image to the location of the click. |
102 | 0 | if (event.is_trusted() && is<UIEvents::MouseEvent>(event)) { |
103 | 0 | auto const& mouse_event = static_cast<UIEvents::MouseEvent const&>(event); |
104 | 0 | x = CSSPixels { mouse_event.offset_x() }; |
105 | 0 | y = CSSPixels { mouse_event.offset_y() }; |
106 | 0 | } |
107 | | |
108 | | // 3. If x is negative, set x to 0. |
109 | 0 | x = max(x, 0); |
110 | | |
111 | | // 4. If y is negative, set y to 0. |
112 | 0 | y = max(y, 0); |
113 | | |
114 | | // 5. Set hyperlinkSuffix to the concatenation of U+003F (?), the value of x expressed as a base-ten integer using ASCII digits, |
115 | | // U+002C (,), and the value of y expressed as a base-ten integer using ASCII digits. |
116 | 0 | hyperlink_suffix = MUST(String::formatted("?{},{}", x.to_int(), y.to_int())); |
117 | 0 | } |
118 | | |
119 | | // 4. Let userInvolvement be event's user navigation involvement. |
120 | 0 | auto user_involvement = user_navigation_involvement(event); |
121 | | |
122 | | // 5. If the user has expressed a preference to download the hyperlink, then set userInvolvement to "browser UI". |
123 | | // NOTE: That is, if the user has expressed a specific preference for downloading, this no longer counts as merely "activation". |
124 | 0 | if (has_download_preference()) |
125 | 0 | user_involvement = UserNavigationInvolvement::BrowserUI; |
126 | | |
127 | | // FIXME: 6. If element has a download attribute, or if the user has expressed a preference to download the |
128 | | // hyperlink, then download the hyperlink created by element with hyperlinkSuffix set to hyperlinkSuffix and |
129 | | // userInvolvement set to userInvolvement. |
130 | | |
131 | | // 7. Otherwise, follow the hyperlink created by element with hyperlinkSuffix set to hyperlinkSuffix and userInvolvement set to userInvolvement. |
132 | 0 | follow_the_hyperlink(hyperlink_suffix, user_involvement); |
133 | 0 | } |
134 | | |
135 | | bool HTMLAnchorElement::has_download_preference() const |
136 | 0 | { |
137 | 0 | return has_attribute(HTML::AttributeNames::download); |
138 | 0 | } |
139 | | |
140 | | // https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex |
141 | | i32 HTMLAnchorElement::default_tab_index_value() const |
142 | 0 | { |
143 | | // See the base function for the spec comments. |
144 | 0 | return 0; |
145 | 0 | } |
146 | | |
147 | | Optional<ARIA::Role> HTMLAnchorElement::default_role() const |
148 | 0 | { |
149 | | // https://www.w3.org/TR/html-aria/#el-a-no-href |
150 | 0 | if (!href().is_empty()) |
151 | 0 | return ARIA::Role::link; |
152 | | // https://www.w3.org/TR/html-aria/#el-a |
153 | 0 | return ARIA::Role::generic; |
154 | 0 | } |
155 | | |
156 | | // https://html.spec.whatwg.org/multipage/text-level-semantics.html#dom-a-rellist |
157 | | JS::NonnullGCPtr<DOM::DOMTokenList> HTMLAnchorElement::rel_list() |
158 | 0 | { |
159 | | // The IDL attribute relList must reflect the rel content attribute. |
160 | 0 | if (!m_rel_list) |
161 | 0 | m_rel_list = DOM::DOMTokenList::create(*this, HTML::AttributeNames::rel); |
162 | 0 | return *m_rel_list; |
163 | 0 | } |
164 | | |
165 | | // https://html.spec.whatwg.org/multipage/text-level-semantics.html#dom-a-text |
166 | | String HTMLAnchorElement::text() const |
167 | 0 | { |
168 | | // The text attribute's getter must return this element's descendant text content. |
169 | 0 | return descendant_text_content(); |
170 | 0 | } |
171 | | |
172 | | // https://html.spec.whatwg.org/multipage/text-level-semantics.html#dom-a-text |
173 | | void HTMLAnchorElement::set_text(String const& text) |
174 | 0 | { |
175 | | // The text attribute's setter must string replace all with the given value within this element. |
176 | 0 | string_replace_all(text); |
177 | 0 | } |
178 | | |
179 | | } |