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.

This commit is contained in:
Ryan McGrath 2020-03-21 16:46:35 -07:00
parent e4f96b4ab5
commit 8910a88a93
No known key found for this signature in database
GPG key ID: 811674B62B666830
6 changed files with 140 additions and 100 deletions

View file

@ -2,12 +2,3 @@
//! Specific to this crate. //! Specific to this crate.
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr"; 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";

View file

@ -32,7 +32,7 @@ impl NSString {
pub fn wrap(object: id) -> Self { pub fn wrap(object: id) -> Self {
NSString(unsafe { NSString(unsafe {
Id::from_retained_ptr(object) Id::from_ptr(object)
}) })
} }

View file

@ -2,8 +2,9 @@
//! this is primarily used as the ContentView for a window. From there, //! this is primarily used as the ContentView for a window. From there,
//! we configure an NSToolbar and WKWebview on top of them. //! we configure an NSToolbar and WKWebview on top of them.
use std::sync::Once;
use std::ffi::c_void; use std::ffi::c_void;
use std::sync::Once;
use std::rc::Rc;
use block::Block; use block::Block;
@ -11,16 +12,16 @@ use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel}; use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl}; 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::{WEBVIEW_DELEGATE_PTR, WebViewDelegate};
use crate::webview::actions::{NavigationAction, NavigationResponse, OpenPanelParameters}; use crate::webview::actions::{NavigationAction, NavigationResponse};//, OpenPanelParameters};
use crate::webview::enums::{NavigationPolicy, NavigationResponsePolicy}; //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 /// Called when an `alert()` from the underlying `WKWebView` is fired. Will call over to your
/// `WebViewController`, where you should handle the event. /// `WebViewController`, where you should handle the event.
extern fn alert<T: WebViewDelegate>(_: &Object, _: Sel, _: id, s: id, _: id, complete: id) { extern fn alert<T: WebViewDelegate>(_: &Object, _: Sel, _: id, s: id, _: id, complete: id) {
let alert = NSString::wrap(s).to_str(); 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 // @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 // needs to be done here involving taking the pointer/invoke/casting... but this is fine for
@ -31,7 +32,7 @@ extern fn alert<T: WebViewDelegate>(_: &Object, _: Sel, _: id, s: id, _: id, com
/*unsafe { /*unsafe {
let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T; let delegate = ptr as *const T;
(*webview).alert(alert); (*webview).alert(alert);
}*/ }*/
@ -44,73 +45,80 @@ extern fn alert<T: WebViewDelegate>(_: &Object, _: Sel, _: id, s: id, _: id, com
/// Fires when a message has been passed from the underlying `WKWebView`. /// Fires when a message has been passed from the underlying `WKWebView`.
extern fn on_message<T: WebViewDelegate>(this: &Object, _: Sel, _: id, script_message: id) { extern fn on_message<T: WebViewDelegate>(this: &Object, _: Sel, _: id, script_message: id) {
let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
unsafe { unsafe {
let name = NSString::wrap(msg_send![script_message, name]).to_str(); let name = NSString::wrap(msg_send![script_message, name]).to_str();
let body = NSString::wrap(msg_send![script_message, body]).to_str(); let body = NSString::wrap(msg_send![script_message, body]).to_str();
let d = delegate.borrow();
let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); (*d).on_message(name, body);
let webview = ptr as *const T;
(*webview).on_message(name, body);
} }
Rc::into_raw(delegate);
} }
/// Fires when deciding a navigation policy - i.e, should something be allowed or not. /// Fires when deciding a navigation policy - i.e, should something be allowed or not.
extern fn decide_policy_for_action<T: WebViewDelegate>(this: &Object, _: Sel, _: id, action: id, handler: usize) { extern fn decide_policy_for_action<T: WebViewDelegate>(this: &Object, _: Sel, _: id, action: id, handler: usize) {
let webview = unsafe { let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T;
&*webview
};
let action = NavigationAction::new(action); 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>; let handler = handler as *const Block<(NSInteger,), c_void>;
(*handler).call((policy.into(),)); (*handler).call((policy.into(),));
} });
}); }
Rc::into_raw(delegate);
} }
/// Fires when deciding a navigation policy - i.e, should something be allowed or not. /// Fires when deciding a navigation policy - i.e, should something be allowed or not.
extern fn decide_policy_for_response<T: WebViewDelegate>(this: &Object, _: Sel, _: id, response: id, handler: usize) { extern fn decide_policy_for_response<T: WebViewDelegate>(this: &Object, _: Sel, _: id, response: id, handler: usize) {
let webview = unsafe { let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T;
&*webview
};
let response = NavigationResponse::new(response); 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. /// Fires when deciding a navigation policy - i.e, should something be allowed or not.
extern fn run_open_panel<T: WebViewDelegate>(this: &Object, _: Sel, _: id, params: id, _: id, handler: usize) { extern fn run_open_panel<T: WebViewDelegate>(this: &Object, _: Sel, _: id, params: id, _: id, handler: usize) {
let webview = unsafe { let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T;
&*webview
};
webview.run_open_panel(params.into(), move |urls| unsafe { {
let handler = handler as *const Block<(id,), c_void>; let d = delegate.borrow();
match urls { (*d).run_open_panel(params.into(), move |urls| unsafe {
Some(u) => { let handler = handler as *const Block<(id,), c_void>;
let nsurls: NSArray = u.iter().map(|s| {
let s = NSString::new(s);
msg_send![class!(NSURL), URLWithString:s]
}).collect::<Vec<id>>().into();
(*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::<Vec<id>>().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 /// Called when a download has been initiated in the WebView, and when the navigation policy
@ -118,27 +126,29 @@ extern fn run_open_panel<T: WebViewDelegate>(this: &Object, _: Sel, _: id, param
/// API. /// API.
#[cfg(feature = "webview-downloading")] #[cfg(feature = "webview-downloading")]
extern fn handle_download<T: WebViewDelegate>(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) { extern fn handle_download<T: WebViewDelegate>(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) {
let webview = unsafe { let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T;
&*webview
};
let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>; let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>;
let filename = NSString::wrap(suggested_filename).to_str(); let filename = NSString::wrap(suggested_filename).to_str();
webview.run_save_panel(filename, move |can_overwrite, path| unsafe { {
if path.is_none() { let d = delegate.borrow();
let _: () = msg_send![download, cancel];
}
let path = NSString::new(&path.unwrap()); (*d).run_save_panel(filename, move |can_overwrite, path| unsafe {
if path.is_none() {
(*handler).call((match can_overwrite { let _: () = msg_send![download, cancel];
true => YES, }
false => NO
}, path.into_inner())); 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 /// Registers an `NSViewController` that we effectively turn into a `WebViewController`. Acts as

View file

@ -5,7 +5,7 @@ use objc_id::Id;
use objc::runtime::Object; use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl}; 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; use crate::webview::enums::InjectAt;
/// A wrapper for `WKWebViewConfiguration`. Holds (retains) pointers for the Objective-C runtime /// 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. /// 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) { pub fn add_user_script(&mut self, script: &str, at: InjectAt, main_frame_only: bool) {
let source = NSString::new(script); let source = NSString::new(script);
let at: NSInteger = at.into();
unsafe { unsafe {
let alloc: id = msg_send![class!(WKUserScript), alloc]; 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, true => YES,
false => NO false => NO
}]; }];
@ -64,11 +65,8 @@ impl WebViewConfig {
} }
} }
pub(crate) fn attach_handlers(&self, target: id) { /// Consumes and returns the underlying `WKWebViewConfiguration`.
pub fn into_inner(mut self) -> id {
}
pub fn into_inner(self) -> id {
&mut *self.objc &mut *self.objc
} }
} }

View file

@ -24,6 +24,24 @@ pub enum NavigationType {
Other 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<NavigationType> 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<NavigationType> for NSInteger { impl From<NavigationType> for NSInteger {
fn from(nav_type: NavigationType) -> Self { fn from(nav_type: NavigationType) -> Self {
match nav_type { match nav_type {

View file

@ -9,10 +9,10 @@ use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use objc_id::ShareId; use objc_id::ShareId;
use objc::runtime::{Class, Object}; use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl}; 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::geometry::Rect;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
@ -20,7 +20,7 @@ pub mod actions;
pub mod enums; pub mod enums;
pub(crate) mod class; 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(crate) mod process_pool;
pub mod traits; pub mod traits;
@ -32,31 +32,38 @@ pub use config::WebViewConfig;
pub(crate) static WEBVIEW_DELEGATE_PTR: &str = "rstWebViewDelegatePtr"; pub(crate) static WEBVIEW_DELEGATE_PTR: &str = "rstWebViewDelegatePtr";
fn allocate_webview( fn allocate_webview(
config: WebViewConfig, mut config: WebViewConfig,
delegate: Option<&Object> objc_delegate: Option<&Object>
) -> id { ) -> id {
unsafe { 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(); let configuration = config.into_inner();
if let Some(delegate) = objc_delegate { if let Some(delegate) = &objc_delegate {
// Technically private! // Technically private!
#[cfg(feature = "webview-downloading")] #[cfg(feature = "webview-downloading")]
let process_pool: id = msg_send![configuration, processPool]; let process_pool: id = msg_send![configuration, processPool];
#[cfg(feature = "webview-downloading")] #[cfg(feature = "webview-downloading")]
let _: () = msg_send![process_pool, _setDownloadDelegate:*delegate]; let _: () = msg_send![process_pool, _setDownloadDelegate:*delegate];
// add handlers let content_controller: id = msg_send![configuration, userContentController];
for 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 zero: CGRect = Rect::zero().into();
let webview_alloc: id = msg_send![register_webview_class(), alloc]; let webview_alloc: id = msg_send![register_webview_class(), alloc];
let webview: id = msg_send![webview_alloc, initWithFrame:zero configuration:configuration]; let webview: id = msg_send![webview_alloc, initWithFrame:zero configuration:configuration];
let _: () = msg_send![webview, setWantsLayer:YES]; let _: () = msg_send![webview, setWantsLayer:YES];
let _: () = msg_send![webview, setTranslatesAutoresizingMaskIntoConstraints:NO]; 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 webview
} }
@ -110,7 +117,7 @@ impl Default for WebView {
impl WebView { impl WebView {
pub fn new(config: WebViewConfig) -> Self { pub fn new(config: WebViewConfig) -> Self {
let view = allocate_webview(register_webview_class, config, None); let view = allocate_webview(config, None);
WebView { WebView {
internal_callback_ptr: None, internal_callback_ptr: None,
@ -141,7 +148,7 @@ impl<T> WebView<T> where T: WebViewDelegate + 'static {
}; };
let objc_delegate = unsafe { let objc_delegate = unsafe {
let objc_delegate: id = msg_send![register_webview_delegate_class::<T>, new]; let objc_delegate: id = msg_send![register_webview_delegate_class::<T>(), new];
(&mut *objc_delegate).set_ivar(WEBVIEW_DELEGATE_PTR, internal_callback_ptr as usize); (&mut *objc_delegate).set_ivar(WEBVIEW_DELEGATE_PTR, internal_callback_ptr as usize);
ShareId::from_ptr(objc_delegate) ShareId::from_ptr(objc_delegate)
}; };
@ -194,6 +201,18 @@ impl<T> WebView<T> {
objc_delegate: None 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<T> Layout for WebView<T> { impl<T> Layout for WebView<T> {
@ -202,9 +221,9 @@ impl<T> Layout for WebView<T> {
self.objc.clone() self.objc.clone()
} }
fn add_subview<V: Layout>(&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<V: Layout>(&self, _: &V) {}
} }
impl<T> std::fmt::Debug for WebView<T> { impl<T> std::fmt::Debug for WebView<T> {
@ -216,10 +235,14 @@ impl<T> std::fmt::Debug for WebView<T> {
impl<T> Drop for WebView<T> { impl<T> Drop for WebView<T> {
/// A bit of extra cleanup for delegate callback pointers. /// A bit of extra cleanup for delegate callback pointers.
fn drop(&mut self) { fn drop(&mut self) {
unsafe { /*println!("... {}", self.delegate.is_some());
if let Some(ptr) = self.internal_callback_ptr { if let Some(delegate) = &self.delegate {
let _ = Rc::from_raw(ptr); println!("Strong count: {}", Rc::strong_count(&delegate));
if Rc::strong_count(&delegate) == 1 {
let _ = unsafe {
Rc::from_raw(self.internal_callback_ptr.unwrap())
};
} }
} }*/
} }
} }