Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly dispatch keypress event #14738

Merged
merged 3 commits into from Jan 4, 2017
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -52,6 +52,7 @@ beforeunload
message
click
keydown
keypress
abort
beforescriptexecute
afterscriptexecute
@@ -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>();
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,39 @@ impl Document {
0);
let ev = event.upcast::<Event>();
ev.fire(target);
prevented = ev.DefaultPrevented();
// TODO: if keypress event is canceled, prevent firing input events
cancel_state = ev.get_cancel_state();
}

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::<Element>();
if let Some(el) = maybe_elem {
synthetic_click_activation(el,
false,
false,
false,
false,
ActivationSource::NotFromClick)
// 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::<Element>();
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::<Element>();
if let Some(el) = maybe_elem {
if let Some(a) = el.as_maybe_activatable() {
a.implicit_submission(ctrl, alt, shift, meta);
Key::Enter if state == KeyState::Released => {
let maybe_elem = target.downcast::<Element>();
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,
@@ -77,14 +77,36 @@ impl From<bool> 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,
current_target: MutNullableJS<EventTarget>,
target: MutNullableJS<EventTarget>,
type_: DOMRefCell<Atom>,
phase: Cell<EventPhase>,
canceled: Cell<bool>,
canceled: Cell<EventDefault>,
stop_propagation: Cell<bool>,
stop_immediate: Cell<bool>,
cancelable: Cell<bool>,
@@ -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)
}
}

@@ -1105,28 +1105,29 @@ 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::<Node>().dirty(NodeDamage::OtherNodeDamage);
event.PreventDefault();
event.mark_as_handled();
}
RedrawSelection => {
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
event.PreventDefault();
event.mark_as_handled();
}
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);
}
}
}
}

@@ -403,27 +403,26 @@ 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::<Node>().dirty(NodeDamage::OtherNodeDamage);
event.PreventDefault();
event.mark_as_handled();
}
KeyReaction::RedrawSelection => {
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
event.PreventDefault();
event.mark_as_handled();
}
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);
}
}
}
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.