mirror of
https://github.com/servo/servo.git
synced 2025-07-19 13:23:46 +01:00
webdriver: Element click waits for navigation complete (#37935)
Step 11 in https://w3c.github.io/webdriver/#dfn-element-click > [Try](https://w3c.github.io/webdriver/#dfn-try) to [wait for navigation to complete](https://w3c.github.io/webdriver/#dfn-wait-for-navigation-to-complete) with session. This fixes issue in which element_click triggers navigation, but incoming commands still interact with old page. Testing: https://github.com/longvatrong111/servo/actions/runs/16175767947 https://github.com/longvatrong111/servo/actions/runs/16175770044 Signed-off-by: batu_hoang <longvatrong111@gmail.com>
This commit is contained in:
parent
ff02fdad6d
commit
f155c95e1b
6 changed files with 137 additions and 63 deletions
|
@ -2499,6 +2499,13 @@ impl ScriptThread {
|
||||||
reply,
|
reply,
|
||||||
can_gc,
|
can_gc,
|
||||||
),
|
),
|
||||||
|
WebDriverScriptCommand::IsDocumentReadyStateComplete(response_sender) => {
|
||||||
|
webdriver_handlers::handle_try_wait_for_document_navigation(
|
||||||
|
&documents,
|
||||||
|
pipeline_id,
|
||||||
|
response_sender,
|
||||||
|
)
|
||||||
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,9 @@ 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::DocumentMethods;
|
use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
|
||||||
|
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;
|
||||||
|
@ -1717,3 +1719,20 @@ pub(crate) fn handle_is_selected(
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_try_wait_for_document_navigation(
|
||||||
|
documents: &DocumentCollection,
|
||||||
|
pipeline: PipelineId,
|
||||||
|
reply: IpcSender<bool>,
|
||||||
|
) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -127,6 +127,8 @@ pub enum WebDriverCommandMsg {
|
||||||
IpcSender<Result<(), ()>>,
|
IpcSender<Result<(), ()>>,
|
||||||
),
|
),
|
||||||
GetAlertText(WebViewId, IpcSender<Result<String, ()>>),
|
GetAlertText(WebViewId, IpcSender<Result<String, ()>>),
|
||||||
|
AddLoadStatusSender(WebViewId, IpcSender<WebDriverLoadStatus>),
|
||||||
|
RemoveLoadStatusSender(WebViewId),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
@ -202,6 +204,7 @@ pub enum WebDriverScriptCommand {
|
||||||
GetTitle(IpcSender<String>),
|
GetTitle(IpcSender<String>),
|
||||||
/// Match the element type before sending the event for webdriver `element send keys`.
|
/// Match the element type before sending the event for webdriver `element send keys`.
|
||||||
WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>),
|
WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>),
|
||||||
|
IsDocumentReadyStateComplete(IpcSender<bool>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
|
|
@ -1871,9 +1871,11 @@ impl Handler {
|
||||||
Ok(WebDriverResponse::Void)
|
Ok(WebDriverResponse::Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/webdriver/#element-click
|
/// <https://w3c.github.io/webdriver/#element-click>
|
||||||
fn handle_element_click(&mut self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
|
fn handle_element_click(&mut self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
|
||||||
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);
|
||||||
|
@ -1882,72 +1884,36 @@ impl Handler {
|
||||||
match wait_for_script_response(receiver)? {
|
match wait_for_script_response(receiver)? {
|
||||||
Ok(element_id) => match element_id {
|
Ok(element_id) => match element_id {
|
||||||
Some(element_id) => {
|
Some(element_id) => {
|
||||||
let id = Uuid::new_v4().to_string();
|
// Load status sender should be set up before we dispatch actions
|
||||||
// Step 8 for elements other than <option>
|
self.send_message_to_embedder(WebDriverCommandMsg::AddLoadStatusSender(
|
||||||
// Step 8.1
|
webview_id,
|
||||||
self.session_mut()?.input_state_table.borrow_mut().insert(
|
self.load_status_sender.clone(),
|
||||||
id.clone(),
|
))?;
|
||||||
InputSourceState::Pointer(PointerInputState::new(PointerType::Mouse)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Step 8.7. Construct a pointer move action.
|
self.perform_element_click(element_id)?;
|
||||||
// Step 8.8. Set a property x to 0 on pointer move action.
|
|
||||||
// Step 8.9. Set a property y to 0 on pointer move action.
|
|
||||||
// Step 8.10. Set a property origin to element on pointer move action.
|
|
||||||
let pointer_move_action = PointerMoveAction {
|
|
||||||
duration: None,
|
|
||||||
origin: PointerOrigin::Element(WebElement(element_id)),
|
|
||||||
x: 0.0,
|
|
||||||
y: 0.0,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 8.11. Construct pointer down action.
|
// Step 11. Try to wait for navigation to complete with session.
|
||||||
// Step 8.12. Set a property button to 0 on pointer down action.
|
// The most reliable way to try to wait for a potential navigation
|
||||||
let pointer_down_action = PointerDownAction {
|
// which is caused by element click to check with script thread
|
||||||
button: i16::from(MouseButton::Left) as u64,
|
let (sender, receiver) = ipc::channel().unwrap();
|
||||||
..Default::default()
|
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
|
||||||
};
|
browsing_context_id,
|
||||||
|
WebDriverScriptCommand::IsDocumentReadyStateComplete(sender),
|
||||||
|
))?;
|
||||||
|
|
||||||
// Step 8.13. Construct pointer up action.
|
if wait_for_script_response(receiver)? {
|
||||||
// Step 8.14. Set a property button to 0 on pointer up action.
|
self.load_status_receiver.recv().map_err(|_| {
|
||||||
let pointer_up_action = PointerUpAction {
|
WebDriverError::new(
|
||||||
button: i16::from(MouseButton::Left) as u64,
|
ErrorStatus::UnknownError,
|
||||||
..Default::default()
|
"Failed to receive load status",
|
||||||
};
|
)
|
||||||
|
})?;
|
||||||
let action_sequence = ActionSequence {
|
} else {
|
||||||
id: id.clone(),
|
self.send_message_to_embedder(
|
||||||
actions: ActionsType::Pointer {
|
WebDriverCommandMsg::RemoveLoadStatusSender(webview_id),
|
||||||
parameters: PointerActionParameters {
|
)?;
|
||||||
pointer_type: PointerType::Mouse,
|
|
||||||
},
|
|
||||||
actions: vec![
|
|
||||||
PointerActionItem::Pointer(PointerAction::Move(
|
|
||||||
pointer_move_action,
|
|
||||||
)),
|
|
||||||
PointerActionItem::Pointer(PointerAction::Down(
|
|
||||||
pointer_down_action,
|
|
||||||
)),
|
|
||||||
PointerActionItem::Pointer(PointerAction::Up(pointer_up_action)),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 8.16. Dispatch a list of actions with session's current browsing context
|
|
||||||
let actions_by_tick = self.actions_by_tick_from_sequence(vec![action_sequence]);
|
|
||||||
if let Err(e) =
|
|
||||||
self.dispatch_actions(actions_by_tick, self.session()?.browsing_context_id)
|
|
||||||
{
|
|
||||||
log::error!("handle_element_click: dispatch_actions failed: {:?}", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 8.17 Remove an input source with input state and input id.
|
|
||||||
self.session_mut()?
|
|
||||||
.input_state_table
|
|
||||||
.borrow_mut()
|
|
||||||
.remove(&id);
|
|
||||||
|
|
||||||
// Step 13
|
// Step 13
|
||||||
Ok(WebDriverResponse::Void)
|
Ok(WebDriverResponse::Void)
|
||||||
},
|
},
|
||||||
|
@ -1958,6 +1924,72 @@ impl Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn perform_element_click(&mut self, element: String) -> WebDriverResult<WebDriverResponse> {
|
||||||
|
// Step 8 for elements other than <option>
|
||||||
|
let id = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
// Step 8.1
|
||||||
|
self.session_mut()?.input_state_table.borrow_mut().insert(
|
||||||
|
id.clone(),
|
||||||
|
InputSourceState::Pointer(PointerInputState::new(PointerType::Mouse)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 8.7. Construct a pointer move action.
|
||||||
|
// Step 8.8. Set a property x to 0 on pointer move action.
|
||||||
|
// Step 8.9. Set a property y to 0 on pointer move action.
|
||||||
|
// Step 8.10. Set a property origin to element on pointer move action.
|
||||||
|
let pointer_move_action = PointerMoveAction {
|
||||||
|
duration: None,
|
||||||
|
origin: PointerOrigin::Element(WebElement(element)),
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 8.11. Construct pointer down action.
|
||||||
|
// Step 8.12. Set a property button to 0 on pointer down action.
|
||||||
|
let pointer_down_action = PointerDownAction {
|
||||||
|
button: i16::from(MouseButton::Left) as u64,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 8.13. Construct pointer up action.
|
||||||
|
// Step 8.14. Set a property button to 0 on pointer up action.
|
||||||
|
let pointer_up_action = PointerUpAction {
|
||||||
|
button: i16::from(MouseButton::Left) as u64,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let action_sequence = ActionSequence {
|
||||||
|
id: id.clone(),
|
||||||
|
actions: ActionsType::Pointer {
|
||||||
|
parameters: PointerActionParameters {
|
||||||
|
pointer_type: PointerType::Mouse,
|
||||||
|
},
|
||||||
|
actions: vec![
|
||||||
|
PointerActionItem::Pointer(PointerAction::Move(pointer_move_action)),
|
||||||
|
PointerActionItem::Pointer(PointerAction::Down(pointer_down_action)),
|
||||||
|
PointerActionItem::Pointer(PointerAction::Up(pointer_up_action)),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 8.16. Dispatch a list of actions with session's current browsing context
|
||||||
|
let actions_by_tick = self.actions_by_tick_from_sequence(vec![action_sequence]);
|
||||||
|
if let Err(e) = self.dispatch_actions(actions_by_tick, self.session()?.browsing_context_id)
|
||||||
|
{
|
||||||
|
log::error!("handle_element_click: dispatch_actions failed: {:?}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 8.17 Remove an input source with input state and input id.
|
||||||
|
self.session_mut()?
|
||||||
|
.input_state_table
|
||||||
|
.borrow_mut()
|
||||||
|
.remove(&id);
|
||||||
|
|
||||||
|
Ok(WebDriverResponse::Void)
|
||||||
|
}
|
||||||
|
|
||||||
fn take_screenshot(&self, rect: Option<Rect<f32, CSSPixel>>) -> WebDriverResult<String> {
|
fn take_screenshot(&self, rect: Option<Rect<f32, CSSPixel>>) -> WebDriverResult<String> {
|
||||||
// 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,
|
||||||
// return error with error code no such window.
|
// return error with error code no such window.
|
||||||
|
|
|
@ -454,6 +454,12 @@ 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) {
|
||||||
webview.load(url.into_url());
|
webview.load(url.into_url());
|
||||||
|
|
|
@ -470,6 +470,13 @@ impl RunningAppState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn remove_load_status_sender(&self, webview_id: WebViewId) {
|
||||||
|
self.webdriver_senders
|
||||||
|
.borrow_mut()
|
||||||
|
.load_status_senders
|
||||||
|
.remove(&webview_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ServoShellServoDelegate;
|
struct ServoShellServoDelegate;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue