libservo: Clean up interfaces for alert()/confirm()/prompt() (#35579)

Signed-off-by: Delan Azabani <dazabani@igalia.com>
This commit is contained in:
Delan Azabani 2025-02-27 10:49:08 +08:00 committed by GitHub
parent 03e953e22c
commit 276f6a3ba7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 278 additions and 222 deletions

View file

@ -201,7 +201,7 @@ mod from_script {
Self::ChangePageTitle(..) => target_variant!("ChangePageTitle"), Self::ChangePageTitle(..) => target_variant!("ChangePageTitle"),
Self::MoveTo(..) => target_variant!("MoveTo"), Self::MoveTo(..) => target_variant!("MoveTo"),
Self::ResizeTo(..) => target_variant!("ResizeTo"), Self::ResizeTo(..) => target_variant!("ResizeTo"),
Self::Prompt(..) => target_variant!("Prompt"), Self::ShowSimpleDialog(..) => target_variant!("ShowSimpleDialog"),
Self::RequestAuthentication(..) => target_variant!("RequestAuthentication"), Self::RequestAuthentication(..) => target_variant!("RequestAuthentication"),
Self::ShowContextMenu(..) => target_variant!("ShowContextMenu"), Self::ShowContextMenu(..) => target_variant!("ShowContextMenu"),
Self::AllowNavigationRequest(..) => target_variant!("AllowNavigationRequest"), Self::AllowNavigationRequest(..) => target_variant!("AllowNavigationRequest"),

View file

@ -26,8 +26,8 @@ use cssparser::{Parser, ParserInput, SourceLocation};
use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType}; use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType};
use dom_struct::dom_struct; use dom_struct::dom_struct;
use embedder_traits::{ use embedder_traits::{
EmbedderMsg, PromptDefinition, PromptOrigin, PromptResult, Theme, WebDriverJSError, AlertResponse, ConfirmResponse, EmbedderMsg, PromptResponse, SimpleDialog, Theme,
WebDriverJSResult, WebDriverJSError, WebDriverJSResult,
}; };
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect}; use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect};
use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; use euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
@ -736,30 +736,43 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
} }
let (sender, receiver) = let (sender, receiver) =
ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
let prompt = PromptDefinition::Alert(s.to_string(), sender); let dialog = SimpleDialog::Alert {
let msg = EmbedderMsg::Prompt(self.webview_id(), prompt, PromptOrigin::Untrusted); message: s.to_string(),
response_sender: sender,
};
let msg = EmbedderMsg::ShowSimpleDialog(self.webview_id(), dialog);
self.send_to_embedder(msg); self.send_to_embedder(msg);
receiver.recv().unwrap(); let AlertResponse::Ok = receiver.recv().unwrap();
} }
// https://html.spec.whatwg.org/multipage/#dom-confirm // https://html.spec.whatwg.org/multipage/#dom-confirm
fn Confirm(&self, s: DOMString) -> bool { fn Confirm(&self, s: DOMString) -> bool {
let (sender, receiver) = let (sender, receiver) =
ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
let prompt = PromptDefinition::OkCancel(s.to_string(), sender); let dialog = SimpleDialog::Confirm {
let msg = EmbedderMsg::Prompt(self.webview_id(), prompt, PromptOrigin::Untrusted); message: s.to_string(),
response_sender: sender,
};
let msg = EmbedderMsg::ShowSimpleDialog(self.webview_id(), dialog);
self.send_to_embedder(msg); self.send_to_embedder(msg);
receiver.recv().unwrap() == PromptResult::Primary receiver.recv().unwrap() == ConfirmResponse::Ok
} }
// https://html.spec.whatwg.org/multipage/#dom-prompt // https://html.spec.whatwg.org/multipage/#dom-prompt
fn Prompt(&self, message: DOMString, default: DOMString) -> Option<DOMString> { fn Prompt(&self, message: DOMString, default: DOMString) -> Option<DOMString> {
let (sender, receiver) = let (sender, receiver) =
ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
let prompt = PromptDefinition::Input(message.to_string(), default.to_string(), sender); let dialog = SimpleDialog::Prompt {
let msg = EmbedderMsg::Prompt(self.webview_id(), prompt, PromptOrigin::Untrusted); message: message.to_string(),
default: default.to_string(),
response_sender: sender,
};
let msg = EmbedderMsg::ShowSimpleDialog(self.webview_id(), dialog);
self.send_to_embedder(msg); 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 // https://html.spec.whatwg.org/multipage/#dom-window-stop

View file

@ -708,11 +708,11 @@ impl Servo {
webview.delegate().request_resize_to(webview, size); 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) { if let Some(webview) = self.get_webview_handle(webview_id) {
webview webview
.delegate() .delegate()
.show_prompt(webview, prompt_definition, prompt_origin); .show_simple_dialog(webview, prompt_definition);
} }
}, },
EmbedderMsg::ShowContextMenu(webview_id, ipc_sender, title, items) => { EmbedderMsg::ShowContextMenu(webview_id, ipc_sender, title, items) => {

View file

@ -9,8 +9,7 @@ use compositing_traits::ConstellationMsg;
use embedder_traits::{ use embedder_traits::{
AllowOrDeny, AuthenticationResponse, ContextMenuResult, Cursor, FilterPattern, AllowOrDeny, AuthenticationResponse, ContextMenuResult, Cursor, FilterPattern,
GamepadHapticEffectType, InputMethodType, LoadStatus, MediaSessionEvent, PermissionFeature, GamepadHapticEffectType, InputMethodType, LoadStatus, MediaSessionEvent, PermissionFeature,
PromptDefinition, PromptOrigin, WebResourceRequest, WebResourceResponse, SimpleDialog, WebResourceRequest, WebResourceResponse, WebResourceResponseMsg,
WebResourceResponseMsg,
}; };
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
use keyboard_types::KeyboardEvent; 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. /// 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) { fn show_simple_dialog(&self, _webview: WebView, dialog: SimpleDialog) {
let _ = match prompt { // Return the DOM-specified default value for when we **cannot show simple dialogs**.
PromptDefinition::Alert(_, response_sender) => response_sender.send(()), let _ = match dialog {
PromptDefinition::OkCancel(_, response_sender) => { SimpleDialog::Alert {
response_sender.send(embedder_traits::PromptResult::Dismissed) response_sender, ..
}, } => response_sender.send(Default::default()),
PromptDefinition::Input(_, _, response_sender) => response_sender.send(None), 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 /// Show a context menu to the user
fn show_context_menu( fn show_context_menu(
&self, &self,

View file

@ -123,14 +123,30 @@ pub enum ContextMenuResult {
Selected(usize), 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)] #[derive(Deserialize, Serialize)]
pub enum PromptDefinition { pub enum SimpleDialog {
/// Show a message. /// [`alert()`](https://html.spec.whatwg.org/multipage/#dom-alert).
Alert(String, IpcSender<()>), /// TODO: Include details about the document origin.
/// Ask a Ok/Cancel question. Alert {
OkCancel(String, IpcSender<PromptResult>), message: String,
/// Ask the user to enter text. response_sender: IpcSender<AlertResponse>,
Input(String, String, IpcSender<Option<String>>), },
/// [`confirm()`](https://html.spec.whatwg.org/multipage/#dom-confirm).
/// TODO: Include details about the document origin.
Confirm {
message: String,
response_sender: IpcSender<ConfirmResponse>,
},
/// [`prompt()`](https://html.spec.whatwg.org/multipage/#dom-prompt).
/// TODO: Include details about the document origin.
Prompt {
message: String,
default: String,
response_sender: IpcSender<PromptResponse>,
},
} }
#[derive(Debug, Default, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, Serialize)]
@ -142,22 +158,52 @@ pub struct AuthenticationResponse {
} }
#[derive(Deserialize, PartialEq, Serialize)] #[derive(Deserialize, PartialEq, Serialize)]
pub enum PromptOrigin { pub enum AlertResponse {
/// Prompt is triggered from content (window.prompt/alert/confirm/…). /// The user chose Ok, or the dialog was otherwise dismissed or ignored.
/// Prompt message is unknown. Ok,
Untrusted, }
/// Prompt is triggered from Servo (ask for permission, show error,…).
Trusted, impl Default for AlertResponse {
fn default() -> Self {
// Per <https://html.spec.whatwg.org/multipage/#dom-alert>,
// 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)] #[derive(Deserialize, PartialEq, Serialize)]
pub enum PromptResult { pub enum ConfirmResponse {
/// Prompt was closed by clicking on the primary button (ok/yes) /// The user chose Ok.
Primary, Ok,
/// Prompt was closed by clicking on the secondary button (cancel/no) /// The user chose Cancel, or the dialog was otherwise dismissed or ignored.
Secondary, Cancel,
/// Prompt was dismissed }
Dismissed,
impl Default for ConfirmResponse {
fn default() -> Self {
// Per <https://html.spec.whatwg.org/multipage/#dom-confirm>,
// 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 <https://html.spec.whatwg.org/multipage/#dom-prompt>,
// 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. /// A response to a request to allow or deny an action.
@ -177,8 +223,10 @@ pub enum EmbedderMsg {
MoveTo(WebViewId, DeviceIntPoint), MoveTo(WebViewId, DeviceIntPoint),
/// Resize the window to size /// Resize the window to size
ResizeTo(WebViewId, DeviceIntSize), ResizeTo(WebViewId, DeviceIntSize),
/// Show dialog to user /// Show the user a [simple dialog](https://html.spec.whatwg.org/multipage/#simple-dialogs) (`alert()`, `confirm()`,
Prompt(WebViewId, PromptDefinition, PromptOrigin), /// 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. /// Request authentication for a load or navigation from the embedder.
RequestAuthentication( RequestAuthentication(
WebViewId, WebViewId,
@ -280,7 +328,7 @@ impl Debug for EmbedderMsg {
EmbedderMsg::ChangePageTitle(..) => write!(f, "ChangePageTitle"), EmbedderMsg::ChangePageTitle(..) => write!(f, "ChangePageTitle"),
EmbedderMsg::MoveTo(..) => write!(f, "MoveTo"), EmbedderMsg::MoveTo(..) => write!(f, "MoveTo"),
EmbedderMsg::ResizeTo(..) => write!(f, "ResizeTo"), EmbedderMsg::ResizeTo(..) => write!(f, "ResizeTo"),
EmbedderMsg::Prompt(..) => write!(f, "Prompt"), EmbedderMsg::ShowSimpleDialog(..) => write!(f, "ShowSimpleDialog"),
EmbedderMsg::RequestAuthentication(..) => write!(f, "RequestAuthentication"), EmbedderMsg::RequestAuthentication(..) => write!(f, "RequestAuthentication"),
EmbedderMsg::AllowUnload(..) => write!(f, "AllowUnload"), EmbedderMsg::AllowUnload(..) => write!(f, "AllowUnload"),
EmbedderMsg::AllowNavigationRequest(..) => write!(f, "AllowNavigationRequest"), EmbedderMsg::AllowNavigationRequest(..) => write!(f, "AllowNavigationRequest"),

View file

@ -18,8 +18,8 @@ use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
use servo::webrender_api::ScrollLocation; use servo::webrender_api::ScrollLocation;
use servo::{ use servo::{
AllowOrDenyRequest, AuthenticationRequest, FilterPattern, GamepadHapticEffectType, LoadStatus, AllowOrDenyRequest, AuthenticationRequest, FilterPattern, GamepadHapticEffectType, LoadStatus,
PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, Servo, ServoDelegate, PermissionRequest, Servo, ServoDelegate, ServoError, SimpleDialog, TouchEventType, WebView,
ServoError, TouchEventType, WebView, WebViewDelegate, WebViewDelegate,
}; };
use url::Url; use url::Url;
@ -415,36 +415,25 @@ impl WebViewDelegate for RunningAppState {
self.inner().window.request_resize(&webview, new_size); self.inner().window.request_resize(&webview, new_size);
} }
fn show_prompt( fn show_simple_dialog(&self, webview: servo::WebView, dialog: SimpleDialog) {
&self,
webview: servo::WebView,
definition: PromptDefinition,
_origin: PromptOrigin,
) {
if self.servoshell_preferences.headless { if self.servoshell_preferences.headless {
let _ = match definition { // TODO: Avoid copying this from the default trait impl?
PromptDefinition::Alert(_message, sender) => sender.send(()), // Return the DOM-specified default value for when we **cannot show simple dialogs**.
PromptDefinition::OkCancel(_message, sender) => sender.send(PromptResult::Primary), let _ = match dialog {
PromptDefinition::Input(_message, default, sender) => { SimpleDialog::Alert {
sender.send(Some(default.to_owned())) 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; return;
} }
match definition { let dialog = Dialog::new_simple_dialog(dialog);
PromptDefinition::Alert(message, sender) => { self.add_dialog(webview, dialog);
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);
},
}
} }
fn request_authentication( fn request_authentication(

View file

@ -9,7 +9,10 @@ use egui::Modal;
use egui_file_dialog::{DialogState, FileDialog as EguiFileDialog}; use egui_file_dialog::{DialogState, FileDialog as EguiFileDialog};
use log::warn; use log::warn;
use servo::ipc_channel::ipc::IpcSender; use servo::ipc_channel::ipc::IpcSender;
use servo::{AuthenticationRequest, FilterPattern, PermissionRequest, PromptResult}; use servo::{
AlertResponse, AuthenticationRequest, ConfirmResponse, FilterPattern, PermissionRequest,
PromptResponse, SimpleDialog,
};
pub enum Dialog { pub enum Dialog {
File { File {
@ -17,19 +20,8 @@ pub enum Dialog {
multiple: bool, multiple: bool,
response_sender: IpcSender<Option<Vec<PathBuf>>>, response_sender: IpcSender<Option<Vec<PathBuf>>>,
}, },
Alert { #[allow(clippy::enum_variant_names, reason = "spec terminology")]
message: String, SimpleDialog(SimpleDialog),
sender: IpcSender<()>,
},
OkCancel {
message: String,
sender: IpcSender<PromptResult>,
},
Input {
message: String,
input_text: String,
sender: IpcSender<Option<String>>,
},
Authentication { Authentication {
username: String, username: String,
password: String, password: String,
@ -76,24 +68,8 @@ impl Dialog {
} }
} }
pub fn new_alert_dialog(message: String, sender: IpcSender<()>) -> Self { pub fn new_simple_dialog(dialog: SimpleDialog) -> Self {
Dialog::Alert { message, sender } Self::SimpleDialog(dialog)
}
pub fn new_okcancel_dialog(message: String, sender: IpcSender<PromptResult>) -> Self {
Dialog::OkCancel { message, sender }
}
pub fn new_input_dialog(
message: String,
default: String,
sender: IpcSender<Option<String>>,
) -> Self {
Dialog::Input {
message,
input_text: default,
sender,
}
} }
pub fn new_authentication_dialog(authentication_request: AuthenticationRequest) -> Self { pub fn new_authentication_dialog(authentication_request: AuthenticationRequest) -> Self {
@ -165,9 +141,12 @@ impl Dialog {
DialogState::Closed => false, DialogState::Closed => false,
} }
}, },
Dialog::Alert { message, sender } => { Dialog::SimpleDialog(SimpleDialog::Alert {
message,
response_sender,
}) => {
let mut is_open = true; let mut is_open = true;
let modal = Modal::new("alert".into()); let modal = Modal::new("Alert".into());
modal.show(ctx, |ui| { modal.show(ctx, |ui| {
make_dialog_label(message, ui, None); make_dialog_label(message, ui, None);
egui::Sides::new().show( egui::Sides::new().show(
@ -176,7 +155,7 @@ impl Dialog {
|ui| { |ui| {
if ui.button("Close").clicked() { if ui.button("Close").clicked() {
is_open = false; 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); warn!("Failed to send alert dialog response: {}", e);
} }
} }
@ -185,9 +164,12 @@ impl Dialog {
}); });
is_open is_open
}, },
Dialog::OkCancel { message, sender } => { Dialog::SimpleDialog(SimpleDialog::Confirm {
message,
response_sender,
}) => {
let mut is_open = true; let mut is_open = true;
let modal = Modal::new("OkCancel".into()); let modal = Modal::new("Confirm".into());
modal.show(ctx, |ui| { modal.show(ctx, |ui| {
make_dialog_label(message, ui, None); make_dialog_label(message, ui, None);
egui::Sides::new().show( egui::Sides::new().show(
@ -196,13 +178,13 @@ impl Dialog {
|ui| { |ui| {
if ui.button("Ok").clicked() { if ui.button("Ok").clicked() {
is_open = false; 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); warn!("Failed to send alert dialog response: {}", e);
} }
} }
if ui.button("Cancel").clicked() { if ui.button("Cancel").clicked() {
is_open = false; 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); warn!("Failed to send alert dialog response: {}", e);
} }
} }
@ -211,27 +193,30 @@ impl Dialog {
}); });
is_open is_open
}, },
Dialog::Input { Dialog::SimpleDialog(SimpleDialog::Prompt {
message, message,
input_text, // The `default` field gets reused as the input buffer.
sender, default: input,
} => { response_sender,
}) => {
let mut is_open = true; let mut is_open = true;
Modal::new("input".into()).show(ctx, |ui| { Modal::new("Prompt".into()).show(ctx, |ui| {
make_dialog_label(message, ui, Some(input_text)); make_dialog_label(message, ui, Some(input));
egui::Sides::new().show( egui::Sides::new().show(
ui, ui,
|_ui| {}, |_ui| {},
|ui| { |ui| {
if ui.button("Ok").clicked() { if ui.button("Ok").clicked() {
is_open = false; 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); warn!("Failed to send input dialog response: {}", e);
} }
} }
if ui.button("Cancel").clicked() { if ui.button("Cancel").clicked() {
is_open = false; 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); warn!("Failed to send input dialog response: {}", e);
} }
} }

View file

@ -19,10 +19,11 @@ use log::{debug, error, info, warn};
use raw_window_handle::{ use raw_window_handle::{
AndroidDisplayHandle, AndroidNdkWindowHandle, RawDisplayHandle, RawWindowHandle, AndroidDisplayHandle, AndroidNdkWindowHandle, RawDisplayHandle, RawWindowHandle,
}; };
use servo::{LoadStatus, MediaSessionActionType}; use servo::{
AlertResponse, LoadStatus, MediaSessionActionType, PermissionRequest, SimpleDialog, WebView,
};
use simpleservo::{ use simpleservo::{
DeviceIntRect, EventLoopWaker, InitOptions, InputMethodType, MediaSessionPlaybackState, DeviceIntRect, EventLoopWaker, InitOptions, InputMethodType, MediaSessionPlaybackState, APP,
PromptResult, APP,
}; };
use super::app_state::{Coordinates, RunningAppState}; use super::app_state::{Coordinates, RunningAppState};
@ -461,11 +462,8 @@ impl HostCallbacks {
let jvm = env.get_java_vm().unwrap(); let jvm = env.get_java_vm().unwrap();
HostCallbacks { callbacks, jvm } HostCallbacks { callbacks, jvm }
} }
}
impl HostTrait for HostCallbacks { fn show_alert(&self, message: String) {
fn prompt_alert(&self, message: String, _trusted: bool) {
debug!("prompt_alert");
let mut env = self.jvm.get_env().unwrap(); let mut env = self.jvm.get_env().unwrap();
let Ok(string) = new_string_as_jvalue(&mut env, &message) else { let Ok(string) = new_string_as_jvalue(&mut env, &message) else {
return; return;
@ -478,20 +476,41 @@ impl HostTrait for HostCallbacks {
) )
.unwrap(); .unwrap();
} }
fn prompt_ok_cancel(&self, message: String, _trusted: bool) -> PromptResult {
warn!("Prompt not implemented. Cancelled. {}", message);
PromptResult::Secondary
} }
fn prompt_yes_no(&self, message: String, _trusted: bool) -> PromptResult { impl HostTrait for HostCallbacks {
warn!("Prompt not implemented. Cancelled. {}", message); fn request_permission(&self, _webview: WebView, request: PermissionRequest) {
PromptResult::Secondary warn!("Permissions prompt not implemented. Denied.");
request.deny();
} }
fn prompt_input(&self, message: String, default: String, _trusted: bool) -> Option<String> { fn show_simple_dialog(&self, _webview: WebView, dialog: SimpleDialog) {
warn!("Input prompt not implemented. {}", message); let _ = match dialog {
Some(default) 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) { fn notify_load_status_changed(&self, load_status: LoadStatus) {

View file

@ -14,7 +14,7 @@ pub use servo::webrender_api::units::DeviceIntRect;
/// and that perform_updates need to be called /// and that perform_updates need to be called
pub use servo::EventLoopWaker; pub use servo::EventLoopWaker;
use servo::{self, resources, Servo}; 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::android::resources::ResourceReaderInstance;
use crate::egl::app_state::{ use crate::egl::app_state::{

View file

@ -22,9 +22,9 @@ use servo::{
AllowOrDenyRequest, ContextMenuResult, EmbedderProxy, EventLoopWaker, ImeEvent, InputEvent, AllowOrDenyRequest, ContextMenuResult, EmbedderProxy, EventLoopWaker, ImeEvent, InputEvent,
InputMethodType, Key, KeyState, KeyboardEvent, LoadStatus, MediaSessionActionType, InputMethodType, Key, KeyState, KeyboardEvent, LoadStatus, MediaSessionActionType,
MediaSessionEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, MediaSessionEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent,
NavigationRequest, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, NavigationRequest, PermissionRequest, RenderingContext, Servo, ServoDelegate, ServoError,
RenderingContext, Servo, ServoDelegate, ServoError, TouchEvent, TouchEventType, TouchId, SimpleDialog, TouchEvent, TouchEventType, TouchId, WebView, WebViewDelegate,
WebView, WebViewDelegate, WindowRenderingContext, WindowRenderingContext,
}; };
use url::Url; use url::Url;
@ -198,15 +198,10 @@ impl WebViewDelegate for RunningAppState {
Some(new_webview) Some(new_webview)
} }
fn request_permission(&self, _webview: WebView, request: PermissionRequest) { fn request_permission(&self, webview: WebView, request: PermissionRequest) {
let message = format!( self.callbacks
"Do you want to grant permission for {:?}?", .host_callbacks
request.feature() .request_permission(webview, request);
);
let result = match self.callbacks.host_callbacks.prompt_yes_no(message, true) {
PromptResult::Primary => request.allow(),
PromptResult::Secondary | PromptResult::Dismissed => request.deny(),
};
} }
fn request_resize_to(&self, _webview: WebView, size: DeviceIntSize) { 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) { fn show_simple_dialog(&self, webview: WebView, dialog: SimpleDialog) {
let cb = &self.callbacks.host_callbacks; self.callbacks
let trusted = origin == PromptOrigin::Trusted; .host_callbacks
let _ = match prompt { .show_simple_dialog(webview, dialog);
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_ime( fn show_ime(

View file

@ -3,18 +3,22 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use servo::webrender_api::units::DeviceIntRect; 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 { pub trait HostTrait {
/// Show alert. /// Content in a [`WebView`] is requesting permission to access a feature requiring
fn prompt_alert(&self, msg: String, trusted: bool); /// permission from the user. The embedder should allow or deny the request, either by
/// Ask Yes/No question. /// reading a cached value or querying the user for permission via the user interface.
fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult; fn request_permission(&self, _webview: WebView, _: PermissionRequest);
/// Ask Ok/Cancel question. /// Show the user a [simple dialog](https://html.spec.whatwg.org/multipage/#simple-dialogs) (`alert()`, `confirm()`,
fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult; /// or `prompt()`). Since their messages are controlled by web content, they should be presented to the user in a
/// Ask for string /// way that makes them impossible to mistake for browser UI.
fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String>; /// 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 /// Show context menu
fn show_context_menu(&self, title: Option<String>, items: Vec<String>); fn show_context_menu(&self, title: Option<String>, items: Vec<String>);
/// Notify that the load status of the page has changed. /// Notify that the load status of the page has changed.

View file

@ -21,7 +21,10 @@ use napi_ohos::{Env, JsObject, JsString, NapiRaw};
use ohos_ime::{AttachOptions, Ime, ImeProxy, RawTextEditorProxy}; use ohos_ime::{AttachOptions, Ime, ImeProxy, RawTextEditorProxy};
use ohos_ime_sys::types::InputMethod_EnterKeyType; use ohos_ime_sys::types::InputMethod_EnterKeyType;
use servo::style::Zero; use servo::style::Zero;
use servo::{InputMethodType, LoadStatus, MediaSessionPlaybackState, PromptResult}; use servo::{
AlertResponse, InputMethodType, LoadStatus, MediaSessionPlaybackState, PermissionRequest,
SimpleDialog, WebView,
};
use simpleservo::EventLoopWaker; use simpleservo::EventLoopWaker;
use xcomponent_sys::{ use xcomponent_sys::{
OH_NativeXComponent, OH_NativeXComponent_Callback, OH_NativeXComponent_GetKeyEvent, OH_NativeXComponent, OH_NativeXComponent_Callback, OH_NativeXComponent_GetKeyEvent,
@ -671,6 +674,19 @@ impl HostCallbacks {
ime_proxy: RefCell::new(None), 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 { struct ServoIme {
@ -698,33 +714,38 @@ impl Ime for ServoIme {
#[allow(unused)] #[allow(unused)]
impl HostTrait for HostCallbacks { impl HostTrait for HostCallbacks {
fn prompt_alert(&self, msg: String, _trusted: bool) { fn request_permission(&self, _webview: WebView, request: PermissionRequest) {
debug!("prompt_alert: {msg}"); warn!("Permissions prompt not implemented. Denied.");
match PROMPT_TOAST.get() { request.deny();
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 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}"), SimpleDialog::Confirm {
} message,
} response_sender,
} => {
fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult { warn!("Confirm dialog not implemented. Cancelled. {}", message);
warn!("Prompt not implemented. Cancelled. {}", msg); response_sender.send(Default::default())
PromptResult::Secondary },
} SimpleDialog::Prompt {
message,
fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult { response_sender,
warn!("Prompt not implemented. Cancelled. {}", msg); ..
PromptResult::Secondary } => {
} warn!("Prompt dialog not implemented. Cancelled. {}", message);
response_sender.send(Default::default())
fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String> { },
warn!("Input prompt not implemented. Cancelled. {}", msg); };
Some(default)
} }
fn show_context_menu(&self, title: Option<String>, items: Vec<String>) { fn show_context_menu(&self, title: Option<String>, items: Vec<String>) {
@ -740,7 +761,7 @@ impl HostTrait for HostCallbacks {
if load_status == LoadStatus::Complete { if load_status == LoadStatus::Complete {
#[cfg(feature = "tracing-hitrace")] #[cfg(feature = "tracing-hitrace")]
let _scope = hitrace::ScopedTrace::start_trace(&c"PageLoadEndedPrompt"); 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 { if let Some(bt) = backtrace {
error!("Backtrace: {bt:?}") error!("Backtrace: {bt:?}")
} }
self.prompt_alert("Servo crashed!".to_string(), true); self.show_alert("Servo crashed!".to_string());
self.prompt_alert(reason, true); self.show_alert(reason);
} }
} }

View file

@ -1,3 +0,0 @@
[iframe_sandbox_block_modals-2.html]
[Frames without `allow-modals` should not be able to open modal dialogs]
expected: FAIL

View file

@ -1,3 +0,0 @@
[iframe_sandbox_block_modals-3.html]
[Frames without `allow-modals` should not be able to open modal dialogs]
expected: FAIL

View file

@ -1,4 +0,0 @@
[confirm-different-origin-frame.sub.html]
[confirm-different-origin-frame]
expected: FAIL

View file

@ -1,4 +0,0 @@
[prompt-different-origin-frame.sub.html]
[prompt-different-origin-frame]
expected: FAIL