From 276f6a3ba7950e90e646df398d6f34df610f1bd0 Mon Sep 17 00:00:00 2001 From: Delan Azabani Date: Thu, 27 Feb 2025 10:49:08 +0800 Subject: [PATCH] libservo: Clean up interfaces for alert()/confirm()/prompt() (#35579) Signed-off-by: Delan Azabani --- components/constellation/tracing.rs | 2 +- components/script/dom/window.rs | 35 ++++--- components/servo/lib.rs | 4 +- components/servo/webview_delegate.rs | 27 ++++-- components/shared/embedder/lib.rs | 94 ++++++++++++++----- ports/servoshell/desktop/app_state.rs | 45 ++++----- ports/servoshell/desktop/dialog.rs | 79 +++++++--------- ports/servoshell/egl/android.rs | 55 +++++++---- ports/servoshell/egl/android/simpleservo.rs | 2 +- ports/servoshell/egl/app_state.rs | 38 +++----- ports/servoshell/egl/host_trait.rs | 24 +++-- ports/servoshell/egl/ohos.rs | 81 ++++++++++------ .../iframe_sandbox_block_modals-2.html.ini | 3 - .../iframe_sandbox_block_modals-3.html.ini | 3 - ...onfirm-different-origin-frame.sub.html.ini | 4 - ...prompt-different-origin-frame.sub.html.ini | 4 - 16 files changed, 278 insertions(+), 222 deletions(-) delete mode 100644 tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html.ini delete mode 100644 tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html.ini delete mode 100644 tests/wpt/meta/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html.ini delete mode 100644 tests/wpt/meta/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html.ini diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index 2310f60ef84..0c098b7bb45 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -201,7 +201,7 @@ mod from_script { Self::ChangePageTitle(..) => target_variant!("ChangePageTitle"), Self::MoveTo(..) => target_variant!("MoveTo"), Self::ResizeTo(..) => target_variant!("ResizeTo"), - Self::Prompt(..) => target_variant!("Prompt"), + Self::ShowSimpleDialog(..) => target_variant!("ShowSimpleDialog"), Self::RequestAuthentication(..) => target_variant!("RequestAuthentication"), Self::ShowContextMenu(..) => target_variant!("ShowContextMenu"), Self::AllowNavigationRequest(..) => target_variant!("AllowNavigationRequest"), diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index be037dcab7f..ec1c4ef38e5 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -26,8 +26,8 @@ use cssparser::{Parser, ParserInput, SourceLocation}; use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType}; use dom_struct::dom_struct; use embedder_traits::{ - EmbedderMsg, PromptDefinition, PromptOrigin, PromptResult, Theme, WebDriverJSError, - WebDriverJSResult, + AlertResponse, ConfirmResponse, EmbedderMsg, PromptResponse, SimpleDialog, Theme, + WebDriverJSError, WebDriverJSResult, }; use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect}; use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; @@ -736,30 +736,43 @@ impl WindowMethods for Window { } let (sender, receiver) = ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); - let prompt = PromptDefinition::Alert(s.to_string(), sender); - let msg = EmbedderMsg::Prompt(self.webview_id(), prompt, PromptOrigin::Untrusted); + let dialog = SimpleDialog::Alert { + message: s.to_string(), + response_sender: sender, + }; + let msg = EmbedderMsg::ShowSimpleDialog(self.webview_id(), dialog); self.send_to_embedder(msg); - receiver.recv().unwrap(); + let AlertResponse::Ok = receiver.recv().unwrap(); } // https://html.spec.whatwg.org/multipage/#dom-confirm fn Confirm(&self, s: DOMString) -> bool { let (sender, receiver) = ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); - let prompt = PromptDefinition::OkCancel(s.to_string(), sender); - let msg = EmbedderMsg::Prompt(self.webview_id(), prompt, PromptOrigin::Untrusted); + let dialog = SimpleDialog::Confirm { + message: s.to_string(), + response_sender: sender, + }; + let msg = EmbedderMsg::ShowSimpleDialog(self.webview_id(), dialog); self.send_to_embedder(msg); - receiver.recv().unwrap() == PromptResult::Primary + receiver.recv().unwrap() == ConfirmResponse::Ok } // https://html.spec.whatwg.org/multipage/#dom-prompt fn Prompt(&self, message: DOMString, default: DOMString) -> Option { let (sender, receiver) = ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); - let prompt = PromptDefinition::Input(message.to_string(), default.to_string(), sender); - let msg = EmbedderMsg::Prompt(self.webview_id(), prompt, PromptOrigin::Untrusted); + let dialog = SimpleDialog::Prompt { + message: message.to_string(), + default: default.to_string(), + response_sender: sender, + }; + let msg = EmbedderMsg::ShowSimpleDialog(self.webview_id(), dialog); self.send_to_embedder(msg); - receiver.recv().unwrap().map(|s| s.into()) + match receiver.recv().unwrap() { + PromptResponse::Ok(input) => Some(input.into()), + PromptResponse::Cancel => None, + } } // https://html.spec.whatwg.org/multipage/#dom-window-stop diff --git a/components/servo/lib.rs b/components/servo/lib.rs index d58adab5be7..1a80ff4ba70 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -708,11 +708,11 @@ impl Servo { webview.delegate().request_resize_to(webview, size); } }, - EmbedderMsg::Prompt(webview_id, prompt_definition, prompt_origin) => { + EmbedderMsg::ShowSimpleDialog(webview_id, prompt_definition) => { if let Some(webview) = self.get_webview_handle(webview_id) { webview .delegate() - .show_prompt(webview, prompt_definition, prompt_origin); + .show_simple_dialog(webview, prompt_definition); } }, EmbedderMsg::ShowContextMenu(webview_id, ipc_sender, title, items) => { diff --git a/components/servo/webview_delegate.rs b/components/servo/webview_delegate.rs index 6e5940a85db..1a1d6e180a1 100644 --- a/components/servo/webview_delegate.rs +++ b/components/servo/webview_delegate.rs @@ -9,8 +9,7 @@ use compositing_traits::ConstellationMsg; use embedder_traits::{ AllowOrDeny, AuthenticationResponse, ContextMenuResult, Cursor, FilterPattern, GamepadHapticEffectType, InputMethodType, LoadStatus, MediaSessionEvent, PermissionFeature, - PromptDefinition, PromptOrigin, WebResourceRequest, WebResourceResponse, - WebResourceResponseMsg, + SimpleDialog, WebResourceRequest, WebResourceResponse, WebResourceResponseMsg, }; use ipc_channel::ipc::IpcSender; use keyboard_types::KeyboardEvent; @@ -338,17 +337,25 @@ pub trait WebViewDelegate { ) { } - /// Show dialog to user + /// Show the user a [simple dialog](https://html.spec.whatwg.org/multipage/#simple-dialogs) (`alert()`, `confirm()`, + /// or `prompt()`). Since their messages are controlled by web content, they should be presented to the user in a + /// way that makes them impossible to mistake for browser UI. /// TODO: This API needs to be reworked to match the new model of how responses are sent. - fn show_prompt(&self, _webview: WebView, prompt: PromptDefinition, _: PromptOrigin) { - let _ = match prompt { - PromptDefinition::Alert(_, response_sender) => response_sender.send(()), - PromptDefinition::OkCancel(_, response_sender) => { - response_sender.send(embedder_traits::PromptResult::Dismissed) - }, - PromptDefinition::Input(_, _, response_sender) => response_sender.send(None), + fn show_simple_dialog(&self, _webview: WebView, dialog: SimpleDialog) { + // Return the DOM-specified default value for when we **cannot show simple dialogs**. + let _ = match dialog { + SimpleDialog::Alert { + response_sender, .. + } => response_sender.send(Default::default()), + SimpleDialog::Confirm { + response_sender, .. + } => response_sender.send(Default::default()), + SimpleDialog::Prompt { + response_sender, .. + } => response_sender.send(Default::default()), }; } + /// Show a context menu to the user fn show_context_menu( &self, diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index 5a257a857dc..a40942500e7 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -123,14 +123,30 @@ pub enum ContextMenuResult { Selected(usize), } +/// [Simple dialogs](https://html.spec.whatwg.org/multipage/#simple-dialogs) are synchronous dialogs +/// that can be opened by web content. Since their messages are controlled by web content, they +/// should be presented to the user in a way that makes them impossible to mistake for browser UI. #[derive(Deserialize, Serialize)] -pub enum PromptDefinition { - /// Show a message. - Alert(String, IpcSender<()>), - /// Ask a Ok/Cancel question. - OkCancel(String, IpcSender), - /// Ask the user to enter text. - Input(String, String, IpcSender>), +pub enum SimpleDialog { + /// [`alert()`](https://html.spec.whatwg.org/multipage/#dom-alert). + /// TODO: Include details about the document origin. + Alert { + message: String, + response_sender: IpcSender, + }, + /// [`confirm()`](https://html.spec.whatwg.org/multipage/#dom-confirm). + /// TODO: Include details about the document origin. + Confirm { + message: String, + response_sender: IpcSender, + }, + /// [`prompt()`](https://html.spec.whatwg.org/multipage/#dom-prompt). + /// TODO: Include details about the document origin. + Prompt { + message: String, + default: String, + response_sender: IpcSender, + }, } #[derive(Debug, Default, Deserialize, Serialize)] @@ -142,22 +158,52 @@ pub struct AuthenticationResponse { } #[derive(Deserialize, PartialEq, Serialize)] -pub enum PromptOrigin { - /// Prompt is triggered from content (window.prompt/alert/confirm/…). - /// Prompt message is unknown. - Untrusted, - /// Prompt is triggered from Servo (ask for permission, show error,…). - Trusted, +pub enum AlertResponse { + /// The user chose Ok, or the dialog was otherwise dismissed or ignored. + Ok, +} + +impl Default for AlertResponse { + fn default() -> Self { + // Per , + // if we **cannot show simple dialogs**, including cases where the user or user agent decides to ignore + // all modal dialogs, we need to return (which represents Ok). + Self::Ok + } } #[derive(Deserialize, PartialEq, Serialize)] -pub enum PromptResult { - /// Prompt was closed by clicking on the primary button (ok/yes) - Primary, - /// Prompt was closed by clicking on the secondary button (cancel/no) - Secondary, - /// Prompt was dismissed - Dismissed, +pub enum ConfirmResponse { + /// The user chose Ok. + Ok, + /// The user chose Cancel, or the dialog was otherwise dismissed or ignored. + Cancel, +} + +impl Default for ConfirmResponse { + fn default() -> Self { + // Per , + // if we **cannot show simple dialogs**, including cases where the user or user agent decides to ignore + // all modal dialogs, we need to return false (which represents Cancel), not true (Ok). + Self::Cancel + } +} + +#[derive(Deserialize, PartialEq, Serialize)] +pub enum PromptResponse { + /// The user chose Ok, with the given input. + Ok(String), + /// The user chose Cancel, or the dialog was otherwise dismissed or ignored. + Cancel, +} + +impl Default for PromptResponse { + fn default() -> Self { + // Per , + // if we **cannot show simple dialogs**, including cases where the user or user agent decides to ignore + // all modal dialogs, we need to return null (which represents Cancel), not the default input. + Self::Cancel + } } /// A response to a request to allow or deny an action. @@ -177,8 +223,10 @@ pub enum EmbedderMsg { MoveTo(WebViewId, DeviceIntPoint), /// Resize the window to size ResizeTo(WebViewId, DeviceIntSize), - /// Show dialog to user - Prompt(WebViewId, PromptDefinition, PromptOrigin), + /// Show the user a [simple dialog](https://html.spec.whatwg.org/multipage/#simple-dialogs) (`alert()`, `confirm()`, + /// or `prompt()`). Since their messages are controlled by web content, they should be presented to the user in a + /// way that makes them impossible to mistake for browser UI. + ShowSimpleDialog(WebViewId, SimpleDialog), /// Request authentication for a load or navigation from the embedder. RequestAuthentication( WebViewId, @@ -280,7 +328,7 @@ impl Debug for EmbedderMsg { EmbedderMsg::ChangePageTitle(..) => write!(f, "ChangePageTitle"), EmbedderMsg::MoveTo(..) => write!(f, "MoveTo"), EmbedderMsg::ResizeTo(..) => write!(f, "ResizeTo"), - EmbedderMsg::Prompt(..) => write!(f, "Prompt"), + EmbedderMsg::ShowSimpleDialog(..) => write!(f, "ShowSimpleDialog"), EmbedderMsg::RequestAuthentication(..) => write!(f, "RequestAuthentication"), EmbedderMsg::AllowUnload(..) => write!(f, "AllowUnload"), EmbedderMsg::AllowNavigationRequest(..) => write!(f, "AllowNavigationRequest"), diff --git a/ports/servoshell/desktop/app_state.rs b/ports/servoshell/desktop/app_state.rs index c2d6c656e28..e4fa041d1fc 100644 --- a/ports/servoshell/desktop/app_state.rs +++ b/ports/servoshell/desktop/app_state.rs @@ -18,8 +18,8 @@ use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize}; use servo::webrender_api::ScrollLocation; use servo::{ AllowOrDenyRequest, AuthenticationRequest, FilterPattern, GamepadHapticEffectType, LoadStatus, - PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, Servo, ServoDelegate, - ServoError, TouchEventType, WebView, WebViewDelegate, + PermissionRequest, Servo, ServoDelegate, ServoError, SimpleDialog, TouchEventType, WebView, + WebViewDelegate, }; use url::Url; @@ -415,36 +415,25 @@ impl WebViewDelegate for RunningAppState { self.inner().window.request_resize(&webview, new_size); } - fn show_prompt( - &self, - webview: servo::WebView, - definition: PromptDefinition, - _origin: PromptOrigin, - ) { + fn show_simple_dialog(&self, webview: servo::WebView, dialog: SimpleDialog) { if self.servoshell_preferences.headless { - let _ = match definition { - PromptDefinition::Alert(_message, sender) => sender.send(()), - PromptDefinition::OkCancel(_message, sender) => sender.send(PromptResult::Primary), - PromptDefinition::Input(_message, default, sender) => { - sender.send(Some(default.to_owned())) - }, + // TODO: Avoid copying this from the default trait impl? + // Return the DOM-specified default value for when we **cannot show simple dialogs**. + let _ = match dialog { + SimpleDialog::Alert { + response_sender, .. + } => response_sender.send(Default::default()), + SimpleDialog::Confirm { + response_sender, .. + } => response_sender.send(Default::default()), + SimpleDialog::Prompt { + response_sender, .. + } => response_sender.send(Default::default()), }; return; } - match definition { - PromptDefinition::Alert(message, sender) => { - let alert_dialog = Dialog::new_alert_dialog(message, sender); - self.add_dialog(webview, alert_dialog); - }, - PromptDefinition::OkCancel(message, sender) => { - let okcancel_dialog = Dialog::new_okcancel_dialog(message, sender); - self.add_dialog(webview, okcancel_dialog); - }, - PromptDefinition::Input(message, default, sender) => { - let input_dialog = Dialog::new_input_dialog(message, default, sender); - self.add_dialog(webview, input_dialog); - }, - } + let dialog = Dialog::new_simple_dialog(dialog); + self.add_dialog(webview, dialog); } fn request_authentication( diff --git a/ports/servoshell/desktop/dialog.rs b/ports/servoshell/desktop/dialog.rs index 3b277b6a424..b1cd00deff4 100644 --- a/ports/servoshell/desktop/dialog.rs +++ b/ports/servoshell/desktop/dialog.rs @@ -9,7 +9,10 @@ use egui::Modal; use egui_file_dialog::{DialogState, FileDialog as EguiFileDialog}; use log::warn; use servo::ipc_channel::ipc::IpcSender; -use servo::{AuthenticationRequest, FilterPattern, PermissionRequest, PromptResult}; +use servo::{ + AlertResponse, AuthenticationRequest, ConfirmResponse, FilterPattern, PermissionRequest, + PromptResponse, SimpleDialog, +}; pub enum Dialog { File { @@ -17,19 +20,8 @@ pub enum Dialog { multiple: bool, response_sender: IpcSender>>, }, - Alert { - message: String, - sender: IpcSender<()>, - }, - OkCancel { - message: String, - sender: IpcSender, - }, - Input { - message: String, - input_text: String, - sender: IpcSender>, - }, + #[allow(clippy::enum_variant_names, reason = "spec terminology")] + SimpleDialog(SimpleDialog), Authentication { username: String, password: String, @@ -76,24 +68,8 @@ impl Dialog { } } - pub fn new_alert_dialog(message: String, sender: IpcSender<()>) -> Self { - Dialog::Alert { message, sender } - } - - pub fn new_okcancel_dialog(message: String, sender: IpcSender) -> Self { - Dialog::OkCancel { message, sender } - } - - pub fn new_input_dialog( - message: String, - default: String, - sender: IpcSender>, - ) -> Self { - Dialog::Input { - message, - input_text: default, - sender, - } + pub fn new_simple_dialog(dialog: SimpleDialog) -> Self { + Self::SimpleDialog(dialog) } pub fn new_authentication_dialog(authentication_request: AuthenticationRequest) -> Self { @@ -165,9 +141,12 @@ impl Dialog { DialogState::Closed => false, } }, - Dialog::Alert { message, sender } => { + Dialog::SimpleDialog(SimpleDialog::Alert { + message, + response_sender, + }) => { let mut is_open = true; - let modal = Modal::new("alert".into()); + let modal = Modal::new("Alert".into()); modal.show(ctx, |ui| { make_dialog_label(message, ui, None); egui::Sides::new().show( @@ -176,7 +155,7 @@ impl Dialog { |ui| { if ui.button("Close").clicked() { is_open = false; - if let Err(e) = sender.send(()) { + if let Err(e) = response_sender.send(AlertResponse::Ok) { warn!("Failed to send alert dialog response: {}", e); } } @@ -185,9 +164,12 @@ impl Dialog { }); is_open }, - Dialog::OkCancel { message, sender } => { + Dialog::SimpleDialog(SimpleDialog::Confirm { + message, + response_sender, + }) => { let mut is_open = true; - let modal = Modal::new("OkCancel".into()); + let modal = Modal::new("Confirm".into()); modal.show(ctx, |ui| { make_dialog_label(message, ui, None); egui::Sides::new().show( @@ -196,13 +178,13 @@ impl Dialog { |ui| { if ui.button("Ok").clicked() { is_open = false; - if let Err(e) = sender.send(PromptResult::Primary) { + if let Err(e) = response_sender.send(ConfirmResponse::Ok) { warn!("Failed to send alert dialog response: {}", e); } } if ui.button("Cancel").clicked() { is_open = false; - if let Err(e) = sender.send(PromptResult::Secondary) { + if let Err(e) = response_sender.send(ConfirmResponse::Cancel) { warn!("Failed to send alert dialog response: {}", e); } } @@ -211,27 +193,30 @@ impl Dialog { }); is_open }, - Dialog::Input { + Dialog::SimpleDialog(SimpleDialog::Prompt { message, - input_text, - sender, - } => { + // The `default` field gets reused as the input buffer. + default: input, + response_sender, + }) => { let mut is_open = true; - Modal::new("input".into()).show(ctx, |ui| { - make_dialog_label(message, ui, Some(input_text)); + Modal::new("Prompt".into()).show(ctx, |ui| { + make_dialog_label(message, ui, Some(input)); egui::Sides::new().show( ui, |_ui| {}, |ui| { if ui.button("Ok").clicked() { is_open = false; - if let Err(e) = sender.send(Some(input_text.clone())) { + if let Err(e) = + response_sender.send(PromptResponse::Ok(input.clone())) + { warn!("Failed to send input dialog response: {}", e); } } if ui.button("Cancel").clicked() { is_open = false; - if let Err(e) = sender.send(None) { + if let Err(e) = response_sender.send(PromptResponse::Cancel) { warn!("Failed to send input dialog response: {}", e); } } diff --git a/ports/servoshell/egl/android.rs b/ports/servoshell/egl/android.rs index f195cc2c910..0f84dadfc14 100644 --- a/ports/servoshell/egl/android.rs +++ b/ports/servoshell/egl/android.rs @@ -19,10 +19,11 @@ use log::{debug, error, info, warn}; use raw_window_handle::{ AndroidDisplayHandle, AndroidNdkWindowHandle, RawDisplayHandle, RawWindowHandle, }; -use servo::{LoadStatus, MediaSessionActionType}; +use servo::{ + AlertResponse, LoadStatus, MediaSessionActionType, PermissionRequest, SimpleDialog, WebView, +}; use simpleservo::{ - DeviceIntRect, EventLoopWaker, InitOptions, InputMethodType, MediaSessionPlaybackState, - PromptResult, APP, + DeviceIntRect, EventLoopWaker, InitOptions, InputMethodType, MediaSessionPlaybackState, APP, }; use super::app_state::{Coordinates, RunningAppState}; @@ -461,11 +462,8 @@ impl HostCallbacks { let jvm = env.get_java_vm().unwrap(); HostCallbacks { callbacks, jvm } } -} -impl HostTrait for HostCallbacks { - fn prompt_alert(&self, message: String, _trusted: bool) { - debug!("prompt_alert"); + fn show_alert(&self, message: String) { let mut env = self.jvm.get_env().unwrap(); let Ok(string) = new_string_as_jvalue(&mut env, &message) else { return; @@ -478,20 +476,41 @@ impl HostTrait for HostCallbacks { ) .unwrap(); } +} - fn prompt_ok_cancel(&self, message: String, _trusted: bool) -> PromptResult { - warn!("Prompt not implemented. Cancelled. {}", message); - PromptResult::Secondary +impl HostTrait for HostCallbacks { + fn request_permission(&self, _webview: WebView, request: PermissionRequest) { + warn!("Permissions prompt not implemented. Denied."); + request.deny(); } - fn prompt_yes_no(&self, message: String, _trusted: bool) -> PromptResult { - warn!("Prompt not implemented. Cancelled. {}", message); - PromptResult::Secondary - } - - fn prompt_input(&self, message: String, default: String, _trusted: bool) -> Option { - warn!("Input prompt not implemented. {}", message); - Some(default) + fn show_simple_dialog(&self, _webview: WebView, dialog: SimpleDialog) { + let _ = match dialog { + SimpleDialog::Alert { + message, + response_sender, + } => { + debug!("SimpleDialog::Alert"); + // TODO: Indicate that this message is untrusted, and what origin it came from. + self.show_alert(message); + response_sender.send(AlertResponse::Ok) + }, + SimpleDialog::Confirm { + message, + response_sender, + } => { + warn!("Confirm dialog not implemented. Cancelled. {}", message); + response_sender.send(Default::default()) + }, + SimpleDialog::Prompt { + message, + response_sender, + .. + } => { + warn!("Prompt dialog not implemented. Cancelled. {}", message); + response_sender.send(Default::default()) + }, + }; } fn notify_load_status_changed(&self, load_status: LoadStatus) { diff --git a/ports/servoshell/egl/android/simpleservo.rs b/ports/servoshell/egl/android/simpleservo.rs index 369e583af08..1dd255402d9 100644 --- a/ports/servoshell/egl/android/simpleservo.rs +++ b/ports/servoshell/egl/android/simpleservo.rs @@ -14,7 +14,7 @@ pub use servo::webrender_api::units::DeviceIntRect; /// and that perform_updates need to be called pub use servo::EventLoopWaker; use servo::{self, resources, Servo}; -pub use servo::{InputMethodType, MediaSessionPlaybackState, PromptResult, WindowRenderingContext}; +pub use servo::{InputMethodType, MediaSessionPlaybackState, WindowRenderingContext}; use crate::egl::android::resources::ResourceReaderInstance; use crate::egl::app_state::{ diff --git a/ports/servoshell/egl/app_state.rs b/ports/servoshell/egl/app_state.rs index a62ee308f7a..d7b2e0a5333 100644 --- a/ports/servoshell/egl/app_state.rs +++ b/ports/servoshell/egl/app_state.rs @@ -22,9 +22,9 @@ use servo::{ AllowOrDenyRequest, ContextMenuResult, EmbedderProxy, EventLoopWaker, ImeEvent, InputEvent, InputMethodType, Key, KeyState, KeyboardEvent, LoadStatus, MediaSessionActionType, MediaSessionEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, - NavigationRequest, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, - RenderingContext, Servo, ServoDelegate, ServoError, TouchEvent, TouchEventType, TouchId, - WebView, WebViewDelegate, WindowRenderingContext, + NavigationRequest, PermissionRequest, RenderingContext, Servo, ServoDelegate, ServoError, + SimpleDialog, TouchEvent, TouchEventType, TouchId, WebView, WebViewDelegate, + WindowRenderingContext, }; use url::Url; @@ -198,15 +198,10 @@ impl WebViewDelegate for RunningAppState { Some(new_webview) } - fn request_permission(&self, _webview: WebView, request: PermissionRequest) { - let message = format!( - "Do you want to grant permission for {:?}?", - request.feature() - ); - let result = match self.callbacks.host_callbacks.prompt_yes_no(message, true) { - PromptResult::Primary => request.allow(), - PromptResult::Secondary | PromptResult::Dismissed => request.deny(), - }; + fn request_permission(&self, webview: WebView, request: PermissionRequest) { + self.callbacks + .host_callbacks + .request_permission(webview, request); } fn request_resize_to(&self, _webview: WebView, size: DeviceIntSize) { @@ -231,21 +226,10 @@ impl WebViewDelegate for RunningAppState { } } - fn show_prompt(&self, _webview: WebView, prompt: PromptDefinition, origin: PromptOrigin) { - let cb = &self.callbacks.host_callbacks; - let trusted = origin == PromptOrigin::Trusted; - let _ = match prompt { - PromptDefinition::Alert(message, response_sender) => { - cb.prompt_alert(message, trusted); - response_sender.send(()) - }, - PromptDefinition::OkCancel(message, response_sender) => { - response_sender.send(cb.prompt_ok_cancel(message, trusted)) - }, - PromptDefinition::Input(message, default, response_sender) => { - response_sender.send(cb.prompt_input(message, default, trusted)) - }, - }; + fn show_simple_dialog(&self, webview: WebView, dialog: SimpleDialog) { + self.callbacks + .host_callbacks + .show_simple_dialog(webview, dialog); } fn show_ime( diff --git a/ports/servoshell/egl/host_trait.rs b/ports/servoshell/egl/host_trait.rs index 1171cf8912a..fe4e8974af5 100644 --- a/ports/servoshell/egl/host_trait.rs +++ b/ports/servoshell/egl/host_trait.rs @@ -3,18 +3,22 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use servo::webrender_api::units::DeviceIntRect; -use servo::{InputMethodType, LoadStatus, MediaSessionPlaybackState, PromptResult}; +use servo::{ + InputMethodType, LoadStatus, MediaSessionPlaybackState, PermissionRequest, SimpleDialog, + WebView, +}; -/// Callbacks. Implemented by embedder. Called by Servo. +/// Callbacks implemented by embedder. Called by our RunningAppState, generally on behalf of Servo. pub trait HostTrait { - /// Show alert. - fn prompt_alert(&self, msg: String, trusted: bool); - /// Ask Yes/No question. - fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult; - /// Ask Ok/Cancel question. - fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult; - /// Ask for string - fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option; + /// Content in a [`WebView`] is requesting permission to access a feature requiring + /// permission from the user. The embedder should allow or deny the request, either by + /// reading a cached value or querying the user for permission via the user interface. + fn request_permission(&self, _webview: WebView, _: PermissionRequest); + /// Show the user a [simple dialog](https://html.spec.whatwg.org/multipage/#simple-dialogs) (`alert()`, `confirm()`, + /// or `prompt()`). Since their messages are controlled by web content, they should be presented to the user in a + /// way that makes them impossible to mistake for browser UI. + /// TODO: This API needs to be reworked to match the new model of how responses are sent. + fn show_simple_dialog(&self, _webview: WebView, dialog: SimpleDialog); /// Show context menu fn show_context_menu(&self, title: Option, items: Vec); /// Notify that the load status of the page has changed. diff --git a/ports/servoshell/egl/ohos.rs b/ports/servoshell/egl/ohos.rs index ed95324e913..ebaab2c4d33 100644 --- a/ports/servoshell/egl/ohos.rs +++ b/ports/servoshell/egl/ohos.rs @@ -21,7 +21,10 @@ use napi_ohos::{Env, JsObject, JsString, NapiRaw}; use ohos_ime::{AttachOptions, Ime, ImeProxy, RawTextEditorProxy}; use ohos_ime_sys::types::InputMethod_EnterKeyType; use servo::style::Zero; -use servo::{InputMethodType, LoadStatus, MediaSessionPlaybackState, PromptResult}; +use servo::{ + AlertResponse, InputMethodType, LoadStatus, MediaSessionPlaybackState, PermissionRequest, + SimpleDialog, WebView, +}; use simpleservo::EventLoopWaker; use xcomponent_sys::{ OH_NativeXComponent, OH_NativeXComponent_Callback, OH_NativeXComponent_GetKeyEvent, @@ -671,6 +674,19 @@ impl HostCallbacks { ime_proxy: RefCell::new(None), } } + + pub fn show_alert(&self, message: String) { + match PROMPT_TOAST.get() { + Some(prompt_fn) => { + let status = prompt_fn.call(message, ThreadsafeFunctionCallMode::NonBlocking); + if status != napi_ohos::Status::Ok { + // Queue could be full. + error!("show_alert failed with {status}"); + } + }, + None => error!("PROMPT_TOAST not set. Dropping message {message}"), + } + } } struct ServoIme { @@ -698,33 +714,38 @@ impl Ime for ServoIme { #[allow(unused)] impl HostTrait for HostCallbacks { - fn prompt_alert(&self, msg: String, _trusted: bool) { - debug!("prompt_alert: {msg}"); - match PROMPT_TOAST.get() { - Some(prompt_fn) => { - let status = prompt_fn.call(msg, ThreadsafeFunctionCallMode::NonBlocking); - if status != napi_ohos::Status::Ok { - // Queue could be full. - error!("prompt_alert failed with {status}"); - } + fn request_permission(&self, _webview: WebView, request: PermissionRequest) { + warn!("Permissions prompt not implemented. Denied."); + request.deny(); + } + + fn show_simple_dialog(&self, _webview: WebView, dialog: SimpleDialog) { + let _ = match dialog { + SimpleDialog::Alert { + message, + response_sender, + } => { + debug!("SimpleDialog::Alert"); + // TODO: Indicate that this message is untrusted, and what origin it came from. + self.show_alert(message); + response_sender.send(AlertResponse::Ok) }, - None => error!("PROMPT_TOAST not set. Dropping msg {msg}"), - } - } - - fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult { - warn!("Prompt not implemented. Cancelled. {}", msg); - PromptResult::Secondary - } - - fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult { - warn!("Prompt not implemented. Cancelled. {}", msg); - PromptResult::Secondary - } - - fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option { - warn!("Input prompt not implemented. Cancelled. {}", msg); - Some(default) + SimpleDialog::Confirm { + message, + response_sender, + } => { + warn!("Confirm dialog not implemented. Cancelled. {}", message); + response_sender.send(Default::default()) + }, + SimpleDialog::Prompt { + message, + response_sender, + .. + } => { + warn!("Prompt dialog not implemented. Cancelled. {}", message); + response_sender.send(Default::default()) + }, + }; } fn show_context_menu(&self, title: Option, items: Vec) { @@ -740,7 +761,7 @@ impl HostTrait for HostCallbacks { if load_status == LoadStatus::Complete { #[cfg(feature = "tracing-hitrace")] let _scope = hitrace::ScopedTrace::start_trace(&c"PageLoadEndedPrompt"); - self.prompt_alert("Page finished loading!".to_string(), true); + self.show_alert("Page finished loading!".to_string()); } } @@ -838,7 +859,7 @@ impl HostTrait for HostCallbacks { if let Some(bt) = backtrace { error!("Backtrace: {bt:?}") } - self.prompt_alert("Servo crashed!".to_string(), true); - self.prompt_alert(reason, true); + self.show_alert("Servo crashed!".to_string()); + self.show_alert(reason); } } diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html.ini deleted file mode 100644 index e0fdb19e1e2..00000000000 --- a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[iframe_sandbox_block_modals-2.html] - [Frames without `allow-modals` should not be able to open modal dialogs] - expected: FAIL diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html.ini deleted file mode 100644 index bd49b820c2a..00000000000 --- a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[iframe_sandbox_block_modals-3.html] - [Frames without `allow-modals` should not be able to open modal dialogs] - expected: FAIL diff --git a/tests/wpt/meta/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html.ini b/tests/wpt/meta/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html.ini deleted file mode 100644 index 95993076087..00000000000 --- a/tests/wpt/meta/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[confirm-different-origin-frame.sub.html] - [confirm-different-origin-frame] - expected: FAIL - diff --git a/tests/wpt/meta/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html.ini b/tests/wpt/meta/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html.ini deleted file mode 100644 index 125dfdbf8d1..00000000000 --- a/tests/wpt/meta/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[prompt-different-origin-frame.sub.html] - [prompt-different-origin-frame] - expected: FAIL -