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::rust::HandleObject;
14use servo_url::ServoUrl;
15use style::attr::AttrValue;
16use stylo_atoms::Atom;
17use stylo_dom::ElementState;
18
19use crate::dom::activation::Activatable;
20use crate::dom::attr::Attr;
21use crate::dom::bindings::cell::DomRefCell;
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::{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::node::{BindContext, Node};
34use crate::dom::virtualmethods::VirtualMethods;
35use crate::links::{LinkRelations, follow_hyperlink};
36use crate::script_runtime::CanGc;
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 local_name: LocalName,
291 prefix: Option<Prefix>,
292 document: &Document,
293 proto: Option<HandleObject>,
294 can_gc: CanGc,
295 ) -> DomRoot<HTMLAreaElement> {
296 Node::reflect_node_with_proto(
297 Box::new(HTMLAreaElement::new_inherited(local_name, prefix, document)),
298 document,
299 proto,
300 can_gc,
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(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
347 self.super_type()
348 .unwrap()
349 .attribute_mutated(attr, mutation, can_gc);
350
351 match *attr.local_name() {
352 local_name!("href") => self
353 .upcast::<Element>()
354 .set_state(ElementState::UNVISITED, !mutation.is_removal()),
355 local_name!("rel") | local_name!("rev") => {
356 self.relations
357 .set(LinkRelations::for_element(self.upcast()));
358 },
359 _ => {},
360 }
361 }
362
363 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
364 if let Some(s) = self.super_type() {
365 s.bind_to_tree(context, can_gc);
366 }
367
368 self.relations
369 .set(LinkRelations::for_element(self.upcast()));
370 }
371}
372
373impl HTMLAreaElementMethods<crate::DomTypeHolder> for HTMLAreaElement {
374 make_getter!(Target, "target");
376
377 make_setter!(SetTarget, "target");
379
380 make_getter!(Rel, "rel");
382
383 fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
385 self.upcast::<Element>()
386 .set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
387 }
388
389 fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
391 self.rel_list.or_init(|| {
392 DOMTokenList::new(
393 self.upcast(),
394 &local_name!("rel"),
395 Some(vec![
396 Atom::from("noopener"),
397 Atom::from("noreferrer"),
398 Atom::from("opener"),
399 ]),
400 can_gc,
401 )
402 })
403 }
404
405 fn ReferrerPolicy(&self) -> DOMString {
407 reflect_referrer_policy_attribute(self.upcast::<Element>())
408 }
409
410 make_setter!(SetReferrerPolicy, "referrerpolicy");
412
413 fn Href(&self) -> USVString {
415 self.get_href()
416 }
417
418 fn SetHref(&self, value: USVString, can_gc: CanGc) {
420 self.set_href(value, can_gc);
421 }
422
423 fn Origin(&self) -> USVString {
425 self.get_origin()
426 }
427
428 fn Protocol(&self) -> USVString {
430 self.get_protocol()
431 }
432
433 fn SetProtocol(&self, value: USVString, can_gc: CanGc) {
435 self.set_protocol(value, can_gc);
436 }
437
438 fn Password(&self) -> USVString {
440 self.get_password()
441 }
442
443 fn SetPassword(&self, value: USVString, can_gc: CanGc) {
445 self.set_password(value, can_gc);
446 }
447
448 fn Hash(&self) -> USVString {
450 self.get_hash()
451 }
452
453 fn SetHash(&self, value: USVString, can_gc: CanGc) {
455 self.set_hash(value, can_gc);
456 }
457
458 fn Host(&self) -> USVString {
460 self.get_host()
461 }
462
463 fn SetHost(&self, value: USVString, can_gc: CanGc) {
465 self.set_host(value, can_gc);
466 }
467
468 fn Hostname(&self) -> USVString {
470 self.get_hostname()
471 }
472
473 fn SetHostname(&self, value: USVString, can_gc: CanGc) {
475 self.set_hostname(value, can_gc);
476 }
477
478 fn Port(&self) -> USVString {
480 self.get_port()
481 }
482
483 fn SetPort(&self, value: USVString, can_gc: CanGc) {
485 self.set_port(value, can_gc);
486 }
487
488 fn Pathname(&self) -> USVString {
490 self.get_pathname()
491 }
492
493 fn SetPathname(&self, value: USVString, can_gc: CanGc) {
495 self.set_pathname(value, can_gc);
496 }
497
498 fn Search(&self) -> USVString {
500 self.get_search()
501 }
502
503 fn SetSearch(&self, value: USVString, can_gc: CanGc) {
505 self.set_search(value, can_gc);
506 }
507
508 fn Username(&self) -> USVString {
510 self.get_username()
511 }
512
513 fn SetUsername(&self, value: USVString, can_gc: CanGc) {
515 self.set_username(value, can_gc);
516 }
517}
518
519impl Activatable for HTMLAreaElement {
520 fn as_element(&self) -> &Element {
522 self.upcast::<Element>()
523 }
524
525 fn is_instance_activatable(&self) -> bool {
526 self.as_element().has_attribute(&local_name!("href"))
527 }
528
529 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
530 follow_hyperlink(self.as_element(), self.relations.get(), None);
531 }
532}