Skip to main content

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