mirror of
https://github.com/servo/servo.git
synced 2025-08-28 16:48:22 +01:00
webdriver: Implement the "Get Window Handles" command (#38622)
Implment get window handles according to [spec](https://w3c.github.io/webdriver/#dfn-window-handles). - Window handles are supposed to identify `browsing context`. However, based on `get window handle command` and `get window handles command`, we only need to care about top level browsing context. - Back then, we use a random generated uuid for eacch webview id, it is not correct but still work because all commands depend on `webview id` and `browsing context id`. The only case we need window handle is is when webdriver gets window object with js script. Since the object is converted to the id of window's document node, `get window handle` should return the same thing. Action run (with updated expectation): https://github.com/longvatrong111/servo/actions/runs/16957610535 https://github.com/longvatrong111/servo/actions/runs/16957612027 Some tests may sporadically timeout due to unstable hit test. cc: @xiaochengh --------- Signed-off-by: batu_hoang <hoang.binh.trong@huawei.com>
This commit is contained in:
parent
dafb0abf31
commit
f24f225db8
13 changed files with 116 additions and 98 deletions
|
@ -4467,11 +4467,13 @@ where
|
||||||
},
|
},
|
||||||
// TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command
|
// TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command
|
||||||
WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => {
|
WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => {
|
||||||
let pipeline_id = self
|
let pipeline_id = if let Some(browsing_context) =
|
||||||
.browsing_contexts
|
self.browsing_contexts.get(&browsing_context_id)
|
||||||
.get(&browsing_context_id)
|
{
|
||||||
.expect("ScriptCommand: Browsing context must exist at this point")
|
browsing_context.pipeline_id
|
||||||
.pipeline_id;
|
} else {
|
||||||
|
return warn!("{}: Browsing context is not ready", browsing_context_id);
|
||||||
|
};
|
||||||
|
|
||||||
match &cmd {
|
match &cmd {
|
||||||
WebDriverScriptCommand::AddLoadStatusSender(_, sender) => {
|
WebDriverScriptCommand::AddLoadStatusSender(_, sender) => {
|
||||||
|
|
|
@ -2431,6 +2431,9 @@ impl ScriptThread {
|
||||||
WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
|
WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
|
||||||
webdriver_handlers::handle_remove_load_status_sender(&documents, pipeline_id)
|
webdriver_handlers::handle_remove_load_status_sender(&documents, pipeline_id)
|
||||||
},
|
},
|
||||||
|
WebDriverScriptCommand::GetWindowHandle(reply) => {
|
||||||
|
webdriver_handlers::handle_get_window_handle(pipeline_id, reply)
|
||||||
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2086,3 +2086,16 @@ pub(crate) fn handle_remove_load_status_sender(
|
||||||
window.set_webdriver_load_status_sender(None);
|
window.set_webdriver_load_status_sender(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_get_window_handle(
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
reply: IpcSender<Result<String, ErrorStatus>>,
|
||||||
|
) {
|
||||||
|
if let Some(res) = ScriptThread::find_document(pipeline_id)
|
||||||
|
.map(|document| document.upcast::<Node>().unique_id(pipeline_id))
|
||||||
|
{
|
||||||
|
reply.send(Ok(res)).ok();
|
||||||
|
} else {
|
||||||
|
reply.send(Err(ErrorStatus::NoSuchWindow)).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -157,6 +157,8 @@ pub enum WebDriverCommandMsg {
|
||||||
FocusWebView(WebViewId, IpcSender<bool>),
|
FocusWebView(WebViewId, IpcSender<bool>),
|
||||||
/// Get focused webview. For now, this is only used when start new session.
|
/// Get focused webview. For now, this is only used when start new session.
|
||||||
GetFocusedWebView(IpcSender<Option<WebViewId>>),
|
GetFocusedWebView(IpcSender<Option<WebViewId>>),
|
||||||
|
/// Get all webviews
|
||||||
|
GetAllWebViews(IpcSender<Result<Vec<WebViewId>, ErrorStatus>>),
|
||||||
/// Check whether top-level browsing context is open.
|
/// Check whether top-level browsing context is open.
|
||||||
IsWebViewOpen(WebViewId, IpcSender<bool>),
|
IsWebViewOpen(WebViewId, IpcSender<bool>),
|
||||||
/// Check whether browsing context is open.
|
/// Check whether browsing context is open.
|
||||||
|
@ -249,6 +251,7 @@ pub enum WebDriverScriptCommand {
|
||||||
WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>),
|
WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>),
|
||||||
AddLoadStatusSender(WebViewId, IpcSender<WebDriverLoadStatus>),
|
AddLoadStatusSender(WebViewId, IpcSender<WebDriverLoadStatus>),
|
||||||
RemoveLoadStatusSender(WebViewId),
|
RemoveLoadStatusSender(WebViewId),
|
||||||
|
GetWindowHandle(IpcSender<Result<String, ErrorStatus>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
|
|
@ -174,6 +174,8 @@ pub struct WebDriverSession {
|
||||||
browsing_context_id: BrowsingContextId,
|
browsing_context_id: BrowsingContextId,
|
||||||
|
|
||||||
/// <https://www.w3.org/TR/webdriver2/#dfn-window-handles>
|
/// <https://www.w3.org/TR/webdriver2/#dfn-window-handles>
|
||||||
|
/// The spec said each browsing context has an associated window handle.
|
||||||
|
/// Actually, each webview has a unique window handle.
|
||||||
window_handles: HashMap<WebViewId, String>,
|
window_handles: HashMap<WebViewId, String>,
|
||||||
|
|
||||||
timeouts: TimeoutsConfiguration,
|
timeouts: TimeoutsConfiguration,
|
||||||
|
@ -193,15 +195,11 @@ pub struct WebDriverSession {
|
||||||
|
|
||||||
impl WebDriverSession {
|
impl WebDriverSession {
|
||||||
pub fn new(browsing_context_id: BrowsingContextId, webview_id: WebViewId) -> WebDriverSession {
|
pub fn new(browsing_context_id: BrowsingContextId, webview_id: WebViewId) -> WebDriverSession {
|
||||||
let mut window_handles = HashMap::new();
|
|
||||||
let handle = Uuid::new_v4().to_string();
|
|
||||||
window_handles.insert(webview_id, handle);
|
|
||||||
|
|
||||||
WebDriverSession {
|
WebDriverSession {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
webview_id,
|
webview_id,
|
||||||
browsing_context_id,
|
browsing_context_id,
|
||||||
window_handles,
|
window_handles: HashMap::new(),
|
||||||
timeouts: TimeoutsConfiguration::default(),
|
timeouts: TimeoutsConfiguration::default(),
|
||||||
page_loading_strategy: PageLoadStrategy::Normal,
|
page_loading_strategy: PageLoadStrategy::Normal,
|
||||||
strict_file_interactability: false,
|
strict_file_interactability: false,
|
||||||
|
@ -615,6 +613,7 @@ impl Handler {
|
||||||
webview_id,
|
webview_id,
|
||||||
browsing_context_id,
|
browsing_context_id,
|
||||||
)?;
|
)?;
|
||||||
|
self.session_mut()?.window_handles = self.get_window_handles()?;
|
||||||
|
|
||||||
// Step 7. Let response be a JSON Object initialized with session's session ID and capabilities
|
// Step 7. Let response be a JSON Object initialized with session's session ID and capabilities
|
||||||
let response = NewSessionResponse::new(session_id.to_string(), Value::Object(capabilities));
|
let response = NewSessionResponse::new(session_id.to_string(), Value::Object(capabilities));
|
||||||
|
@ -758,7 +757,9 @@ impl Handler {
|
||||||
Ok(WebDriverResponse::Void)
|
Ok(WebDriverResponse::Void)
|
||||||
},
|
},
|
||||||
Ok(WebDriverLoadStatus::Complete) |
|
Ok(WebDriverLoadStatus::Complete) |
|
||||||
Ok(WebDriverLoadStatus::NavigationStop) => Ok(WebDriverResponse::Void),
|
Ok(WebDriverLoadStatus::NavigationStop) =>
|
||||||
|
Ok(WebDriverResponse::Void)
|
||||||
|
,
|
||||||
_ => Err(WebDriverError::new(
|
_ => Err(WebDriverError::new(
|
||||||
ErrorStatus::UnknownError,
|
ErrorStatus::UnknownError,
|
||||||
"Unexpected load status received while waiting for document ready state",
|
"Unexpected load status received while waiting for document ready state",
|
||||||
|
@ -1079,12 +1080,19 @@ impl Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://w3c.github.io/webdriver/#get-window-handle>
|
/// <https://w3c.github.io/webdriver/#get-window-handle>
|
||||||
fn handle_window_handle(&self) -> WebDriverResult<WebDriverResponse> {
|
fn handle_window_handle(&mut self) -> WebDriverResult<WebDriverResponse> {
|
||||||
let session = self.session()?;
|
let webview_id = self.session()?.webview_id;
|
||||||
|
let browsing_context_id = self.session()?.browsing_context_id;
|
||||||
|
|
||||||
// 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.
|
||||||
self.verify_top_level_browsing_context_is_open(session.webview_id)?;
|
self.verify_top_level_browsing_context_is_open(webview_id)?;
|
||||||
match session.window_handles.get(&session.webview_id) {
|
let handle = self.get_window_handle(browsing_context_id)?;
|
||||||
|
self.session_mut()?
|
||||||
|
.window_handles
|
||||||
|
.insert(webview_id, handle);
|
||||||
|
|
||||||
|
match self.session()?.window_handles.get(&webview_id) {
|
||||||
Some(handle) => Ok(WebDriverResponse::Generic(ValueResponse(
|
Some(handle) => Ok(WebDriverResponse::Generic(ValueResponse(
|
||||||
serde_json::to_value(handle)?,
|
serde_json::to_value(handle)?,
|
||||||
))),
|
))),
|
||||||
|
@ -1093,18 +1101,59 @@ impl Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://w3c.github.io/webdriver/#get-window-handles>
|
/// <https://w3c.github.io/webdriver/#get-window-handles>
|
||||||
fn handle_window_handles(&self) -> WebDriverResult<WebDriverResponse> {
|
fn handle_window_handles(&mut self) -> WebDriverResult<WebDriverResponse> {
|
||||||
|
self.handle_any_user_prompts(self.session()?.webview_id)?;
|
||||||
|
|
||||||
|
self.session_mut()?.window_handles = self.get_window_handles()?;
|
||||||
|
|
||||||
let handles = self
|
let handles = self
|
||||||
.session()?
|
.session()?
|
||||||
.window_handles
|
.window_handles
|
||||||
.values()
|
.values()
|
||||||
.map(serde_json::to_value)
|
.map(serde_json::to_value)
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
Ok(WebDriverResponse::Generic(ValueResponse(
|
Ok(WebDriverResponse::Generic(ValueResponse(
|
||||||
serde_json::to_value(handles)?,
|
serde_json::to_value(handles)?,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_window_handles(&self) -> WebDriverResult<HashMap<WebViewId, String>> {
|
||||||
|
let (sender, receiver) = ipc::channel().unwrap();
|
||||||
|
self.send_message_to_embedder(WebDriverCommandMsg::GetAllWebViews(sender))?;
|
||||||
|
|
||||||
|
let webviews = match wait_for_ipc_response(receiver)? {
|
||||||
|
Ok(webviews) => webviews,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(WebDriverError::new(
|
||||||
|
ErrorStatus::UnknownError,
|
||||||
|
"Failed to get window handles",
|
||||||
|
));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = HashMap::new();
|
||||||
|
for id in webviews.iter() {
|
||||||
|
let handle = self.get_window_handle(BrowsingContextId::from(*id))?;
|
||||||
|
res.insert(*id, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_window_handle(&self, browsing_context_id: BrowsingContextId) -> WebDriverResult<String> {
|
||||||
|
let (sender, receiver) = ipc::channel().unwrap();
|
||||||
|
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
|
||||||
|
browsing_context_id,
|
||||||
|
WebDriverScriptCommand::GetWindowHandle(sender),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
match wait_for_ipc_response(receiver)? {
|
||||||
|
Ok(handle) => Ok(handle),
|
||||||
|
Err(err) => Err(WebDriverError::new(err, "Failed to get window handle")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <https://w3c.github.io/webdriver/#find-element>
|
/// <https://w3c.github.io/webdriver/#find-element>
|
||||||
fn handle_find_element(
|
fn handle_find_element(
|
||||||
&self,
|
&self,
|
||||||
|
@ -1172,20 +1221,22 @@ impl Handler {
|
||||||
// This MUST be done without invoking the focusing steps.
|
// This MUST be done without invoking the focusing steps.
|
||||||
self.send_message_to_embedder(cmd_msg)?;
|
self.send_message_to_embedder(cmd_msg)?;
|
||||||
|
|
||||||
let mut handle = self.session()?.id.to_string();
|
if let Ok(webview_id) = receiver.recv() {
|
||||||
if let Ok(new_webview_id) = receiver.recv() {
|
let _ = self.wait_for_document_ready_state();
|
||||||
let session = self.session_mut()?;
|
let handle = self.get_window_handle(BrowsingContextId::from(webview_id))?;
|
||||||
let new_handle = Uuid::new_v4().to_string();
|
self.session_mut()?
|
||||||
handle = new_handle.clone();
|
.window_handles
|
||||||
session.window_handles.insert(new_webview_id, new_handle);
|
.insert(webview_id, handle.clone());
|
||||||
|
Ok(WebDriverResponse::NewWindow(NewWindowResponse {
|
||||||
|
handle,
|
||||||
|
typ: "tab".to_string(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(WebDriverError::new(
|
||||||
|
ErrorStatus::UnknownError,
|
||||||
|
"No webview ID received",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.wait_for_document_ready_state();
|
|
||||||
|
|
||||||
Ok(WebDriverResponse::NewWindow(NewWindowResponse {
|
|
||||||
handle,
|
|
||||||
typ: "tab".to_string(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://w3c.github.io/webdriver/#dfn-switch-to-frame>
|
/// <https://w3c.github.io/webdriver/#dfn-switch-to-frame>
|
||||||
|
@ -2464,6 +2515,10 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
|
||||||
) -> WebDriverResult<WebDriverResponse> {
|
) -> WebDriverResult<WebDriverResponse> {
|
||||||
info!("{:?}", msg.command);
|
info!("{:?}", msg.command);
|
||||||
|
|
||||||
|
// Drain the load status receiver to avoid blocking
|
||||||
|
// the command processing.
|
||||||
|
while self.load_status_receiver.try_recv().is_ok() {}
|
||||||
|
|
||||||
// Unless we are trying to create a new session, we need to ensure that a
|
// Unless we are trying to create a new session, we need to ensure that a
|
||||||
// session has previously been created
|
// session has previously been created
|
||||||
match msg.command {
|
match msg.command {
|
||||||
|
|
|
@ -381,6 +381,17 @@ impl App {
|
||||||
running_state.set_pending_focus(focus_id, response_sender);
|
running_state.set_pending_focus(focus_id, response_sender);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
WebDriverCommandMsg::GetAllWebViews(response_sender) => {
|
||||||
|
let webviews = running_state
|
||||||
|
.webviews()
|
||||||
|
.iter()
|
||||||
|
.map(|(id, _)| *id)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if let Err(error) = response_sender.send(Ok(webviews)) {
|
||||||
|
warn!("Failed to send response of GetAllWebViews: {error}");
|
||||||
|
}
|
||||||
|
},
|
||||||
WebDriverCommandMsg::GetWindowRect(_webview_id, response_sender) => {
|
WebDriverCommandMsg::GetWindowRect(_webview_id, response_sender) => {
|
||||||
let window = self
|
let window = self
|
||||||
.windows
|
.windows
|
||||||
|
|
|
@ -1,18 +1,3 @@
|
||||||
[accept.py]
|
[accept.py]
|
||||||
[test_no_top_level_browsing_context]
|
|
||||||
expected: ERROR
|
|
||||||
|
|
||||||
[test_no_browsing_context]
|
|
||||||
expected: ERROR
|
|
||||||
|
|
||||||
[test_accept_alert]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_accept_confirm]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_accept_prompt]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_accept_in_popup_window]
|
[test_accept_in_popup_window]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
|
@ -1,18 +1,3 @@
|
||||||
[dismiss.py]
|
[dismiss.py]
|
||||||
[test_no_top_browsing_context]
|
|
||||||
expected: ERROR
|
|
||||||
|
|
||||||
[test_dismiss_confirm]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_dismiss_prompt]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_dismiss_in_popup_window]
|
[test_dismiss_in_popup_window]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[test_no_browsing_context]
|
|
||||||
expected: ERROR
|
|
||||||
|
|
||||||
[test_dismiss_alert]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
[navigate.py]
|
[navigate.py]
|
||||||
[test_link_from_toplevel_context_with_target[_blank\]]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_link_from_nested_context_with_target[\]]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_link_from_nested_context_with_target[_parent\]]
|
[test_link_from_nested_context_with_target[_parent\]]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[test_link_from_nested_context_with_target[_self\]]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_link_from_nested_context_with_target[_top\]]
|
[test_link_from_nested_context_with_target[_top\]]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[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[_blank\]]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_link_closes_window]
|
[test_link_closes_window]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[window.py]
|
|
||||||
[test_web_reference[window\]]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_window_open]
|
|
||||||
expected: FAIL
|
|
|
@ -1,15 +1,3 @@
|
||||||
[window.py]
|
[window.py]
|
||||||
[test_web_reference[window\]]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_web_reference_in_array[window\]]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_web_reference_in_object[window\]]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_window_open]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_same_id_after_cross_origin_navigation]
|
[test_same_id_after_cross_origin_navigation]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
|
@ -4,6 +4,3 @@
|
||||||
|
|
||||||
[test_control_click[\\ue051-ctrlKey\]]
|
[test_control_click[\\ue051-ctrlKey\]]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[test_release_control_click]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
[test_chained_alert_element_not_interactable[alert\]]
|
[test_chained_alert_element_not_interactable[alert\]]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[test_chained_alert_element_not_interactable[confirm\]]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[test_send_alert_text[\]]
|
[test_send_alert_text[\]]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -16,6 +13,3 @@
|
||||||
|
|
||||||
[test_send_alert_text[Fed\\terer\]]
|
[test_send_alert_text[Fed\\terer\]]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[test_unexpected_alert]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue