1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
//! Wraps a `WKWebView`, `WKWebViewConfiguration` & `WKUserContentController`
use cocoa::appkit::{NSView, NSViewHeightSizable, NSViewWidthSizable};
use cocoa::base::{id, nil};
use cocoa::foundation::{NSRect, NSString};
use block::ConcreteBlock;
use crate::foundation::*;
use crate::webkit::wk_script_message_handler::make_new_handler;
use crate::webkit::*;
/// Wraps a `WKWebView`, `WKWebViewConfiguration` & `WKUserContentController`
///
/// Can be used from a cocoa application with `get_native_handle` or with `DWKApp`.
///
/// # Example
/// ```no_run
/// extern crate cocoa;
/// use cocoa::base::id;
/// use darwin_webkit::helpers::dwk_app::DarwinWKApp;
/// use std::sync::Arc;
///
/// unsafe {
/// let app = DarwinWKApp::new("Host an app");
/// let webview = Arc::new(app.create_webview());
///
/// let callback_webview = webview.clone();
/// let callback = Box::into_raw(Box::new(Box::new(|_: id, _: id| {
/// println!("JavaScript called rust!");
/// callback_webview.evaluate_javascript(
/// "document.body.innerHTML += ' -> response from rust<br />';"
/// );
/// })));
///
/// webview.add_message_handler("hello", callback);
/// webview.load_html_string("
/// <script>
/// document.body.innerHTML += 'start';
/// window.webkit.messageHandlers.hello.postMessage('hello');
/// </script>
/// ", "", );
/// }
/// ```
pub struct DarwinWKWebView {
webview: id,
configuration: id,
content_controller: id,
}
impl DarwinWKWebView {
/// Create a webview with the given frame rect. Also creates the supporting configuration and
/// content controller.
///
/// Thee view is resizable other options are empty.
///
/// # Safety
/// All the FFI functions are unsafe.
pub unsafe fn new(frame: NSRect) -> DarwinWKWebView {
let configuration = WKWebViewConfiguration::init(WKWebViewConfiguration::alloc(nil));
let content_controller = WKUserContentController::init(WKUserContentController::alloc(nil));
configuration.setUserContentController(content_controller);
let webview = WKWebView::alloc(nil).initWithFrame_configuration_(frame, configuration);
NSView::setAutoresizingMask_(webview, NSViewWidthSizable | NSViewHeightSizable);
DarwinWKWebView {
webview,
configuration,
content_controller,
}
}
/// Get the `WKWebView` instance
pub fn get_native_handle(&self) -> id {
self.webview
}
/// Get the `WKUserContentController` instance
pub fn get_user_content_controller_handle(&self) -> id {
self.content_controller
}
/// Get the `WKWebViewConfiguration` instance
pub fn get_configuration_handle(&self) -> id {
self.configuration
}
/// Load an URL onto the WebView.
///
/// # Safety
/// All the FFI functions are unsafe.
pub unsafe fn load_url(&self, url: &str) {
let url = NSString::alloc(nil).init_str(url);
let url = NSURL::alloc(nil).initWithString_(url);
let req = NSURLRequest::alloc(nil).initWithURL_(url);
self.webview.loadRequest_(req);
}
/// Load an HTML string onto the WebView.
///
/// # Safety
/// All the FFI functions are unsafe.
pub unsafe fn load_html_string(&self, html: &str, base_url: &str) {
let html = NSString::alloc(nil).init_str(html);
let base_url = NSString::alloc(nil).init_str(base_url);
let base_url = NSURL::alloc(nil).initWithString_(base_url);
self.webview.loadHTMLString_baseURL_(html, base_url);
}
/// Evaluate a JavaScript string on the WebView
///
/// # Safety
/// All the FFI functions are unsafe.
pub unsafe fn evaluate_javascript(&self, javascript: &str) {
let javascript = NSString::alloc(nil).init_str(javascript);
let b = |_: id, error: id| {
if error != nil {
let str = msg_send![error, localizedDescription];
let str = string_from_nsstring(str);
println!("Error {}", str.as_ref().unwrap().as_str());
}
};
let b = ConcreteBlock::new(b);
let b = b.copy();
self.webview.evaluateJavaScript_(javascript, &b);
}
/// Register a callback into the WebView.
///
/// Calls `make_new_handler` under the hood. The callback should have form:
///
/// ```compile_fail
/// FnMut(id /* WKUserContentController */, id /* WKScriptMessage */)
/// ```
///
/// The handler will be available from JavaScript through:
///
/// ```javascript
/// window.webkit.messageHandlers.name.postMessage('some message');
/// ```
///
/// # Safety
/// All the FFI functions are unsafe.
///
/// Your callback will be called from WebKit. If the WebView outlives it: 💥.
pub unsafe fn add_message_handler<Func>(&self, name: &str, callback: *mut Func)
where
Func: FnMut(id, id),
{
let handler = make_new_handler(format!("DWKHandler_{}", name).as_str(), callback);
let name = NSString::alloc(nil).init_str(name);
self.content_controller
.addScriptMessageHandler(handler, name);
}
}
pub extern "C" fn javascript_callback(_: id, _: id) {}
unsafe impl Send for DarwinWKWebView {}
unsafe impl Sync for DarwinWKWebView {}
/// Create a `String` pointer from a `NSString`.
///
/// # Safety
/// All the FFI functions are unsafe.
pub unsafe fn string_from_nsstring(nsstring: id) -> *mut String {
let len = nsstring.len();
let str = Box::new(String::from_utf8_unchecked(Vec::from_raw_parts(
nsstring.UTF8String() as *mut u8,
len,
len,
)));
Box::into_raw(str)
}