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.
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 {
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,
//! 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<T: WebViewDelegate>(_: &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<T: WebViewDelegate>(_: &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<T: WebViewDelegate>(_: &Object, _: Sel, _: id, s: id, _: id, com
/// Fires when a message has been passed from the underlying `WKWebView`.
extern fn on_message<T: WebViewDelegate>(this: &Object, _: Sel, _: id, script_message: id) {
let delegate = load::<T>(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<T: WebViewDelegate>(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::<T>(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<T: WebViewDelegate>(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::<T>(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<T: WebViewDelegate>(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::<T>(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::<Vec<id>>().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::<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
@ -118,27 +126,29 @@ extern fn run_open_panel<T: WebViewDelegate>(this: &Object, _: Sel, _: id, param
/// API.
#[cfg(feature = "webview-downloading")]
extern fn handle_download<T: WebViewDelegate>(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::<T>(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

View file

@ -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
}
}

View file

@ -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<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 {
fn from(nav_type: NavigationType) -> Self {
match nav_type {

View file

@ -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<T> WebView<T> where T: WebViewDelegate + 'static {
};
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);
ShareId::from_ptr(objc_delegate)
};
@ -194,6 +201,18 @@ impl<T> WebView<T> {
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> {
@ -202,9 +221,9 @@ impl<T> Layout for WebView<T> {
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> {
@ -216,10 +235,14 @@ impl<T> std::fmt::Debug for WebView<T> {
impl<T> Drop for WebView<T> {
/// 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())
};
}
}
}*/
}
}