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