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