Skip to content

Commit f24f225

Browse files
webdriver: Implement the "Get Window Handles" command (servo#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 <[email protected]>
1 parent dafb0ab commit f24f225

File tree

13 files changed

+116
-98
lines changed

13 files changed

+116
-98
lines changed

components/constellation/constellation.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4467,11 +4467,13 @@ where
44674467
},
44684468
// TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command
44694469
WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => {
4470-
let pipeline_id = self
4471-
.browsing_contexts
4472-
.get(&browsing_context_id)
4473-
.expect("ScriptCommand: Browsing context must exist at this point")
4474-
.pipeline_id;
4470+
let pipeline_id = if let Some(browsing_context) =
4471+
self.browsing_contexts.get(&browsing_context_id)
4472+
{
4473+
browsing_context.pipeline_id
4474+
} else {
4475+
return warn!("{}: Browsing context is not ready", browsing_context_id);
4476+
};
44754477

44764478
match &cmd {
44774479
WebDriverScriptCommand::AddLoadStatusSender(_, sender) => {

components/script/script_thread.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2431,6 +2431,9 @@ impl ScriptThread {
24312431
WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
24322432
webdriver_handlers::handle_remove_load_status_sender(&documents, pipeline_id)
24332433
},
2434+
WebDriverScriptCommand::GetWindowHandle(reply) => {
2435+
webdriver_handlers::handle_get_window_handle(pipeline_id, reply)
2436+
},
24342437
_ => (),
24352438
}
24362439
}

components/script/webdriver_handlers.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2086,3 +2086,16 @@ pub(crate) fn handle_remove_load_status_sender(
20862086
window.set_webdriver_load_status_sender(None);
20872087
}
20882088
}
2089+
2090+
pub(crate) fn handle_get_window_handle(
2091+
pipeline_id: PipelineId,
2092+
reply: IpcSender<Result<String, ErrorStatus>>,
2093+
) {
2094+
if let Some(res) = ScriptThread::find_document(pipeline_id)
2095+
.map(|document| document.upcast::<Node>().unique_id(pipeline_id))
2096+
{
2097+
reply.send(Ok(res)).ok();
2098+
} else {
2099+
reply.send(Err(ErrorStatus::NoSuchWindow)).ok();
2100+
}
2101+
}

components/shared/embedder/webdriver.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ pub enum WebDriverCommandMsg {
157157
FocusWebView(WebViewId, IpcSender<bool>),
158158
/// Get focused webview. For now, this is only used when start new session.
159159
GetFocusedWebView(IpcSender<Option<WebViewId>>),
160+
/// Get all webviews
161+
GetAllWebViews(IpcSender<Result<Vec<WebViewId>, ErrorStatus>>),
160162
/// Check whether top-level browsing context is open.
161163
IsWebViewOpen(WebViewId, IpcSender<bool>),
162164
/// Check whether browsing context is open.
@@ -249,6 +251,7 @@ pub enum WebDriverScriptCommand {
249251
WillSendKeys(String, String, bool, IpcSender<Result<bool, ErrorStatus>>),
250252
AddLoadStatusSender(WebViewId, IpcSender<WebDriverLoadStatus>),
251253
RemoveLoadStatusSender(WebViewId),
254+
GetWindowHandle(IpcSender<Result<String, ErrorStatus>>),
252255
}
253256

254257
#[derive(Clone, Debug, Deserialize, Serialize)]

components/webdriver_server/lib.rs

Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ pub struct WebDriverSession {
174174
browsing_context_id: BrowsingContextId,
175175

176176
/// <https://www.w3.org/TR/webdriver2/#dfn-window-handles>
177+
/// The spec said each browsing context has an associated window handle.
178+
/// Actually, each webview has a unique window handle.
177179
window_handles: HashMap<WebViewId, String>,
178180

179181
timeouts: TimeoutsConfiguration,
@@ -193,15 +195,11 @@ pub struct WebDriverSession {
193195

194196
impl WebDriverSession {
195197
pub fn new(browsing_context_id: BrowsingContextId, webview_id: WebViewId) -> WebDriverSession {
196-
let mut window_handles = HashMap::new();
197-
let handle = Uuid::new_v4().to_string();
198-
window_handles.insert(webview_id, handle);
199-
200198
WebDriverSession {
201199
id: Uuid::new_v4(),
202200
webview_id,
203201
browsing_context_id,
204-
window_handles,
202+
window_handles: HashMap::new(),
205203
timeouts: TimeoutsConfiguration::default(),
206204
page_loading_strategy: PageLoadStrategy::Normal,
207205
strict_file_interactability: false,
@@ -615,6 +613,7 @@ impl Handler {
615613
webview_id,
616614
browsing_context_id,
617615
)?;
616+
self.session_mut()?.window_handles = self.get_window_handles()?;
618617

619618
// Step 7. Let response be a JSON Object initialized with session's session ID and capabilities
620619
let response = NewSessionResponse::new(session_id.to_string(), Value::Object(capabilities));
@@ -758,7 +757,9 @@ impl Handler {
758757
Ok(WebDriverResponse::Void)
759758
},
760759
Ok(WebDriverLoadStatus::Complete) |
761-
Ok(WebDriverLoadStatus::NavigationStop) => Ok(WebDriverResponse::Void),
760+
Ok(WebDriverLoadStatus::NavigationStop) =>
761+
Ok(WebDriverResponse::Void)
762+
,
762763
_ => Err(WebDriverError::new(
763764
ErrorStatus::UnknownError,
764765
"Unexpected load status received while waiting for document ready state",
@@ -1079,12 +1080,19 @@ impl Handler {
10791080
}
10801081

10811082
/// <https://w3c.github.io/webdriver/#get-window-handle>
1082-
fn handle_window_handle(&self) -> WebDriverResult<WebDriverResponse> {
1083-
let session = self.session()?;
1083+
fn handle_window_handle(&mut self) -> WebDriverResult<WebDriverResponse> {
1084+
let webview_id = self.session()?.webview_id;
1085+
let browsing_context_id = self.session()?.browsing_context_id;
1086+
10841087
// Step 1. If session's current top-level browsing context is no longer open,
10851088
// return error with error code no such window.
1086-
self.verify_top_level_browsing_context_is_open(session.webview_id)?;
1087-
match session.window_handles.get(&session.webview_id) {
1089+
self.verify_top_level_browsing_context_is_open(webview_id)?;
1090+
let handle = self.get_window_handle(browsing_context_id)?;
1091+
self.session_mut()?
1092+
.window_handles
1093+
.insert(webview_id, handle);
1094+
1095+
match self.session()?.window_handles.get(&webview_id) {
10881096
Some(handle) => Ok(WebDriverResponse::Generic(ValueResponse(
10891097
serde_json::to_value(handle)?,
10901098
))),
@@ -1093,18 +1101,59 @@ impl Handler {
10931101
}
10941102

10951103
/// <https://w3c.github.io/webdriver/#get-window-handles>
1096-
fn handle_window_handles(&self) -> WebDriverResult<WebDriverResponse> {
1104+
fn handle_window_handles(&mut self) -> WebDriverResult<WebDriverResponse> {
1105+
self.handle_any_user_prompts(self.session()?.webview_id)?;
1106+
1107+
self.session_mut()?.window_handles = self.get_window_handles()?;
1108+
10971109
let handles = self
10981110
.session()?
10991111
.window_handles
11001112
.values()
11011113
.map(serde_json::to_value)
11021114
.collect::<Result<Vec<_>, _>>()?;
1115+
11031116
Ok(WebDriverResponse::Generic(ValueResponse(
11041117
serde_json::to_value(handles)?,
11051118
)))
11061119
}
11071120

1121+
fn get_window_handles(&self) -> WebDriverResult<HashMap<WebViewId, String>> {
1122+
let (sender, receiver) = ipc::channel().unwrap();
1123+
self.send_message_to_embedder(WebDriverCommandMsg::GetAllWebViews(sender))?;
1124+
1125+
let webviews = match wait_for_ipc_response(receiver)? {
1126+
Ok(webviews) => webviews,
1127+
Err(_) => {
1128+
return Err(WebDriverError::new(
1129+
ErrorStatus::UnknownError,
1130+
"Failed to get window handles",
1131+
));
1132+
},
1133+
};
1134+
1135+
let mut res = HashMap::new();
1136+
for id in webviews.iter() {
1137+
let handle = self.get_window_handle(BrowsingContextId::from(*id))?;
1138+
res.insert(*id, handle);
1139+
}
1140+
1141+
Ok(res)
1142+
}
1143+
1144+
fn get_window_handle(&self, browsing_context_id: BrowsingContextId) -> WebDriverResult<String> {
1145+
let (sender, receiver) = ipc::channel().unwrap();
1146+
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
1147+
browsing_context_id,
1148+
WebDriverScriptCommand::GetWindowHandle(sender),
1149+
))?;
1150+
1151+
match wait_for_ipc_response(receiver)? {
1152+
Ok(handle) => Ok(handle),
1153+
Err(err) => Err(WebDriverError::new(err, "Failed to get window handle")),
1154+
}
1155+
}
1156+
11081157
/// <https://w3c.github.io/webdriver/#find-element>
11091158
fn handle_find_element(
11101159
&self,
@@ -1172,20 +1221,22 @@ impl Handler {
11721221
// This MUST be done without invoking the focusing steps.
11731222
self.send_message_to_embedder(cmd_msg)?;
11741223

1175-
let mut handle = self.session()?.id.to_string();
1176-
if let Ok(new_webview_id) = receiver.recv() {
1177-
let session = self.session_mut()?;
1178-
let new_handle = Uuid::new_v4().to_string();
1179-
handle = new_handle.clone();
1180-
session.window_handles.insert(new_webview_id, new_handle);
1224+
if let Ok(webview_id) = receiver.recv() {
1225+
let _ = self.wait_for_document_ready_state();
1226+
let handle = self.get_window_handle(BrowsingContextId::from(webview_id))?;
1227+
self.session_mut()?
1228+
.window_handles
1229+
.insert(webview_id, handle.clone());
1230+
Ok(WebDriverResponse::NewWindow(NewWindowResponse {
1231+
handle,
1232+
typ: "tab".to_string(),
1233+
}))
1234+
} else {
1235+
Err(WebDriverError::new(
1236+
ErrorStatus::UnknownError,
1237+
"No webview ID received",
1238+
))
11811239
}
1182-
1183-
let _ = self.wait_for_document_ready_state();
1184-
1185-
Ok(WebDriverResponse::NewWindow(NewWindowResponse {
1186-
handle,
1187-
typ: "tab".to_string(),
1188-
}))
11891240
}
11901241

11911242
/// <https://w3c.github.io/webdriver/#dfn-switch-to-frame>
@@ -2464,6 +2515,10 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
24642515
) -> WebDriverResult<WebDriverResponse> {
24652516
info!("{:?}", msg.command);
24662517

2518+
// Drain the load status receiver to avoid blocking
2519+
// the command processing.
2520+
while self.load_status_receiver.try_recv().is_ok() {}
2521+
24672522
// Unless we are trying to create a new session, we need to ensure that a
24682523
// session has previously been created
24692524
match msg.command {

ports/servoshell/desktop/app.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,17 @@ impl App {
381381
running_state.set_pending_focus(focus_id, response_sender);
382382
}
383383
},
384+
WebDriverCommandMsg::GetAllWebViews(response_sender) => {
385+
let webviews = running_state
386+
.webviews()
387+
.iter()
388+
.map(|(id, _)| *id)
389+
.collect::<Vec<_>>();
390+
391+
if let Err(error) = response_sender.send(Ok(webviews)) {
392+
warn!("Failed to send response of GetAllWebViews: {error}");
393+
}
394+
},
384395
WebDriverCommandMsg::GetWindowRect(_webview_id, response_sender) => {
385396
let window = self
386397
.windows
Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
11
[accept.py]
2-
[test_no_top_level_browsing_context]
3-
expected: ERROR
4-
5-
[test_no_browsing_context]
6-
expected: ERROR
7-
8-
[test_accept_alert]
9-
expected: FAIL
10-
11-
[test_accept_confirm]
12-
expected: FAIL
13-
14-
[test_accept_prompt]
15-
expected: FAIL
16-
172
[test_accept_in_popup_window]
183
expected: FAIL
Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
11
[dismiss.py]
2-
[test_no_top_browsing_context]
3-
expected: ERROR
4-
5-
[test_dismiss_confirm]
6-
expected: FAIL
7-
8-
[test_dismiss_prompt]
9-
expected: FAIL
10-
112
[test_dismiss_in_popup_window]
123
expected: FAIL
13-
14-
[test_no_browsing_context]
15-
expected: ERROR
16-
17-
[test_dismiss_alert]
18-
expected: FAIL
Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,12 @@
11
[navigate.py]
2-
[test_link_from_toplevel_context_with_target[_blank\]]
3-
expected: FAIL
4-
5-
[test_link_from_nested_context_with_target[\]]
6-
expected: FAIL
7-
82
[test_link_from_nested_context_with_target[_parent\]]
93
expected: FAIL
104

11-
[test_link_from_nested_context_with_target[_self\]]
12-
expected: FAIL
13-
145
[test_link_from_nested_context_with_target[_top\]]
156
expected: FAIL
167

178
[test_link_from_toplevel_context_with_target[_parent\]]
189
expected: FAIL
1910

20-
[test_link_from_nested_context_with_target[_blank\]]
21-
expected: FAIL
22-
2311
[test_link_closes_window]
2412
expected: FAIL

tests/wpt/meta/webdriver/tests/classic/execute_async_script/window.py.ini

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)