diff options
-rw-r--r-- | components/script/dom/activation.rs | 74 | ||||
-rw-r--r-- | components/script/dom/element.rs | 81 |
2 files changed, 131 insertions, 24 deletions
diff --git a/components/script/dom/activation.rs b/components/script/dom/activation.rs index cb803d67ba1..98e8d435c12 100644 --- a/components/script/dom/activation.rs +++ b/components/script/dom/activation.rs @@ -2,26 +2,60 @@ * 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::bindings::codegen::InheritTypes::HTMLInputElementCast; -use dom::bindings::js::JSRef; -use dom::element::HTMLInputElementTypeId; -use dom::htmlinputelement::HTMLInputElement; -use dom::node::{ElementNodeTypeId, Node, NodeHelpers}; - -pub trait Activatable {} - - -/// Obtain an Activatable instance for a given Node-derived object, -/// if it is activatable -pub fn activation_vtable_for<'a>(node: &'a JSRef<'a, Node>) -> Option<&'a Activatable + 'a> { - match node.type_id() { - ElementNodeTypeId(HTMLInputElementTypeId) => { - let _element: &'a JSRef<'a, HTMLInputElement> = HTMLInputElementCast::to_borrowed_ref(node).unwrap(); - // Some(element as &'a VirtualMethods + 'a) - None - }, - _ => { - None +use dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use dom::bindings::codegen::InheritTypes::{EventCast, EventTargetCast}; +use dom::bindings::js::{JSRef, Temporary, OptionalRootable}; +use dom::element::{Element, ActivationElementHelpers}; +use dom::event::{Event, EventHelpers}; +use dom::eventtarget::{EventTarget, EventTargetHelpers}; +use dom::mouseevent::MouseEvent; +use dom::node::window_from_node; + + +/// Trait for elements with defined activation behavior +pub trait Activatable : Copy { + fn as_element(&self) -> Temporary<Element>; + + // https://html.spec.whatwg.org/multipage/interaction.html#run-pre-click-activation-steps + fn pre_click_activation(&self); + + // https://html.spec.whatwg.org/multipage/interaction.html#run-canceled-activation-steps + fn canceled_activation(&self); + + // https://html.spec.whatwg.org/multipage/interaction.html#run-post-click-activation-steps + fn post_click_activation(&self); + + // https://html.spec.whatwg.org/multipage/interaction.html#run-synthetic-click-activation-steps + fn synthetic_click_activation(&self, ctrlKey: bool, shiftKey: bool, altKey: bool, metaKey: bool) { + let element = self.as_element().root(); + // Step 1 + if element.click_in_progress() { + return; } + // Step 2 + element.set_click_in_progress(true); + // Step 3 + self.pre_click_activation(); + + // Step 4 + // https://html.spec.whatwg.org/multipage/webappapis.html#fire-a-synthetic-mouse-event + let win = window_from_node(*element).root(); + let target: JSRef<EventTarget> = EventTargetCast::from_ref(*element); + let mouse = MouseEvent::new(*win, "click".to_string(), false, false, Some(*win), 1, + 0, 0, 0, 0, ctrlKey, shiftKey, altKey, metaKey, + 0, None).root(); + let event: JSRef<Event> = EventCast::from_ref(*mouse); + event.set_trusted(true); + target.dispatch_event_with_target(None, event).ok(); + + // Step 5 + if event.DefaultPrevented() { + self.canceled_activation(); + } else { + self.post_click_activation(); + } + + // Step 6 + element.set_click_in_progress(false); } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index d3b5b1946aa..ca12d6f2afd 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -4,16 +4,18 @@ //! Element nodes. +use dom::activation::Activatable; use dom::attr::{Attr, ReplacedAttr, FirstSetAttr, AttrHelpers, AttrHelpersForLayout}; use dom::attr::{AttrValue, StringAttrValue, UIntAttrValue, AtomAttrValue}; use dom::namednodemap::NamedNodeMap; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; +use dom::bindings::codegen::Bindings::EventBinding::EventMethods; use dom::bindings::codegen::Bindings::ElementBinding; use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods; -use dom::bindings::codegen::InheritTypes::{ElementDerived, HTMLInputElementDerived}; -use dom::bindings::codegen::InheritTypes::{HTMLTableCellElementDerived, NodeCast}; +use dom::bindings::codegen::InheritTypes::{ElementDerived, HTMLInputElementDerived, HTMLTableCellElementDerived}; +use dom::bindings::codegen::InheritTypes::{HTMLInputElementCast, NodeCast, EventTargetCast, ElementCast}; use dom::bindings::js::{MutNullableJS, JS, JSRef, Temporary, TemporaryPushable}; use dom::bindings::js::{OptionalSettable, OptionalRootable, Root}; use dom::bindings::utils::{Reflectable, Reflector}; @@ -24,7 +26,8 @@ use dom::domrect::DOMRect; use dom::domrectlist::DOMRectList; use dom::document::{Document, DocumentHelpers, LayoutDocumentHelpers}; use dom::domtokenlist::DOMTokenList; -use dom::eventtarget::{EventTarget, NodeTargetTypeId}; +use dom::event::Event; +use dom::eventtarget::{EventTarget, NodeTargetTypeId, EventTargetHelpers}; use dom::htmlcollection::HTMLCollection; use dom::htmlinputelement::{HTMLInputElement, RawLayoutHTMLInputElementHelpers}; use dom::htmlserializer::serialize; @@ -41,7 +44,7 @@ use servo_util::namespace; use servo_util::str::{DOMString, LengthOrPercentageOrAuto}; use std::ascii::AsciiExt; -use std::cell::{Ref, RefMut}; +use std::cell::{Cell, Ref, RefMut}; use std::default::Default; use std::mem; use string_cache::{Atom, Namespace, QualName}; @@ -57,6 +60,7 @@ pub struct Element { style_attribute: DOMRefCell<Option<style::PropertyDeclarationBlock>>, attr_list: MutNullableJS<NamedNodeMap>, class_list: MutNullableJS<DOMTokenList>, + click_in_progress: Cell<bool>, } impl ElementDerived for EventTarget { @@ -175,6 +179,7 @@ impl Element { attr_list: Default::default(), class_list: Default::default(), style_attribute: DOMRefCell::new(None), + click_in_progress: Cell::new(false), } } @@ -1183,3 +1188,71 @@ impl<'a> style::TElement<'a> for JSRef<'a, Element> { } } } + +pub trait ActivationElementHelpers<'a> { + fn as_maybe_activatable(&'a self) -> Option<&'a Activatable + 'a>; + fn click_in_progress(self) -> bool; + fn set_click_in_progress(self, click: bool); + fn nearest_activable_element(self) -> Option<JSRef<'a, Element>>; + fn authentic_click_activation<'b>(self, event: JSRef<'b, Event>); +} + +impl<'a> ActivationElementHelpers<'a> for JSRef<'a, Element> { + fn as_maybe_activatable(&'a self) -> Option<&'a Activatable + 'a> { + let node: JSRef<Node> = NodeCast::from_ref(*self); + match node.type_id() { + ElementNodeTypeId(HTMLInputElementTypeId) => { + let _element: &'a JSRef<'a, HTMLInputElement> = HTMLInputElementCast::to_borrowed_ref(self).unwrap(); + // Some(element as &'a Activatable + 'a) + None + }, + _ => { + None + } + } + } + + fn click_in_progress(self) -> bool { + self.click_in_progress.get() + } + + fn set_click_in_progress(self, click: bool) { + self.click_in_progress.set(click) + } + + // https://html.spec.whatwg.org/multipage/interaction.html#nearest-activatable-element + fn nearest_activable_element(self) -> Option<JSRef<'a, Element>> { + let node: JSRef<Node> = NodeCast::from_ref(self); + node.ancestors() + .filter_map(|node| ElementCast::to_ref(node)) + .filter(|e| e.as_maybe_activatable().is_some()).next() + } + + // https://html.spec.whatwg.org/multipage/interaction.html#run-authentic-click-activation-steps + fn authentic_click_activation<'b>(self, event: JSRef<'b, Event>) { + let target: JSRef<EventTarget> = EventTargetCast::from_ref(self); + // Step 2 (requires canvas support) + // Step 3 + self.set_click_in_progress(true); + // Step 4 + let e = self.nearest_activable_element(); + // Step 5 + e.map(|a| a.as_maybe_activatable().map(|el| el.pre_click_activation())); + // Step 6 + target.dispatch_event_with_target(None, event).ok(); + e.map(|el| { + if NodeCast::from_ref(el).is_in_doc() { + return; // XXXManishearth do we need this check? + } + el.as_maybe_activatable().map(|a| { + if event.DefaultPrevented() { + a.post_click_activation(); + } else { + a.canceled_activation(); + } + }); + }); + // Step 7 + self.set_click_in_progress(false); + } +} |