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
Support sequential focus navigation #15936
Changes from all commits
File filter...
Jump to…
| @@ -85,6 +85,7 @@ use dom::touchevent::TouchEvent; | ||
| use dom::touchlist::TouchList; | ||
| use dom::treewalker::TreeWalker; | ||
| use dom::uievent::UIEvent; | ||
| use dom::virtualmethods::VirtualMethods; | ||
| use dom::webglcontextevent::WebGLContextEvent; | ||
| use dom::window::{ReflowReason, Window}; | ||
| use dom_struct::dom_struct; | ||
| @@ -284,6 +285,9 @@ pub struct Document { | ||
| dom_complete: Cell<u64>, | ||
| load_event_start: Cell<u64>, | ||
| load_event_end: Cell<u64>, | ||
| /// Vector to store focusable elements | ||
| focus_list: DOMRefCell<Vec<JS<Element>>>, | ||
| focus_list_index: Cell<usize>, | ||
| /// https://html.spec.whatwg.org/multipage/#concept-document-https-state | ||
| https_state: Cell<HttpsState>, | ||
| touchpad_pressure_phase: Cell<TouchpadPressurePhase>, | ||
| @@ -592,6 +596,24 @@ impl Document { | ||
| } | ||
| } | ||
|
|
||
| /// Add a focusable element to this document's ordering focus list. | ||
| pub fn add_focusable_element(&self, element: &Element) { | ||
| self.focus_list.borrow_mut().push(JS::from_ref(element)); | ||
|
||
| } | ||
|
|
||
| /// Removes a focusable element from this document's ordering focus list. | ||
| pub fn remove_focusable_element(&self, element: &Element) { | ||
| let mut list = self.focus_list.borrow_mut(); | ||
|
|
||
| let index = list.iter().position(|item| { | ||
| &**item == element | ||
| }); | ||
|
|
||
| if let Some(index) = index { | ||
| list.remove(index); | ||
| } | ||
jdm
Member
|
||
| } | ||
|
|
||
| /// Associate an element present in this document with the provided id. | ||
| pub fn register_named_element(&self, element: &Element, id: Atom) { | ||
| debug!("Adding named element to document {:p}: {:p} id={}", | ||
| @@ -1343,6 +1365,9 @@ impl Document { | ||
| event.fire(target); | ||
| let mut cancel_state = event.get_cancel_state(); | ||
|
|
||
| self.handle_event(event); | ||
jdm
Member
|
||
| self.commit_focus_transaction(FocusType::Element); | ||
|
|
||
| // https://w3c.github.io/uievents/#keys-cancelable-keys | ||
| if state != KeyState::Released && props.is_printable() && cancel_state != EventDefault::Prevented { | ||
| // https://w3c.github.io/uievents/#keypress-event-order | ||
| @@ -1935,6 +1960,61 @@ impl Document { | ||
|
|
||
| self.window.layout().nodes_from_point_response() | ||
| } | ||
|
|
||
| // https://html.spec.whatwg.org/multipage/#sequential-focus-navigation | ||
| fn sequential_focus_navigation(&self, event: &KeyboardEvent) { | ||
| // Step 1 | ||
| // Step 2 | ||
| // TODO: Implement the sequential focus navigation starting point. | ||
| // This can be used to change the starting point for navigation. | ||
CYBAI
Collaborator
|
||
|
|
||
| let modifier = event.get_key_modifiers(); | ||
|
|
||
| // Step 3 | ||
| let direction = | ||
| if modifier.is_empty() { | ||
| NavigationDirection::Forward | ||
| } else { | ||
| NavigationDirection::Backward | ||
| }; | ||
|
|
||
| // TODO: Step 4 | ||
|
|
||
| // Step 5 | ||
| let candidate = self.sequential_search(direction); | ||
|
|
||
| // Step 6 | ||
| self.request_focus(&candidate); | ||
| } | ||
|
|
||
| // https://html.spec.whatwg.org/multipage/#sequential-navigation-search-algorithm | ||
| // TODO: Use the starting point and selection mechanism for searching | ||
| fn sequential_search(&self, direction: NavigationDirection) -> Root<Element> { | ||
| let list = self.focus_list.borrow(); | ||
| let ref current_index = self.focus_list_index; | ||
| let focus_state = list[current_index.get()].focus_state(); | ||
|
|
||
| if focus_state { | ||
| match direction { | ||
| NavigationDirection::Forward => { | ||
| if current_index.get() != list.len() - 1 { | ||
| current_index.set(current_index.get() + 1); | ||
| } else { | ||
| current_index.set(0); | ||
| } | ||
| }, | ||
| NavigationDirection::Backward => { | ||
| if current_index.get() != 0 { | ||
| current_index.set(current_index.get() - 1); | ||
| } else { | ||
| current_index.set(list.len() - 1); | ||
| } | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| Root::from_ref(&*list[current_index.get()]) | ||
| } | ||
| } | ||
|
|
||
| #[derive(PartialEq, HeapSizeOf)] | ||
| @@ -2090,6 +2170,8 @@ impl Document { | ||
| dom_complete: Cell::new(Default::default()), | ||
| load_event_start: Cell::new(Default::default()), | ||
| load_event_end: Cell::new(Default::default()), | ||
| focus_list: DOMRefCell::new(vec![]), | ||
| focus_list_index: Cell::new(0), | ||
| https_state: Cell::new(HttpsState::None), | ||
| touchpad_pressure_phase: Cell::new(TouchpadPressurePhase::BeforeClick), | ||
| origin: origin, | ||
| @@ -2416,6 +2498,31 @@ impl Document { | ||
| } | ||
| } | ||
|
|
||
| impl VirtualMethods for Document { | ||
| fn super_type(&self) -> Option<&VirtualMethods> { | ||
| Some(self.upcast::<Document>() as &VirtualMethods) | ||
| } | ||
|
|
||
| fn handle_event(&self, event: &Event) { | ||
| if let Some(key_event) = event.downcast::<KeyboardEvent>() { | ||
| if event.type_() == atom!("keydown") { | ||
| let key = key_event.get_key().unwrap(); | ||
|
||
|
|
||
| match key { | ||
| Key::Tab => { | ||
| self.sequential_focus_navigation(key_event); | ||
| }, | ||
| _ => (), | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| enum NavigationDirection { | ||
| Forward, | ||
| Backward | ||
| } | ||
|
|
||
| impl Element { | ||
| fn click_event_filter_by_disabled_state(&self) -> bool { | ||
| @@ -2245,6 +2245,20 @@ impl VirtualMethods for Element { | ||
| } | ||
|
|
||
| let doc = document_from_node(self); | ||
|
|
||
| if self.is_focusable_area() { | ||
| let doc = document_from_node(self); | ||
| let children = self.upcast::<Node>().traverse_preorder(); | ||
jdm
Member
|
||
|
|
||
| for child in children { | ||
| if let Some(element) = child.downcast::<Element>() { | ||
| if element.is_focusable_area() && !element.disabled_state() { | ||
jdm
Member
|
||
| doc.add_focusable_element(element); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if let Some(ref value) = *self.id_attribute.borrow() { | ||
| doc.register_named_element(self, value.clone()); | ||
| } | ||
| @@ -2260,10 +2274,14 @@ impl VirtualMethods for Element { | ||
| } | ||
|
|
||
| let doc = document_from_node(self); | ||
|
|
||
| doc.remove_focusable_element(self); | ||
jdm
Member
|
||
|
|
||
| let fullscreen = doc.GetFullscreenElement(); | ||
| if fullscreen.r() == Some(self) { | ||
| doc.exit_fullscreen(); | ||
| } | ||
|
|
||
| if let Some(ref value) = *self.id_attribute.borrow() { | ||
| doc.unregister_named_element(self, value.clone()); | ||
| } | ||
| @@ -20,12 +20,14 @@ use dom::event::Event; | ||
| use dom::eventtarget::EventTarget; | ||
| use dom::htmlelement::HTMLElement; | ||
| use dom::htmlimageelement::HTMLImageElement; | ||
| use dom::keyboardevent::KeyboardEvent; | ||
| use dom::mouseevent::MouseEvent; | ||
| use dom::node::{Node, document_from_node}; | ||
| use dom::urlhelper::UrlHelper; | ||
| use dom::virtualmethods::VirtualMethods; | ||
| use dom_struct::dom_struct; | ||
| use html5ever_atoms::LocalName; | ||
| use msg::constellation_msg::Key; | ||
| use net_traits::ReferrerPolicy; | ||
| use num_traits::ToPrimitive; | ||
| use script_traits::MozBrowserEvent; | ||
| @@ -100,6 +102,23 @@ impl VirtualMethods for HTMLAnchorElement { | ||
| _ => self.super_type().unwrap().parse_plain_attribute(name, value), | ||
| } | ||
| } | ||
|
|
||
| fn handle_event(&self, event: &Event) { | ||
| if let Some(s) = self.super_type() { | ||
| s.handle_event(event); | ||
| } | ||
|
|
||
| if let Some(key_event) = event.downcast::<KeyboardEvent>() { | ||
| if event.type_() == atom!("keydown") { | ||
| match key_event.get_key() { | ||
| Some(Key::Enter) => { | ||
| follow_hyperlink(self.upcast::<Element>(), Some(String::new()), None); | ||
jdm
Member
|
||
| }, | ||
| _ => () | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl HTMLAnchorElementMethods for HTMLAnchorElement { | ||
This does not account for the tree ordering of the elements, such as if a control is inserted dynamically into the tree after the initial page is parsed. Also, it doesn't protect against duplicates.