1use std::cell::Cell;
6use std::default::Default;
7use std::{f32, str};
8
9use cssparser::match_ignore_ascii_case;
10use dom_struct::dom_struct;
11use euclid::default::Point2D;
12use html5ever::{LocalName, Prefix, local_name};
13use js::context::JSContext;
14use js::rust::HandleObject;
15use script_bindings::cell::DomRefCell;
16use servo_url::ServoUrl;
17use style::attr::AttrValue;
18use stylo_atoms::Atom;
19use stylo_dom::ElementState;
20
21use crate::dom::activation::Activatable;
22use crate::dom::bindings::codegen::Bindings::HTMLAreaElementBinding::HTMLAreaElementMethods;
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::attributes::storage::AttrRef;
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::node::Node;
35use crate::dom::virtualmethods::VirtualMethods;
36use crate::links::{LinkRelations, follow_hyperlink};
37
38#[derive(Debug, PartialEq)]
39pub enum Area {
40 Circle {
41 left: f32,
42 top: f32,
43 radius: f32,
44 },
45 Rectangle {
46 top_left: (f32, f32),
47 bottom_right: (f32, f32),
48 },
49 Polygon {
50 points: Vec<f32>,
53 },
54}
55
56pub enum Shape {
57 Circle,
58 Rectangle,
59 Polygon,
60}
61
62impl Area {
65 pub fn parse(coord: &str, target: Shape) -> Option<Area> {
66 let points_count = match target {
67 Shape::Circle => 3,
68 Shape::Rectangle => 4,
69 Shape::Polygon => 0,
70 };
71
72 let size = coord.len();
73 let num = coord.as_bytes();
74 let mut index = 0;
75
76 while index < size {
78 let val = num[index];
79 match val {
80 b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => {},
81 _ => break,
82 }
83
84 index += 1;
85 }
86
87 let mut number_list = Vec::new();
89 let mut array = Vec::new();
90
91 while index < size {
93 while index < size {
95 let val = num[index];
96 match val {
97 b'0'..=b'9' | b'.' | b'-' | b'E' | b'e' => break,
98 _ => {},
99 }
100
101 index += 1;
102 }
103
104 while index < size {
106 let val = num[index];
107
108 match val {
109 b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => break,
110 _ => array.push(val),
111 }
112
113 index += 1;
114 }
115
116 if array.is_empty() {
118 break;
119 }
120
121 match str::from_utf8(&array)
123 .ok()
124 .and_then(|s| s.parse::<f32>().ok())
125 {
126 Some(v) => number_list.push(v),
127 None => number_list.push(0.0),
128 };
129
130 array.clear();
131
132 if points_count > 0 && number_list.len() == points_count {
135 break;
136 }
137 }
138
139 let final_size = number_list.len();
140
141 match target {
142 Shape::Circle => {
143 if final_size == 3 {
144 if number_list[2] <= 0.0 {
145 None
146 } else {
147 Some(Area::Circle {
148 left: number_list[0],
149 top: number_list[1],
150 radius: number_list[2],
151 })
152 }
153 } else {
154 None
155 }
156 },
157
158 Shape::Rectangle => {
159 if final_size == 4 {
160 if number_list[0] > number_list[2] {
161 number_list.swap(0, 2);
162 }
163
164 if number_list[1] > number_list[3] {
165 number_list.swap(1, 3);
166 }
167
168 Some(Area::Rectangle {
169 top_left: (number_list[0], number_list[1]),
170 bottom_right: (number_list[2], number_list[3]),
171 })
172 } else {
173 None
174 }
175 },
176
177 Shape::Polygon => {
178 if final_size >= 6 {
179 if final_size % 2 != 0 {
180 number_list.remove(final_size - 1);
182 }
183 Some(Area::Polygon {
184 points: number_list,
185 })
186 } else {
187 None
188 }
189 },
190 }
191 }
192
193 pub fn hit_test(&self, p: &Point2D<f32>) -> bool {
194 match *self {
195 Area::Circle { left, top, radius } => {
196 (p.x - left) * (p.x - left) + (p.y - top) * (p.y - top) - radius * radius <= 0.0
197 },
198
199 Area::Rectangle {
200 top_left,
201 bottom_right,
202 } => {
203 p.x <= bottom_right.0 &&
204 p.x >= top_left.0 &&
205 p.y <= bottom_right.1 &&
206 p.y >= top_left.1
207 },
208
209 Area::Polygon { ref points } => {
210 let mut inside = false;
213
214 debug_assert!(points.len() % 2 == 0);
215 let vertices = points.len() / 2;
216
217 for i in 0..vertices {
218 let next_i = if i + 1 == vertices { 0 } else { i + 1 };
219
220 let xi = points[2 * i];
221 let yi = points[2 * i + 1];
222 let xj = points[2 * next_i];
223 let yj = points[2 * next_i + 1];
224
225 if (yi > p.y) != (yj > p.y) && p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi {
226 inside = !inside;
227 }
228 }
229 inside
230 },
231 }
232 }
233
234 pub(crate) fn absolute_coords(&self, p: Point2D<f32>) -> Area {
235 match *self {
236 Area::Rectangle {
237 top_left,
238 bottom_right,
239 } => Area::Rectangle {
240 top_left: (top_left.0 + p.x, top_left.1 + p.y),
241 bottom_right: (bottom_right.0 + p.x, bottom_right.1 + p.y),
242 },
243 Area::Circle { left, top, radius } => Area::Circle {
244 left: (left + p.x),
245 top: (top + p.y),
246 radius,
247 },
248 Area::Polygon { ref points } => {
249 let iter = points
251 .iter()
252 .enumerate()
253 .map(|(index, point)| match index % 2 {
254 0 => point + p.x,
255 _ => point + p.y,
256 });
257 Area::Polygon {
258 points: iter.collect::<Vec<_>>(),
259 }
260 },
261 }
262 }
263}
264
265#[dom_struct]
266pub(crate) struct HTMLAreaElement {
267 htmlelement: HTMLElement,
268 rel_list: MutNullableDom<DOMTokenList>,
269 #[no_trace]
270 relations: Cell<LinkRelations>,
271 #[no_trace]
272 url: DomRefCell<Option<ServoUrl>>,
273}
274
275impl HTMLAreaElement {
276 fn new_inherited(
277 local_name: LocalName,
278 prefix: Option<Prefix>,
279 document: &Document,
280 ) -> HTMLAreaElement {
281 HTMLAreaElement {
282 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
283 rel_list: Default::default(),
284 relations: Cell::new(LinkRelations::empty()),
285 url: DomRefCell::new(None),
286 }
287 }
288
289 pub(crate) fn new(
290 cx: &mut js::context::JSContext,
291 local_name: LocalName,
292 prefix: Option<Prefix>,
293 document: &Document,
294 proto: Option<HandleObject>,
295 ) -> DomRoot<HTMLAreaElement> {
296 Node::reflect_node_with_proto(
297 cx,
298 Box::new(HTMLAreaElement::new_inherited(local_name, prefix, document)),
299 document,
300 proto,
301 )
302 }
303
304 pub(crate) fn get_shape_from_coords(&self) -> Option<Area> {
305 let elem = self.upcast::<Element>();
306 let shape = elem.get_string_attribute(&"shape".into());
307 let shp: Shape = match_ignore_ascii_case! { &*shape.str(),
308 "circle" => Shape::Circle,
309 "circ" => Shape::Circle,
310 "rectangle" => Shape::Rectangle,
311 "rect" => Shape::Rectangle,
312 "polygon" => Shape::Rectangle,
313 "poly" => Shape::Polygon,
314 _ => return None,
315 };
316 if elem.has_attribute(&"coords".into()) {
317 let attribute = elem.get_string_attribute(&"coords".into());
318 Area::parse(&attribute.str(), shp)
319 } else {
320 None
321 }
322 }
323}
324
325impl HyperlinkElement for HTMLAreaElement {
326 fn get_url(&self) -> &DomRefCell<Option<ServoUrl>> {
327 &self.url
328 }
329}
330
331impl VirtualMethods for HTMLAreaElement {
332 fn super_type(&self) -> Option<&dyn VirtualMethods> {
333 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
334 }
335
336 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
337 match name {
338 &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
339 _ => self
340 .super_type()
341 .unwrap()
342 .parse_plain_attribute(name, value),
343 }
344 }
345
346 fn attribute_mutated(
347 &self,
348 cx: &mut js::context::JSContext,
349 attr: AttrRef<'_>,
350 mutation: AttributeMutation,
351 ) {
352 self.super_type()
353 .unwrap()
354 .attribute_mutated(cx, attr, mutation);
355
356 match *attr.local_name() {
357 local_name!("href") => self
358 .upcast::<Element>()
359 .set_state(ElementState::UNVISITED, !mutation.is_removal()),
360 local_name!("rel") | local_name!("rev") => {
361 self.relations
362 .set(LinkRelations::for_element(self.upcast()));
363 },
364 _ => {},
365 }
366 }
367}
368
369impl HTMLAreaElementMethods<crate::DomTypeHolder> for HTMLAreaElement {
370 make_getter!(Target, "target");
372
373 make_setter!(cx, SetTarget, "target");
375
376 make_getter!(Rel, "rel");
378
379 fn SetRel(&self, cx: &mut JSContext, rel: DOMString) {
381 self.upcast::<Element>()
382 .set_tokenlist_attribute(cx, &local_name!("rel"), rel);
383 }
384
385 fn RelList(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
387 self.rel_list.or_init(|| {
388 DOMTokenList::new(
389 cx,
390 self.upcast(),
391 &local_name!("rel"),
392 Some(vec![
393 Atom::from("noopener"),
394 Atom::from("noreferrer"),
395 Atom::from("opener"),
396 ]),
397 )
398 })
399 }
400
401 fn ReferrerPolicy(&self) -> DOMString {
403 reflect_referrer_policy_attribute(self.upcast::<Element>())
404 }
405
406 make_setter!(cx, SetReferrerPolicy, "referrerpolicy");
408
409 fn Href(&self) -> USVString {
411 self.get_href()
412 }
413
414 fn SetHref(&self, cx: &mut JSContext, value: USVString) {
416 self.set_href(cx, value);
417 }
418
419 fn Origin(&self) -> USVString {
421 self.get_origin()
422 }
423
424 fn Protocol(&self) -> USVString {
426 self.get_protocol()
427 }
428
429 fn SetProtocol(&self, cx: &mut JSContext, value: USVString) {
431 self.set_protocol(cx, value);
432 }
433
434 fn Password(&self) -> USVString {
436 self.get_password()
437 }
438
439 fn SetPassword(&self, cx: &mut JSContext, value: USVString) {
441 self.set_password(cx, value);
442 }
443
444 fn Hash(&self) -> USVString {
446 self.get_hash()
447 }
448
449 fn SetHash(&self, cx: &mut JSContext, value: USVString) {
451 self.set_hash(cx, value);
452 }
453
454 fn Host(&self) -> USVString {
456 self.get_host()
457 }
458
459 fn SetHost(&self, cx: &mut JSContext, value: USVString) {
461 self.set_host(cx, value);
462 }
463
464 fn Hostname(&self) -> USVString {
466 self.get_hostname()
467 }
468
469 fn SetHostname(&self, cx: &mut JSContext, value: USVString) {
471 self.set_hostname(cx, value);
472 }
473
474 fn Port(&self) -> USVString {
476 self.get_port()
477 }
478
479 fn SetPort(&self, cx: &mut JSContext, value: USVString) {
481 self.set_port(cx, value);
482 }
483
484 fn Pathname(&self) -> USVString {
486 self.get_pathname()
487 }
488
489 fn SetPathname(&self, cx: &mut JSContext, value: USVString) {
491 self.set_pathname(cx, value);
492 }
493
494 fn Search(&self) -> USVString {
496 self.get_search()
497 }
498
499 fn SetSearch(&self, cx: &mut JSContext, value: USVString) {
501 self.set_search(cx, value);
502 }
503
504 fn Username(&self) -> USVString {
506 self.get_username()
507 }
508
509 fn SetUsername(&self, cx: &mut JSContext, value: USVString) {
511 self.set_username(cx, value);
512 }
513
514 make_bool_getter!(NoHref, "nohref");
516
517 make_bool_setter!(cx, SetNoHref, "nohref");
519}
520
521impl Activatable for HTMLAreaElement {
522 fn as_element(&self) -> &Element {
524 self.upcast::<Element>()
525 }
526
527 fn is_instance_activatable(&self) -> bool {
528 self.as_element().has_attribute(&local_name!("href"))
529 }
530
531 fn activation_behavior(
532 &self,
533 cx: &mut js::context::JSContext,
534 _event: &Event,
535 _target: &EventTarget,
536 ) {
537 follow_hyperlink(cx, self.as_element(), self.relations.get(), None);
538 }
539}