From 51f15a055e6fe14d4bc5fa2564da61941ed77bd2 Mon Sep 17 00:00:00 2001 From: Paul Rouget Date: Mon, 9 Dec 2019 10:26:47 +0100 Subject: [PATCH] Embedder Prompt API --- components/embedder_traits/lib.rs | 37 +++++- components/script/dom/webidls/Window.webidl | 4 +- components/script/dom/window.rs | 25 +++- ports/glutin/browser.rs | 83 ++++++++++-- ports/libmlservo/src/lib.rs | 24 +++- ports/libsimpleservo/api/src/lib.rs | 39 ++++-- ports/libsimpleservo/capi/src/lib.rs | 65 +++++++-- ports/libsimpleservo/jniapi/src/lib.rs | 22 ++- .../hololens/ServoApp/ServoControl/Servo.cpp | 33 ++++- .../hololens/ServoApp/ServoControl/Servo.h | 52 ++++---- .../ServoApp/ServoControl/ServoControl.cpp | 125 ++++++++++++++++-- .../ServoApp/ServoControl/ServoControl.h | 17 ++- .../window-properties.https.html.ini | 6 - .../html/dom/idlharness.https.html.ini | 18 --- .../iframe_sandbox_block_modals-2.html.ini | 3 +- .../iframe_sandbox_block_modals-3.html.ini | 3 +- .../iframe_sandbox_popups_escaping-3.html.ini | 1 - 17 files changed, 447 insertions(+), 110 deletions(-) diff --git a/components/embedder_traits/lib.rs b/components/embedder_traits/lib.rs index 4ac1b4f793d7..246ef63f2504 100644 --- a/components/embedder_traits/lib.rs +++ b/components/embedder_traits/lib.rs @@ -106,6 +106,37 @@ impl EmbedderReceiver { } } +#[derive(Deserialize, Serialize)] +pub enum PromptDefinition { + /// Show a message. + Alert(String, IpcSender<()>), + /// Ask a Ok/Cancel question. + OkCancel(String, IpcSender), + /// Ask a Yes/No question. + YesNo(String, IpcSender), + /// Ask the user to enter text. + Input(String, String, IpcSender>), +} + +#[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, +} + +#[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, +} + #[derive(Deserialize, Serialize)] pub enum EmbedderMsg { /// A status message to be displayed by the browser chrome. @@ -116,8 +147,8 @@ pub enum EmbedderMsg { MoveTo(DeviceIntPoint), /// Resize the window to size ResizeTo(DeviceIntSize), - // Show an alert message. - Alert(String, IpcSender<()>), + /// Show dialog to user + Prompt(PromptDefinition, PromptOrigin), /// Wether or not to allow a pipeline to load a url. AllowNavigationRequest(PipelineId, ServoUrl), /// Whether or not to allow script to open a new tab/browser @@ -174,7 +205,7 @@ impl Debug for EmbedderMsg { EmbedderMsg::ChangePageTitle(..) => write!(f, "ChangePageTitle"), EmbedderMsg::MoveTo(..) => write!(f, "MoveTo"), EmbedderMsg::ResizeTo(..) => write!(f, "ResizeTo"), - EmbedderMsg::Alert(..) => write!(f, "Alert"), + EmbedderMsg::Prompt(..) => write!(f, "Prompt"), EmbedderMsg::AllowUnload(..) => write!(f, "AllowUnload"), EmbedderMsg::AllowNavigationRequest(..) => write!(f, "AllowNavigationRequest"), EmbedderMsg::Keyboard(..) => write!(f, "Keyboard"), diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index afd5183de331..eb8e42328865 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -55,8 +55,8 @@ // user prompts void alert(DOMString message); void alert(); - //boolean confirm(optional DOMString message = ""); - //DOMString? prompt(optional DOMString message = "", optional DOMString default = ""); + boolean confirm(optional DOMString message = ""); + DOMString? prompt(optional DOMString message = "", optional DOMString default = ""); //void print(); //any showModalDialog(DOMString url, optional any argument); diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index dc30017e7d14..3c71acf894ef 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -74,7 +74,7 @@ use crossbeam_channel::{unbounded, Sender, TryRecvError}; use cssparser::{Parser, ParserInput, SourceLocation}; use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType}; use dom_struct::dom_struct; -use embedder_traits::{EmbedderMsg, EventLoopWaker}; +use embedder_traits::{EmbedderMsg, EventLoopWaker, PromptDefinition, PromptOrigin, PromptResult}; use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect}; use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; use ipc_channel::ipc::{channel, IpcSender}; @@ -620,11 +620,32 @@ impl WindowMethods for Window { } let (sender, receiver) = ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); - let msg = EmbedderMsg::Alert(s.to_string(), sender); + let prompt = PromptDefinition::Alert(s.to_string(), sender); + let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted); self.send_to_embedder(msg); 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(prompt, PromptOrigin::Untrusted); + self.send_to_embedder(msg); + receiver.recv().unwrap() == PromptResult::Primary + } + + // 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(prompt, PromptOrigin::Untrusted); + self.send_to_embedder(msg); + receiver.recv().unwrap().map(|s| s.into()) + } + // https://html.spec.whatwg.org/multipage/#dom-window-stop fn Stop(&self) { // TODO: Cancel ongoing navigation. diff --git a/ports/glutin/browser.rs b/ports/glutin/browser.rs index f5da82936dd6..1b21bfebf5fa 100644 --- a/ports/glutin/browser.rs +++ b/ports/glutin/browser.rs @@ -7,7 +7,7 @@ use crate::window_trait::{WindowPortsMethods, LINE_HEIGHT}; use euclid::{Point2D, Vector2D}; use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher}; use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent}; -use servo::embedder_traits::{EmbedderMsg, FilterPattern}; +use servo::embedder_traits::{EmbedderMsg, FilterPattern, PromptDefinition, PromptOrigin, PromptResult}; use servo::msg::constellation_msg::TopLevelBrowsingContextId as BrowserId; use servo::msg::constellation_msg::TraversalDirection; use servo::net_traits::pub_domains::is_reg_domain; @@ -24,7 +24,7 @@ use std::mem; use std::rc::Rc; use std::thread; use std::time::Duration; -use tinyfiledialogs::{self, MessageBoxIcon}; +use tinyfiledialogs::{self, MessageBoxIcon, OkCancel, YesNo}; pub struct Browser { current_url: Option, @@ -299,23 +299,78 @@ where EmbedderMsg::ResizeTo(size) => { self.window.set_inner_size(size); }, - EmbedderMsg::Alert(message, sender) => { - if !opts::get().headless { - let _ = thread::Builder::new() + EmbedderMsg::Prompt(definition, origin) => { + let res = if opts::get().headless { + match definition { + PromptDefinition::Alert(_message, sender) => { + sender.send(()) + } + PromptDefinition::YesNo(_message, sender) => { + sender.send(PromptResult::Primary) + } + PromptDefinition::OkCancel(_message, sender) => { + sender.send(PromptResult::Primary) + } + PromptDefinition::Input(_message, default, sender) => { + sender.send(Some(default.to_owned())) + } + } + } else { + thread::Builder::new() .name("display alert dialog".to_owned()) .spawn(move || { - tinyfiledialogs::message_box_ok( - "Alert!", - &tiny_dialog_escape(&message), - MessageBoxIcon::Warning, - ); + match definition { + PromptDefinition::Alert(mut message, sender) => { + if origin == PromptOrigin::Untrusted { + message = tiny_dialog_escape(&message); + } + tinyfiledialogs::message_box_ok( + "Alert!", + &message, + MessageBoxIcon::Warning, + ); + sender.send(()) + } + PromptDefinition::YesNo(mut message, sender) => { + if origin == PromptOrigin::Untrusted { + message = tiny_dialog_escape(&message); + } + let result = tinyfiledialogs::message_box_yes_no( + "", &message, MessageBoxIcon::Warning, YesNo::No, + ); + sender.send(match result { + YesNo::Yes => PromptResult::Primary, + YesNo::No => PromptResult::Secondary, + }) + } + PromptDefinition::OkCancel(mut message, sender) => { + if origin == PromptOrigin::Untrusted { + message = tiny_dialog_escape(&message); + } + let result = tinyfiledialogs::message_box_ok_cancel( + "", &message, MessageBoxIcon::Warning, OkCancel::Cancel, + ); + sender.send(match result { + OkCancel::Ok => PromptResult::Primary, + OkCancel::Cancel => PromptResult::Secondary, + }) + } + PromptDefinition::Input(mut message, mut default, sender) => { + if origin == PromptOrigin::Untrusted { + message = tiny_dialog_escape(&message); + default = tiny_dialog_escape(&default); + } + let result = tinyfiledialogs::input_box("", &message, &default); + sender.send(result) + } + } }) .unwrap() .join() - .expect("Thread spawning failed"); - } - if let Err(e) = sender.send(()) { - let reason = format!("Failed to send Alert response: {}", e); + .expect("Thread spawning failed") + }; + if let Err(e) = res { + let reason = format!("Failed to send Prompt response: {}", e); self.event_queue .push(WindowEvent::SendError(browser_id, reason)); } diff --git a/ports/libmlservo/src/lib.rs b/ports/libmlservo/src/lib.rs index 5cb2c12b10ce..cd7c023e5c6f 100644 --- a/ports/libmlservo/src/lib.rs +++ b/ports/libmlservo/src/lib.rs @@ -16,7 +16,9 @@ use servo::keyboard_types::Key; use servo::servo_url::ServoUrl; use servo::webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel}; use simpleservo::{self, deinit, gl_glue, MouseButton, ServoGlue, SERVO}; -use simpleservo::{Coordinates, EventLoopWaker, HostTrait, InitOptions, VRInitOptions}; +use simpleservo::{ + Coordinates, EventLoopWaker, HostTrait, InitOptions, PromptResult, VRInitOptions, +}; use smallvec::SmallVec; use std::cell::Cell; use std::ffi::CStr; @@ -365,7 +367,25 @@ impl HostTrait for HostCallbacks { MakeCurrent(self.disp, self.surf, self.surf, self.ctxt); } - fn on_alert(&self, _message: String) {} + fn prompt_alert(&self, message: String, _trusted: bool) { + warn!("Prompt Alert: {}", message); + } + + 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 { + 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 on_load_started(&self) {} fn on_load_ended(&self) {} fn on_title_changed(&self, _title: String) {} diff --git a/ports/libsimpleservo/api/src/lib.rs b/ports/libsimpleservo/api/src/lib.rs index 0b127eddb9a6..2489760052f0 100644 --- a/ports/libsimpleservo/api/src/lib.rs +++ b/ports/libsimpleservo/api/src/lib.rs @@ -7,7 +7,7 @@ extern crate log; pub mod gl_glue; -pub use servo::embedder_traits::MediaSessionPlaybackState; +pub use servo::embedder_traits::{MediaSessionPlaybackState, PromptResult}; pub use servo::script_traits::{MediaSessionActionType, MouseButton}; use getopts::Options; @@ -16,7 +16,7 @@ use servo::compositing::windowing::{ WindowMethods, }; use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods}; -use servo::embedder_traits::{EmbedderMsg, MediaSessionEvent}; +use servo::embedder_traits::{EmbedderMsg, MediaSessionEvent, PromptDefinition, PromptOrigin}; use servo::euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; use servo::keyboard_types::{Key, KeyState, KeyboardEvent}; use servo::msg::constellation_msg::TraversalDirection; @@ -92,8 +92,14 @@ pub trait HostTrait { /// Will be called before drawing. /// Time to make the targetted GL context current. fn make_current(&self); - /// javascript window.alert() - fn on_alert(&self, msg: String); + /// 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; /// Page starts loading. /// "Reload button" should be disabled. /// "Stop button" should be enabled. @@ -540,10 +546,27 @@ impl ServoGlue { EmbedderMsg::AllowUnload(sender) => { let _ = sender.send(true); }, - EmbedderMsg::Alert(message, sender) => { - info!("Alert: {}", message); - self.callbacks.host_callbacks.on_alert(message); - let _ = sender.send(()); + EmbedderMsg::Prompt(definition, origin) => { + let cb = &self.callbacks.host_callbacks; + let trusted = origin == PromptOrigin::Trusted; + let res = match definition { + PromptDefinition::Alert(message, sender) => { + sender.send(cb.prompt_alert(message, trusted)) + }, + PromptDefinition::OkCancel(message, sender) => { + sender.send(cb.prompt_ok_cancel(message, trusted)) + }, + PromptDefinition::YesNo(message, sender) => { + sender.send(cb.prompt_yes_no(message, trusted)) + }, + PromptDefinition::Input(message, default, sender) => { + sender.send(cb.prompt_input(message, default, trusted)) + }, + }; + if let Err(e) = res { + let reason = format!("Failed to send Prompt response: {}", e); + self.events.push(WindowEvent::SendError(browser_id, reason)); + } }, EmbedderMsg::AllowOpeningBrowser(response_chan) => { // Note: would be a place to handle pop-ups config. diff --git a/ports/libsimpleservo/capi/src/lib.rs b/ports/libsimpleservo/capi/src/lib.rs index 89d3b0f5c1ab..5bec2fb31ea8 100644 --- a/ports/libsimpleservo/capi/src/lib.rs +++ b/ports/libsimpleservo/capi/src/lib.rs @@ -18,7 +18,7 @@ use log::LevelFilter; use simpleservo::{self, gl_glue, ServoGlue, SERVO}; use simpleservo::{ Coordinates, EventLoopWaker, HostTrait, InitOptions, MediaSessionActionType, - MediaSessionPlaybackState, MouseButton, VRInitOptions, + MediaSessionPlaybackState, MouseButton, PromptResult, VRInitOptions, }; use std::ffi::{CStr, CString}; #[cfg(target_os = "windows")] @@ -205,7 +205,6 @@ where pub struct CHostCallbacks { pub flush: extern "C" fn(), pub make_current: extern "C" fn(), - pub on_alert: extern "C" fn(message: *const c_char), pub on_load_started: extern "C" fn(), pub on_load_ended: extern "C" fn(), pub on_title_changed: extern "C" fn(title: *const c_char), @@ -222,6 +221,14 @@ pub struct CHostCallbacks { pub on_media_session_playback_state_change: extern "C" fn(state: CMediaSessionPlaybackState), pub on_media_session_set_position_state: extern "C" fn(duration: f64, position: f64, playback_rate: f64), + pub prompt_alert: extern "C" fn(message: *const c_char, trusted: bool), + pub prompt_ok_cancel: extern "C" fn(message: *const c_char, trusted: bool) -> CPromptResult, + pub prompt_yes_no: extern "C" fn(message: *const c_char, trusted: bool) -> CPromptResult, + pub prompt_input: extern "C" fn( + message: *const c_char, + default: *const c_char, + trusted: bool, + ) -> *const c_char, } /// Servo options @@ -255,6 +262,23 @@ impl CMouseButton { } } +#[repr(C)] +pub enum CPromptResult { + Dismissed, + Primary, + Secondary, +} + +impl CPromptResult { + pub fn convert(&self) -> PromptResult { + match self { + CPromptResult::Primary => PromptResult::Primary, + CPromptResult::Secondary => PromptResult::Secondary, + CPromptResult::Dismissed => PromptResult::Dismissed, + } + } +} + #[repr(C)] pub enum CMediaSessionPlaybackState { None = 1, @@ -698,12 +722,6 @@ impl HostTrait for HostCallbacks { (self.0.make_current)(); } - fn on_alert(&self, message: String) { - debug!("on_alert"); - let message = CString::new(message).expect("Can't create string"); - (self.0.on_alert)(message.as_ptr()); - } - fn on_load_started(&self) { debug!("on_load_started"); (self.0.on_load_started)(); @@ -797,4 +815,35 @@ impl HostTrait for HostCallbacks { ); (self.0.on_media_session_set_position_state)(duration, position, playback_rate); } + + fn prompt_alert(&self, message: String, trusted: bool) { + debug!("prompt_alert"); + let message = CString::new(message).expect("Can't create string"); + (self.0.prompt_alert)(message.as_ptr(), trusted); + } + + fn prompt_ok_cancel(&self, message: String, trusted: bool) -> PromptResult { + debug!("prompt_ok_cancel"); + let message = CString::new(message).expect("Can't create string"); + (self.0.prompt_ok_cancel)(message.as_ptr(), trusted).convert() + } + + fn prompt_yes_no(&self, message: String, trusted: bool) -> PromptResult { + debug!("prompt_yes_no"); + let message = CString::new(message).expect("Can't create string"); + (self.0.prompt_yes_no)(message.as_ptr(), trusted).convert() + } + + fn prompt_input(&self, message: String, default: String, trusted: bool) -> Option { + debug!("prompt_input"); + let message = CString::new(message).expect("Can't create string"); + let default = CString::new(default).expect("Can't create string"); + let raw_contents = (self.0.prompt_input)(message.as_ptr(), default.as_ptr(), trusted); + if raw_contents.is_null() { + return None; + } + let c_str = unsafe { CStr::from_ptr(raw_contents) }; + let contents_str = c_str.to_str().expect("Can't create str"); + Some(contents_str.to_owned()) + } } diff --git a/ports/libsimpleservo/jniapi/src/lib.rs b/ports/libsimpleservo/jniapi/src/lib.rs index 1c4020652e80..9a7a6b80dd98 100644 --- a/ports/libsimpleservo/jniapi/src/lib.rs +++ b/ports/libsimpleservo/jniapi/src/lib.rs @@ -16,7 +16,8 @@ use libc::{dup2, pipe, read}; use log::Level; use simpleservo::{self, gl_glue, ServoGlue, SERVO}; use simpleservo::{ - Coordinates, EventLoopWaker, HostTrait, InitOptions, MediaSessionPlaybackState, VRInitOptions, + Coordinates, EventLoopWaker, HostTrait, InitOptions, MediaSessionPlaybackState, PromptResult, + VRInitOptions, }; use std::os::raw::{c_char, c_int, c_void}; use std::ptr::{null, null_mut}; @@ -394,8 +395,8 @@ impl HostTrait for HostCallbacks { .unwrap(); } - fn on_alert(&self, message: String) { - debug!("on_alert"); + fn prompt_alert(&self, message: String, _trusted: bool) { + debug!("prompt_alert"); let env = self.jvm.get_env().unwrap(); let s = match new_string(&env, &message) { Ok(s) => s, @@ -411,6 +412,21 @@ impl HostTrait for HostCallbacks { .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 { + 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 on_load_started(&self) { debug!("on_load_started"); let env = self.jvm.get_env().unwrap(); diff --git a/support/hololens/ServoApp/ServoControl/Servo.cpp b/support/hololens/ServoApp/ServoControl/Servo.cpp index 6a3a93cc34b5..a6e4952956e8 100644 --- a/support/hololens/ServoApp/ServoControl/Servo.cpp +++ b/support/hololens/ServoApp/ServoControl/Servo.cpp @@ -13,10 +13,6 @@ void on_history_changed(bool back, bool forward) { void on_shutdown_complete() { sServo->Delegate().OnServoShutdownComplete(); } -void on_alert(const char *message) { - sServo->Delegate().OnServoAlert(char2hstring(message)); -} - void on_title_changed(const char *title) { sServo->Delegate().OnServoTitleChanged(char2hstring(title)); } @@ -67,6 +63,30 @@ void on_media_session_playback_state_change( return sServo->Delegate().OnServoMediaSessionPlaybackStateChange(state); } +void prompt_alert(const char *message, bool trusted) { + sServo->Delegate().OnServoPromptAlert(char2hstring(message), trusted); +} + +Servo::PromptResult prompt_ok_cancel(const char *message, bool trusted) { + return sServo->Delegate().OnServoPromptOkCancel(char2hstring(message), + trusted); +} + +Servo::PromptResult prompt_yes_no(const char *message, bool trusted) { + return sServo->Delegate().OnServoPromptYesNo(char2hstring(message), trusted); +} + +const char *prompt_input(const char *message, const char *default, + bool trusted) { + auto input = sServo->Delegate().OnServoPromptInput( + char2hstring(message), char2hstring(default), trusted); + if (input.has_value()) { + return *hstring2char(*input); + } else { + return nullptr; + } +} + Servo::Servo(hstring url, hstring args, GLsizei width, GLsizei height, float dpi, ServoDelegate &aDelegate) : mWindowHeight(height), mWindowWidth(width), mDelegate(aDelegate) { @@ -109,7 +129,6 @@ Servo::Servo(hstring url, hstring args, GLsizei width, GLsizei height, capi::CHostCallbacks c; c.flush = &flush; c.make_current = &make_current; - c.on_alert = &on_alert; c.on_load_started = &on_load_started; c.on_load_ended = &on_load_ended; c.on_title_changed = &on_title_changed; @@ -124,6 +143,10 @@ Servo::Servo(hstring url, hstring args, GLsizei width, GLsizei height, c.on_media_session_metadata = &on_media_session_metadata; c.on_media_session_playback_state_change = &on_media_session_playback_state_change; + c.prompt_alert = &prompt_alert; + c.prompt_ok_cancel = &prompt_ok_cancel; + c.prompt_yes_no = &prompt_yes_no; + c.prompt_input = &prompt_input; capi::register_panic_handler(&on_panic); diff --git a/support/hololens/ServoApp/ServoControl/Servo.h b/support/hololens/ServoApp/ServoControl/Servo.h index 43da01607550..e0aef56c4eda 100644 --- a/support/hololens/ServoApp/ServoControl/Servo.h +++ b/support/hololens/ServoApp/ServoControl/Servo.h @@ -19,29 +19,7 @@ extern "C" { hstring char2hstring(const char *); std::unique_ptr hstring2char(hstring); -class ServoDelegate { -public: - // Called from any thread - virtual void WakeUp() = 0; - // Called from GL thread - virtual void OnServoLoadStarted() = 0; - virtual void OnServoLoadEnded() = 0; - virtual void OnServoHistoryChanged(bool, bool) = 0; - virtual void OnServoShutdownComplete() = 0; - virtual void OnServoTitleChanged(hstring) = 0; - virtual void OnServoAlert(hstring) = 0; - virtual void OnServoURLChanged(hstring) = 0; - virtual bool OnServoAllowNavigation(hstring) = 0; - virtual void OnServoAnimatingChanged(bool) = 0; - virtual void OnServoIMEStateChanged(bool) = 0; - virtual void Flush() = 0; - virtual void MakeCurrent() = 0; - virtual void OnServoMediaSessionMetadata(hstring, hstring, hstring) = 0; - virtual void OnServoMediaSessionPlaybackStateChange(int) = 0; - -protected: - virtual ~ServoDelegate(){}; -}; +class ServoDelegate; class Servo { public: @@ -50,6 +28,7 @@ class Servo { ServoDelegate &Delegate() { return mDelegate; } typedef capi::CMouseButton MouseButton; + typedef capi::CPromptResult PromptResult; typedef capi::CMediaSessionActionType MediaSessionActionType; typedef capi::CMediaSessionPlaybackState MediaSessionPlaybackState; @@ -100,6 +79,33 @@ class Servo { GLsizei mWindowHeight; }; +class ServoDelegate { +public: + // Called from any thread + virtual void WakeUp() = 0; + // Called from GL thread + virtual void OnServoLoadStarted() = 0; + virtual void OnServoLoadEnded() = 0; + virtual void OnServoHistoryChanged(bool, bool) = 0; + virtual void OnServoShutdownComplete() = 0; + virtual void OnServoTitleChanged(hstring) = 0; + virtual void OnServoURLChanged(hstring) = 0; + virtual bool OnServoAllowNavigation(hstring) = 0; + virtual void OnServoAnimatingChanged(bool) = 0; + virtual void OnServoIMEStateChanged(bool) = 0; + virtual void Flush() = 0; + virtual void MakeCurrent() = 0; + virtual void OnServoMediaSessionMetadata(hstring, hstring, hstring) = 0; + virtual void OnServoMediaSessionPlaybackStateChange(int) = 0; + virtual void OnServoPromptAlert(hstring, bool) = 0; + virtual Servo::PromptResult OnServoPromptOkCancel(hstring, bool) = 0; + virtual Servo::PromptResult OnServoPromptYesNo(hstring, bool) = 0; + virtual std::optional OnServoPromptInput(hstring, hstring, bool) = 0; + +protected: + virtual ~ServoDelegate(){}; +}; + // This is sad. We need a static pointer to Servo because we use function // pointer as callback in Servo, and these functions need a way to get // the Servo instance. See https://github.com/servo/servo/issues/22967 diff --git a/support/hololens/ServoApp/ServoControl/ServoControl.cpp b/support/hololens/ServoApp/ServoControl/ServoControl.cpp index 9e3321f74b9e..e4df10318043 100644 --- a/support/hololens/ServoApp/ServoControl/ServoControl.cpp +++ b/support/hololens/ServoApp/ServoControl/ServoControl.cpp @@ -6,6 +6,7 @@ using namespace std::placeholders; using namespace winrt::Windows::Graphics::Display; using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Popups; using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::System; @@ -69,6 +70,8 @@ void ServoControl::OnLoaded(IInspectable const &, RoutedEventArgs const &) { Panel().SizeChanged(std::bind(&ServoControl::OnSurfaceResized, this, _1, _2)); InitializeConditionVariable(&mGLCondVar); InitializeCriticalSection(&mGLLock); + InitializeConditionVariable(&mDialogCondVar); + InitializeCriticalSection(&mDialogLock); CreateRenderSurface(); StartRenderLoop(); } @@ -285,7 +288,7 @@ void ServoControl::TryLoadUri(hstring input) { RunOnGLThread([=] { if (!mServo->LoadUri(input)) { RunOnUIThread([=] { - Windows::UI::Popups::MessageDialog msg{L"URI not valid"}; + MessageDialog msg{L"URI not valid"}; msg.ShowAsync(); }); } @@ -389,20 +392,15 @@ void ServoControl::OnServoShutdownComplete() { LeaveCriticalSection(&mGLLock); } -void ServoControl::OnServoAlert(hstring message) { - // FIXME: make this sync - RunOnUIThread([=] { - Windows::UI::Popups::MessageDialog msg{message}; - msg.ShowAsync(); - }); -} - void ServoControl::OnServoTitleChanged(hstring title) { RunOnUIThread([=] { mOnTitleChangedEvent(*this, title); }); } void ServoControl::OnServoURLChanged(hstring url) { - RunOnUIThread([=] { mOnURLChangedEvent(*this, url); }); + RunOnUIThread([=] { + mCurrentUrl = url; + mOnURLChangedEvent(*this, url); + }); } void ServoControl::Flush() { @@ -448,6 +446,113 @@ void ServoControl::OnServoMediaSessionPlaybackStateChange(int state) { RunOnUIThread([=] { mOnMediaSessionPlaybackStateChangeEvent(*this, state); }); } +std::tuple> +ServoControl::PromptSync(hstring title, hstring message, hstring primaryButton, + std::optional secondaryButton, + std::optional input) { + + bool showing = true; + Controls::ContentDialogResult retButton = Controls::ContentDialogResult::None; + std::optional retString = {}; + + EnterCriticalSection(&mDialogLock); + + Dispatcher().RunAsync(CoreDispatcherPriority::High, [&] { + auto dialog = Controls::ContentDialog(); + dialog.IsPrimaryButtonEnabled(true); + dialog.PrimaryButtonText(primaryButton); + + if (secondaryButton.has_value()) { + dialog.IsPrimaryButtonEnabled(true); + dialog.SecondaryButtonText(*secondaryButton); + } else { + dialog.IsPrimaryButtonEnabled(false); + } + + auto titleBlock = Controls::TextBlock(); + titleBlock.Text(title); + + auto messageBlock = Controls::TextBlock(); + messageBlock.TextWrapping(TextWrapping::Wrap); + messageBlock.Text(message); + Controls::StackPanel stack = Controls::StackPanel(); + stack.Children().Append(titleBlock); + stack.Children().Append(messageBlock); + + dialog.Content(stack); + + auto textbox = Controls::TextBox(); + textbox.KeyDown([=](auto sender, auto args) { + if (args.Key() == Windows::System::VirtualKey::Enter) { + dialog.Hide(); + } + }); + if (input.has_value()) { + textbox.Text(*input); + stack.Children().Append(textbox); + } + + dialog.Closed([&, textbox](Controls::ContentDialog d, auto closed) { + EnterCriticalSection(&mDialogLock); + retButton = closed.Result(); + showing = false; + if (retButton == Controls::ContentDialogResult::Primary && + input.has_value()) { + retString = hstring(textbox.Text()); + } + LeaveCriticalSection(&mDialogLock); + WakeConditionVariable(&mDialogCondVar); + }); + dialog.ShowAsync(); + }); + + while (showing) { + SleepConditionVariableCS(&mDialogCondVar, &mDialogLock, INFINITE); + } + LeaveCriticalSection(&mDialogLock); + + return {retButton, retString}; +} + +void ServoControl::OnServoPromptAlert(winrt::hstring message, bool trusted) { + auto title = trusted ? L"" : mCurrentUrl + L" says:"; + PromptSync(title, message, L"OK", {}, {}); +} + +servo::Servo::PromptResult +ServoControl::OnServoPromptOkCancel(winrt::hstring message, bool trusted) { + auto title = trusted ? L"" : mCurrentUrl + L" says:"; + auto [button, string] = PromptSync(title, message, L"OK", L"Cancel", {}); + if (button == Controls::ContentDialogResult::Primary) { + return servo::Servo::PromptResult::Primary; + } else if (button == Controls::ContentDialogResult::Secondary) { + return servo::Servo::PromptResult::Secondary; + } else { + return servo::Servo::PromptResult::Dismissed; + } +} + +servo::Servo::PromptResult +ServoControl::OnServoPromptYesNo(winrt::hstring message, bool trusted) { + auto title = trusted ? L"" : mCurrentUrl + L" says:"; + auto [button, string] = PromptSync(title, message, L"Yes", L"No", {}); + if (button == Controls::ContentDialogResult::Primary) { + return servo::Servo::PromptResult::Primary; + } else if (button == Controls::ContentDialogResult::Secondary) { + return servo::Servo::PromptResult::Secondary; + } else { + return servo::Servo::PromptResult::Dismissed; + } +} + +std::optional ServoControl::OnServoPromptInput(winrt::hstring message, + winrt::hstring default, + bool trusted) { + auto title = trusted ? L"" : mCurrentUrl + L" says:"; + auto [button, string] = PromptSync(title, message, L"Ok", L"Cancel", default); + return string; +} + template void ServoControl::RunOnUIThread(Callable cb) { Dispatcher().RunAsync(CoreDispatcherPriority::High, cb); } diff --git a/support/hololens/ServoApp/ServoControl/ServoControl.h b/support/hololens/ServoApp/ServoControl/ServoControl.h index 5ca3f906e993..ca05373ce967 100644 --- a/support/hololens/ServoApp/ServoControl/ServoControl.h +++ b/support/hololens/ServoApp/ServoControl/ServoControl.h @@ -98,7 +98,6 @@ struct ServoControl : ServoControlT, public servo::ServoDelegate { virtual void OnServoHistoryChanged(bool, bool); virtual void OnServoShutdownComplete(); virtual void OnServoTitleChanged(winrt::hstring); - virtual void OnServoAlert(winrt::hstring); virtual void OnServoURLChanged(winrt::hstring); virtual void Flush(); virtual void MakeCurrent(); @@ -108,6 +107,12 @@ struct ServoControl : ServoControlT, public servo::ServoDelegate { virtual void OnServoMediaSessionMetadata(winrt::hstring, winrt::hstring, winrt::hstring); virtual void OnServoMediaSessionPlaybackStateChange(int); + virtual void OnServoPromptAlert(winrt::hstring, bool); + virtual servo::Servo::PromptResult OnServoPromptOkCancel(winrt::hstring, + bool); + virtual servo::Servo::PromptResult OnServoPromptYesNo(winrt::hstring, bool); + virtual std::optional OnServoPromptInput(winrt::hstring, + winrt::hstring, bool); private: winrt::event> mOnURLChangedEvent; @@ -121,8 +126,18 @@ struct ServoControl : ServoControlT, public servo::ServoDelegate { winrt::event> mOnMediaSessionPlaybackStateChangeEvent; + CRITICAL_SECTION mDialogLock; + CONDITION_VARIABLE mDialogCondVar; + + std::tuple> + PromptSync(hstring title, hstring message, hstring primaryButton, + std::optional secondaryButton, + std::optional input); + float mDPI = 1; hstring mInitialURL = DEFAULT_URL; + hstring mCurrentUrl = L""; bool mTransient = false; Windows::UI::Xaml::Controls::SwapChainPanel ServoControl::Panel(); diff --git a/tests/wpt/metadata/html/browsers/the-window-object/window-properties.https.html.ini b/tests/wpt/metadata/html/browsers/the-window-object/window-properties.https.html.ini index d2a0fd7bb381..10e493e051c6 100644 --- a/tests/wpt/metadata/html/browsers/the-window-object/window-properties.https.html.ini +++ b/tests/wpt/metadata/html/browsers/the-window-object/window-properties.https.html.ini @@ -5,12 +5,6 @@ [Window method: blur] expected: FAIL - [Window method: confirm] - expected: FAIL - - [Window method: prompt] - expected: FAIL - [Window method: print] expected: FAIL diff --git a/tests/wpt/metadata/html/dom/idlharness.https.html.ini b/tests/wpt/metadata/html/dom/idlharness.https.html.ini index 7d0fadaea7db..1c094dd479f9 100644 --- a/tests/wpt/metadata/html/dom/idlharness.https.html.ini +++ b/tests/wpt/metadata/html/dom/idlharness.https.html.ini @@ -1471,9 +1471,6 @@ [Document interface: operation queryCommandEnabled(DOMString)] expected: FAIL - [Window interface: window must inherit property "prompt(DOMString, DOMString)" with the proper type] - expected: FAIL - [Window interface: window must inherit property "onsecuritypolicyviolation" with the proper type] expected: FAIL @@ -1486,9 +1483,6 @@ [Document interface: calling queryCommandSupported(DOMString) on new Document() with too few arguments must throw TypeError] expected: FAIL - [Window interface: calling confirm(DOMString) on window with too few arguments must throw TypeError] - expected: FAIL - [Window interface: operation focus()] expected: FAIL @@ -1504,9 +1498,6 @@ [Document interface: iframe.contentDocument must inherit property "linkColor" with the proper type] expected: FAIL - [Window interface: operation prompt(DOMString, DOMString)] - expected: FAIL - [Document interface: documentWithHandlers must inherit property "dir" with the proper type] expected: FAIL @@ -1525,9 +1516,6 @@ [Document interface: iframe.contentDocument must inherit property "queryCommandValue(DOMString)" with the proper type] expected: FAIL - [Window interface: window must inherit property "confirm(DOMString)" with the proper type] - expected: FAIL - [Document interface: operation queryCommandSupported(DOMString)] expected: FAIL @@ -1657,9 +1645,6 @@ [Window interface: window must inherit property "focus()" with the proper type] expected: FAIL - [Window interface: calling prompt(DOMString, DOMString) on window with too few arguments must throw TypeError] - expected: FAIL - [Document interface: new Document() must inherit property "onauxclick" with the proper type] expected: FAIL @@ -1696,9 +1681,6 @@ [Document interface: operation execCommand(DOMString, boolean, DOMString)] expected: FAIL - [Window interface: operation confirm(DOMString)] - expected: FAIL - [Window interface: window must inherit property "toolbar" with the proper type] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html.ini index f263d01b825f..d639c7956721 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-2.html.ini @@ -1,5 +1,4 @@ [iframe_sandbox_block_modals-2.html] - expected: TIMEOUT [Frames without `allow-modals` should not be able to open modal dialogs] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html.ini index 770c94d12946..e67d34e80309 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_block_modals-3.html.ini @@ -1,5 +1,4 @@ [iframe_sandbox_block_modals-3.html] - expected: TIMEOUT [Frames without `allow-modals` should not be able to open modal dialogs] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini index cb0211f1d217..f6a7aca3306c 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini @@ -1,6 +1,5 @@ [iframe_sandbox_popups_escaping-3.html] type: testharness - expected: CRASH [Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used] expected: FAIL