script/dom/html/
htmlanchorelement.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 std::cell::Cell;
6use std::default::Default;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name};
10use js::context::JSContext;
11use js::rust::HandleObject;
12use num_traits::ToPrimitive;
13use servo_url::ServoUrl;
14use style::attr::AttrValue;
15use stylo_atoms::Atom;
16use stylo_dom::ElementState;
17
18use crate::dom::activation::Activatable;
19use crate::dom::attr::Attr;
20use crate::dom::bindings::cell::DomRefCell;
21use crate::dom::bindings::codegen::Bindings::HTMLAnchorElementBinding::HTMLAnchorElementMethods;
22use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
23use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
24use crate::dom::bindings::inheritance::Castable;
25use crate::dom::bindings::root::{DomRoot, MutNullableDom};
26use crate::dom::bindings::str::{DOMString, USVString};
27use crate::dom::document::Document;
28use crate::dom::domtokenlist::DOMTokenList;
29use crate::dom::element::{AttributeMutation, Element, reflect_referrer_policy_attribute};
30use crate::dom::event::Event;
31use crate::dom::eventtarget::EventTarget;
32use crate::dom::html::htmlelement::HTMLElement;
33use crate::dom::html::htmlhyperlinkelementutils::{HyperlinkElement, HyperlinkElementTraits};
34use crate::dom::html::htmlimageelement::HTMLImageElement;
35use crate::dom::mouseevent::MouseEvent;
36use crate::dom::node::{BindContext, Node, NodeTraits};
37use crate::dom::virtualmethods::VirtualMethods;
38use crate::links::{LinkRelations, follow_hyperlink};
39use crate::script_runtime::CanGc;
40
41#[dom_struct]
42pub(crate) struct HTMLAnchorElement {
43    htmlelement: HTMLElement,
44    rel_list: MutNullableDom<DOMTokenList>,
45    #[no_trace]
46    relations: Cell<LinkRelations>,
47    #[no_trace]
48    url: DomRefCell<Option<ServoUrl>>,
49}
50
51impl HTMLAnchorElement {
52    fn new_inherited(
53        local_name: LocalName,
54        prefix: Option<Prefix>,
55        document: &Document,
56    ) -> HTMLAnchorElement {
57        HTMLAnchorElement {
58            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
59            rel_list: Default::default(),
60            relations: Cell::new(LinkRelations::empty()),
61            url: DomRefCell::new(None),
62        }
63    }
64
65    pub(crate) fn new(
66        cx: &mut js::context::JSContext,
67        local_name: LocalName,
68        prefix: Option<Prefix>,
69        document: &Document,
70        proto: Option<HandleObject>,
71    ) -> DomRoot<HTMLAnchorElement> {
72        Node::reflect_node_with_proto(
73            cx,
74            Box::new(HTMLAnchorElement::new_inherited(
75                local_name, prefix, document,
76            )),
77            document,
78            proto,
79        )
80    }
81
82    /// Get the full URL of the `href` attribute of this `<a>` element, returning `None` if
83    /// the URL could not be joined with the `Document` URL.
84    pub(crate) fn full_href_url_for_user_interface(&self) -> Option<ServoUrl> {
85        self.upcast::<Element>()
86            .get_attribute(&local_name!("href"))?;
87        self.owner_document().base_url().join(&self.Href()).ok()
88    }
89}
90
91impl HyperlinkElement for HTMLAnchorElement {
92    fn get_url(&self) -> &DomRefCell<Option<ServoUrl>> {
93        &self.url
94    }
95}
96
97impl VirtualMethods for HTMLAnchorElement {
98    fn super_type(&self) -> Option<&dyn VirtualMethods> {
99        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
100    }
101
102    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
103        match name {
104            &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
105            _ => self
106                .super_type()
107                .unwrap()
108                .parse_plain_attribute(name, value),
109        }
110    }
111
112    fn attribute_mutated(
113        &self,
114        cx: &mut js::context::JSContext,
115        attr: &Attr,
116        mutation: AttributeMutation,
117    ) {
118        self.super_type()
119            .unwrap()
120            .attribute_mutated(cx, attr, mutation);
121
122        match *attr.local_name() {
123            local_name!("href") => self
124                .upcast::<Element>()
125                .set_state(ElementState::UNVISITED, !mutation.is_removal()),
126            local_name!("rel") | local_name!("rev") => {
127                self.relations
128                    .set(LinkRelations::for_element(self.upcast()));
129            },
130            _ => {},
131        }
132    }
133
134    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
135        if let Some(s) = self.super_type() {
136            s.bind_to_tree(cx, context);
137        }
138
139        self.relations
140            .set(LinkRelations::for_element(self.upcast()));
141    }
142}
143
144impl HTMLAnchorElementMethods<crate::DomTypeHolder> for HTMLAnchorElement {
145    /// <https://html.spec.whatwg.org/multipage/#dom-a-text>
146    fn Text(&self) -> DOMString {
147        self.upcast::<Node>().GetTextContent().unwrap()
148    }
149
150    /// <https://html.spec.whatwg.org/multipage/#dom-a-text>
151    fn SetText(&self, cx: &mut JSContext, value: DOMString) {
152        self.upcast::<Node>()
153            .set_text_content_for_element(cx, Some(value))
154    }
155
156    // https://html.spec.whatwg.org/multipage/#dom-a-rel
157    make_getter!(Rel, "rel");
158
159    /// <https://html.spec.whatwg.org/multipage/#dom-a-rel>
160    fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
161        self.upcast::<Element>()
162            .set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
163    }
164
165    /// <https://html.spec.whatwg.org/multipage/#dom-a-rellist>
166    fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
167        self.rel_list.or_init(|| {
168            DOMTokenList::new(
169                self.upcast(),
170                &local_name!("rel"),
171                Some(vec![
172                    Atom::from("noopener"),
173                    Atom::from("noreferrer"),
174                    Atom::from("opener"),
175                ]),
176                can_gc,
177            )
178        })
179    }
180
181    // https://html.spec.whatwg.org/multipage/#dom-a-hreflang
182    make_getter!(Hreflang, "hreflang");
183
184    // https://html.spec.whatwg.org/multipage/#dom-a-hreflang
185    make_setter!(SetHreflang, "hreflang");
186
187    // https://html.spec.whatwg.org/multipage/#dom-a-type
188    make_getter!(Type, "type");
189
190    // https://html.spec.whatwg.org/multipage/#dom-a-type
191    make_setter!(SetType, "type");
192
193    // https://html.spec.whatwg.org/multipage/#dom-a-coords
194    make_getter!(Coords, "coords");
195
196    // https://html.spec.whatwg.org/multipage/#dom-a-coords
197    make_setter!(SetCoords, "coords");
198
199    // https://html.spec.whatwg.org/multipage/#dom-a-charset
200    make_getter!(Charset, "charset");
201
202    // https://html.spec.whatwg.org/multipage/#dom-a-charset
203    make_setter!(SetCharset, "charset");
204
205    // https://html.spec.whatwg.org/multipage/#dom-a-name
206    make_getter!(Name, "name");
207
208    // https://html.spec.whatwg.org/multipage/#dom-a-name
209    make_atomic_setter!(SetName, "name");
210
211    // https://html.spec.whatwg.org/multipage/#dom-a-rev
212    make_getter!(Rev, "rev");
213
214    // https://html.spec.whatwg.org/multipage/#dom-a-rev
215    make_setter!(SetRev, "rev");
216
217    // https://html.spec.whatwg.org/multipage/#dom-a-shape
218    make_getter!(Shape, "shape");
219
220    // https://html.spec.whatwg.org/multipage/#dom-a-shape
221    make_setter!(SetShape, "shape");
222
223    // https://html.spec.whatwg.org/multipage/#attr-hyperlink-target
224    make_getter!(Target, "target");
225
226    // https://html.spec.whatwg.org/multipage/#attr-hyperlink-target
227    make_setter!(SetTarget, "target");
228
229    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href>
230    fn Href(&self) -> USVString {
231        self.get_href()
232    }
233
234    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href>
235    fn SetHref(&self, value: USVString, can_gc: CanGc) {
236        self.set_href(value, can_gc);
237    }
238
239    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-origin>
240    fn Origin(&self) -> USVString {
241        self.get_origin()
242    }
243
244    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
245    fn Protocol(&self) -> USVString {
246        self.get_protocol()
247    }
248
249    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
250    fn SetProtocol(&self, value: USVString, can_gc: CanGc) {
251        self.set_protocol(value, can_gc);
252    }
253
254    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
255    fn Password(&self) -> USVString {
256        self.get_password()
257    }
258
259    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
260    fn SetPassword(&self, value: USVString, can_gc: CanGc) {
261        self.set_password(value, can_gc);
262    }
263
264    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
265    fn Hash(&self) -> USVString {
266        self.get_hash()
267    }
268
269    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
270    fn SetHash(&self, value: USVString, can_gc: CanGc) {
271        self.set_hash(value, can_gc);
272    }
273
274    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
275    fn Host(&self) -> USVString {
276        self.get_host()
277    }
278
279    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
280    fn SetHost(&self, value: USVString, can_gc: CanGc) {
281        self.set_host(value, can_gc);
282    }
283
284    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
285    fn Hostname(&self) -> USVString {
286        self.get_hostname()
287    }
288
289    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
290    fn SetHostname(&self, value: USVString, can_gc: CanGc) {
291        self.set_hostname(value, can_gc);
292    }
293
294    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
295    fn Port(&self) -> USVString {
296        self.get_port()
297    }
298
299    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
300    fn SetPort(&self, value: USVString, can_gc: CanGc) {
301        self.set_port(value, can_gc);
302    }
303
304    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
305    fn Pathname(&self) -> USVString {
306        self.get_pathname()
307    }
308
309    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
310    fn SetPathname(&self, value: USVString, can_gc: CanGc) {
311        self.set_pathname(value, can_gc);
312    }
313
314    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
315    fn Search(&self) -> USVString {
316        self.get_search()
317    }
318
319    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
320    fn SetSearch(&self, value: USVString, can_gc: CanGc) {
321        self.set_search(value, can_gc);
322    }
323
324    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
325    fn Username(&self) -> USVString {
326        self.get_username()
327    }
328
329    /// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
330    fn SetUsername(&self, value: USVString, can_gc: CanGc) {
331        self.set_username(value, can_gc);
332    }
333
334    /// <https://html.spec.whatwg.org/multipage/#dom-a-referrerpolicy>
335    fn ReferrerPolicy(&self) -> DOMString {
336        reflect_referrer_policy_attribute(self.upcast::<Element>())
337    }
338
339    // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy
340    make_setter!(SetReferrerPolicy, "referrerpolicy");
341}
342
343impl Activatable for HTMLAnchorElement {
344    fn as_element(&self) -> &Element {
345        self.upcast::<Element>()
346    }
347
348    fn is_instance_activatable(&self) -> bool {
349        // https://html.spec.whatwg.org/multipage/#hyperlink
350        // "a [...] element[s] with an href attribute [...] must [..] create a
351        // hyperlink"
352        // https://html.spec.whatwg.org/multipage/#the-a-element
353        // "The activation behaviour of a elements *that create hyperlinks*"
354        self.as_element().has_attribute(&local_name!("href"))
355    }
356
357    /// <https://html.spec.whatwg.org/multipage/#the-a-element:activation-behaviour>
358    fn activation_behavior(&self, event: &Event, target: &EventTarget, _: CanGc) {
359        let element = self.as_element();
360        let mouse_event = event.downcast::<MouseEvent>().unwrap();
361        let mut ismap_suffix = None;
362
363        // Step 1: If the target of the click event is an img element with an ismap attribute
364        // specified, then server-side image map processing must be performed.
365        if let Some(element) = target.downcast::<Element>() {
366            if target.is::<HTMLImageElement>() && element.has_attribute(&local_name!("ismap")) {
367                let target_node = element.upcast::<Node>();
368                let rect = target_node.border_box().unwrap_or_default();
369                ismap_suffix = Some(format!(
370                    "?{},{}",
371                    mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(),
372                    mouse_event.ClientY().to_f32().unwrap() - rect.origin.y.to_f32_px()
373                ))
374            }
375        }
376
377        // Step 2.
378        // TODO: Download the link is `download` attribute is set.
379        follow_hyperlink(element, self.relations.get(), ismap_suffix);
380    }
381}