-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Conversation
I've tested this locally, it works, but it cycles through every focusable element, not just form elements. Do we care? |
r? @jdm I can't always be taking tasks from you Josh, sometimes I need to give some to you. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good start, but there are a bunch of tricky details that we should sort out before merging this, I think.
if event.type_() == atom!("keydown") { | ||
match key_event.get_key() { | ||
Some(Key::Enter) => { | ||
follow_hyperlink(self.upcast::<Element>(), Some(String::new()), None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be calling into activation_behaviour here, which will need to be updated to account for non-mouse activations. We should also handle space key events similarly.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unwrap looks scary here.
@@ -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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
|
||
if let Some(index) = index { | ||
list.remove(index); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will need to ensure that the current focused index is updated to account for this change.
|
||
for child in children { | ||
if let Some(element) = child.downcast::<Element>() { | ||
if element.is_focusable_area() && !element.disabled_state() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will need to account for elements that have their disabled state modified dynamically, too.
@@ -1343,6 +1365,9 @@ impl Document { | |||
event.fire(target); | |||
let mut cancel_state = event.get_cancel_state(); | |||
|
|||
self.handle_event(event); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should inline the code from handle_event in here instead. No need to call a different method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdm If we move the code from handle_event
here, I think we don't need impl VirtualMethods for Document
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go ahead and remove it if that's the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I'll try it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to this comment in the most original PR from @nox , I think that's why nox made it inside VirtualMethods
.
However, after trying to make the code in here, the focus sequential still works.
Not sure if there's any concerns about moving the code in here instead of inside VirtualMethods
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment you point out was made in a version that did not include the parent type's handle_event call, which is required when using VirtualMethods. I don't think we need to use VirtualMethods for that code, however.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood. We can handle the event without VirtualMethods
but I didn't know too much about it; thus, I left the comment.
Now, I think it should be okay to remove it :)
Thanks!
@@ -1343,6 +1365,9 @@ impl Document { | |||
event.fire(target); | |||
let mut cancel_state = event.get_cancel_state(); | |||
|
|||
self.handle_event(event); | |||
self.commit_focus_transaction(FocusType::Element); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Running this for every single key event that is processed seems odd.
|
||
if self.is_focusable_area() { | ||
let doc = document_from_node(self); | ||
let children = self.upcast::<Node>().traverse_preorder(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to iterate over all the children here, since they should have their own bind_to_tree methods called already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we only try to find the starting point
of focusable elements, I'll expect we don't need to traverse the children here and add them into the list.
@@ -2260,10 +2274,14 @@ impl VirtualMethods for Element { | |||
} | |||
|
|||
let doc = document_from_node(self); | |||
|
|||
doc.remove_focusable_element(self); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should only call this if we know that this element is has previously been added to the list.
Closing since nobody is working on this. |
// Step 1 | ||
// Step 2 | ||
// TODO: Implement the sequential focus navigation starting point. | ||
// This can be used to change the starting point for navigation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdm I'm stuck by the add_focusable_element
and remove_focusable_element
methods above.
According to the spec,
When the user requests that focus move from the currently focused area of a top-level browsing context to the next or previous focusable area (e.g. as the default action of pressing the tab key), or when the user requests that focus sequentially move to a top-level browsing context in the first place (e.g. from the browser's location bar), the user agent must use the following algorithm
Thus, maybe we don't need to save a focus_list
inside Document
? We might only need to know the starting point
?
I'm wondering if it's possible to calculate the starting point
and also its next and previous focus candidate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we store the node for the starting point and not the list and starting index then it might be easier, like you suggest. It would require:
- determining and storing the new starting point if the current starting point ever becomes unfocusable
- recalculating the list every time we need to change the current starting point
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdm Thanks for your confirmation. I'll try to only store the starting point for it!
This change is