From b10bfeaeb6dff4031f62ee14b05102db0db53b2c Mon Sep 17 00:00:00 2001 From: Ravi Shankar Date: Tue, 27 Dec 2016 12:42:19 +0530 Subject: [PATCH 1/3] Change event cancellation from bool to an enum --- components/script/dom/event.rs | 42 ++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index d471c25d873..fa3b9bfae6b 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -77,6 +77,28 @@ impl From for EventCancelable { } } +/// An enum to indicate whether the default action of an event is allowed. +/// +/// This should've been a bool. Instead, it's an enum, because, aside from the allowed/canceled +/// states, we also need something to stop the event from being handled again (without cancelling +/// the event entirely). For example, an Up/Down `KeyEvent` inside a `textarea` element will +/// trigger the cursor to go up/down if the text inside the element spans multiple lines. This enum +/// helps us to prevent such events from being [sent to the constellation][msg] where it will be +/// handled once again for page scrolling (which is definitely not what we'd want). +/// +/// [msg]: https://doc.servo.org/script_traits/enum.ConstellationMsg.html#variant.KeyEvent +/// +#[derive(JSTraceable, HeapSizeOf, Copy, Clone, PartialEq)] +pub enum EventDefault { + /// The default action of the event is allowed (constructor's default) + Allowed, + /// The default action has been prevented by calling `PreventDefault` + Prevented, + /// The event has been handled somewhere in the DOM, and it should be prevented from being + /// re-handled elsewhere. This doesn't affect the judgement of `DefaultPrevented` + Handled, +} + #[dom_struct] pub struct Event { reflector_: Reflector, @@ -84,7 +106,7 @@ pub struct Event { target: MutNullableJS, type_: DOMRefCell, phase: Cell, - canceled: Cell, + canceled: Cell, stop_propagation: Cell, stop_immediate: Cell, cancelable: Cell, @@ -103,7 +125,7 @@ impl Event { target: Default::default(), type_: DOMRefCell::new(atom!("")), phase: Cell::new(EventPhase::None), - canceled: Cell::new(false), + canceled: Cell::new(EventDefault::Allowed), stop_propagation: Cell::new(false), stop_immediate: Cell::new(false), cancelable: Cell::new(false), @@ -146,7 +168,7 @@ impl Event { self.initialized.set(true); self.stop_propagation.set(false); self.stop_immediate.set(false); - self.canceled.set(false); + self.canceled.set(EventDefault::Allowed); self.trusted.set(false); self.target.set(None); *self.type_.borrow_mut() = type_; @@ -229,6 +251,16 @@ impl Event { pub fn type_(&self) -> Atom { self.type_.borrow().clone() } + + #[inline] + pub fn mark_as_handled(&self) { + self.canceled.set(EventDefault::Handled); + } + + #[inline] + pub fn get_cancel_state(&self) -> EventDefault { + self.canceled.get() + } } impl EventMethods for Event { @@ -254,13 +286,13 @@ impl EventMethods for Event { // https://dom.spec.whatwg.org/#dom-event-defaultprevented fn DefaultPrevented(&self) -> bool { - self.canceled.get() + self.canceled.get() == EventDefault::Prevented } // https://dom.spec.whatwg.org/#dom-event-preventdefault fn PreventDefault(&self) { if self.cancelable.get() { - self.canceled.set(true) + self.canceled.set(EventDefault::Prevented) } } From 08662cc64e0674b910c1579ca550afd77e559eca Mon Sep 17 00:00:00 2001 From: Ravi Shankar Date: Tue, 27 Dec 2016 13:01:45 +0530 Subject: [PATCH 2/3] Allow 'keypress' event to emerge from input and textarea elements --- components/script/dom/document.rs | 59 ++++++++++---------- components/script/dom/htmlinputelement.rs | 4 +- components/script/dom/htmltextareaelement.rs | 4 +- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 6f8f2435311..0f427cdb069 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -13,7 +13,6 @@ use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods; use dom::bindings::codegen::Bindings::DocumentBinding; use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; @@ -41,7 +40,7 @@ use dom::documenttype::DocumentType; use dom::domimplementation::DOMImplementation; use dom::element::{Element, ElementCreator, ElementPerformFullscreenEnter, ElementPerformFullscreenExit}; use dom::errorevent::ErrorEvent; -use dom::event::{Event, EventBubbles, EventCancelable}; +use dom::event::{Event, EventBubbles, EventCancelable, EventDefault}; use dom::eventdispatcher::EventStatus; use dom::eventtarget::EventTarget; use dom::focusevent::FocusEvent; @@ -1308,10 +1307,10 @@ impl Document { props.key_code); let event = keyevent.upcast::(); event.fire(target); - let mut prevented = event.DefaultPrevented(); + let mut cancel_state = event.get_cancel_state(); // https://w3c.github.io/uievents/#keys-cancelable-keys - if state != KeyState::Released && props.is_printable() && !prevented { + if state != KeyState::Released && props.is_printable() && cancel_state != EventDefault::Prevented { // https://w3c.github.io/uievents/#keypress-event-order let event = KeyboardEvent::new(&self.window, DOMString::from("keypress"), @@ -1334,40 +1333,40 @@ impl Document { 0); let ev = event.upcast::(); ev.fire(target); - prevented = ev.DefaultPrevented(); + cancel_state = ev.get_cancel_state(); // TODO: if keypress event is canceled, prevent firing input events } - if !prevented { + if cancel_state == EventDefault::Allowed { constellation.send(ConstellationMsg::SendKeyEvent(ch, key, state, modifiers)).unwrap(); - } - // This behavior is unspecced - // We are supposed to dispatch synthetic click activation for Space and/or Return, - // however *when* we do it is up to us - // I'm dispatching it after the key event so the script has a chance to cancel it - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27337 - match key { - Key::Space if !prevented && state == KeyState::Released => { - let maybe_elem = target.downcast::(); - if let Some(el) = maybe_elem { - synthetic_click_activation(el, - false, - false, - false, - false, - ActivationSource::NotFromClick) - } - } - Key::Enter if !prevented && state == KeyState::Released => { - let maybe_elem = target.downcast::(); - if let Some(el) = maybe_elem { - if let Some(a) = el.as_maybe_activatable() { - a.implicit_submission(ctrl, alt, shift, meta); + // This behavior is unspecced + // We are supposed to dispatch synthetic click activation for Space and/or Return, + // however *when* we do it is up to us. + // Here, we're dispatching it after the key event so the script has a chance to cancel it + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27337 + match key { + Key::Space if state == KeyState::Released => { + let maybe_elem = target.downcast::(); + if let Some(el) = maybe_elem { + synthetic_click_activation(el, + false, + false, + false, + false, + ActivationSource::NotFromClick) } } + Key::Enter if state == KeyState::Released => { + let maybe_elem = target.downcast::(); + if let Some(el) = maybe_elem { + if let Some(a) = el.as_maybe_activatable() { + a.implicit_submission(ctrl, alt, shift, meta); + } + } + } + _ => (), } - _ => (), } self.window.reflow(ReflowGoal::ForDisplay, diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index eb344ef0a79..8784c1f39df 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -1117,11 +1117,11 @@ impl VirtualMethods for HTMLInputElement { } self.upcast::().dirty(NodeDamage::OtherNodeDamage); - event.PreventDefault(); + event.mark_as_handled(); } RedrawSelection => { self.upcast::().dirty(NodeDamage::OtherNodeDamage); - event.PreventDefault(); + event.mark_as_handled(); } Nothing => (), } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index fd5b42fa25c..74db028859a 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -415,11 +415,11 @@ impl VirtualMethods for HTMLTextAreaElement { } self.upcast::().dirty(NodeDamage::OtherNodeDamage); - event.PreventDefault(); + event.mark_as_handled(); } KeyReaction::RedrawSelection => { self.upcast::().dirty(NodeDamage::OtherNodeDamage); - event.PreventDefault(); + event.mark_as_handled(); } KeyReaction::Nothing => (), } From 5f0b3bd53c25c158117c91e9da36aaec0342244f Mon Sep 17 00:00:00 2001 From: Ravi Shankar Date: Tue, 27 Dec 2016 13:18:45 +0530 Subject: [PATCH 3/3] Fire 'input' event after 'keypress' in input and textarea elements --- components/atoms/static_atoms.txt | 1 + components/script/dom/document.rs | 1 - components/script/dom/htmlinputelement.rs | 25 ++++++++++---------- components/script/dom/htmltextareaelement.rs | 21 ++++++++-------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index e59c9cad809..c2a4ea84ec2 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -52,6 +52,7 @@ beforeunload message click keydown +keypress abort beforescriptexecute afterscriptexecute diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 0f427cdb069..f263eee9210 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -1334,7 +1334,6 @@ impl Document { let ev = event.upcast::(); ev.fire(target); cancel_state = ev.get_cancel_state(); - // TODO: if keypress event is canceled, prevent firing input events } if cancel_state == EventDefault::Allowed { diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 8784c1f39df..d133efd5e14 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -1105,17 +1105,6 @@ impl VirtualMethods for HTMLInputElement { DispatchInput => { self.value_changed.set(true); self.update_placeholder_shown_state(); - - if event.IsTrusted() { - let window = window_from_node(self); - let _ = window.user_interaction_task_source().queue_event( - &self.upcast(), - atom!("input"), - EventBubbles::Bubbles, - EventCancelable::NotCancelable, - &window); - } - self.upcast::().dirty(NodeDamage::OtherNodeDamage); event.mark_as_handled(); } @@ -1126,7 +1115,19 @@ impl VirtualMethods for HTMLInputElement { Nothing => (), } } - } + } else if event.type_() == atom!("keypress") && !event.DefaultPrevented() && + (self.input_type.get() == InputType::InputText || + self.input_type.get() == InputType::InputPassword) { + if event.IsTrusted() { + let window = window_from_node(self); + let _ = window.user_interaction_task_source() + .queue_event(&self.upcast(), + atom!("input"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + &window); + } + } } } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 74db028859a..2582b13514a 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -403,17 +403,6 @@ impl VirtualMethods for HTMLTextAreaElement { KeyReaction::DispatchInput => { self.value_changed.set(true); self.update_placeholder_shown_state(); - - if event.IsTrusted() { - let window = window_from_node(self); - let _ = window.user_interaction_task_source().queue_event( - &self.upcast(), - atom!("input"), - EventBubbles::Bubbles, - EventCancelable::NotCancelable, - &window); - } - self.upcast::().dirty(NodeDamage::OtherNodeDamage); event.mark_as_handled(); } @@ -424,6 +413,16 @@ impl VirtualMethods for HTMLTextAreaElement { KeyReaction::Nothing => (), } } + } else if event.type_() == atom!("keypress") && !event.DefaultPrevented() { + if event.IsTrusted() { + let window = window_from_node(self); + let _ = window.user_interaction_task_source() + .queue_event(&self.upcast(), + atom!("input"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + &window); + } } } }