From 8910a88a93fb933f613bc8a3f44244494759d9ec Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Sat, 21 Mar 2020 16:46:35 -0700 Subject: [PATCH] Fix up the WebView implementation so that Subatomic compiles on the latest appkit codebase. Undo an experimental NSString wrapper type that caused problems. Docs forthcoming. --- appkit/constants.rs | 9 --- appkit/foundation/string.rs | 2 +- appkit/webview/class.rs | 136 +++++++++++++++++++----------------- appkit/webview/config.rs | 12 ++-- appkit/webview/enums.rs | 18 +++++ appkit/webview/mod.rs | 63 +++++++++++------ 6 files changed, 140 insertions(+), 100 deletions(-) diff --git a/appkit/constants.rs b/appkit/constants.rs index 0c17a1e..9b2f4b8 100644 --- a/appkit/constants.rs +++ b/appkit/constants.rs @@ -2,12 +2,3 @@ //! Specific to this crate. pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr"; - -#[cfg(feature = "webview")] -pub(crate) static WEBVIEW_CONFIG_VAR: &str = "rstWebViewConfig"; - -#[cfg(feature = "webview")] -pub(crate) static WEBVIEW_VAR: &str = "rstWebView"; - -#[cfg(feature = "webview")] -pub(crate) static WEBVIEW_CONTROLLER_PTR: &str = "rstWebViewControllerPtr"; diff --git a/appkit/foundation/string.rs b/appkit/foundation/string.rs index d3de15e..f647ec7 100644 --- a/appkit/foundation/string.rs +++ b/appkit/foundation/string.rs @@ -32,7 +32,7 @@ impl NSString { pub fn wrap(object: id) -> Self { NSString(unsafe { - Id::from_retained_ptr(object) + Id::from_ptr(object) }) } diff --git a/appkit/webview/class.rs b/appkit/webview/class.rs index d17ca3b..3695392 100644 --- a/appkit/webview/class.rs +++ b/appkit/webview/class.rs @@ -2,8 +2,9 @@ //! this is primarily used as the ContentView for a window. From there, //! we configure an NSToolbar and WKWebview on top of them. -use std::sync::Once; use std::ffi::c_void; +use std::sync::Once; +use std::rc::Rc; use block::Block; @@ -11,16 +12,16 @@ use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{class, msg_send, sel, sel_impl}; -use crate::foundation::{id, nil, YES, NO, CGRect, NSString, NSArray, NSInteger}; +use crate::foundation::{id, nil, YES, NO, NSString, NSArray, NSInteger}; use crate::webview::{WEBVIEW_DELEGATE_PTR, WebViewDelegate}; -use crate::webview::actions::{NavigationAction, NavigationResponse, OpenPanelParameters}; -use crate::webview::enums::{NavigationPolicy, NavigationResponsePolicy}; +use crate::webview::actions::{NavigationAction, NavigationResponse};//, OpenPanelParameters}; +//use crate::webview::enums::{NavigationPolicy, NavigationResponsePolicy}; +use crate::utils::load; /// Called when an `alert()` from the underlying `WKWebView` is fired. Will call over to your /// `WebViewController`, where you should handle the event. extern fn alert(_: &Object, _: Sel, _: id, s: id, _: id, complete: id) { let alert = NSString::wrap(s).to_str(); - println!("Alert: {}", alert); // @TODO: This is technically (I think?) a private method, and there's some other dance that // needs to be done here involving taking the pointer/invoke/casting... but this is fine for @@ -31,7 +32,7 @@ extern fn alert(_: &Object, _: Sel, _: id, s: id, _: id, com /*unsafe { let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); - let webview = ptr as *const T; + let delegate = ptr as *const T; (*webview).alert(alert); }*/ @@ -44,73 +45,80 @@ extern fn alert(_: &Object, _: Sel, _: id, s: id, _: id, com /// Fires when a message has been passed from the underlying `WKWebView`. extern fn on_message(this: &Object, _: Sel, _: id, script_message: id) { + let delegate = load::(this, WEBVIEW_DELEGATE_PTR); + unsafe { let name = NSString::wrap(msg_send![script_message, name]).to_str(); let body = NSString::wrap(msg_send![script_message, body]).to_str(); - - let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); - let webview = ptr as *const T; - (*webview).on_message(name, body); + let d = delegate.borrow(); + (*d).on_message(name, body); } + + Rc::into_raw(delegate); } /// Fires when deciding a navigation policy - i.e, should something be allowed or not. extern fn decide_policy_for_action(this: &Object, _: Sel, _: id, action: id, handler: usize) { - let webview = unsafe { - let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); - let webview = ptr as *const T; - &*webview - }; + let delegate = load::(this, WEBVIEW_DELEGATE_PTR); let action = NavigationAction::new(action); - webview.policy_for_navigation_action(action, |policy| { - // This is very sketch and should be heavily checked. :| - unsafe { + + { + let d = delegate.borrow(); + + (*d).policy_for_navigation_action(action, |policy| unsafe { let handler = handler as *const Block<(NSInteger,), c_void>; (*handler).call((policy.into(),)); - } - }); + }); + } + + Rc::into_raw(delegate); } /// Fires when deciding a navigation policy - i.e, should something be allowed or not. extern fn decide_policy_for_response(this: &Object, _: Sel, _: id, response: id, handler: usize) { - let webview = unsafe { - let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); - let webview = ptr as *const T; - &*webview - }; + let delegate = load::(this, WEBVIEW_DELEGATE_PTR); let response = NavigationResponse::new(response); - webview.policy_for_navigation_response(response, |policy| unsafe { - let handler = handler as *const Block<(NSInteger,), c_void>; - (*handler).call((policy.into(),)); - }); + + { + let d = delegate.borrow(); + + (*d).policy_for_navigation_response(response, |policy| unsafe { + let handler = handler as *const Block<(NSInteger,), c_void>; + (*handler).call((policy.into(),)); + }); + } + + Rc::into_raw(delegate); } /// Fires when deciding a navigation policy - i.e, should something be allowed or not. extern fn run_open_panel(this: &Object, _: Sel, _: id, params: id, _: id, handler: usize) { - let webview = unsafe { - let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); - let webview = ptr as *const T; - &*webview - }; + let delegate = load::(this, WEBVIEW_DELEGATE_PTR); - webview.run_open_panel(params.into(), move |urls| unsafe { - let handler = handler as *const Block<(id,), c_void>; + { + let d = delegate.borrow(); - match urls { - Some(u) => { - let nsurls: NSArray = u.iter().map(|s| { - let s = NSString::new(s); - msg_send![class!(NSURL), URLWithString:s] - }).collect::>().into(); + (*d).run_open_panel(params.into(), move |urls| unsafe { + let handler = handler as *const Block<(id,), c_void>; - (*handler).call((nsurls.into_inner(),)); - }, + match urls { + Some(u) => { + let nsurls: NSArray = u.iter().map(|s| { + let s = NSString::new(s); + msg_send![class!(NSURL), URLWithString:s.into_inner()] + }).collect::>().into(); - None => { (*handler).call((nil,)); } - } - }); + (*handler).call((nsurls.into_inner(),)); + }, + + None => { (*handler).call((nil,)); } + } + }); + } + + Rc::into_raw(delegate); } /// Called when a download has been initiated in the WebView, and when the navigation policy @@ -118,27 +126,29 @@ extern fn run_open_panel(this: &Object, _: Sel, _: id, param /// API. #[cfg(feature = "webview-downloading")] extern fn handle_download(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) { - let webview = unsafe { - let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); - let webview = ptr as *const T; - &*webview - }; + let delegate = load::(this, WEBVIEW_DELEGATE_PTR); let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>; let filename = NSString::wrap(suggested_filename).to_str(); - webview.run_save_panel(filename, move |can_overwrite, path| unsafe { - if path.is_none() { - let _: () = msg_send![download, cancel]; - } + { + let d = delegate.borrow(); - let path = NSString::new(&path.unwrap()); - - (*handler).call((match can_overwrite { - true => YES, - false => NO - }, path.into_inner())); - }); + (*d).run_save_panel(filename, move |can_overwrite, path| unsafe { + if path.is_none() { + let _: () = msg_send![download, cancel]; + } + + let path = NSString::new(&path.unwrap()); + + (*handler).call((match can_overwrite { + true => YES, + false => NO + }, path.into_inner())); + }); + } + + Rc::into_raw(delegate); } /// Registers an `NSViewController` that we effectively turn into a `WebViewController`. Acts as diff --git a/appkit/webview/config.rs b/appkit/webview/config.rs index 731b8f9..37cc597 100644 --- a/appkit/webview/config.rs +++ b/appkit/webview/config.rs @@ -5,7 +5,7 @@ use objc_id::Id; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; -use crate::foundation::{id, YES, NO, NSString}; +use crate::foundation::{id, YES, NO, NSString, NSInteger}; use crate::webview::enums::InjectAt; /// A wrapper for `WKWebViewConfiguration`. Holds (retains) pointers for the Objective-C runtime @@ -40,10 +40,11 @@ impl WebViewConfig { /// Adds the given user script to the underlying `WKWebView` user content controller. pub fn add_user_script(&mut self, script: &str, at: InjectAt, main_frame_only: bool) { let source = NSString::new(script); + let at: NSInteger = at.into(); unsafe { let alloc: id = msg_send![class!(WKUserScript), alloc]; - let user_script: id = msg_send![alloc, initWithSource:source injectionTime:inject_at forMainFrameOnly:match main_frame_only { + let user_script: id = msg_send![alloc, initWithSource:source injectionTime:at forMainFrameOnly:match main_frame_only { true => YES, false => NO }]; @@ -64,11 +65,8 @@ impl WebViewConfig { } } - pub(crate) fn attach_handlers(&self, target: id) { - - } - - pub fn into_inner(self) -> id { + /// Consumes and returns the underlying `WKWebViewConfiguration`. + pub fn into_inner(mut self) -> id { &mut *self.objc } } diff --git a/appkit/webview/enums.rs b/appkit/webview/enums.rs index 92f4200..8cb3d33 100644 --- a/appkit/webview/enums.rs +++ b/appkit/webview/enums.rs @@ -24,6 +24,24 @@ pub enum NavigationType { Other } +// For whatever reason, impl From<> below doesn't generate the reciprocal impl Into<> we need. +// So I guess we'll do it ourselves. +// +// This panic will be removed and is for testing purposes only right now. +impl Into for NSInteger { + fn into(self) -> NavigationType { + match self { + -1 => NavigationType::Other, + 0 => NavigationType::LinkActivated, + 1 => NavigationType::FormSubmitted, + 2 => NavigationType::BackForward, + 3 => NavigationType::Reload, + 4 => NavigationType::FormResubmitted, + _ => { panic!("Unsupported WKWebView NavigationType value found!"); } + } + } +} + impl From for NSInteger { fn from(nav_type: NavigationType) -> Self { match nav_type { diff --git a/appkit/webview/mod.rs b/appkit/webview/mod.rs index 7a29d88..7f20be6 100644 --- a/appkit/webview/mod.rs +++ b/appkit/webview/mod.rs @@ -9,10 +9,10 @@ use std::rc::Rc; use std::cell::RefCell; use objc_id::ShareId; -use objc::runtime::{Class, Object}; +use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; -use crate::foundation::{id, nil, YES, NO, CGRect, NSString}; +use crate::foundation::{id, YES, NO, CGRect, NSString}; use crate::geometry::Rect; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; @@ -20,7 +20,7 @@ pub mod actions; pub mod enums; pub(crate) mod class; -use class::{register_webview_class, register_webview_class_with_delegate}; +use class::{register_webview_class, register_webview_delegate_class}; //pub(crate) mod process_pool; pub mod traits; @@ -32,31 +32,38 @@ pub use config::WebViewConfig; pub(crate) static WEBVIEW_DELEGATE_PTR: &str = "rstWebViewDelegatePtr"; fn allocate_webview( - config: WebViewConfig, - delegate: Option<&Object> + mut config: WebViewConfig, + objc_delegate: Option<&Object> ) -> id { unsafe { + // Not a fan of this, but we own it anyway, so... meh. + let handlers = std::mem::take(&mut config.handlers); let configuration = config.into_inner(); - if let Some(delegate) = objc_delegate { + if let Some(delegate) = &objc_delegate { // Technically private! #[cfg(feature = "webview-downloading")] let process_pool: id = msg_send![configuration, processPool]; #[cfg(feature = "webview-downloading")] let _: () = msg_send![process_pool, _setDownloadDelegate:*delegate]; - // add handlers - for + let content_controller: id = msg_send![configuration, userContentController]; + for handler in handlers { + let name = NSString::new(&handler); + let _: () = msg_send![content_controller, addScriptMessageHandler:*delegate name:name]; + } } let zero: CGRect = Rect::zero().into(); let webview_alloc: id = msg_send![register_webview_class(), alloc]; let webview: id = msg_send![webview_alloc, initWithFrame:zero configuration:configuration]; - let _: () = msg_send![webview, setWantsLayer:YES]; let _: () = msg_send![webview, setTranslatesAutoresizingMaskIntoConstraints:NO]; - let _: () = msg_send![webview, setNavigationDelegate:webview]; - let _: () = msg_send![webview, setUIDelegate:webview]; + + if let Some(delegate) = &objc_delegate { + let _: () = msg_send![webview, setNavigationDelegate:*delegate]; + let _: () = msg_send![webview, setUIDelegate:*delegate]; + } webview } @@ -110,7 +117,7 @@ impl Default for WebView { impl WebView { pub fn new(config: WebViewConfig) -> Self { - let view = allocate_webview(register_webview_class, config, None); + let view = allocate_webview(config, None); WebView { internal_callback_ptr: None, @@ -141,7 +148,7 @@ impl WebView where T: WebViewDelegate + 'static { }; let objc_delegate = unsafe { - let objc_delegate: id = msg_send![register_webview_delegate_class::, new]; + let objc_delegate: id = msg_send![register_webview_delegate_class::(), new]; (&mut *objc_delegate).set_ivar(WEBVIEW_DELEGATE_PTR, internal_callback_ptr as usize); ShareId::from_ptr(objc_delegate) }; @@ -194,6 +201,18 @@ impl WebView { objc_delegate: None } } + + /// Given a URL, instructs the WebView to load it. + // @TODO: Make this take Url instead? Fine for testing now I suppose. + pub fn load_url(&self, url: &str) { + let url = NSString::new(url); + + unsafe { + let u: id = msg_send![class!(NSURL), URLWithString:url.into_inner()]; + let request: id = msg_send![class!(NSURLRequest), requestWithURL:u]; + let _: () = msg_send![&*self.objc, loadRequest:request]; + } + } } impl Layout for WebView { @@ -202,9 +221,9 @@ impl Layout for WebView { self.objc.clone() } - fn add_subview(&self, subview: &V) { - - } + /// Currently, this is a noop. Theoretically there is reason to support this, but in practice + /// I've never seen it needed... but am open to discussion. + fn add_subview(&self, _: &V) {} } impl std::fmt::Debug for WebView { @@ -216,10 +235,14 @@ impl std::fmt::Debug for WebView { impl Drop for WebView { /// A bit of extra cleanup for delegate callback pointers. fn drop(&mut self) { - unsafe { - if let Some(ptr) = self.internal_callback_ptr { - let _ = Rc::from_raw(ptr); + /*println!("... {}", self.delegate.is_some()); + if let Some(delegate) = &self.delegate { + println!("Strong count: {}", Rc::strong_count(&delegate)); + if Rc::strong_count(&delegate) == 1 { + let _ = unsafe { + Rc::from_raw(self.internal_callback_ptr.unwrap()) + }; } - } + }*/ } }