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

View file

@ -34,7 +34,7 @@ use embedder_traits::user_content_manager::{UserContentManager, UserScript};
use embedder_traits::{ use embedder_traits::{
AlertResponse, ConfirmResponse, EmbedderMsg, GamepadEvent, GamepadSupportedHapticEffects, AlertResponse, ConfirmResponse, EmbedderMsg, GamepadEvent, GamepadSupportedHapticEffects,
GamepadUpdateType, PromptResponse, SimpleDialog, Theme, ViewportDetails, WebDriverJSError, GamepadUpdateType, PromptResponse, SimpleDialog, Theme, ViewportDetails, WebDriverJSError,
WebDriverJSResult, WebDriverJSResult, WebDriverLoadStatus,
}; };
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as UntypedSize2D}; use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as UntypedSize2D};
use euclid::{Point2D, Scale, Size2D, Vector2D}; use euclid::{Point2D, Scale, Size2D, Vector2D};
@ -311,6 +311,10 @@ pub(crate) struct Window {
#[no_trace] #[no_trace]
webdriver_script_chan: DomRefCell<Option<IpcSender<WebDriverJSResult>>>, webdriver_script_chan: DomRefCell<Option<IpcSender<WebDriverJSResult>>>,
/// A channel to notify webdriver if there is a navigation
#[no_trace]
webdriver_load_status_sender: RefCell<Option<IpcSender<WebDriverLoadStatus>>>,
/// The current state of the window object /// The current state of the window object
current_state: Cell<WindowState>, current_state: Cell<WindowState>,
@ -2641,6 +2645,11 @@ impl Window {
// Step 6 // Step 6
// TODO: Fragment handling appears to have moved to step 13 // TODO: Fragment handling appears to have moved to step 13
if let Some(fragment) = load_data.url.fragment() { if let Some(fragment) = load_data.url.fragment() {
let webdriver_sender = self.webdriver_load_status_sender.borrow().clone();
if let Some(ref sender) = webdriver_sender {
let _ = sender.send(WebDriverLoadStatus::NavigationStart);
}
self.send_to_constellation(ScriptToConstellationMessage::NavigatedToFragment( self.send_to_constellation(ScriptToConstellationMessage::NavigatedToFragment(
load_data.url.clone(), load_data.url.clone(),
history_handling, history_handling,
@ -2660,6 +2669,9 @@ impl Window {
new_url, new_url,
CanGc::note()); CanGc::note());
event.upcast::<Event>().fire(this.upcast::<EventTarget>(), CanGc::note()); event.upcast::<Event>().fire(this.upcast::<EventTarget>(), CanGc::note());
if let Some(sender) = webdriver_sender {
let _ = sender.send(WebDriverLoadStatus::NavigationStop);
}
}); });
self.as_global_scope() self.as_global_scope()
.task_manager() .task_manager()
@ -2714,6 +2726,10 @@ impl Window {
NavigationHistoryBehavior::Push NavigationHistoryBehavior::Push
}; };
if let Some(sender) = self.webdriver_load_status_sender.borrow().as_ref() {
let _ = sender.send(WebDriverLoadStatus::NavigationStart);
}
// Step 13 // Step 13
ScriptThread::navigate(pipeline_id, load_data, resolved_history_handling); ScriptThread::navigate(pipeline_id, load_data, resolved_history_handling);
}; };
@ -2845,6 +2861,13 @@ impl Window {
*self.webdriver_script_chan.borrow_mut() = chan; *self.webdriver_script_chan.borrow_mut() = chan;
} }
pub(crate) fn set_webdriver_load_status_sender(
&self,
sender: Option<IpcSender<WebDriverLoadStatus>>,
) {
*self.webdriver_load_status_sender.borrow_mut() = sender;
}
pub(crate) fn is_alive(&self) -> bool { pub(crate) fn is_alive(&self) -> bool {
self.current_state.get() == WindowState::Alive self.current_state.get() == WindowState::Alive
} }
@ -3139,6 +3162,7 @@ impl Window {
devtools_marker_sender: Default::default(), devtools_marker_sender: Default::default(),
devtools_markers: Default::default(), devtools_markers: Default::default(),
webdriver_script_chan: Default::default(), webdriver_script_chan: Default::default(),
webdriver_load_status_sender: Default::default(),
error_reporter, error_reporter,
media_query_lists: DOMTracker::new(), media_query_lists: DOMTracker::new(),
#[cfg(feature = "bluetooth")] #[cfg(feature = "bluetooth")]

View file

@ -1091,11 +1091,7 @@ impl ScriptThread {
for event in document.take_pending_input_events().into_iter() { for event in document.take_pending_input_events().into_iter() {
document.update_active_keyboard_modifiers(event.active_keyboard_modifiers); document.update_active_keyboard_modifiers(event.active_keyboard_modifiers);
// We do this now, because the event is consumed below, but the order doesn't really match event.event.clone() {
// matter as the event will be handled before any new ScriptThread messages are processed.
self.notify_webdriver_input_event_completed(pipeline_id, &event.event);
match event.event {
InputEvent::MouseButton(mouse_button_event) => { InputEvent::MouseButton(mouse_button_event) => {
document.handle_mouse_button_event(mouse_button_event, &event, can_gc); document.handle_mouse_button_event(mouse_button_event, &event, can_gc);
}, },
@ -1151,21 +1147,37 @@ impl ScriptThread {
document.handle_embedder_scroll_event(scroll_event); document.handle_embedder_scroll_event(scroll_event);
}, },
} }
self.notify_webdriver_input_event_completed(pipeline_id, event.event, &document);
} }
ScriptThread::set_user_interacting(false); ScriptThread::set_user_interacting(false);
} }
fn notify_webdriver_input_event_completed(&self, pipeline_id: PipelineId, event: &InputEvent) { fn notify_webdriver_input_event_completed(
&self,
pipeline_id: PipelineId,
event: InputEvent,
document: &Document,
) {
let Some(id) = event.webdriver_message_id() else { let Some(id) = event.webdriver_message_id() else {
return; return;
}; };
if let Err(error) = self.senders.pipeline_to_constellation_sender.send(( let sender = self.senders.pipeline_to_constellation_sender.clone();
pipeline_id,
ScriptToConstellationMessage::WebDriverInputComplete(id), // Webdriver should be notified once all current dom events have been processed.
)) { document
warn!("ScriptThread failed to send WebDriverInputComplete {id:?}: {error:?}",); .owner_global()
} .task_manager()
.dom_manipulation_task_source()
.queue(task!(notify_webdriver_input_event_completed: move || {
if let Err(error) = sender.send((
pipeline_id,
ScriptToConstellationMessage::WebDriverInputComplete(id),
)) {
warn!("ScriptThread failed to send WebDriverInputComplete {id:?}: {error:?}",);
}
}));
} }
/// <https://html.spec.whatwg.org/multipage/#update-the-rendering> /// <https://html.spec.whatwg.org/multipage/#update-the-rendering>
@ -2516,13 +2528,16 @@ impl ScriptThread {
reply, reply,
can_gc, can_gc,
), ),
WebDriverScriptCommand::IsDocumentReadyStateComplete(response_sender) => { WebDriverScriptCommand::AddLoadStatusSender(_, response_sender) => {
webdriver_handlers::handle_try_wait_for_document_navigation( webdriver_handlers::handle_add_load_status_sender(
&documents, &documents,
pipeline_id, pipeline_id,
response_sender, response_sender,
) )
}, },
WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
webdriver_handlers::handle_remove_load_status_sender(&documents, pipeline_id)
},
_ => (), _ => (),
} }
} }

View file

@ -8,7 +8,9 @@ use std::ptr::NonNull;
use base::id::{BrowsingContextId, PipelineId}; use base::id::{BrowsingContextId, PipelineId};
use cookie::Cookie; use cookie::Cookie;
use embedder_traits::{WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue}; use embedder_traits::{
WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus,
};
use euclid::default::{Point2D, Rect, Size2D}; use euclid::default::{Point2D, Rect, Size2D};
use hyper_serde::Serde; use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::ipc::{self, IpcSender};
@ -33,9 +35,7 @@ use webdriver::error::ErrorStatus;
use crate::document_collection::DocumentCollection; use crate::document_collection::DocumentCollection;
use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods; use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods; use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
DocumentMethods, DocumentReadyState,
};
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
@ -1926,19 +1926,23 @@ pub(crate) fn handle_is_selected(
.unwrap(); .unwrap();
} }
pub(crate) fn handle_try_wait_for_document_navigation( pub(crate) fn handle_add_load_status_sender(
documents: &DocumentCollection, documents: &DocumentCollection,
pipeline: PipelineId, pipeline: PipelineId,
reply: IpcSender<bool>, reply: IpcSender<WebDriverLoadStatus>,
) { ) {
let document = match documents.find_document(pipeline) { if let Some(document) = documents.find_document(pipeline) {
Some(document) => document, let window = document.window();
None => { window.set_webdriver_load_status_sender(Some(reply));
return reply.send(false).unwrap(); }
}, }
};
pub(crate) fn handle_remove_load_status_sender(
let wait_for_document_ready = document.ReadyState() != DocumentReadyState::Complete; documents: &DocumentCollection,
pipeline: PipelineId,
reply.send(wait_for_document_ready).unwrap(); ) {
if let Some(document) = documents.find_document(pipeline) {
let window = document.window();
window.set_webdriver_load_status_sender(None);
}
} }

View file

@ -169,8 +169,6 @@ pub enum WebDriverCommandMsg {
), ),
GetAlertText(WebViewId, IpcSender<Result<String, ()>>), GetAlertText(WebViewId, IpcSender<Result<String, ()>>),
SendAlertText(WebViewId, String), SendAlertText(WebViewId, String),
AddLoadStatusSender(WebViewId, IpcSender<WebDriverLoadStatus>),
RemoveLoadStatusSender(WebViewId),
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
@ -247,7 +245,8 @@ pub enum WebDriverScriptCommand {
GetTitle(IpcSender<String>), GetTitle(IpcSender<String>),
/// Deal with the case of input element for Element Send Keys, which does not send keys. /// Deal with the case of input element for Element Send Keys, which does not send keys.
WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>), WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>),
IsDocumentReadyStateComplete(IpcSender<bool>), AddLoadStatusSender(WebViewId, IpcSender<WebDriverLoadStatus>),
RemoveLoadStatusSender(WebViewId),
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -292,8 +291,13 @@ pub struct WebDriverCommandResponse {
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum WebDriverLoadStatus { pub enum WebDriverLoadStatus {
NavigationStart,
// Navigation stops for any reason
NavigationStop,
// Document ready state is complete
Complete, Complete,
// Load timeout
Timeout, Timeout,
Canceled, // Navigation is blocked by a user prompt
Blocked, Blocked,
} }

View file

@ -499,6 +499,23 @@ impl Handler {
Ok(()) Ok(())
} }
fn add_load_status_sender(&self) -> WebDriverResult<()> {
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
self.session()?.browsing_context_id,
WebDriverScriptCommand::AddLoadStatusSender(
self.session()?.webview_id,
self.load_status_sender.clone(),
),
))
}
fn clear_load_status_sender(&self) -> WebDriverResult<()> {
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
self.session()?.browsing_context_id,
WebDriverScriptCommand::RemoveLoadStatusSender(self.session()?.webview_id),
))
}
// This function is called only if session and webview are verified. // This function is called only if session and webview are verified.
fn verified_webview_id(&self) -> WebViewId { fn verified_webview_id(&self) -> WebViewId {
self.session().unwrap().webview_id self.session().unwrap().webview_id
@ -687,7 +704,7 @@ impl Handler {
self.send_message_to_embedder(cmd_msg)?; self.send_message_to_embedder(cmd_msg)?;
// Step 8.2.1: try to wait for navigation to complete. // Step 8.2.1: try to wait for navigation to complete.
self.wait_for_navigation_to_complete()?; self.wait_for_document_ready_state()?;
// Step 8.3. Set current browsing context with session and current top browsing context // Step 8.3. Set current browsing context with session and current top browsing context
self.session_mut()?.browsing_context_id = BrowsingContextId::from(webview_id); self.session_mut()?.browsing_context_id = BrowsingContextId::from(webview_id);
@ -695,8 +712,7 @@ impl Handler {
Ok(WebDriverResponse::Void) Ok(WebDriverResponse::Void)
} }
/// <https://w3c.github.io/webdriver/#dfn-wait-for-navigation-to-complete> fn wait_for_document_ready_state(&self) -> WebDriverResult<WebDriverResponse> {
fn wait_for_navigation_to_complete(&self) -> WebDriverResult<WebDriverResponse> {
debug!("waiting for load"); debug!("waiting for load");
let session = self.session()?; let session = self.session()?;
@ -723,7 +739,8 @@ impl Handler {
let result = select! { let result = select! {
recv(self.load_status_receiver) -> res => { recv(self.load_status_receiver) -> res => {
match res { match res {
// If the navigation is navigation to IFrame, no document state event is fired.
Ok(WebDriverLoadStatus::Blocked) => { Ok(WebDriverLoadStatus::Blocked) => {
// TODO: evaluate the correctness later // TODO: evaluate the correctness later
// Load status is block means an user prompt is shown. // Load status is block means an user prompt is shown.
@ -732,10 +749,13 @@ impl Handler {
// If user prompt can't be handler, next command returns // If user prompt can't be handler, next command returns
// an error anyway. // an error anyway.
Ok(WebDriverResponse::Void) Ok(WebDriverResponse::Void)
} },
_ => { Ok(WebDriverLoadStatus::Complete) |
Ok(WebDriverResponse::Void) Ok(WebDriverLoadStatus::NavigationStop) => Ok(WebDriverResponse::Void),
} _ => Err(WebDriverError::new(
ErrorStatus::UnknownError,
"Unexpected load status received while waiting for document ready state",
)),
} }
}, },
recv(after(Duration::from_millis(timeout))) -> _ => Err( recv(after(Duration::from_millis(timeout))) -> _ => Err(
@ -746,6 +766,38 @@ impl Handler {
result result
} }
/// <https://w3c.github.io/webdriver/#dfn-wait-for-navigation-to-complete>
fn wait_for_navigation(&self) -> WebDriverResult<WebDriverResponse> {
let navigation_status = match self.load_status_receiver.try_recv() {
Ok(status) => status,
// Empty channel means no navigation started. Nothing to wait for.
Err(crossbeam_channel::TryRecvError::Empty) => {
return Ok(WebDriverResponse::Void);
},
Err(crossbeam_channel::TryRecvError::Disconnected) => {
return Err(WebDriverError::new(
ErrorStatus::UnknownError,
"Load status channel disconnected",
));
},
};
match navigation_status {
WebDriverLoadStatus::NavigationStart => self.wait_for_document_ready_state(),
// If the load status is timeout, return an error
WebDriverLoadStatus::Timeout => Err(WebDriverError::new(
ErrorStatus::Timeout,
"Navigation timed out",
)),
// If the load status is blocked, it means a user prompt is shown.
// We should handle the user prompt in the next command.
WebDriverLoadStatus::Blocked => Ok(WebDriverResponse::Void),
WebDriverLoadStatus::NavigationStop | WebDriverLoadStatus::Complete => {
unreachable!("Unexpected load status received")
},
}
}
/// <https://w3c.github.io/webdriver/#dfn-get-current-url> /// <https://w3c.github.io/webdriver/#dfn-get-current-url>
fn handle_current_url(&self) -> WebDriverResult<WebDriverResponse> { fn handle_current_url(&self) -> WebDriverResult<WebDriverResponse> {
// Step 1. If session's current top-level browsing context is no longer open, // Step 1. If session's current top-level browsing context is no longer open,
@ -938,7 +990,7 @@ impl Handler {
webview_id, webview_id,
self.load_status_sender.clone(), self.load_status_sender.clone(),
))?; ))?;
self.wait_for_navigation_to_complete() self.wait_for_document_ready_state()
} }
/// <https://w3c.github.io/webdriver/#forward> /// <https://w3c.github.io/webdriver/#forward>
@ -955,7 +1007,7 @@ impl Handler {
webview_id, webview_id,
self.load_status_sender.clone(), self.load_status_sender.clone(),
))?; ))?;
self.wait_for_navigation_to_complete() self.wait_for_document_ready_state()
} }
/// <https://w3c.github.io/webdriver/#refresh> /// <https://w3c.github.io/webdriver/#refresh>
@ -972,7 +1024,7 @@ impl Handler {
self.send_message_to_embedder(cmd_msg)?; self.send_message_to_embedder(cmd_msg)?;
// Step 4.1: Try to wait for navigation to complete. // Step 4.1: Try to wait for navigation to complete.
self.wait_for_navigation_to_complete()?; self.wait_for_document_ready_state()?;
// Step 5. Set current browsing context with session and current top browsing context. // Step 5. Set current browsing context with session and current top browsing context.
self.session_mut()?.browsing_context_id = BrowsingContextId::from(webview_id); self.session_mut()?.browsing_context_id = BrowsingContextId::from(webview_id);
@ -1107,7 +1159,7 @@ impl Handler {
session.window_handles.insert(new_webview_id, new_handle); session.window_handles.insert(new_webview_id, new_handle);
} }
let _ = self.wait_for_navigation_to_complete(); let _ = self.wait_for_document_ready_state();
Ok(WebDriverResponse::NewWindow(NewWindowResponse { Ok(WebDriverResponse::NewWindow(NewWindowResponse {
handle, handle,
@ -2088,8 +2140,6 @@ impl Handler {
self.handle_any_user_prompts(self.session()?.webview_id)?; self.handle_any_user_prompts(self.session()?.webview_id)?;
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
let webview_id = self.session()?.webview_id;
let browsing_context_id = self.session()?.browsing_context_id;
// Steps 1 - 7 + Step 8 for <option> // Steps 1 - 7 + Step 8 for <option>
let cmd = WebDriverScriptCommand::ElementClick(element.to_string(), sender); let cmd = WebDriverScriptCommand::ElementClick(element.to_string(), sender);
@ -2099,37 +2149,19 @@ impl Handler {
Ok(element_id) => match element_id { Ok(element_id) => match element_id {
Some(element_id) => { Some(element_id) => {
// Load status sender should be set up before we dispatch actions // Load status sender should be set up before we dispatch actions
self.send_message_to_embedder(WebDriverCommandMsg::AddLoadStatusSender( // to ensure webdriver can capture any navigation events.
webview_id, self.add_load_status_sender()?;
self.load_status_sender.clone(),
))?;
self.perform_element_click(element_id)?; self.perform_element_click(element_id)?;
// Step 11. Try to wait for navigation to complete with session. // Step 11. Try to wait for navigation to complete with session.
// The most reliable way to try to wait for a potential navigation // Check if there is a navigation with script
// which is caused by element click to check with script thread let res = self.wait_for_navigation()?;
let (sender, receiver) = ipc::channel().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
browsing_context_id,
WebDriverScriptCommand::IsDocumentReadyStateComplete(sender),
))?;
if wait_for_script_response(receiver)? { // Clear the load status sender
self.load_status_receiver.recv().map_err(|_| { self.clear_load_status_sender()?;
WebDriverError::new(
ErrorStatus::UnknownError,
"Failed to receive load status",
)
})?;
} else {
self.send_message_to_embedder(
WebDriverCommandMsg::RemoveLoadStatusSender(webview_id),
)?;
}
// Step 13 Ok(res)
Ok(WebDriverResponse::Void)
}, },
// Step 13 // Step 13
None => Ok(WebDriverResponse::Void), None => Ok(WebDriverResponse::Void),

View file

@ -455,12 +455,6 @@ impl App {
warn!("Failed to send response of GetFocusedWebView: {error}"); warn!("Failed to send response of GetFocusedWebView: {error}");
}; };
}, },
WebDriverCommandMsg::AddLoadStatusSender(webview_id, load_status_sender) => {
running_state.set_load_status_sender(webview_id, load_status_sender);
},
WebDriverCommandMsg::RemoveLoadStatusSender(webview_id) => {
running_state.remove_load_status_sender(webview_id);
},
WebDriverCommandMsg::LoadUrl(webview_id, url, load_status_sender) => { WebDriverCommandMsg::LoadUrl(webview_id, url, load_status_sender) => {
if let Some(webview) = running_state.webview_by_id(webview_id) { if let Some(webview) = running_state.webview_by_id(webview_id) {
running_state.set_load_status_sender(webview_id, load_status_sender); running_state.set_load_status_sender(webview_id, load_status_sender);
@ -571,7 +565,7 @@ impl App {
} }
}, },
WebDriverCommandMsg::ScriptCommand(_, ref webdriver_script_command) => { WebDriverCommandMsg::ScriptCommand(_, ref webdriver_script_command) => {
self.handle_webdriver_script_commnd(webdriver_script_command, running_state); self.handle_webdriver_script_command(webdriver_script_command, running_state);
running_state.servo().execute_webdriver_command(msg); running_state.servo().execute_webdriver_command(msg);
}, },
WebDriverCommandMsg::CurrentUserPrompt(webview_id, response_sender) => { WebDriverCommandMsg::CurrentUserPrompt(webview_id, response_sender) => {
@ -627,7 +621,7 @@ impl App {
} }
} }
fn handle_webdriver_script_commnd( fn handle_webdriver_script_command(
&self, &self,
msg: &WebDriverScriptCommand, msg: &WebDriverScriptCommand,
running_state: &RunningAppState, running_state: &RunningAppState,
@ -640,6 +634,12 @@ impl App {
// safely set a new interrupt sender and remove the previous one here. // safely set a new interrupt sender and remove the previous one here.
running_state.set_script_command_interrupt_sender(Some(response_sender.clone())); running_state.set_script_command_interrupt_sender(Some(response_sender.clone()));
}, },
WebDriverScriptCommand::AddLoadStatusSender(webview_id, load_status_sender) => {
running_state.set_load_status_sender(*webview_id, load_status_sender.clone());
},
WebDriverScriptCommand::RemoveLoadStatusSender(webview_id) => {
running_state.remove_load_status_sender(*webview_id);
},
_ => { _ => {
running_state.set_script_command_interrupt_sender(None); running_state.set_script_command_interrupt_sender(None);
}, },

View file

@ -1,13 +1,4 @@
[navigate.py] [navigate.py]
[test_numbers_link]
expected: FAIL
[test_multi_line_link]
expected: FAIL
[test_navigation_retains_input_state]
expected: FAIL
[test_link_hash] [test_link_hash]
expected: FAIL expected: FAIL
@ -17,23 +8,8 @@
[test_link_from_toplevel_context_with_target[_parent\]] [test_link_from_toplevel_context_with_target[_parent\]]
expected: FAIL expected: FAIL
[test_link_from_nested_context_with_target[\]]
expected: FAIL
[test_link_from_nested_context_with_target[_blank\]] [test_link_from_nested_context_with_target[_blank\]]
expected: FAIL expected: FAIL
[test_link_from_nested_context_with_target[_parent\]]
expected: FAIL
[test_link_from_nested_context_with_target[_self\]]
expected: FAIL
[test_link_from_nested_context_with_target[_top\]]
expected: FAIL
[test_link_cross_origin]
expected: FAIL
[test_link_closes_window] [test_link_closes_window]
expected: FAIL expected: FAIL

View file

@ -1,18 +0,0 @@
[user_prompts.py]
[test_accept[beforeunload-None\]]
expected: FAIL
[test_accept_and_notify[beforeunload-None\]]
expected: FAIL
[test_dismiss[beforeunload-None\]]
expected: FAIL
[test_dismiss_and_notify[beforeunload-None\]]
expected: FAIL
[test_ignore[beforeunload\]]
expected: FAIL
[test_default[beforeunload-None\]]
expected: FAIL