mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
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:
parent
8b3e7b1c6a
commit
37ac4ffeb4
9 changed files with 188 additions and 125 deletions
|
@ -34,7 +34,7 @@ use embedder_traits::user_content_manager::{UserContentManager, UserScript};
|
|||
use embedder_traits::{
|
||||
AlertResponse, ConfirmResponse, EmbedderMsg, GamepadEvent, GamepadSupportedHapticEffects,
|
||||
GamepadUpdateType, PromptResponse, SimpleDialog, Theme, ViewportDetails, WebDriverJSError,
|
||||
WebDriverJSResult,
|
||||
WebDriverJSResult, WebDriverLoadStatus,
|
||||
};
|
||||
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as UntypedSize2D};
|
||||
use euclid::{Point2D, Scale, Size2D, Vector2D};
|
||||
|
@ -311,6 +311,10 @@ pub(crate) struct Window {
|
|||
#[no_trace]
|
||||
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
|
||||
current_state: Cell<WindowState>,
|
||||
|
||||
|
@ -2641,6 +2645,11 @@ impl Window {
|
|||
// Step 6
|
||||
// TODO: Fragment handling appears to have moved to step 13
|
||||
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(
|
||||
load_data.url.clone(),
|
||||
history_handling,
|
||||
|
@ -2660,6 +2669,9 @@ impl Window {
|
|||
new_url,
|
||||
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()
|
||||
.task_manager()
|
||||
|
@ -2714,6 +2726,10 @@ impl Window {
|
|||
NavigationHistoryBehavior::Push
|
||||
};
|
||||
|
||||
if let Some(sender) = self.webdriver_load_status_sender.borrow().as_ref() {
|
||||
let _ = sender.send(WebDriverLoadStatus::NavigationStart);
|
||||
}
|
||||
|
||||
// Step 13
|
||||
ScriptThread::navigate(pipeline_id, load_data, resolved_history_handling);
|
||||
};
|
||||
|
@ -2845,6 +2861,13 @@ impl Window {
|
|||
*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 {
|
||||
self.current_state.get() == WindowState::Alive
|
||||
}
|
||||
|
@ -3139,6 +3162,7 @@ impl Window {
|
|||
devtools_marker_sender: Default::default(),
|
||||
devtools_markers: Default::default(),
|
||||
webdriver_script_chan: Default::default(),
|
||||
webdriver_load_status_sender: Default::default(),
|
||||
error_reporter,
|
||||
media_query_lists: DOMTracker::new(),
|
||||
#[cfg(feature = "bluetooth")]
|
||||
|
|
|
@ -1091,11 +1091,7 @@ impl ScriptThread {
|
|||
for event in document.take_pending_input_events().into_iter() {
|
||||
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
|
||||
// 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 {
|
||||
match event.event.clone() {
|
||||
InputEvent::MouseButton(mouse_button_event) => {
|
||||
document.handle_mouse_button_event(mouse_button_event, &event, can_gc);
|
||||
},
|
||||
|
@ -1151,21 +1147,37 @@ impl ScriptThread {
|
|||
document.handle_embedder_scroll_event(scroll_event);
|
||||
},
|
||||
}
|
||||
|
||||
self.notify_webdriver_input_event_completed(pipeline_id, event.event, &document);
|
||||
}
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(error) = self.senders.pipeline_to_constellation_sender.send((
|
||||
pipeline_id,
|
||||
ScriptToConstellationMessage::WebDriverInputComplete(id),
|
||||
)) {
|
||||
warn!("ScriptThread failed to send WebDriverInputComplete {id:?}: {error:?}",);
|
||||
}
|
||||
let sender = self.senders.pipeline_to_constellation_sender.clone();
|
||||
|
||||
// Webdriver should be notified once all current dom events have been processed.
|
||||
document
|
||||
.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>
|
||||
|
@ -2516,13 +2528,16 @@ impl ScriptThread {
|
|||
reply,
|
||||
can_gc,
|
||||
),
|
||||
WebDriverScriptCommand::IsDocumentReadyStateComplete(response_sender) => {
|
||||
webdriver_handlers::handle_try_wait_for_document_navigation(
|
||||
WebDriverScriptCommand::AddLoadStatusSender(_, response_sender) => {
|
||||
webdriver_handlers::handle_add_load_status_sender(
|
||||
&documents,
|
||||
pipeline_id,
|
||||
response_sender,
|
||||
)
|
||||
},
|
||||
WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
|
||||
webdriver_handlers::handle_remove_load_status_sender(&documents, pipeline_id)
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,9 @@ use std::ptr::NonNull;
|
|||
|
||||
use base::id::{BrowsingContextId, PipelineId};
|
||||
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 hyper_serde::Serde;
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
|
@ -33,9 +35,7 @@ use webdriver::error::ErrorStatus;
|
|||
use crate::document_collection::DocumentCollection;
|
||||
use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
|
||||
DocumentMethods, DocumentReadyState,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
|
||||
|
@ -1926,19 +1926,23 @@ pub(crate) fn handle_is_selected(
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) fn handle_try_wait_for_document_navigation(
|
||||
pub(crate) fn handle_add_load_status_sender(
|
||||
documents: &DocumentCollection,
|
||||
pipeline: PipelineId,
|
||||
reply: IpcSender<bool>,
|
||||
reply: IpcSender<WebDriverLoadStatus>,
|
||||
) {
|
||||
let document = match documents.find_document(pipeline) {
|
||||
Some(document) => document,
|
||||
None => {
|
||||
return reply.send(false).unwrap();
|
||||
},
|
||||
};
|
||||
|
||||
let wait_for_document_ready = document.ReadyState() != DocumentReadyState::Complete;
|
||||
|
||||
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(Some(reply));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_remove_load_status_sender(
|
||||
documents: &DocumentCollection,
|
||||
pipeline: PipelineId,
|
||||
) {
|
||||
if let Some(document) = documents.find_document(pipeline) {
|
||||
let window = document.window();
|
||||
window.set_webdriver_load_status_sender(None);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue