Improve inter-document focus handling (#36649)

*Describe the changes that this pull request makes here. This will be
the commit message.*
rewritten the PR #28571
Implement
[Window#focus](https://html.spec.whatwg.org/multipage/#dom-window-focus),
[Window#blur](https://html.spec.whatwg.org/multipage/#dom-window-blur)
Testing: WPT
Fixes: #8981 #9421

---------

Signed-off-by: kongbai1996 <1782765876@qq.com>
Co-authored-by: yvt <i@yvt.jp>
This commit is contained in:
Fuguo 2025-04-30 12:37:53 +08:00 committed by GitHub
parent 27570987fd
commit 0c0ee04b8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1123 additions and 242 deletions

View file

@ -15,7 +15,8 @@ use base::id::{
use canvas_traits::canvas::{CanvasId, CanvasMsg};
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId};
use embedder_traits::{
AnimationState, EmbedderMsg, MediaSessionEvent, TouchEventResult, ViewportDetails,
AnimationState, EmbedderMsg, FocusSequenceNumber, MediaSessionEvent, TouchEventResult,
ViewportDetails,
};
use euclid::default::Size2D as UntypedSize2D;
use http::{HeaderMap, Method};
@ -519,8 +520,21 @@ pub enum ScriptToConstellationMessage {
UntypedSize2D<u64>,
IpcSender<(IpcSender<CanvasMsg>, CanvasId, ImageKey)>,
),
/// Notifies the constellation that this frame has received focus.
Focus,
/// Notifies the constellation that this pipeline is requesting focus.
///
/// When this message is sent, the sender pipeline has already its local
/// focus state updated. The constellation, after receiving this message,
/// will broadcast messages to other pipelines that are affected by this
/// focus operation.
///
/// The first field contains the browsing context ID of the container
/// element if one was focused.
///
/// The second field is a sequence number that the constellation should use
/// when sending a focus-related message to the sender pipeline next time.
Focus(Option<BrowsingContextId>, FocusSequenceNumber),
/// Requests the constellation to focus the specified browsing context.
FocusRemoteDocument(BrowsingContextId),
/// Get the top-level browsing context info for a given browsing context.
GetTopForBrowsingContext(BrowsingContextId, IpcSender<Option<WebViewId>>),
/// Get the browsing context id of the browsing context in which pipeline is

View file

@ -14,7 +14,7 @@ pub mod user_content_manager;
mod webdriver;
use std::ffi::c_void;
use std::fmt::{Debug, Error, Formatter};
use std::fmt::{Debug, Display, Error, Formatter};
use std::path::PathBuf;
use std::sync::Arc;
@ -784,3 +784,76 @@ pub enum AnimationState {
/// No animations are active but callbacks are queued
NoAnimationCallbacksPresent,
}
/// A sequence number generated by a script thread for its pipelines. The
/// constellation attaches the target pipeline's last seen `FocusSequenceNumber`
/// to every focus-related message it sends.
///
/// This is used to resolve the inconsistency that occurs due to bidirectional
/// focus state synchronization and provide eventual consistency. Example:
///
/// ```text
/// script constellation
/// -----------------------------------------------------------------------
/// send ActivateDocument ----------> receive ActivateDocument
/// ,---- send FocusDocument
/// |
/// focus an iframe |
/// send Focus -----------------|---> receive Focus
/// | focus the iframe's content document
/// receive FocusDocument <-----' send FocusDocument to the content pipeline --> ...
/// unfocus the iframe
/// focus the document
///
/// Final state: Final state:
/// the iframe is not focused the iframe is focused
/// ```
///
/// When the above sequence completes, from the script thread's point of view,
/// the iframe is unfocused, but from the constellation's point of view, the
/// iframe is still focused.
///
/// This inconsistency can be resolved by associating a sequence number to each
/// message. Whenever a script thread initiates a focus operation, it generates
/// and sends a brand new sequence number. The constellation attaches the
/// last-received sequence number to each message it sends. This way, the script
/// thread can discard out-dated incoming focus messages, and eventually, all
/// actors converge to the consistent state which is determined based on the
/// last focus message received by the constellation.
///
/// ```text
/// script constellation
/// -----------------------------------------------------------------------
/// send ActivateDocument ----------> receive ActivateDocument
/// ,---- send FocusDocument (0)
/// |
/// seq_number += 1 |
/// focus an iframe |
/// send Focus (1) -------------|---> receive Focus (1)
/// | focus the iframe's content document
/// receive FocusDocument (0) <-' send FocusDocument to the content pipeline --> ...
/// ignore it because 0 < 1
///
/// Final state: Final state:
/// the iframe is focused the iframe is focused
/// ```
#[derive(
Clone,
Copy,
Debug,
Default,
Deserialize,
Eq,
Hash,
MallocSizeOf,
PartialEq,
Serialize,
PartialOrd,
)]
pub struct FocusSequenceNumber(pub u64);
impl Display for FocusSequenceNumber {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
Display::fmt(&self.0, f)
}
}

View file

@ -27,8 +27,8 @@ use crossbeam_channel::{RecvTimeoutError, Sender};
use devtools_traits::ScriptToDevtoolsControlMsg;
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{
CompositorHitTestResult, InputEvent, MediaSessionActionType, Theme, ViewportDetails,
WebDriverScriptCommand,
CompositorHitTestResult, FocusSequenceNumber, InputEvent, MediaSessionActionType, Theme,
ViewportDetails, WebDriverScriptCommand,
};
use euclid::{Rect, Scale, Size2D, UnknownUnit};
use ipc_channel::ipc::{IpcReceiver, IpcSender};
@ -191,7 +191,15 @@ pub enum ScriptThreadMessage {
RemoveHistoryStates(PipelineId, Vec<HistoryStateId>),
/// Set an iframe to be focused. Used when an element in an iframe gains focus.
/// PipelineId is for the parent, BrowsingContextId is for the nested browsing context
FocusIFrame(PipelineId, BrowsingContextId),
FocusIFrame(PipelineId, BrowsingContextId, FocusSequenceNumber),
/// Focus the document. Used when the container gains focus.
FocusDocument(PipelineId, FocusSequenceNumber),
/// Notifies that the document's container (e.g., an iframe) is not included
/// in the top-level browsing context's focus chain (not considering system
/// focus) anymore.
///
/// Obviously, this message is invalid for a top-level document.
Unfocus(PipelineId, FocusSequenceNumber),
/// Passes a webdriver command to the script thread for execution
WebDriverScriptCommand(PipelineId, WebDriverScriptCommand),
/// Notifies script thread that all animations are done