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;
17
18use crate::dom::activation::Activatable;
19use crate::dom::attr::Attr;
20use crate::dom::bindings::cell::DomRefCell;
21use crate::dom::bindings::codegen::Bindings::HTMLAreaElementBinding::HTMLAreaElementMethods;
22use crate::dom::bindings::inheritance::Castable;
23use crate::dom::bindings::root::{DomRoot, MutNullableDom};
24use crate::dom::bindings::str::{DOMString, USVString};
25use crate::dom::document::Document;
26use crate::dom::domtokenlist::DOMTokenList;
27use crate::dom::element::{AttributeMutation, Element, reflect_referrer_policy_attribute};
28use crate::dom::event::Event;
29use crate::dom::eventtarget::EventTarget;
30use crate::dom::html::htmlelement::HTMLElement;
31use crate::dom::html::htmlhyperlinkelementutils::{HyperlinkElement, HyperlinkElementTraits};
32use crate::dom::node::{BindContext, Node};
33use crate::dom::virtualmethods::VirtualMethods;
34use crate::links::{LinkRelations, follow_hyperlink};
35use crate::script_runtime::CanGc;
36
37#[derive(Debug, PartialEq)]
38pub enum Area {
39 Circle {
40 left: f32,
41 top: f32,
42 radius: f32,
43 },
44 Rectangle {
45 top_left: (f32, f32),
46 bottom_right: (f32, f32),
47 },
48 Polygon {
49 points: Vec<f32>,
52 },
53}
54
55pub enum Shape {
56 Circle,
57 Rectangle,
58 Polygon,
59}
60
61impl Area {
64 pub fn parse(coord: &str, target: Shape) -> Option<Area> {
65 let points_count = match target {
66 Shape::Circle => 3,
67 Shape::Rectangle => 4,
68 Shape::Polygon => 0,
69 };
70
71 let size = coord.len();
72 let num = coord.as_bytes();
73 let mut index = 0;
74
75 while index < size {
77 let val = num[index];
78 match val {
79 b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => {},
80 _ => break,
81 }
82
83 index += 1;
84 }
85
86 let mut number_list = Vec::new();
88 let mut array = Vec::new();
89
90 while index < size {
92 while index < size {
94 let val = num[index];
95 match val {
96 b'0'..=b'9' | b'.' | b'-' | b'E' | b'e' => break,
97 _ => {},
98 }
99
100 index += 1;
101 }
102
103 while index < size {
105 let val = num[index];
106
107 match val {
108 b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => break,
109 _ => array.push(val),
110 }
111
112 index += 1;
113 }
114
115 if array.is_empty() {
117 break;
118 }
119
120 match str::from_utf8(&array)
122 .ok()
123 .and_then(|s| s.parse::<f32>().ok())
124 {
125 Some(v) => number_list.push(v),
126 None => number_list.push(0.0),
127 };
128
129 array.clear();
130
131 if points_count > 0 && number_list.len() == points_count {
134 break;
135 }
136 }
137
138 let final_size = number_list.len();
139
140 match target {
141 Shape::Circle => {
142 if final_size == 3 {
143 if number_list[2] <= 0.0 {
144 None
145 } else {
146 Some(Area::Circle {
147 left: number_list[0],
148 top: number_list[1],
149 radius: number_list[2],
150 })
151 }
152 } else {
153 None
154 }
155 },
156
157 Shape::Rectangle => {
158 if final_size == 4 {
159 if number_list[0] > number_list[2] {
160 number_list.swap(0, 2);
161 }
162
163 if number_list[1] > number_list[3] {
164 number_list.swap(1, 3);
165 }
166
167 Some(Area::Rectangle {
168 top_left: (number_list[0], number_list[1]),
169 bottom_right: (number_list[2], number_list[3]),
170 })
171 } else {
172 None
173 }
174 },
175
176 Shape::Polygon => {
177 if final_size >= 6 {
178 if final_size % 2 != 0 {
179 number_list.remove(final_size - 1);
181 }
182 Some(Area::Polygon {
183 points: number_list,
184 })
185 } else {
186 None
187 }
188 },
189 }
190 }
191
192 pub fn hit_test(&self, p: &Point2D<f32>) -> bool {
193 match *self {
194 Area::Circle { left, top, radius } => {
195 (p.x - left) * (p.x - left) + (p.y - top) * (p.y - top) - radius * radius <= 0.0
196 },
197
198 Area::Rectangle {
199 top_left,
200 bottom_right,
201 } => {
202 p.x <= bottom_right.0 &&
203 p.x >= top_left.0 &&
204 p.y <= bottom_right.1 &&
205 p.y >= top_left.1
206 },
207
208 Area::Polygon { ref points } => {
209 let mut inside = false;
212
213 debug_assert!(points.len() % 2 == 0);
214 let vertices = points.len() / 2;
215
216 for i in 0..vertices {
217 let next_i = if i + 1 == vertices { 0 } else { i + 1 };
218
219 let xi = points[2 * i];
220 let yi = points[2 * i + 1];
221 let xj = points[2 * next_i];
222 let yj = points[2 * next_i + 1];
223
224 if (yi > p.y) != (yj > p.y) && p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi {
225 inside = !inside;
226 }
227 }
228 inside
229 },
230 }
231 }
232
233 pub(crate) fn absolute_coords(&self, p: Point2D<f32>) -> Area {
234 match *self {
235 Area::Rectangle {
236 top_left,
237 bottom_right,
238 } => Area::Rectangle {
239 top_left: (top_left.0 + p.x, top_left.1 + p.y),
240 bottom_right: (bottom_right.0 + p.x, bottom_right.1 + p.y),
241 },
242 Area::Circle { left, top, radius } => Area::Circle {
243 left: (left + p.x),
244 top: (top + p.y),
245 radius,
246 },
247 Area::Polygon { ref points } => {
248 let iter = points
250 .iter()
251 .enumerate()
252 .map(|(index, point)| match index % 2 {
253 0 => point + p.x,
254 _ => point + p.y,
255 });
256 Area::Polygon {
257 points: iter.collect::<Vec<_>>(),
258 }
259 },
260 }
261 }
262}
263
264#[dom_struct]
265pub(crate) struct HTMLAreaElement {
266 htmlelement: HTMLElement,
267 rel_list: MutNullableDom<DOMTokenList>,
268 #[no_trace]
269 relations: Cell<LinkRelations>,
270 #[no_trace]
271 url: DomRefCell<Option<ServoUrl>>,
272}
273
274impl HTMLAreaElement {
275 fn new_inherited(
276 local_name: LocalName,
277 prefix: Option<Prefix>,
278 document: &Document,
279 ) -> HTMLAreaElement {
280 HTMLAreaElement {
281 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
282 rel_list: Default::default(),
283 relations: Cell::new(LinkRelations::empty()),
284 url: DomRefCell::new(None),
285 }
286 }
287
288 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
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,
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, 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!("rel") | local_name!("rev") => {
353 self.relations
354 .set(LinkRelations::for_element(self.upcast()));
355 },
356 _ => {},
357 }
358 }
359
360 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
361 if let Some(s) = self.super_type() {
362 s.bind_to_tree(context, can_gc);
363 }
364
365 self.relations
366 .set(LinkRelations::for_element(self.upcast()));
367 }
368}
369
370impl HTMLAreaElementMethods<crate::DomTypeHolder> for HTMLAreaElement {
371 make_getter!(Target, "target");
373
374 make_setter!(SetTarget, "target");
376
377 make_getter!(Rel, "rel");
379
380 fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
382 self.upcast::<Element>()
383 .set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
384 }
385
386 fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
388 self.rel_list.or_init(|| {
389 DOMTokenList::new(
390 self.upcast(),
391 &local_name!("rel"),
392 Some(vec![
393 Atom::from("noopener"),
394 Atom::from("noreferrer"),
395 Atom::from("opener"),
396 ]),
397 can_gc,
398 )
399 })
400 }
401
402 fn ReferrerPolicy(&self) -> DOMString {
404 reflect_referrer_policy_attribute(self.upcast::<Element>())
405 }
406
407 make_setter!(SetReferrerPolicy, "referrerpolicy");
409
410 fn Href(&self) -> USVString {
412 self.get_href()
413 }
414
415 fn SetHref(&self, value: USVString, can_gc: CanGc) {
417 self.set_href(value, can_gc);
418 }
419
420 fn Origin(&self) -> USVString {
422 self.get_origin()
423 }
424
425 fn Protocol(&self) -> USVString {
427 self.get_protocol()
428 }
429
430 fn SetProtocol(&self, value: USVString, can_gc: CanGc) {
432 self.set_protocol(value, can_gc);
433 }
434
435 fn Password(&self) -> USVString {
437 self.get_password()
438 }
439
440 fn SetPassword(&self, value: USVString, can_gc: CanGc) {
442 self.set_password(value, can_gc);
443 }
444
445 fn Hash(&self) -> USVString {
447 self.get_hash()
448 }
449
450 fn SetHash(&self, value: USVString, can_gc: CanGc) {
452 self.set_hash(value, can_gc);
453 }
454
455 fn Host(&self) -> USVString {
457 self.get_host()
458 }
459
460 fn SetHost(&self, value: USVString, can_gc: CanGc) {
462 self.set_host(value, can_gc);
463 }
464
465 fn Hostname(&self) -> USVString {
467 self.get_hostname()
468 }
469
470 fn SetHostname(&self, value: USVString, can_gc: CanGc) {
472 self.set_hostname(value, can_gc);
473 }
474
475 fn Port(&self) -> USVString {
477 self.get_port()
478 }
479
480 fn SetPort(&self, value: USVString, can_gc: CanGc) {
482 self.set_port(value, can_gc);
483 }
484
485 fn Pathname(&self) -> USVString {
487 self.get_pathname()
488 }
489
490 fn SetPathname(&self, value: USVString, can_gc: CanGc) {
492 self.set_pathname(value, can_gc);
493 }
494
495 fn Search(&self) -> USVString {
497 self.get_search()
498 }
499
500 fn SetSearch(&self, value: USVString, can_gc: CanGc) {
502 self.set_search(value, can_gc);
503 }
504
505 fn Username(&self) -> USVString {
507 self.get_username()
508 }
509
510 fn SetUsername(&self, value: USVString, can_gc: CanGc) {
512 self.set_username(value, can_gc);
513 }
514}
515
516impl Activatable for HTMLAreaElement {
517 fn as_element(&self) -> &Element {
519 self.upcast::<Element>()
520 }
521
522 fn is_instance_activatable(&self) -> bool {
523 self.as_element().has_attribute(&local_name!("href"))
524 }
525
526 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
527 follow_hyperlink(self.as_element(), self.relations.get(), None);
528 }
529}