mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
script: Add an initial implementation of the "focus update steps" (#44360)
This moves Servo closer to the focus parts of the HTML specification. The behavior should be the same as before, but now the code in `script` matches the structure of the specification. The main goal is to set us up for: - Firing focus events in the right order on nested documents - A proper implementation of the unfocusing steps. Testing: This should not change behavior so is covered by existing tests. Signed-off-by: Martin Robinson <mrobinson@fastmail.fm> Co-authored-by: Martin Robinson <mrobinson@fastmail.fm>
This commit is contained in:
@@ -60,7 +60,7 @@ use crate::dom::bindings::root::MutNullableDom;
|
||||
use crate::dom::bindings::trace::NoTrace;
|
||||
use crate::dom::clipboardevent::ClipboardEventType;
|
||||
use crate::dom::document::FireMouseEventType;
|
||||
use crate::dom::document::focus::{FocusInitiator, FocusOperation, FocusableArea};
|
||||
use crate::dom::document::focus::FocusableArea;
|
||||
use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
|
||||
#[cfg(feature = "gamepad")]
|
||||
use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
|
||||
@@ -406,6 +406,9 @@ impl DocumentEventHandler {
|
||||
let document = self.window.Document();
|
||||
match &*document.focus_handler().focused_area() {
|
||||
FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
|
||||
FocusableArea::IFrameViewport { iframe_element, .. } => {
|
||||
DomRoot::from_ref(iframe_element.upcast())
|
||||
},
|
||||
FocusableArea::Viewport => document
|
||||
.GetBody()
|
||||
.map(DomRoot::upcast)
|
||||
@@ -920,11 +923,10 @@ impl DocumentEventHandler {
|
||||
// Note that this differs from the specification, because we are going to look
|
||||
// for the first inclusive ancestor that is click focusable and then focus it.
|
||||
// See documentation for [`Node::find_click_focusable_area`].
|
||||
self.window.Document().focus_handler().focus(
|
||||
FocusOperation::Focus(node.find_click_focusable_area()),
|
||||
FocusInitiator::Local,
|
||||
CanGc::from_cx(cx),
|
||||
);
|
||||
self.window
|
||||
.Document()
|
||||
.focus_handler()
|
||||
.focus(node.find_click_focusable_area(), CanGc::from_cx(cx));
|
||||
}
|
||||
|
||||
// Step 9. If mbutton is the secondary mouse button, then
|
||||
@@ -1442,11 +1444,9 @@ impl DocumentEventHandler {
|
||||
let document = self.window.Document();
|
||||
let composition_event = match event {
|
||||
ImeEvent::Dismissed => {
|
||||
document.focus_handler().focus(
|
||||
FocusOperation::Focus(FocusableArea::Viewport),
|
||||
FocusInitiator::Local,
|
||||
CanGc::from_cx(cx),
|
||||
);
|
||||
document
|
||||
.focus_handler()
|
||||
.focus(FocusableArea::Viewport, CanGc::from_cx(cx));
|
||||
return Default::default();
|
||||
},
|
||||
ImeEvent::Composition(composition_event) => composition_event,
|
||||
|
||||
@@ -17,11 +17,6 @@ use crate::dom::focusevent::FocusEventType;
|
||||
use crate::dom::types::{Element, EventTarget, FocusEvent, HTMLElement, HTMLIFrameElement, Window};
|
||||
use crate::dom::{Document, Event, EventBubbles, EventCancelable, Node, NodeTraits};
|
||||
|
||||
pub(crate) enum FocusOperation {
|
||||
Focus(FocusableArea),
|
||||
Unfocus,
|
||||
}
|
||||
|
||||
/// The kind of focusable area a [`FocusableArea`] is. A [`FocusableArea`] may be click focusable,
|
||||
/// sequentially focusable, or both.
|
||||
#[derive(Clone, Copy, Debug, Default, JSTraceable, MallocSizeOf, PartialEq)]
|
||||
@@ -44,21 +39,49 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, JSTraceable, MallocSizeOf, PartialEq)]
|
||||
/// <https://html.spec.whatwg.org/multipage/#focusable-area>
|
||||
#[derive(Clone, Default, JSTraceable, MallocSizeOf, PartialEq)]
|
||||
pub(crate) enum FocusableArea {
|
||||
Node {
|
||||
node: DomRoot<Node>,
|
||||
kind: FocusableAreaKind,
|
||||
},
|
||||
/// The viewport of an `<iframe>` element in its containing `Document`. `<iframe>`s
|
||||
/// are focusable areas, but have special behavior when focusing.
|
||||
IFrameViewport {
|
||||
iframe_element: DomRoot<HTMLIFrameElement>,
|
||||
kind: FocusableAreaKind,
|
||||
},
|
||||
#[default]
|
||||
Viewport,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for FocusableArea {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Node { node, kind } => f
|
||||
.debug_struct("Node")
|
||||
.field("node", node)
|
||||
.field("kind", kind)
|
||||
.finish(),
|
||||
Self::IFrameViewport {
|
||||
iframe_element,
|
||||
kind,
|
||||
} => f
|
||||
.debug_struct("IFrameViewport")
|
||||
.field("pipeline", &iframe_element.pipeline_id())
|
||||
.field("kind", kind)
|
||||
.finish(),
|
||||
Self::Viewport => write!(f, "Viewport"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableArea {
|
||||
pub(crate) fn kind(&self) -> FocusableAreaKind {
|
||||
match self {
|
||||
FocusableArea::Node { kind, .. } => *kind,
|
||||
FocusableArea::Viewport => FocusableAreaKind::Click | FocusableAreaKind::Sequential,
|
||||
Self::Node { kind, .. } | Self::IFrameViewport { kind, .. } => *kind,
|
||||
Self::Viewport => FocusableAreaKind::Click | FocusableAreaKind::Sequential,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,29 +93,31 @@ impl FocusableArea {
|
||||
/// specification.
|
||||
pub(crate) fn element(&self) -> Option<&Element> {
|
||||
match self {
|
||||
FocusableArea::Node { node, .. } => node.downcast(),
|
||||
FocusableArea::Viewport => None,
|
||||
Self::Node { node, .. } => node.downcast(),
|
||||
Self::IFrameViewport { iframe_element, .. } => Some(iframe_element.upcast()),
|
||||
Self::Viewport => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-anchor>
|
||||
pub(crate) fn dom_anchor(&self, document: &Document) -> DomRoot<Node> {
|
||||
match self {
|
||||
FocusableArea::Node { node, .. } => node.clone(),
|
||||
FocusableArea::Viewport => DomRoot::from_ref(document.upcast()),
|
||||
Self::Node { node, .. } => node.clone(),
|
||||
Self::IFrameViewport { iframe_element, .. } => {
|
||||
DomRoot::from_ref(iframe_element.upcast())
|
||||
},
|
||||
Self::Viewport => DomRoot::from_ref(document.upcast()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies the initiator of a focus operation.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum FocusInitiator {
|
||||
/// The operation is initiated by a focus change in this [`Document`]. This
|
||||
/// means the change might trigger focus changes in parent [`Document`]s.
|
||||
Local,
|
||||
/// The operation is initiated somewhere else, and we are updating our
|
||||
/// internal state accordingly.
|
||||
Remote,
|
||||
pub(crate) fn focus_chain(&self) -> Vec<FocusableArea> {
|
||||
match self {
|
||||
FocusableArea::Node { .. } | FocusableArea::IFrameViewport { .. } => {
|
||||
vec![self.clone(), FocusableArea::Viewport]
|
||||
},
|
||||
FocusableArea::Viewport => vec![self.clone()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`DocumentFocusHandler`] is a structure responsible for handling and storing data related to
|
||||
@@ -130,6 +155,10 @@ impl DocumentFocusHandler {
|
||||
self.has_focus.get()
|
||||
}
|
||||
|
||||
pub(crate) fn set_has_focus(&self, has_focus: bool) {
|
||||
self.has_focus.set(has_focus);
|
||||
}
|
||||
|
||||
/// Return the element that currently has focus. If `None` is returned the viewport itself has focus.
|
||||
pub(crate) fn focused_area<'a>(&'a self) -> Ref<'a, FocusableArea> {
|
||||
let focused_area = self.focused_area.borrow();
|
||||
@@ -140,7 +169,7 @@ impl DocumentFocusHandler {
|
||||
/// set element (if any) and the new one, as well as the new one. This will not do anything if
|
||||
/// the new element is the same as the previous one. Note that this *will not* fire any focus
|
||||
/// events. If that is necessary the [`DocumentFocusHandler::focus`] should be used.
|
||||
pub(crate) fn set_focused_element(&self, new_focusable_area: FocusableArea) {
|
||||
pub(crate) fn set_focused_area(&self, new_focusable_area: FocusableArea) {
|
||||
if new_focusable_area == *self.focused_area.borrow() {
|
||||
return;
|
||||
}
|
||||
@@ -195,91 +224,34 @@ impl DocumentFocusHandler {
|
||||
self.focus_sequence.get()
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#current-focus-chain-of-a-top-level-traversable>
|
||||
pub(crate) fn current_focus_chain(&self) -> Vec<FocusableArea> {
|
||||
// > The current focus chain of a top-level traversable is the focus chain of the
|
||||
// > currently focused area of traversable, if traversable is non-null, or an empty list
|
||||
// > otherwise.
|
||||
|
||||
// We cannot easily get the full focus chain of the top-level traversable, so we just
|
||||
// get the bits that intersect with this `Document`. The rest will be handled
|
||||
// internally in [`Self::focus_update_steps`].
|
||||
if !self.has_focus() {
|
||||
return vec![];
|
||||
}
|
||||
self.focused_area().focus_chain()
|
||||
}
|
||||
|
||||
/// Reassign the focus context to the element that last requested focus during this
|
||||
/// transaction, or the document if no elements requested it.
|
||||
pub(crate) fn focus(
|
||||
&self,
|
||||
focus_operation: FocusOperation,
|
||||
focus_initiator: FocusInitiator,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
let (new_focused, new_focus_state) = match focus_operation {
|
||||
FocusOperation::Focus(focusable_area) => (focusable_area, true),
|
||||
FocusOperation::Unfocus => (FocusableArea::Viewport, false),
|
||||
};
|
||||
pub(crate) fn focus(&self, new_focus_target: FocusableArea, can_gc: CanGc) {
|
||||
let old_focus_chain = self.current_focus_chain();
|
||||
let new_focus_chain = new_focus_target.focus_chain();
|
||||
self.focus_update_steps(new_focus_chain, old_focus_chain, &new_focus_target, can_gc);
|
||||
|
||||
let old_focus_state = self.has_focus.get();
|
||||
|
||||
// `*_focused_filtered` indicates the local element (if any) included in
|
||||
// the top-level BC's focus chain.
|
||||
let old_focused_filtered = old_focus_state.then(|| self.focused_area().clone());
|
||||
let new_focused_filtered = new_focus_state.then(|| new_focused.clone());
|
||||
debug!("Committing focus transaction: {old_focused_filtered:?} → {new_focused_filtered:?}",);
|
||||
|
||||
if old_focused_filtered != new_focused_filtered {
|
||||
// Although the "focusing steps" in the HTML specification say to wait until after firing
|
||||
// the "blur" event to change the currently focused area of the Document, browsers tend
|
||||
// to set it to the viewport before firing the "blur" event.
|
||||
//
|
||||
// See https://github.com/whatwg/html/issues/1569
|
||||
self.set_focused_element(FocusableArea::Viewport);
|
||||
|
||||
if let Some(element) = old_focused_filtered
|
||||
.as_ref()
|
||||
.and_then(|focusable_area| focusable_area.element())
|
||||
{
|
||||
if element.upcast::<Node>().is_connected() {
|
||||
self.fire_focus_event(
|
||||
FocusEventType::Blur,
|
||||
element.upcast(),
|
||||
new_focused_filtered.as_ref(),
|
||||
can_gc,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if old_focus_state != new_focus_state && !new_focus_state {
|
||||
self.fire_focus_event(FocusEventType::Blur, self.window.upcast(), None, can_gc);
|
||||
}
|
||||
|
||||
self.set_focused_element(new_focused.clone());
|
||||
self.has_focus.set(new_focus_state);
|
||||
|
||||
if old_focus_state != new_focus_state && new_focus_state {
|
||||
self.fire_focus_event(FocusEventType::Focus, self.window.upcast(), None, can_gc);
|
||||
}
|
||||
|
||||
if old_focused_filtered != new_focused_filtered {
|
||||
if let Some(element) = new_focused_filtered
|
||||
.as_ref()
|
||||
.and_then(|focusable_area| focusable_area.element())
|
||||
{
|
||||
if let Some(html_element) = element.downcast::<HTMLElement>() {
|
||||
html_element.handle_focus_state_for_contenteditable(can_gc);
|
||||
}
|
||||
|
||||
self.fire_focus_event(
|
||||
FocusEventType::Focus,
|
||||
element.upcast(),
|
||||
old_focused_filtered.as_ref(),
|
||||
can_gc,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if focus_initiator == FocusInitiator::Remote {
|
||||
return;
|
||||
}
|
||||
|
||||
// We are the initiator of the focus operation, so we must broadcast
|
||||
// the change we intend to make.
|
||||
match (old_focus_state, new_focus_state) {
|
||||
(_, true) => {
|
||||
// Advertise the change in the focus chain.
|
||||
// <https://html.spec.whatwg.org/multipage/#focus-chain>
|
||||
// <https://html.spec.whatwg.org/multipage/#focusing-steps>
|
||||
//
|
||||
// TODO: Integrate this into the "focus update steps."
|
||||
//
|
||||
// If the top-level BC doesn't have system focus, this won't
|
||||
// have an immediate effect, but it will when we gain system
|
||||
// focus again. Therefore we still have to send `ScriptMsg::
|
||||
@@ -296,65 +268,225 @@ impl DocumentFocusHandler {
|
||||
// > with non-null nested browsing context, then set
|
||||
// > `new focus target` to the nested browsing context's
|
||||
// > active document.
|
||||
let child_browsing_context_id = new_focused
|
||||
.element()
|
||||
.and_then(|element| element.downcast::<HTMLIFrameElement>())
|
||||
.and_then(|iframe| iframe.browsing_context_id());
|
||||
|
||||
let child_browsing_context_id = match new_focus_target {
|
||||
FocusableArea::IFrameViewport { iframe_element, .. } => {
|
||||
iframe_element.browsing_context_id()
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
let sequence = self.increment_fetch_focus_sequence();
|
||||
|
||||
debug!(
|
||||
"Advertising the focus request to the constellation \
|
||||
with sequence number {} and child BC ID {}",
|
||||
sequence,
|
||||
child_browsing_context_id
|
||||
.as_ref()
|
||||
.map(|id| id as &dyn std::fmt::Display)
|
||||
.unwrap_or(&"(none)"),
|
||||
with sequence number {sequence:?} and child \
|
||||
{child_browsing_context_id:?}",
|
||||
);
|
||||
|
||||
self.window.send_to_constellation(
|
||||
ScriptToConstellationMessage::FocusAncestorBrowsingContextsForFocusingSteps(
|
||||
child_browsing_context_id,
|
||||
sequence,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#focus-update-steps>
|
||||
pub(crate) fn focus_update_steps(
|
||||
&self,
|
||||
mut new_focus_chain: Vec<FocusableArea>,
|
||||
mut old_focus_chain: Vec<FocusableArea>,
|
||||
new_focus_target: &FocusableArea,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
let new_focus_chain_was_empty = new_focus_chain.is_empty();
|
||||
|
||||
// Step 1: If the last entry in old chain and the last entry in new chain are the same,
|
||||
// pop the last entry from old chain and the last entry from new chain and redo this
|
||||
// step.
|
||||
//
|
||||
// We avoid recursion here.
|
||||
while let (Some(last_new), Some(last_old)) =
|
||||
(new_focus_chain.last(), old_focus_chain.last())
|
||||
{
|
||||
if last_new == last_old {
|
||||
new_focus_chain.pop();
|
||||
old_focus_chain.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the two focus chains are both empty, focus hasn't changed. This isn't in the
|
||||
// specification, but we must do it because we set the focused area to the viewport
|
||||
// before blurring. If no focus changes, that would mean the currently focused element
|
||||
// loses focus.
|
||||
if old_focus_chain.is_empty() && new_focus_chain.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Although the "focusing steps" in the HTML specification say to wait until after firing
|
||||
// the "blur" event to change the currently focused area of the Document, browsers tend
|
||||
// to set it to the viewport before firing the "blur" event.
|
||||
//
|
||||
// See https://github.com/whatwg/html/issues/1569
|
||||
self.set_focused_area(FocusableArea::Viewport);
|
||||
|
||||
// Step 2: For each entry entry in old chain, in order, run these substeps:
|
||||
// Note: `old_focus_chain` might be empty!
|
||||
let last_old_focus_chain_entry = old_focus_chain.len().saturating_sub(1);
|
||||
for (index, entry) in old_focus_chain.iter().enumerate() {
|
||||
// Step 2.1: If entry is an input element, and the change event applies to the element,
|
||||
// and the element does not have a defined activation behavior, and the user has
|
||||
// changed the element's value or its list of selected files while the control was
|
||||
// focused without committing that change (such that it is different to what it was
|
||||
// when the control was first focused), then:
|
||||
// Step 2.1.1: Set entry's user validity to true.
|
||||
// Step 2.1.2: Fire an event named change at the element, with the bubbles attribute initialized to true.
|
||||
// TODO: Implement this.
|
||||
|
||||
// Step 2.2:
|
||||
// - If entry is an element, let blur event target be entry.
|
||||
// - If entry is a Document object, let blur event target be that Document object's
|
||||
// relevant global object.
|
||||
// - Otherwise, let blur event target be null.
|
||||
//
|
||||
// Note: We always send focus and blur events for `<iframe>` elements, but other
|
||||
// browsers only seem to do that conditionally. This needs a bit more research.
|
||||
let blur_event_target = match entry {
|
||||
FocusableArea::Node { node, .. } => Some(node.upcast::<EventTarget>()),
|
||||
FocusableArea::IFrameViewport { iframe_element, .. } => {
|
||||
Some(iframe_element.upcast())
|
||||
},
|
||||
(false, false) => {
|
||||
// Our `Document` doesn't have focus, and we intend to keep it
|
||||
// this way.
|
||||
FocusableArea::Viewport => Some(self.window.upcast::<EventTarget>()),
|
||||
};
|
||||
|
||||
// Step 2.3: If entry is the last entry in old chain, and entry is an Element, and
|
||||
// the last entry in new chain is also an Element, then let related blur target be
|
||||
// the last entry in new chain. Otherwise, let related blur target be null.
|
||||
//
|
||||
// Note: This can only happen when the focused `Document` doesn't change and we are
|
||||
// moving focus from one element to another. These elements are the last in the chain
|
||||
// because of the popping we do at the start of these steps.
|
||||
let related_blur_target = match new_focus_chain.last() {
|
||||
Some(FocusableArea::Node { node, .. })
|
||||
if index == last_old_focus_chain_entry &&
|
||||
matches!(entry, FocusableArea::Node { .. }) =>
|
||||
{
|
||||
Some(node.upcast())
|
||||
},
|
||||
(true, false) => {
|
||||
unreachable!(
|
||||
"Can't lose the document's focus without specifying \
|
||||
another one to focus"
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Step 2.4: If blur event target is not null, fire a focus event named blur at
|
||||
// blur event target, with related blur target as the related target.
|
||||
if let Some(blur_event_target) = blur_event_target {
|
||||
self.fire_focus_event(
|
||||
FocusEventType::Blur,
|
||||
blur_event_target,
|
||||
related_blur_target,
|
||||
can_gc,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Apply any relevant platform-specific conventions for focusing new focus
|
||||
// target. (For example, some platforms select the contents of a text control when that
|
||||
// control is focused.)
|
||||
if &*self.focused_area() != new_focus_target {
|
||||
if let Some(html_element) = new_focus_target
|
||||
.element()
|
||||
.and_then(|element| element.downcast::<HTMLElement>())
|
||||
{
|
||||
html_element.handle_focus_state_for_contenteditable(can_gc);
|
||||
}
|
||||
}
|
||||
|
||||
self.set_has_focus(!new_focus_chain_was_empty);
|
||||
|
||||
// Step 4: For each entry entry in new chain, in reverse order, run these substeps:
|
||||
// Note: `new_focus_chain` might be empty!
|
||||
let last_new_focus_chain_entry = new_focus_chain.len().saturating_sub(1); // Might be empty, so calculated here.
|
||||
for (index, entry) in new_focus_chain.iter().enumerate().rev() {
|
||||
// Step 4.1: If entry is a focusable area, and the focused area of the document is
|
||||
// not entry:
|
||||
//
|
||||
// Here we deviate from the specification a bit, as all focus chain elements are
|
||||
// focusable areas currently. We just assume that it means the first entry of the
|
||||
// chain, which is the new focus target
|
||||
if index == 0 {
|
||||
// Step 4.1.1: Set document's relevant global object's navigation API's focus
|
||||
// changed during ongoing navigation to true.
|
||||
// TODO: Implement this.
|
||||
|
||||
// Step 4.1.2: Designate entry as the focused area of the document.
|
||||
self.set_focused_area(entry.clone());
|
||||
}
|
||||
|
||||
// Step 4.2:
|
||||
// - If entry is an element, let focus event target be entry.
|
||||
// - If entry is a Document object, let focus event target be that Document
|
||||
// object's relevant global object.
|
||||
// - Otherwise, let focus event target be null.
|
||||
//
|
||||
// Note: We always send focus and blur events for `<iframe>` elements, but other
|
||||
// browsers only seem to do that conditionally. This needs a bit more research.
|
||||
let focus_event_target = match entry {
|
||||
FocusableArea::Node { node, .. } => Some(node.upcast::<EventTarget>()),
|
||||
FocusableArea::IFrameViewport { iframe_element, .. } => {
|
||||
Some(iframe_element.upcast())
|
||||
},
|
||||
FocusableArea::Viewport => Some(self.window.upcast::<EventTarget>()),
|
||||
};
|
||||
|
||||
// Step 4.3: If entry is the last entry in new chain, and entry is an Element, and
|
||||
// the last entry in old chain is also an Element, then let related focus target be
|
||||
// the last entry in old chain. Otherwise, let related focus target be null.
|
||||
//
|
||||
// Note: This can only happen when the focused `Document` doesn't change and we are
|
||||
// moving focus from one element to another. These elements are the last in the chain
|
||||
// because of the popping we do at the start of these steps.
|
||||
let related_focus_target = match old_focus_chain.last() {
|
||||
Some(FocusableArea::Node { node, .. })
|
||||
if index == last_new_focus_chain_entry &&
|
||||
matches!(entry, FocusableArea::Node { .. }) =>
|
||||
{
|
||||
Some(node.upcast())
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Step 4.4: If focus event target is not null, fire a focus event named focus at
|
||||
// focus event target, with related focus target as the related target.
|
||||
if let Some(focus_event_target) = focus_event_target {
|
||||
self.fire_focus_event(
|
||||
FocusEventType::Focus,
|
||||
focus_event_target,
|
||||
related_focus_target,
|
||||
can_gc,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#fire-a-focus-event>
|
||||
fn fire_focus_event(
|
||||
pub(crate) fn fire_focus_event(
|
||||
&self,
|
||||
focus_event_type: FocusEventType,
|
||||
event_target: &EventTarget,
|
||||
related_target: Option<&FocusableArea>,
|
||||
related_target: Option<&EventTarget>,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
let (event_name, does_bubble) = match focus_event_type {
|
||||
FocusEventType::Focus => ("focus".into(), EventBubbles::DoesNotBubble),
|
||||
FocusEventType::Blur => ("blur".into(), EventBubbles::DoesNotBubble),
|
||||
let event_name = match focus_event_type {
|
||||
FocusEventType::Focus => "focus".into(),
|
||||
FocusEventType::Blur => "blur".into(),
|
||||
};
|
||||
let related_target_element =
|
||||
related_target.and_then(|focusable_area| focusable_area.element());
|
||||
|
||||
let event = FocusEvent::new(
|
||||
&self.window,
|
||||
event_name,
|
||||
does_bubble,
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable,
|
||||
Some(&self.window),
|
||||
0i32,
|
||||
related_target_element.map(|element| element.upcast()),
|
||||
related_target,
|
||||
can_gc,
|
||||
);
|
||||
let event = event.upcast::<Event>();
|
||||
@@ -377,10 +509,6 @@ impl DocumentFocusHandler {
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.focus(
|
||||
FocusOperation::Focus(FocusableArea::Viewport),
|
||||
FocusInitiator::Local,
|
||||
can_gc,
|
||||
);
|
||||
self.focus(FocusableArea::Viewport, can_gc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ use crate::dom::css::cssstyledeclaration::{
|
||||
};
|
||||
use crate::dom::customelementregistry::{CallbackReaction, CustomElementState};
|
||||
use crate::dom::document::Document;
|
||||
use crate::dom::document::focus::{FocusInitiator, FocusOperation, FocusableArea};
|
||||
use crate::dom::document::focus::FocusableArea;
|
||||
use crate::dom::document_event_handler::character_to_code;
|
||||
use crate::dom::documentfragment::DocumentFragment;
|
||||
use crate::dom::domstringmap::DOMStringMap;
|
||||
@@ -498,12 +498,10 @@ impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
|
||||
if !self.as_element().focus_state() {
|
||||
return;
|
||||
}
|
||||
// https://html.spec.whatwg.org/multipage/#unfocusing-steps
|
||||
self.owner_document().focus_handler().focus(
|
||||
FocusOperation::Focus(FocusableArea::Viewport),
|
||||
FocusInitiator::Local,
|
||||
can_gc,
|
||||
);
|
||||
// <https://html.spec.whatwg.org/multipage/#unfocusing-steps>
|
||||
self.owner_document()
|
||||
.focus_handler()
|
||||
.focus(FocusableArea::Viewport, can_gc);
|
||||
}
|
||||
|
||||
/// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-scrollparent>
|
||||
@@ -1348,7 +1346,7 @@ impl VirtualMethods for HTMLElement {
|
||||
{
|
||||
document
|
||||
.focus_handler()
|
||||
.set_focused_element(FocusableArea::Viewport);
|
||||
.set_focused_area(FocusableArea::Viewport);
|
||||
}
|
||||
|
||||
// 3. If removedNode is an element whose namespace is the HTML namespace, and this standard
|
||||
|
||||
@@ -7,10 +7,8 @@ use script_bindings::inheritance::Castable;
|
||||
use script_bindings::root::DomRoot;
|
||||
use script_bindings::script_runtime::CanGc;
|
||||
|
||||
use crate::dom::document::focus::{
|
||||
FocusInitiator, FocusOperation, FocusableArea, FocusableAreaKind,
|
||||
};
|
||||
use crate::dom::types::{Element, HTMLDialogElement};
|
||||
use crate::dom::document::focus::{FocusableArea, FocusableAreaKind};
|
||||
use crate::dom::types::{Element, HTMLDialogElement, HTMLIFrameElement};
|
||||
use crate::dom::{Node, NodeTraits, ShadowIncluding};
|
||||
|
||||
impl Node {
|
||||
@@ -44,6 +42,13 @@ impl Node {
|
||||
.map(Element::focusable_area_kind)
|
||||
.unwrap_or_default();
|
||||
if !kind.is_empty() {
|
||||
if let Some(iframe_element) = self.downcast::<HTMLIFrameElement>() {
|
||||
return Some(FocusableArea::IFrameViewport {
|
||||
iframe_element: DomRoot::from_ref(iframe_element),
|
||||
kind,
|
||||
});
|
||||
}
|
||||
|
||||
return Some(FocusableArea::Node {
|
||||
node: DomRoot::from_ref(self),
|
||||
kind,
|
||||
@@ -227,11 +232,7 @@ impl Node {
|
||||
// TODO: Handle all of these steps by converting the focus transaction code to follow
|
||||
// the HTML focus specification.
|
||||
let document = self.owner_document();
|
||||
document.focus_handler().focus(
|
||||
FocusOperation::Focus(focusable_area),
|
||||
FocusInitiator::Local,
|
||||
can_gc,
|
||||
);
|
||||
document.focus_handler().focus(focusable_area, can_gc);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::dom::css::cssstyledeclaration::{
|
||||
CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
|
||||
};
|
||||
use crate::dom::document::Document;
|
||||
use crate::dom::document::focus::{FocusInitiator, FocusOperation, FocusableArea};
|
||||
use crate::dom::document::focus::FocusableArea;
|
||||
use crate::dom::element::{AttributeMutation, Element};
|
||||
use crate::dom::node::{Node, NodeTraits};
|
||||
use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement};
|
||||
@@ -187,12 +187,10 @@ impl SVGElementMethods<crate::DomTypeHolder> for SVGElement {
|
||||
if !self.as_element().focus_state() {
|
||||
return;
|
||||
}
|
||||
// https://html.spec.whatwg.org/multipage/#unfocusing-steps
|
||||
self.owner_document().focus_handler().focus(
|
||||
FocusOperation::Focus(FocusableArea::Viewport),
|
||||
FocusInitiator::Local,
|
||||
CanGc::from_cx(cx),
|
||||
);
|
||||
// <https://html.spec.whatwg.org/multipage/#unfocusing-steps>
|
||||
self.owner_document()
|
||||
.focus_handler()
|
||||
.focus(FocusableArea::Viewport, CanGc::from_cx(cx));
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
|
||||
|
||||
@@ -145,7 +145,7 @@ use crate::dom::css::cssstyledeclaration::{
|
||||
CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
|
||||
};
|
||||
use crate::dom::customelementregistry::CustomElementRegistry;
|
||||
use crate::dom::document::focus::{FocusInitiator, FocusOperation, FocusableArea};
|
||||
use crate::dom::document::focus::FocusableArea;
|
||||
use crate::dom::document::{
|
||||
AnimationFrameCallback, Document, SameOriginDescendantNavigablesIterator,
|
||||
};
|
||||
@@ -1298,11 +1298,9 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
|
||||
// TODO: Implement this.
|
||||
|
||||
// Step 4. Run the focusing steps with current.
|
||||
document.focus_handler().focus(
|
||||
FocusOperation::Focus(FocusableArea::Viewport),
|
||||
FocusInitiator::Local,
|
||||
CanGc::from_cx(cx),
|
||||
);
|
||||
document
|
||||
.focus_handler()
|
||||
.focus(FocusableArea::Viewport, CanGc::from_cx(cx));
|
||||
|
||||
// Step 5. If current is a top-level traversable, user agents are encouraged to trigger some
|
||||
// sort of notification to indicate to the user that the page is attempting to gain focus.
|
||||
|
||||
@@ -128,7 +128,7 @@ use crate::dom::csp::{CspReporting, GlobalCspReporting, Violation};
|
||||
use crate::dom::customelementregistry::{
|
||||
CallbackReaction, CustomElementDefinition, CustomElementReactionStack,
|
||||
};
|
||||
use crate::dom::document::focus::{FocusInitiator, FocusOperation, FocusableArea};
|
||||
use crate::dom::document::focus::FocusableArea;
|
||||
use crate::dom::document::{
|
||||
Document, DocumentSource, HasBrowsingContext, IsHTMLDocument, RenderingUpdateReason,
|
||||
};
|
||||
@@ -2867,21 +2867,28 @@ impl ScriptThread {
|
||||
return;
|
||||
}
|
||||
|
||||
let focusable_area = browsing_context_id
|
||||
.and_then(|browsing_context_id| {
|
||||
// This is separate from the next few lines in order to drop the borrow
|
||||
// on `document.iframes()`.
|
||||
let iframe_element = browsing_context_id.and_then(|browsing_context_id| {
|
||||
document
|
||||
.iframes()
|
||||
.get(browsing_context_id)
|
||||
.map(|iframe| FocusableArea::Node {
|
||||
node: DomRoot::from_ref(iframe.element.upcast()),
|
||||
kind: Default::default(),
|
||||
})
|
||||
.map(|iframe| iframe.element.as_rooted())
|
||||
});
|
||||
let focusable_area = iframe_element
|
||||
.map(|iframe_element| {
|
||||
let kind = iframe_element.upcast::<Element>().focusable_area_kind();
|
||||
FocusableArea::IFrameViewport {
|
||||
iframe_element,
|
||||
kind,
|
||||
}
|
||||
})
|
||||
.unwrap_or(FocusableArea::Viewport);
|
||||
|
||||
focus_handler.focus(
|
||||
FocusOperation::Focus(focusable_area),
|
||||
FocusInitiator::Remote,
|
||||
focus_handler.focus_update_steps(
|
||||
focusable_area.focus_chain(),
|
||||
focus_handler.current_focus_chain(),
|
||||
&focusable_area,
|
||||
CanGc::from_cx(cx),
|
||||
);
|
||||
}
|
||||
@@ -2907,7 +2914,8 @@ impl ScriptThread {
|
||||
|
||||
// We ignore unfocus requests for top-level `Document`s as they *always* have focus.
|
||||
// Note that this does not take into account system focus.
|
||||
if document.window().is_top_level() {
|
||||
let window = document.window();
|
||||
if window.is_top_level() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2921,9 +2929,11 @@ impl ScriptThread {
|
||||
);
|
||||
return;
|
||||
}
|
||||
focus_handler.focus(
|
||||
FocusOperation::Unfocus,
|
||||
FocusInitiator::Remote,
|
||||
|
||||
focus_handler.focus_update_steps(
|
||||
vec![],
|
||||
focus_handler.current_focus_chain(),
|
||||
&FocusableArea::Viewport,
|
||||
CanGc::from_cx(cx),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user