Working on getting WKWebView working again - close

This commit is contained in:
Ryan McGrath 2020-03-20 18:37:00 -07:00
parent c1da7b1b37
commit e4f96b4ab5
No known key found for this signature in database
GPG key ID: 811674B62B666830
10 changed files with 259 additions and 264 deletions

View file

@ -69,13 +69,13 @@ pub mod prelude {
pub use crate::networking::URLRequest; pub use crate::networking::URLRequest;
pub use crate::window::{ pub use crate::window::{
Window, /*WindowController,*/ WindowDelegate Window, WindowConfig, WindowDelegate
}; };
#[cfg(feature = "webview")] #[cfg(feature = "webview")]
pub use crate::webview::{ pub use crate::webview::{
WebView, WebViewConfig, WebViewController WebView, WebViewConfig, WebViewDelegate
}; };
//pub use crate::view::{View, ViewController, ViewDelegate}; pub use crate::view::{View, ViewDelegate};
} }

View file

@ -7,6 +7,7 @@ use objc_id::Id;
use crate::foundation::{id, NSString}; use crate::foundation::{id, NSString};
#[derive(Debug)]
pub struct URLRequest { pub struct URLRequest {
pub inner: Id<Object> pub inner: Id<Object>
} }

View file

@ -9,7 +9,7 @@ use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use objc_id::ShareId; use objc_id::ShareId;
use objc::runtime::Object; use objc::runtime::{Class, Object};
use objc::{msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};
use crate::foundation::{id, YES, NO, NSArray, NSString}; use crate::foundation::{id, YES, NO, NSArray, NSString};
@ -28,6 +28,16 @@ pub use traits::ViewDelegate;
pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr"; pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr";
/// A helper method for instantiating view classes and applying default settings to them.
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
unsafe {
let view: id = msg_send![registration_fn(), new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
let _: () = msg_send![view, setWantsLayer:YES];
view
}
}
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this /// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
/// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that /// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that
/// side anyway. /// side anyway.
@ -77,12 +87,7 @@ impl Default for View {
impl View { impl View {
/// Returns a default `View`, suitable for /// Returns a default `View`, suitable for
pub fn new() -> Self { pub fn new() -> Self {
let view: id = unsafe { let view = allocate_view(register_view_class);
let view: id = msg_send![register_view_class(), new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
let _: () = msg_send![view, setWantsLayer:YES];
view
};
View { View {
internal_callback_ptr: None, internal_callback_ptr: None,
@ -111,11 +116,11 @@ impl<T> View<T> where T: ViewDelegate + 'static {
Rc::into_raw(cloned) Rc::into_raw(cloned)
}; };
let view = unsafe { let view = allocate_view(register_view_class_with_delegate::<T>);
let view: id = msg_send![register_view_class_with_delegate::<T>(), new]; unsafe {
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; //let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
(&mut *view).set_ivar(VIEW_DELEGATE_PTR, internal_callback_ptr as usize); (&mut *view).set_ivar(VIEW_DELEGATE_PTR, internal_callback_ptr as usize);
view
}; };
let mut view = View { let mut view = View {

View file

@ -4,6 +4,7 @@ use objc::{msg_send, sel, sel_impl};
use crate::foundation::{id, BOOL, YES, NO, NSInteger}; use crate::foundation::{id, BOOL, YES, NO, NSInteger};
use crate::networking::URLRequest; use crate::networking::URLRequest;
use crate::webview::enums::NavigationType;
#[derive(Debug)] #[derive(Debug)]
pub struct NavigationAction { pub struct NavigationAction {
@ -42,7 +43,6 @@ impl NavigationResponse {
} }
} }
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
pub struct OpenPanelParameters { pub struct OpenPanelParameters {
pub allows_directories: bool, pub allows_directories: bool,

View file

@ -12,51 +12,13 @@ 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, CGRect, NSString, NSArray, NSInteger};
use crate::constants::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR}; use crate::webview::{WEBVIEW_DELEGATE_PTR, WebViewDelegate};
use crate::geometry::Rect; use crate::webview::actions::{NavigationAction, NavigationResponse, OpenPanelParameters};
use crate::view::traits::ViewController; use crate::webview::enums::{NavigationPolicy, NavigationResponsePolicy};
use crate::webview::action::{NavigationAction, NavigationResponse};
use crate::webview::traits::WebViewController;
/// Loads and configures ye old WKWebView/View for this controller.
extern fn load_view<T: ViewController + WebViewController>(this: &mut Object, _: Sel) {
unsafe {
let configuration: id = *this.get_ivar(WEBVIEW_CONFIG_VAR);
// Technically private!
#[cfg(feature = "webview-downloading")]
let process_pool: id = msg_send![configuration, processPool];
#[cfg(feature = "webview-downloading")]
let _: () = msg_send![process_pool, _setDownloadDelegate:&*this];
let zero: CGRect = Rect::zero().into();
let webview_alloc: id = msg_send![class!(WKWebView), alloc];
let webview: id = msg_send![webview_alloc, initWithFrame:zero configuration:configuration];
let _: () = msg_send![webview, setWantsLayer:YES];
let _: () = msg_send![webview, setTranslatesAutoresizingMaskIntoConstraints:NO];
// Provide an easy way to grab this later
(*this).set_ivar(WEBVIEW_VAR, webview);
// Clean this up to be safe, as WKWebView makes a copy and we don't need it anymore.
(*this).set_ivar(WEBVIEW_CONFIG_VAR, nil);
let _: () = msg_send![this, setView:webview];
}
}
/// Used to connect delegates - doing this in `loadView` can be... bug-inducing.
extern fn view_did_load<T: ViewController + WebViewController>(this: &Object, _: Sel) {
unsafe {
let webview: id = *this.get_ivar(WEBVIEW_VAR);
let _: () = msg_send![webview, setNavigationDelegate:&*this];
let _: () = msg_send![webview, setUIDelegate:&*this];
}
}
/// 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: WebViewController + 'static>(_: &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); println!("Alert: {}", alert);
@ -68,7 +30,7 @@ extern fn alert<T: WebViewController + 'static>(_: &Object, _: Sel, _: id, s: id
} }
/*unsafe { /*unsafe {
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T; let webview = ptr as *const T;
(*webview).alert(alert); (*webview).alert(alert);
}*/ }*/
@ -81,21 +43,21 @@ extern fn alert<T: WebViewController + 'static>(_: &Object, _: Sel, _: id, s: id
} }
/// 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: WebViewController + 'static>(this: &Object, _: Sel, _: id, script_message: id) { extern fn on_message<T: WebViewDelegate>(this: &Object, _: Sel, _: id, script_message: id) {
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 ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T; let webview = ptr as *const T;
(*webview).on_message(name, body); (*webview).on_message(name, body);
} }
} }
/// 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: WebViewController + 'static>(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 webview = unsafe {
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T; let webview = ptr as *const T;
&*webview &*webview
}; };
@ -111,9 +73,9 @@ extern fn decide_policy_for_action<T: WebViewController + 'static>(this: &Object
} }
/// 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: WebViewController + 'static>(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 webview = unsafe {
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T; let webview = ptr as *const T;
&*webview &*webview
}; };
@ -126,9 +88,9 @@ extern fn decide_policy_for_response<T: WebViewController + 'static>(this: &Obje
} }
/// 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: WebViewController + 'static>(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 webview = unsafe {
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T; let webview = ptr as *const T;
&*webview &*webview
}; };
@ -139,7 +101,7 @@ extern fn run_open_panel<T: WebViewController + 'static>(this: &Object, _: Sel,
match urls { match urls {
Some(u) => { Some(u) => {
let nsurls: NSArray = u.iter().map(|s| { let nsurls: NSArray = u.iter().map(|s| {
let s = NSString::new(&s); let s = NSString::new(s);
msg_send![class!(NSURL), URLWithString:s] msg_send![class!(NSURL), URLWithString:s]
}).collect::<Vec<id>>().into(); }).collect::<Vec<id>>().into();
@ -155,9 +117,9 @@ extern fn run_open_panel<T: WebViewController + 'static>(this: &Object, _: Sel,
/// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private /// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private
/// API. /// API.
#[cfg(feature = "webview-downloading")] #[cfg(feature = "webview-downloading")]
extern fn handle_download<T: WebViewController + 'static>(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 webview = unsafe {
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR);
let webview = ptr as *const T; let webview = ptr as *const T;
&*webview &*webview
}; };
@ -179,27 +141,34 @@ extern fn handle_download<T: WebViewController + 'static>(this: &Object, _: Sel,
}); });
} }
/// Registers an `NSViewController` that we effectively turn into a `WebViewController`. Acts as /// 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 /// both a subclass of `NSViewController` and a delegate of the held `WKWebView` (for the various
/// varieties of delegates needed there). /// varieties of delegates needed there).
pub fn register_controller_class< pub fn register_webview_class() -> *const Class {
T: ViewController + WebViewController + 'static,
>() -> *const Class {
static mut VIEW_CLASS: *const Class = 0 as *const Class; static mut VIEW_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new(); static INIT: Once = Once::new();
INIT.call_once(|| unsafe { INIT.call_once(|| unsafe {
let superclass = Class::get("NSViewController").unwrap(); let superclass = class!(WKWebView);
let mut decl = ClassDecl::new("RSTWebViewController", superclass).unwrap(); let decl = ClassDecl::new("RSTWebView", superclass).unwrap();
VIEW_CLASS = decl.register();
});
decl.add_ivar::<id>(WEBVIEW_CONFIG_VAR); unsafe { VIEW_CLASS }
decl.add_ivar::<id>(WEBVIEW_VAR); }
decl.add_ivar::<usize>(WEBVIEW_CONTROLLER_PTR);
// NSViewController /// Registers an `NSViewController` that we effectively turn into a `WebViewController`. Acts as
decl.add_method(sel!(loadView), load_view::<T> as extern fn(&mut Object, _)); /// both a subclass of `NSViewController` and a delegate of the held `WKWebView` (for the various
decl.add_method(sel!(viewDidLoad), view_did_load::<T> as extern fn(&Object, _)); /// varieties of delegates needed there).
pub fn register_webview_delegate_class<T: WebViewDelegate>() -> *const Class {
static mut VIEW_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSObject);
let mut decl = ClassDecl::new("RSTWebViewDelegate", superclass).unwrap();
decl.add_ivar::<usize>(WEBVIEW_DELEGATE_PTR);
// WKNavigationDelegate // WKNavigationDelegate
decl.add_method(sel!(webView:decidePolicyForNavigationAction:decisionHandler:), decide_policy_for_action::<T> as extern fn(&Object, _, _, id, usize)); decl.add_method(sel!(webView:decidePolicyForNavigationAction:decisionHandler:), decide_policy_for_action::<T> as extern fn(&Object, _, _, id, usize));
@ -214,7 +183,7 @@ pub fn register_controller_class<
// WKDownloadDelegate is a private class on macOS that handles downloading (saving) files. // WKDownloadDelegate is a private class on macOS that handles downloading (saving) files.
// It's absurd that this is still private in 2020. This probably couldn't get into the app // It's absurd that this is still private in 2020. This probably couldn't get into the app
// store, so... screw it, fine for now. // store, so... screw it, feature-gate it.
#[cfg(feature = "webview-downloading")] #[cfg(feature = "webview-downloading")]
decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download::<T> as extern fn(&Object, _, id, id, usize)); decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download::<T> as extern fn(&Object, _, id, id, usize));

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, NSString}; use crate::foundation::{id, YES, NO, NSString};
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
@ -63,4 +63,12 @@ impl WebViewConfig {
let _: () = msg_send![preferences, setValue:yes forKey:key]; let _: () = msg_send![preferences, setValue:yes forKey:key];
} }
} }
pub(crate) fn attach_handlers(&self, target: id) {
}
pub fn into_inner(self) -> id {
&mut *self.objc
}
} }

View file

@ -3,6 +3,7 @@
use crate::foundation::NSInteger; use crate::foundation::NSInteger;
/// Describes a navigation type from within the `WebView`. /// Describes a navigation type from within the `WebView`.
#[derive(Clone, Copy, Debug)]
pub enum NavigationType { pub enum NavigationType {
/// A user activated a link. /// A user activated a link.
LinkActivated, LinkActivated,
@ -23,21 +24,21 @@ pub enum NavigationType {
Other Other
} }
impl From<NSInteger> for NavigationType { impl From<NavigationType> for NSInteger {
fn from(i: NSInteger) -> Self { fn from(nav_type: NavigationType) -> Self {
match i { match nav_type {
-1 => NavigationType::Other, NavigationType::Other => -1,
0 => NavigationType::LinkActivated, NavigationType::LinkActivated => 0,
1 => NavigationType::FormSubmitted, NavigationType::FormSubmitted => 1,
2 => NavigationType::BackForward, NavigationType::BackForward => 2,
3 => NavigationType::Reload, NavigationType::Reload => 3,
4 => NavigationType::FormResubmitted, NavigationType::FormResubmitted => 4
e => { panic!("Unsupported navigation type: {}", e); }
} }
} }
} }
/// Describes the policy for a given navigation. /// Describes the policy for a given navigation.
#[derive(Clone, Copy, Debug)]
pub enum NavigationPolicy { pub enum NavigationPolicy {
/// Should be canceled. /// Should be canceled.
Cancel, Cancel,
@ -47,8 +48,8 @@ pub enum NavigationPolicy {
} }
impl From<NavigationPolicy> for NSInteger { impl From<NavigationPolicy> for NSInteger {
fn into(self) -> Self { fn from(policy: NavigationPolicy) -> Self {
match self { match policy {
NavigationPolicy::Cancel => 0, NavigationPolicy::Cancel => 0,
NavigationPolicy::Allow => 1 NavigationPolicy::Allow => 1
} }
@ -56,6 +57,7 @@ impl From<NavigationPolicy> for NSInteger {
} }
/// Describes a response policy for a given navigation. /// Describes a response policy for a given navigation.
#[derive(Clone, Copy, Debug)]
pub enum NavigationResponsePolicy { pub enum NavigationResponsePolicy {
/// Should be canceled. /// Should be canceled.
Cancel, Cancel,
@ -70,8 +72,8 @@ pub enum NavigationResponsePolicy {
} }
impl From<NavigationResponsePolicy> for NSInteger { impl From<NavigationResponsePolicy> for NSInteger {
fn into(self) -> Self { fn from(policy: NavigationResponsePolicy) -> Self {
match self { match policy {
NavigationResponsePolicy::Cancel => 0, NavigationResponsePolicy::Cancel => 0,
NavigationResponsePolicy::Allow => 1, NavigationResponsePolicy::Allow => 1,
@ -82,6 +84,7 @@ impl From<NavigationResponsePolicy> for NSInteger {
} }
/// Dictates where a given user script should be injected. /// Dictates where a given user script should be injected.
#[derive(Clone, Copy, Debug)]
pub enum InjectAt { pub enum InjectAt {
/// Inject at the start of the document. /// Inject at the start of the document.
Start = 0, Start = 0,

View file

@ -1,69 +0,0 @@
//! A `WebViewHandle` represents an underlying `WKWebView`. You're passed a reference to one during your
//! `WebViewController::did_load()` method. This method is safe to store and use, however as it's
//! UI-specific it's not thread safe.
//!
//! You can use this struct to configure how a view should look and layout. It implements
//! AutoLayout - for more information, see the AutoLayout tutorial.
use objc_id::ShareId;
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
use crate::foundation::{id, YES, NSArray, NSString};
use crate::color::Color;
use crate::constants::BACKGROUND_COLOR;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
/// instead of a stock `WKWebView` for easier recordkeeping, since it'll need to hold the `WKWebView` on that
/// side anyway.
#[derive(Debug, Default, Clone)]
pub struct WebViewHandle {
/// A pointer to the Objective-C runtime view controller.
pub objc: Option<ShareId<Object>>,
/// A pointer to the Objective-C runtime top layout constraint.
pub top: LayoutAnchorY,
/// A pointer to the Objective-C runtime leading layout constraint.
pub leading: LayoutAnchorX,
/// A pointer to the Objective-C runtime trailing layout constraint.
pub trailing: LayoutAnchorX,
/// A pointer to the Objective-C runtime bottom layout constraint.
pub bottom: LayoutAnchorY,
/// A pointer to the Objective-C runtime width layout constraint.
pub width: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime height layout constraint.
pub height: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime center X layout constraint.
pub center_x: LayoutAnchorX,
/// A pointer to the Objective-C runtime center Y layout constraint.
pub center_y: LayoutAnchorY
}
impl WebViewHandle {
pub(crate) fn new(object: ShareId<Object>) -> Self {
let view: id = unsafe {
msg_send![&*object, view]
};
WebViewHandle {
objc: Some(object),
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }),
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }),
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
}
}
}

View file

@ -4,133 +4,222 @@
//! - `WKWebView` //! - `WKWebView`
//! - `WKUIDelegate` //! - `WKUIDelegate`
//! - `WKScriptMessageHandler` //! - `WKScriptMessageHandler`
//! - `NSViewController`
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use objc_id::ShareId; use objc_id::ShareId;
use objc::runtime::Object; use objc::runtime::{Class, Object};
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, YES, NO, NSString}; use crate::foundation::{id, nil, YES, NO, CGRect, NSString};
use crate::constants::WEBVIEW_CONTROLLER_PTR; use crate::geometry::Rect;
use crate::webview::controller::register_controller_class; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
pub mod actions; pub mod actions;
pub mod enums;
pub(crate) mod controller; pub(crate) mod class;
use class::{register_webview_class, register_webview_class_with_delegate};
//pub(crate) mod process_pool; //pub(crate) mod process_pool;
pub mod traits; pub mod traits;
pub use traits::WebViewController; pub use traits::WebViewDelegate;
pub mod config; pub mod config;
pub use config::WebViewConfig; pub use config::WebViewConfig;
/// A `View` wraps two different controllers - one on the Objective-C/Cocoa side, which forwards pub(crate) static WEBVIEW_DELEGATE_PTR: &str = "rstWebViewDelegatePtr";
/// calls into your supplied `ViewController` trait object. This involves heap allocation, but all
/// of Cocoa is essentially Heap'd, so... well, enjoy. fn allocate_webview(
#[derive(Clone)] config: WebViewConfig,
pub struct WebView<T> { delegate: Option<&Object>
internal_callback_ptr: *const RefCell<T>, ) -> id {
pub objc_controller: WebViewHandle, unsafe {
pub controller: Rc<RefCell<T>> let configuration = config.into_inner();
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 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];
webview
}
} }
impl<T> WebView<T> where T: WebViewController + 'static { pub struct WebView<T = ()> {
/// Allocates and configures a `ViewController` in the Objective-C/Cocoa runtime that maps over /// A pointer to the Objective-C runtime view controller.
/// to your supplied view controller. pub objc: ShareId<Object>,
pub fn new(controller: T) -> Self {
let config = controller.config(); /// We need to store the underlying delegate separately from the `WKWebView` - this is a where
let controller = Rc::new(RefCell::new(controller)); /// we do so.
pub objc_delegate: Option<ShareId<Object>>,
/// An internal callback pointer that we use in delegate loopbacks. Default implementations
/// don't require this.
pub(crate) internal_callback_ptr: Option<*const RefCell<T>>,
/// A pointer to the delegate for this view.
pub delegate: Option<Rc<RefCell<T>>>,
/// A pointer to the Objective-C runtime top layout constraint.
pub top: LayoutAnchorY,
/// A pointer to the Objective-C runtime leading layout constraint.
pub leading: LayoutAnchorX,
/// A pointer to the Objective-C runtime trailing layout constraint.
pub trailing: LayoutAnchorX,
/// A pointer to the Objective-C runtime bottom layout constraint.
pub bottom: LayoutAnchorY,
/// A pointer to the Objective-C runtime width layout constraint.
pub width: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime height layout constraint.
pub height: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime center X layout constraint.
pub center_x: LayoutAnchorX,
/// A pointer to the Objective-C runtime center Y layout constraint.
pub center_y: LayoutAnchorY
}
impl Default for WebView {
fn default() -> Self {
WebView::new(WebViewConfig::default())
}
}
impl WebView {
pub fn new(config: WebViewConfig) -> Self {
let view = allocate_webview(register_webview_class, config, None);
WebView {
internal_callback_ptr: None,
delegate: None,
objc_delegate: None,
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }),
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }),
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
objc: unsafe { ShareId::from_ptr(view) },
}
}
}
impl<T> WebView<T> where T: WebViewDelegate + 'static {
/// Initializes a new WebView with a given `WebViewDelegate`. This enables you to respond to events
/// and customize the view as a module, similar to class-based systems.
pub fn with(config: WebViewConfig, delegate: T) -> WebView<T> {
let delegate = Rc::new(RefCell::new(delegate));
let internal_callback_ptr = { let internal_callback_ptr = {
let cloned = Rc::clone(&controller); let cloned = Rc::clone(&delegate);
Rc::into_raw(cloned) Rc::into_raw(cloned)
}; };
let handle = WebViewHandle::new(unsafe { let objc_delegate = unsafe {
let view_controller: id = msg_send![register_controller_class::<T>(), new]; let objc_delegate: id = msg_send![register_webview_delegate_class::<T>, new];
(&mut *view_controller).set_ivar(WEBVIEW_CONTROLLER_PTR, internal_callback_ptr as usize); (&mut *objc_delegate).set_ivar(WEBVIEW_DELEGATE_PTR, internal_callback_ptr as usize);
ShareId::from_ptr(objc_delegate)
// WKWebView isn't really great to subclass, so we don't bother here unlike other };
// widgets in this framework. Just set and forget.
let frame: CGRect = Rect::zero().into(); let view = allocate_webview(config, Some(&objc_delegate));
let alloc: id = msg_send![class!(WKWebView), alloc];
let view: id = msg_send![alloc, initWithFrame:frame configuration:&*config.0]; let mut view = WebView {
let _: () = msg_send![&*view_controller, setView:view]; internal_callback_ptr: Some(internal_callback_ptr),
delegate: None,
ShareId::from_ptr(view_controller) objc_delegate: Some(objc_delegate),
}); top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }),
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }),
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
objc: unsafe { ShareId::from_ptr(view) },
};
{ {
let mut vc = controller.borrow_mut(); let mut delegate = delegate.borrow_mut();
(*vc).did_load(handle.clone()); (*delegate).did_load(view.clone_as_handle());
} }
WebView { view.delegate = Some(delegate);
internal_callback_ptr: internal_callback_ptr, view
objc_controller: handle,
controller: controller
}
}
pub fn set_background_color(&self, color: Color) {
self.objc_controller.set_background_color(color);
}
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
self.objc_controller.register_for_dragged_types(types);
}
pub fn top(&self) -> &LayoutAnchorY {
&self.objc_controller.top
}
pub fn leading(&self) -> &LayoutAnchorX {
&self.objc_controller.leading
}
pub fn trailing(&self) -> &LayoutAnchorX {
&self.objc_controller.trailing
}
pub fn bottom(&self) -> &LayoutAnchorY {
&self.objc_controller.bottom
}
pub fn width(&self) -> &LayoutAnchorDimension {
&self.objc_controller.width
}
pub fn height(&self) -> &LayoutAnchorDimension {
&self.objc_controller.height
} }
} }
impl<T> Layout for View<T> { impl<T> WebView<T> {
/// An internal method that returns a clone of this object, sans references to the delegate or
/// callback pointer. We use this in calling `did_load()` - implementing delegates get a way to
/// reference, customize and use the view but without the trickery of holding pieces of the
/// delegate - the `View` is the only true holder of those.
pub(crate) fn clone_as_handle(&self) -> WebView {
WebView {
internal_callback_ptr: None,
delegate: None,
top: self.top.clone(),
leading: self.leading.clone(),
trailing: self.trailing.clone(),
bottom: self.bottom.clone(),
width: self.width.clone(),
height: self.height.clone(),
center_x: self.center_x.clone(),
center_y: self.center_y.clone(),
objc: self.objc.clone(),
objc_delegate: None
}
}
}
impl<T> Layout for WebView<T> {
/// Returns the Objective-C object used for handling the view heirarchy. /// Returns the Objective-C object used for handling the view heirarchy.
fn get_backing_node(&self) -> Option<ShareId<Object>> { fn get_backing_node(&self) -> ShareId<Object> {
self.objc_controller.objc.clone() self.objc.clone()
} }
fn add_subview<V: Layout>(&self, subview: &V) { fn add_subview<V: Layout>(&self, subview: &V) {
self.objc_controller.add_subview(subview);
} }
} }
impl<T> std::fmt::Debug for View<T> { impl<T> std::fmt::Debug for WebView<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "View ({:p})", self) write!(f, "WebView ({:p})", self)
} }
} }
impl<T> Drop for View<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 { unsafe {
let _ = Rc::from_raw(self.internal_callback_ptr); if let Some(ptr) = self.internal_callback_ptr {
let _ = Rc::from_raw(ptr);
}
} }
} }
} }

View file

@ -2,27 +2,16 @@
//! `WKWebView`. It allows you to do things such as handle opening a file (for uploads or //! `WKWebView`. It allows you to do things such as handle opening a file (for uploads or
//! in-browser-processing), handling navigation actions or JS message callbacks, and so on. //! in-browser-processing), handling navigation actions or JS message callbacks, and so on.
use crate::webview::config::WebViewConfig; use crate::webview::WebView;
use crate::webview::enums::{ use crate::webview::actions::{NavigationAction, NavigationResponse, OpenPanelParameters};
NavigationAction, NavigationPolicy, use crate::webview::enums::{NavigationPolicy, NavigationResponsePolicy};
NavigationResponse, NavigationResponsePolicy,
OpenPanelParameters
};
use crate::webview::handle::WebViewHandle;
/// You can implement this on structs to handle callbacks from the underlying `WKWebView`. /// You can implement this on structs to handle callbacks from the underlying `WKWebView`.
pub trait WebViewController { pub trait WebViewDelegate {
/// Due to a quirk in how the underlying `WKWebView` works, the configuration object must be
/// set up before initializing anything. To enable this, you can implement this method and
/// return whatever `WebViewConfig` object you want.
///
/// By default, this returns `WebViewConfig::default()`.
fn configure(&mut self) -> WebViewConfig { WebViewConfig::default() }
/// Called when the View is ready to work with. You're passed a `ViewHandle` - this is safe to /// Called when the View is ready to work with. You're passed a `ViewHandle` - this is safe to
/// store and use repeatedly, but it's not thread safe - any UI calls must be made from the /// store and use repeatedly, but it's not thread safe - any UI calls must be made from the
/// main thread! /// main thread!
fn did_load(&mut self, _view: WebViewHandle) {} fn did_load(&mut self, _webview: WebView) {}
/// Called when this is about to be added to the view heirarchy. /// Called when this is about to be added to the view heirarchy.
fn will_appear(&self) {} fn will_appear(&self) {}