servo/components/script/dom/htmlareaelement.rs
Simon Sapin aa15dc269f Remove use of unstable box syntax.
http://www.robohornet.org gives a score of 101.36 on master,
and 102.68 with this PR. The latter is slightly better,
but probably within noise level.
So it looks like this PR does not affect DOM performance.

This is expected since `Box::new` is defined as:

```rust
impl<T> Box<T> {
    #[inline(always)]
    pub fn new(x: T) -> Box<T> {
        box x
    }
}
```

With inlining, it should compile to the same as box syntax.
2017-10-16 17:16:20 +02:00

318 lines
10 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::activation::Activatable;
use dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
use dom::bindings::codegen::Bindings::HTMLAreaElementBinding;
use dom::bindings::codegen::Bindings::HTMLAreaElementBinding::HTMLAreaElementMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::root::{DomRoot, MutNullableDom};
use dom::bindings::str::DOMString;
use dom::document::Document;
use dom::domtokenlist::DOMTokenList;
use dom::element::Element;
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::htmlanchorelement::follow_hyperlink;
use dom::htmlelement::HTMLElement;
use dom::node::{Node, document_from_node};
use dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
use euclid::Point2D;
use html5ever::{LocalName, Prefix};
use net_traits::ReferrerPolicy;
use std::default::Default;
use std::f32;
use std::str;
use style::attr::AttrValue;
#[derive(PartialEq)]
#[derive(Debug)]
pub enum Area {
Circle { left: f32, top: f32, radius: f32 },
Rectangle { top_left: (f32, f32), bottom_right: (f32, f32) },
Polygon { points: Vec<f32> },
}
pub enum Shape {
Circle,
Rectangle,
Polygon,
}
// https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-list-of-floating-point-numbers
// https://html.spec.whatwg.org/multipage/#image-map-processing-model
impl Area {
pub fn parse(coord: &str, target: Shape) -> Option<Area> {
let points_count = match target {
Shape::Circle => 3,
Shape::Rectangle => 4,
Shape::Polygon => 0,
};
let size = coord.len();
let num = coord.as_bytes();
let mut index = 0;
// Step 4: Walk till char is not a delimiter
while index < size {
let val = num[index];
match val {
b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => {},
_ => break,
}
index += 1;
}
//This vector will hold all parsed coordinates
let mut number_list = Vec::new();
let mut array = Vec::new();
// Step 5: walk till end of string
while index < size {
// Step 5.1: walk till we hit a valid char i.e., 0 to 9, dot or dash, e, E
while index < size {
let val = num[index];
match val {
b'0'...b'9' | b'.' | b'-' | b'E' | b'e' => break,
_ => {},
}
index += 1;
}
// Step 5.2: collect valid symbols till we hit another delimiter
while index < size {
let val = num[index];
match val {
b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => break,
_ => array.push(val),
}
index += 1;
}
// The input does not consist any valid charecters
if array.is_empty() {
break;
}
// Convert String to float
match str::from_utf8(&array).ok().and_then(|s| s.parse::<f32>().ok()) {
Some(v) => number_list.push(v),
None => number_list.push(0.0),
};
array.clear();
// For rectangle and circle, stop parsing once we have three
// and four coordinates respectively
if points_count > 0 && number_list.len() == points_count {
break;
}
}
let final_size = number_list.len();
match target {
Shape::Circle => {
if final_size == 3 {
if number_list[2] <= 0.0 {
None
} else {
Some(Area::Circle {
left: number_list[0],
top: number_list[1],
radius: number_list[2]
})
}
} else {
None
}
},
Shape::Rectangle => {
if final_size == 4 {
if number_list[0] > number_list[2] {
number_list.swap(0, 2);
}
if number_list[1] > number_list[3] {
number_list.swap(1, 3);
}
Some(Area::Rectangle {
top_left: (number_list[0], number_list[1]),
bottom_right: (number_list[2], number_list[3])
})
} else {
None
}
},
Shape::Polygon => {
if final_size >= 6 {
if final_size % 2 != 0 {
// Drop last element if there are odd number of coordinates
number_list.remove(final_size - 1);
}
Some(Area::Polygon { points: number_list })
} else {
None
}
},
}
}
pub fn hit_test(&self, p: &Point2D<f32>) -> bool {
match *self {
Area::Circle { left, top, radius } => {
(p.x - left) * (p.x - left) +
(p.y - top) * (p.y - top) -
radius * radius <= 0.0
},
Area::Rectangle { top_left, bottom_right } => {
p.x <= bottom_right.0 && p.x >= top_left.0 &&
p.y <= bottom_right.1 && p.y >= top_left.1
},
//TODO polygon hit_test
_ => false,
}
}
pub fn absolute_coords(&self, p: Point2D<f32>) -> Area {
match *self {
Area::Rectangle { top_left, bottom_right } => {
Area::Rectangle {
top_left: (top_left.0 + p.x, top_left.1 + p.y),
bottom_right: (bottom_right.0 + p.x, bottom_right.1 + p.y)
}
},
Area::Circle { left, top, radius } => {
Area::Circle {
left: (left + p.x),
top: (top + p.y),
radius: radius
}
},
Area::Polygon { ref points } => {
// let new_points = Vec::new();
let iter = points.iter().enumerate().map(|(index, point)| {
match index % 2 {
0 => point + p.x as f32,
_ => point + p.y as f32,
}
});
Area::Polygon { points: iter.collect::<Vec<_>>() }
},
}
}
}
#[dom_struct]
pub struct HTMLAreaElement {
htmlelement: HTMLElement,
rel_list: MutNullableDom<DOMTokenList>,
}
impl HTMLAreaElement {
fn new_inherited(local_name: LocalName, prefix: Option<Prefix>, document: &Document) -> HTMLAreaElement {
HTMLAreaElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
rel_list: Default::default(),
}
}
#[allow(unrooted_must_root)]
pub fn new(local_name: LocalName,
prefix: Option<Prefix>,
document: &Document) -> DomRoot<HTMLAreaElement> {
Node::reflect_node(Box::new(HTMLAreaElement::new_inherited(local_name, prefix, document)),
document,
HTMLAreaElementBinding::Wrap)
}
pub fn get_shape_from_coords(&self) -> Option<Area> {
let elem = self.upcast::<Element>();
let shape = elem.get_string_attribute(&"shape".into());
let shp: Shape = match_ignore_ascii_case! { &shape,
"circle" => Shape::Circle,
"circ" => Shape::Circle,
"rectangle" => Shape::Rectangle,
"rect" => Shape::Rectangle,
"polygon" => Shape::Rectangle,
"poly" => Shape::Polygon,
_ => return None,
};
if elem.has_attribute(&"coords".into()) {
let attribute = elem.get_string_attribute(&"coords".into());
Area::parse(&attribute, shp)
} else {
None
}
}
}
impl VirtualMethods for HTMLAreaElement {
fn super_type(&self) -> Option<&VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &VirtualMethods)
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match name {
&local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
_ => self.super_type().unwrap().parse_plain_attribute(name, value),
}
}
}
impl HTMLAreaElementMethods for HTMLAreaElement {
// https://html.spec.whatwg.org/multipage/#dom-area-rellist
fn RelList(&self) -> DomRoot<DOMTokenList> {
self.rel_list.or_init(|| {
DOMTokenList::new(self.upcast(), &local_name!("rel"))
})
}
}
impl Activatable for HTMLAreaElement {
// https://html.spec.whatwg.org/multipage/#the-area-element:activation-behaviour
fn as_element(&self) -> &Element {
self.upcast::<Element>()
}
fn is_instance_activatable(&self) -> bool {
self.as_element().has_attribute(&local_name!("href"))
}
fn pre_click_activation(&self) {
}
fn canceled_activation(&self) {
}
fn implicit_submission(&self, _ctrl_key: bool, _shift_key: bool,
_alt_key: bool, _meta_key: bool) {
}
fn activation_behavior(&self, _event: &Event, _target: &EventTarget) {
// Step 1
let doc = document_from_node(self);
if !doc.is_fully_active() {
return;
}
// Step 2
// TODO : We should be choosing a browsing context and navigating to it.
// Step 3
let referrer_policy = match self.RelList().Contains("noreferrer".into()) {
true => Some(ReferrerPolicy::NoReferrer),
false => None,
};
follow_hyperlink(self.upcast::<Element>(), None, referrer_policy);
}
}