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:
parent
e4f96b4ab5
commit
8910a88a93
|
@ -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";
|
||||
|
|
|
@ -32,7 +32,7 @@ impl NSString {
|
|||
|
||||
pub fn wrap(object: id) -> Self {
|
||||
NSString(unsafe {
|
||||
Id::from_retained_ptr(object)
|
||||
Id::from_ptr(object)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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,65 +45,69 @@ 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 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 d = delegate.borrow();
|
||||
|
||||
(*d).run_open_panel(params.into(), move |urls| unsafe {
|
||||
let handler = handler as *const Block<(id,), c_void>;
|
||||
|
||||
match urls {
|
||||
Some(u) => {
|
||||
let nsurls: NSArray = u.iter().map(|s| {
|
||||
let s = NSString::new(s);
|
||||
msg_send![class!(NSURL), URLWithString:s]
|
||||
msg_send![class!(NSURL), URLWithString:s.into_inner()]
|
||||
}).collect::<Vec<id>>().into();
|
||||
|
||||
(*handler).call((nsurls.into_inner(),));
|
||||
|
@ -113,21 +118,23 @@ extern fn run_open_panel<T: WebViewDelegate>(this: &Object, _: Sel, _: id, param
|
|||
});
|
||||
}
|
||||
|
||||
Rc::into_raw(delegate);
|
||||
}
|
||||
|
||||
/// Called when a download has been initiated in the WebView, and when the navigation policy
|
||||
/// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private
|
||||
/// 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 {
|
||||
{
|
||||
let d = delegate.borrow();
|
||||
|
||||
(*d).run_save_panel(filename, move |can_overwrite, path| unsafe {
|
||||
if path.is_none() {
|
||||
let _: () = msg_send![download, cancel];
|
||||
}
|
||||
|
@ -141,6 +148,9 @@ extern fn handle_download<T: WebViewDelegate>(this: &Object, _: Sel, download: i
|
|||
});
|
||||
}
|
||||
|
||||
Rc::into_raw(delegate);
|
||||
}
|
||||
|
||||
/// Registers an `NSViewController` that we effectively turn into a `WebViewController`. Acts as
|
||||
/// both a subclass of `NSViewController` and a delegate of the held `WKWebView` (for the various
|
||||
/// varieties of delegates needed there).
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
};
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue