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