Rework on webdriver wait for navigation complete (#38234)

For current implementation, when a command may trigger a navigation,
webdriver only waits for document readiness state.
However, not all navigations make change in document.
This PR handles more cases for waiting for a navigation, and apply to
`element_click`.

- Before sending a command which may trigger a navigation, `webdriver`
sets `load status send` to `embedder`, `constelltation` and `script
thread` to listen to `navigation events`.
- Webdriver check if there is a navigation with `script thread`.
- If the navigation is loading a new url, webdriver checks if the
request is approved with `constellation`, then waits for document
readiness state.
- If the navigation is a hashchange, webdriver waits untill all new
generated dom events have been processed.

Testing: 
`tests/wpt/tests/webdriver/tests/classic/element_click/navigate.py`
`tests/wpt/tests/webdriver/tests/classic/element_click/user_prompts.py`
https://github.com/longvatrong111/servo/actions/runs/16488690749

cc: @xiaochengh

---------

Signed-off-by: batu_hoang <hoang.binh.trong@huawei.com>
This commit is contained in:
batu_hoang 2025-07-30 15:24:07 +08:00 committed by GitHub
parent 8b3e7b1c6a
commit 37ac4ffeb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 188 additions and 125 deletions

View file

@ -133,7 +133,7 @@ use embedder_traits::{
FocusSequenceNumber, InputEvent, JSValue, JavaScriptEvaluationError, JavaScriptEvaluationId,
KeyboardEvent, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState,
MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
WebDriverCommandResponse,
WebDriverCommandResponse, WebDriverLoadStatus, WebDriverScriptCommand,
};
use euclid::Size2D;
use euclid::default::Size2D as UntypedSize2D;
@ -396,6 +396,9 @@ pub struct Constellation<STF, SWF> {
/// and the namespaces are allocated by the constellation.
next_pipeline_namespace_id: PipelineNamespaceId,
/// An [`IpcSender`] to notify navigation events to webdriver.
webdriver_load_status_sender: Option<(IpcSender<WebDriverLoadStatus>, PipelineId)>,
/// An [`IpcSender`] to forward responses from the `ScriptThread` to the WebDriver server.
webdriver_input_command_reponse_sender: Option<IpcSender<WebDriverCommandResponse>>,
@ -666,6 +669,7 @@ where
time_profiler_chan: state.time_profiler_chan,
mem_profiler_chan: state.mem_profiler_chan.clone(),
phantom: PhantomData,
webdriver_load_status_sender: None,
webdriver_input_command_reponse_sender: None,
document_states: HashMap::new(),
#[cfg(feature = "webgpu")]
@ -1254,6 +1258,12 @@ where
if allowed {
self.load_url(webview_id, pipeline_id, load_data, history_handling);
} else {
if let Some((sender, id)) = &self.webdriver_load_status_sender {
if pipeline_id == *id {
let _ = sender.send(WebDriverLoadStatus::NavigationStop);
}
}
let pipeline_is_top_level_pipeline = self
.browsing_contexts
.get(&BrowsingContextId::from(webview_id))
@ -3518,7 +3528,12 @@ where
};
if let Err(e) = result {
self.handle_send_error(parent_pipeline_id, e);
} else if let Some((sender, id)) = &self.webdriver_load_status_sender {
if source_id == *id {
let _ = sender.send(WebDriverLoadStatus::NavigationStop);
}
}
None
},
None => {
@ -4414,6 +4429,17 @@ where
.get(&browsing_context_id)
.expect("ScriptCommand: Browsing context must exist at this point")
.pipeline_id;
match &cmd {
WebDriverScriptCommand::AddLoadStatusSender(_, sender) => {
self.webdriver_load_status_sender = Some((sender.clone(), pipeline_id));
},
WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
self.webdriver_load_status_sender = None;
},
_ => {},
};
let control_msg = ScriptThreadMessage::WebDriverScriptCommand(pipeline_id, cmd);
let result = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline.event_loop.send(control_msg),