diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index a5f63cac31a..c539787a759 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -4467,11 +4467,13 @@ where }, // TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => { - let pipeline_id = self - .browsing_contexts - .get(&browsing_context_id) - .expect("ScriptCommand: Browsing context must exist at this point") - .pipeline_id; + let pipeline_id = if let Some(browsing_context) = + self.browsing_contexts.get(&browsing_context_id) + { + browsing_context.pipeline_id + } else { + return warn!("{}: Browsing context is not ready", browsing_context_id); + }; match &cmd { WebDriverScriptCommand::AddLoadStatusSender(_, sender) => { diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 27041edf8d7..83f69bf9e0e 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -2431,6 +2431,9 @@ impl ScriptThread { WebDriverScriptCommand::RemoveLoadStatusSender(_) => { webdriver_handlers::handle_remove_load_status_sender(&documents, pipeline_id) }, + WebDriverScriptCommand::GetWindowHandle(reply) => { + webdriver_handlers::handle_get_window_handle(pipeline_id, reply) + }, _ => (), } } diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index 737fc784a51..5be9ce33765 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -2086,3 +2086,16 @@ pub(crate) fn handle_remove_load_status_sender( window.set_webdriver_load_status_sender(None); } } + +pub(crate) fn handle_get_window_handle( + pipeline_id: PipelineId, + reply: IpcSender>, +) { + if let Some(res) = ScriptThread::find_document(pipeline_id) + .map(|document| document.upcast::().unique_id(pipeline_id)) + { + reply.send(Ok(res)).ok(); + } else { + reply.send(Err(ErrorStatus::NoSuchWindow)).ok(); + } +} diff --git a/components/shared/embedder/webdriver.rs b/components/shared/embedder/webdriver.rs index 93313edad3c..26f8e1ed8a7 100644 --- a/components/shared/embedder/webdriver.rs +++ b/components/shared/embedder/webdriver.rs @@ -157,6 +157,8 @@ pub enum WebDriverCommandMsg { FocusWebView(WebViewId, IpcSender), /// Get focused webview. For now, this is only used when start new session. GetFocusedWebView(IpcSender>), + /// Get all webviews + GetAllWebViews(IpcSender, ErrorStatus>>), /// Check whether top-level browsing context is open. IsWebViewOpen(WebViewId, IpcSender), /// Check whether browsing context is open. @@ -249,6 +251,7 @@ pub enum WebDriverScriptCommand { WillSendKeys(String, String, bool, IpcSender>), AddLoadStatusSender(WebViewId, IpcSender), RemoveLoadStatusSender(WebViewId), + GetWindowHandle(IpcSender>), } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index c175d037b0a..c566630e28f 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -174,6 +174,8 @@ pub struct WebDriverSession { browsing_context_id: BrowsingContextId, /// + /// The spec said each browsing context has an associated window handle. + /// Actually, each webview has a unique window handle. window_handles: HashMap, timeouts: TimeoutsConfiguration, @@ -193,15 +195,11 @@ pub struct WebDriverSession { impl 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 { id: Uuid::new_v4(), webview_id, browsing_context_id, - window_handles, + window_handles: HashMap::new(), timeouts: TimeoutsConfiguration::default(), page_loading_strategy: PageLoadStrategy::Normal, strict_file_interactability: false, @@ -615,6 +613,7 @@ impl Handler { webview_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 let response = NewSessionResponse::new(session_id.to_string(), Value::Object(capabilities)); @@ -758,7 +757,9 @@ impl Handler { Ok(WebDriverResponse::Void) }, Ok(WebDriverLoadStatus::Complete) | - Ok(WebDriverLoadStatus::NavigationStop) => Ok(WebDriverResponse::Void), + Ok(WebDriverLoadStatus::NavigationStop) => + Ok(WebDriverResponse::Void) + , _ => Err(WebDriverError::new( ErrorStatus::UnknownError, "Unexpected load status received while waiting for document ready state", @@ -1079,12 +1080,19 @@ impl Handler { } /// - fn handle_window_handle(&self) -> WebDriverResult { - let session = self.session()?; + fn handle_window_handle(&mut self) -> WebDriverResult { + 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, // return error with error code no such window. - self.verify_top_level_browsing_context_is_open(session.webview_id)?; - match session.window_handles.get(&session.webview_id) { + self.verify_top_level_browsing_context_is_open(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( serde_json::to_value(handle)?, ))), @@ -1093,18 +1101,59 @@ impl Handler { } /// - fn handle_window_handles(&self) -> WebDriverResult { + fn handle_window_handles(&mut self) -> WebDriverResult { + self.handle_any_user_prompts(self.session()?.webview_id)?; + + self.session_mut()?.window_handles = self.get_window_handles()?; + let handles = self .session()? .window_handles .values() .map(serde_json::to_value) .collect::, _>>()?; + Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(handles)?, ))) } + fn get_window_handles(&self) -> WebDriverResult> { + 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 { + 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")), + } + } + /// fn handle_find_element( &self, @@ -1172,20 +1221,22 @@ impl Handler { // This MUST be done without invoking the focusing steps. self.send_message_to_embedder(cmd_msg)?; - let mut handle = self.session()?.id.to_string(); - if let Ok(new_webview_id) = receiver.recv() { - let session = self.session_mut()?; - let new_handle = Uuid::new_v4().to_string(); - handle = new_handle.clone(); - session.window_handles.insert(new_webview_id, new_handle); + if let Ok(webview_id) = receiver.recv() { + let _ = self.wait_for_document_ready_state(); + let handle = self.get_window_handle(BrowsingContextId::from(webview_id))?; + self.session_mut()? + .window_handles + .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(), - })) } /// @@ -2464,6 +2515,10 @@ impl WebDriverHandler for Handler { ) -> WebDriverResult { 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 // session has previously been created match msg.command { diff --git a/ports/servoshell/desktop/app.rs b/ports/servoshell/desktop/app.rs index 477ec974e37..5cda64fb482 100644 --- a/ports/servoshell/desktop/app.rs +++ b/ports/servoshell/desktop/app.rs @@ -381,6 +381,17 @@ impl App { running_state.set_pending_focus(focus_id, response_sender); } }, + WebDriverCommandMsg::GetAllWebViews(response_sender) => { + let webviews = running_state + .webviews() + .iter() + .map(|(id, _)| *id) + .collect::>(); + + if let Err(error) = response_sender.send(Ok(webviews)) { + warn!("Failed to send response of GetAllWebViews: {error}"); + } + }, WebDriverCommandMsg::GetWindowRect(_webview_id, response_sender) => { let window = self .windows diff --git a/tests/wpt/meta/webdriver/tests/classic/accept_alert/accept.py.ini b/tests/wpt/meta/webdriver/tests/classic/accept_alert/accept.py.ini index 04e5861754c..44d5bd2b6ed 100644 --- a/tests/wpt/meta/webdriver/tests/classic/accept_alert/accept.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/accept_alert/accept.py.ini @@ -1,18 +1,3 @@ [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] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/dismiss_alert/dismiss.py.ini b/tests/wpt/meta/webdriver/tests/classic/dismiss_alert/dismiss.py.ini index 814b3c470e6..3bb980bd3e1 100644 --- a/tests/wpt/meta/webdriver/tests/classic/dismiss_alert/dismiss.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/dismiss_alert/dismiss.py.ini @@ -1,18 +1,3 @@ [dismiss.py] - [test_no_top_browsing_context] - expected: ERROR - - [test_dismiss_confirm] - expected: FAIL - - [test_dismiss_prompt] - expected: FAIL - [test_dismiss_in_popup_window] expected: FAIL - - [test_no_browsing_context] - expected: ERROR - - [test_dismiss_alert] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_click/navigate.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_click/navigate.py.ini index 4599966fd64..40ed565c649 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_click/navigate.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_click/navigate.py.ini @@ -1,24 +1,12 @@ [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\]] expected: FAIL - [test_link_from_nested_context_with_target[_self\]] - expected: FAIL - [test_link_from_nested_context_with_target[_top\]] expected: FAIL [test_link_from_toplevel_context_with_target[_parent\]] expected: FAIL - [test_link_from_nested_context_with_target[_blank\]] - expected: FAIL - [test_link_closes_window] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/window.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_async_script/window.py.ini deleted file mode 100644 index 5cd736463b1..00000000000 --- a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/window.py.ini +++ /dev/null @@ -1,6 +0,0 @@ -[window.py] - [test_web_reference[window\]] - expected: FAIL - - [test_window_open] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_script/window.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_script/window.py.ini index 9de464ed4f0..8368e28165a 100644 --- a/tests/wpt/meta/webdriver/tests/classic/execute_script/window.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/execute_script/window.py.ini @@ -1,15 +1,3 @@ [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] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_contextmenu.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_contextmenu.py.ini index 62af3a877f5..b64bac15ff9 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_contextmenu.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_contextmenu.py.ini @@ -4,6 +4,3 @@ [test_control_click[\\ue051-ctrlKey\]] expected: FAIL - - [test_release_control_click] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/send_alert_text/send.py.ini b/tests/wpt/meta/webdriver/tests/classic/send_alert_text/send.py.ini index 8db2218ee62..b5f761ee43d 100644 --- a/tests/wpt/meta/webdriver/tests/classic/send_alert_text/send.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/send_alert_text/send.py.ini @@ -2,9 +2,6 @@ [test_chained_alert_element_not_interactable[alert\]] expected: FAIL - [test_chained_alert_element_not_interactable[confirm\]] - expected: FAIL - [test_send_alert_text[\]] expected: FAIL @@ -16,6 +13,3 @@ [test_send_alert_text[Fed\\terer\]] expected: FAIL - - [test_unexpected_alert] - expected: FAIL