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