diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2017-01-11 12:25:46 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-11 12:25:46 -0800 |
commit | f6940f686cce249626e4fe32cc0fe5e0dcd40dd6 (patch) | |
tree | 3f63cd2d96fe9b60d1788eb6c30a0bf08ca42826 /components/script | |
parent | 5d3847dddc9bb7907abfa5d38a7927d6c656fbc1 (diff) | |
parent | d3be70b4de755ead9e6fef6995c8085e0763b88a (diff) | |
download | servo-f6940f686cce249626e4fe32cc0fe5e0dcd40dd6.tar.gz servo-f6940f686cce249626e4fe32cc0fe5e0dcd40dd6.zip |
Auto merge of #13972 - shravan-achar:master, r=Manishearth,emilio,jdm
ImageMaps: Implemented support for parsing coord attribute to a shape…
<!-- Please describe your changes on the following line: -->
Image Maps: (Part 1) Implemented support for parsing coord attribute to a shape object.
Implemented a hit_test method to see if a point is within the area. Tests for constructors and hit_test included
---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [ ] `./mach build -d` does not report any errors
- [ ] `./mach test-tidy` does not report any errors
- [ ] These changes fix #__ (github issue number if applicable).
<!-- Either: -->
- [ ] There are tests for these changes OR
- [ ] These changes do not require tests because _____
<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
… object in HTMLAreaElement
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/13972)
<!-- Reviewable:end -->
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/htmlanchorelement.rs | 2 | ||||
-rw-r--r-- | components/script/dom/htmlareaelement.rs | 256 | ||||
-rw-r--r-- | components/script/dom/htmlimageelement.rs | 79 | ||||
-rw-r--r-- | components/script/test.rs | 4 |
4 files changed, 339 insertions, 2 deletions
diff --git a/components/script/dom/htmlanchorelement.rs b/components/script/dom/htmlanchorelement.rs index 52c355f2bab..3731b9d46de 100644 --- a/components/script/dom/htmlanchorelement.rs +++ b/components/script/dom/htmlanchorelement.rs @@ -576,7 +576,7 @@ fn is_current_browsing_context(target: DOMString) -> bool { } /// https://html.spec.whatwg.org/multipage/#following-hyperlinks-2 -fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option<String>, referrer_policy: Option<ReferrerPolicy>) { +pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option<String>, referrer_policy: Option<ReferrerPolicy>) { // Step 1: replace. // Step 2: source browsing context. // Step 3: target browsing context. diff --git a/components/script/dom/htmlareaelement.rs b/components/script/dom/htmlareaelement.rs index 79240885167..7b86a61a773 100644 --- a/components/script/dom/htmlareaelement.rs +++ b/components/script/dom/htmlareaelement.rs @@ -2,6 +2,8 @@ * 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; @@ -9,13 +11,208 @@ use dom::bindings::js::{MutNullableJS, Root}; 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; +use dom::node::{Node, document_from_node}; use dom::virtualmethods::VirtualMethods; +use euclid::point::Point2D; use html5ever_atoms::LocalName; +use net_traits::ReferrerPolicy; use std::default::Default; +use std::f32; 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(); + let ar_ref = &mut array; + + // 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, + _ => (*ar_ref).push(val), + } + + index += 1; + } + + // The input does not consist any valid charecters + if (*ar_ref).is_empty() { + break; + } + + // Convert String to float + match String::from_utf8((*ar_ref).clone()).unwrap().parse::<f32>() { + Ok(v) => number_list.push(v), + Err(_) => number_list.push(0.0), + }; + + (*ar_ref).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, @@ -38,6 +235,26 @@ impl HTMLAreaElement { 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 shape.to_lowercase().as_ref() { + "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 { @@ -61,3 +278,40 @@ impl HTMLAreaElementMethods for HTMLAreaElement { }) } } + +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); + } +} diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index a86f0258918..29ddcd0a7c4 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -3,10 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use app_units::{Au, AU_PER_PX}; +use dom::activation::Activatable; use dom::attr::Attr; use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectBinding::DOMRectMethods; +use dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMethods; use dom::bindings::codegen::Bindings::HTMLImageElementBinding; use dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods; +use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::error::Fallible; use dom::bindings::inheritance::Castable; @@ -15,17 +19,23 @@ use dom::bindings::refcounted::Trusted; use dom::bindings::str::DOMString; use dom::document::Document; use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; +use dom::event::Event; use dom::eventtarget::EventTarget; +use dom::htmlareaelement::HTMLAreaElement; use dom::htmlelement::HTMLElement; +use dom::htmlmapelement::HTMLMapElement; +use dom::mouseevent::MouseEvent; use dom::node::{Node, NodeDamage, document_from_node, window_from_node}; use dom::values::UNSIGNED_LONG_MAX; use dom::virtualmethods::VirtualMethods; use dom::window::Window; +use euclid::point::Point2D; use html5ever_atoms::LocalName; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache_thread::{ImageResponder, ImageResponse}; +use num_traits::ToPrimitive; use script_thread::Runnable; use servo_url::ServoUrl; use std::i32; @@ -234,6 +244,38 @@ impl HTMLImageElement { Ok(image) } + pub fn areas(&self) -> Option<Vec<Root<HTMLAreaElement>>> { + let elem = self.upcast::<Element>(); + let usemap_attr; + if elem.has_attribute(&LocalName::from("usemap")) { + usemap_attr = elem.get_string_attribute(&local_name!("usemap")); + } else { + return None; + } + + let (first, last) = usemap_attr.split_at(1); + + match first { + "#" => {}, + _ => return None, + }; + + match last.len() { + 0 => return None, + _ => {}, + }; + + let map = self.upcast::<Node>() + .following_siblings() + .filter_map(Root::downcast::<HTMLMapElement>) + .find(|n| n.upcast::<Element>().get_string_attribute(&LocalName::from("name")) == last); + + let elements: Vec<Root<HTMLAreaElement>> = map.unwrap().upcast::<Node>() + .children() + .filter_map(Root::downcast::<HTMLAreaElement>) + .collect(); + Some(elements) + } } pub trait LayoutHTMLImageElementHelpers { @@ -429,6 +471,43 @@ impl VirtualMethods for HTMLImageElement { _ => self.super_type().unwrap().parse_plain_attribute(name, value), } } + + fn handle_event(&self, event: &Event) { + if (event.type_() == atom!("click")) { + let area_elements = self.areas(); + let elements = if let Some(x) = area_elements { + x + } else { + return + }; + + // Fetch click coordinates + let mouse_event = if let Some(x) = event.downcast::<MouseEvent>() { + x + } else { + return; + }; + + let point = Point2D::new(mouse_event.ClientX().to_f32().unwrap(), mouse_event.ClientY().to_f32().unwrap()); + // Walk HTMLAreaElements + let mut index = 0; + while index < elements.len() { + let shape = elements[index].get_shape_from_coords(); + let p = Point2D::new(self.upcast::<Element>().GetBoundingClientRect().X() as f32, + self.upcast::<Element>().GetBoundingClientRect().Y() as f32); + let shp = if let Some(x) = shape { + x.absolute_coords(p) + } else { + return + }; + if shp.hit_test(point) { + elements[index].activation_behavior(event, self.upcast()); + return + } + index += 1; + } + } + } } fn image_dimension_setter(element: &Element, attr: LocalName, value: u32) { diff --git a/components/script/test.rs b/components/script/test.rs index 31353b5ffa3..83523c47817 100644 --- a/components/script/test.rs +++ b/components/script/test.rs @@ -10,6 +10,10 @@ pub use dom::bindings::cell::DOMRefCell; pub use dom::bindings::js::JS; pub use dom::node::Node; +pub mod area { + pub use dom::htmlareaelement::{Area, Shape}; +} + pub mod size_of { use dom::characterdata::CharacterData; use dom::element::Element; |