feat: 大屏桌面壳(Tauri 2)

This commit is contained in:
2026-08-23 22:34:50 +08:00
parent 28e233520b
commit 9700eb652b
123 changed files with 26695 additions and 0 deletions
@@ -0,0 +1,90 @@
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{ffi::c_void, ptr::null_mut};
use objc2::{
define_class, msg_send,
rc::Retained,
runtime::{AnyObject, NSObject},
AllocAnyThread, DefinedClass,
};
use objc2_foundation::{
ns_string, NSDictionary, NSKeyValueChangeKey, NSKeyValueObservingOptions,
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSString,
};
use crate::WryWebView;
pub struct DocumentTitleChangedObserverIvars {
pub object: Retained<WryWebView>,
pub handler: Box<dyn Fn(String)>,
}
define_class!(
#[unsafe(super(NSObject))]
#[ivars = DocumentTitleChangedObserverIvars]
pub struct DocumentTitleChangedObserver;
/// NSKeyValueObserving.
impl DocumentTitleChangedObserver {
#[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]
fn observe_value_for_key_path(
&self,
key_path: Option<&NSString>,
of_object: Option<&AnyObject>,
_change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
_context: *mut c_void,
) {
if let (Some(key_path), Some(object)) = (key_path, of_object) {
unsafe {
if key_path.isEqualToString(ns_string!("title")) {
let handler = &self.ivars().handler;
// if !handler.is_null() {
let title: *const NSString = msg_send![object, title];
handler((*title).to_string());
// }
}
}
}
}
}
unsafe impl NSObjectProtocol for DocumentTitleChangedObserver {}
);
impl DocumentTitleChangedObserver {
pub fn new(webview: Retained<WryWebView>, handler: Box<dyn Fn(String)>) -> Retained<Self> {
let observer = Self::alloc().set_ivars(DocumentTitleChangedObserverIvars {
object: webview,
handler,
});
let observer: Retained<Self> = unsafe { msg_send![super(observer), init] };
unsafe {
observer
.ivars()
.object
.addObserver_forKeyPath_options_context(
&observer,
ns_string!("title"),
NSKeyValueObservingOptions::New,
null_mut(),
);
}
observer
}
}
impl Drop for DocumentTitleChangedObserver {
fn drop(&mut self) {
unsafe {
self
.ivars()
.object
.removeObserver_forKeyPath(self, ns_string!("title"));
}
}
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
pub mod document_title_changed_observer;
pub mod url_scheme_handler;
pub mod wry_download_delegate;
pub mod wry_navigation_delegate;
pub mod wry_web_view;
pub mod wry_web_view_delegate;
pub mod wry_web_view_parent;
pub mod wry_web_view_ui_delegate;
@@ -0,0 +1,346 @@
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
borrow::Cow,
ffi::{c_char, c_void, CStr},
panic::AssertUnwindSafe,
ptr::NonNull,
};
use http::{
header::{CONTENT_LENGTH, CONTENT_TYPE},
Request, Response as HttpResponse, StatusCode, Version,
};
use objc2::{
rc::Retained,
runtime::{AnyClass, AnyObject, ClassBuilder, ProtocolObject},
AllocAnyThread, ClassType, Message,
};
use objc2_foundation::{
NSData, NSHTTPURLResponse, NSMutableDictionary, NSObject, NSObjectProtocol, NSString, NSURL,
NSUUID,
};
use objc2_web_kit::{WKURLSchemeHandler, WKURLSchemeTask};
use crate::{wkwebview::WEBVIEW_STATE, RequestAsyncResponder, WryWebView};
pub fn create(name: &str) -> &AnyClass {
unsafe {
// Include the address of WEBVIEW_STATE in the class name so that each dylib in the process
// gets its own ObjC class with method pointers into its own code and data segments.
let unique_id = std::ptr::addr_of!(WEBVIEW_STATE) as usize;
let scheme_name = format!("{name}URLSchemeHandler_{unique_id:x}\0");
let scheme_name = CStr::from_bytes_with_nul(scheme_name.as_bytes()).unwrap();
let cls = ClassBuilder::new(scheme_name, NSObject::class());
match cls {
Some(mut cls) => {
cls.add_ivar::<*mut c_char>(c"webview_id");
cls.add_ivar::<usize>(c"protocol_index");
cls.add_method(
objc2::sel!(webView:startURLSchemeTask:),
start_task as extern "C" fn(_, _, _, _),
);
cls.add_method(
objc2::sel!(webView:stopURLSchemeTask:),
stop_task as extern "C" fn(_, _, _, _),
);
cls.register()
}
None => AnyClass::get(scheme_name).expect("Failed to get the class definition"),
}
}
}
// Task handler for custom protocol
extern "C" fn start_task(
this: &AnyObject,
_sel: objc2::runtime::Sel,
webview: &WryWebView,
task: &ProtocolObject<dyn WKURLSchemeTask>,
) {
unsafe {
#[cfg(feature = "tracing")]
let span = tracing::info_span!(parent: None, "wry::custom_protocol::handle", uri = tracing::field::Empty)
.entered();
let task_key = task.hash(); // hash by task object address
let task_uuid = webview.add_custom_task_key(task_key);
let ivar = this.class().instance_variable(c"webview_id").unwrap();
let webview_id_ptr: *mut c_char = *ivar.load(this);
let webview_id = CStr::from_ptr(webview_id_ptr)
.to_str()
.ok()
.unwrap_or_default();
let ivar = this.class().instance_variable(c"protocol_index").unwrap();
let protocol_index: usize = *ivar.load(this);
let function = WEBVIEW_STATE
.read()
.unwrap()
.get(webview_id)
.and_then(|v| v.protocol_ptrs.get(protocol_index))
.cloned();
if let Some(function) = function {
// Get url request
let request = task.request();
let url = request.URL().unwrap();
let uri = url.absoluteString().unwrap().to_string();
#[cfg(feature = "tracing")]
span.record("uri", uri.clone());
// Get request method (GET, POST, PUT etc...)
let method = request.HTTPMethod().unwrap().to_string();
// Prepare our HttpRequest
let mut http_request = Request::builder().uri(uri).method(method.as_str());
// Get body
let mut sent_form_body = Vec::new();
let body = request.HTTPBody();
let body_stream = request.HTTPBodyStream();
if let Some(body) = body {
sent_form_body = body.to_vec();
} else if let Some(body_stream) = body_stream {
body_stream.open();
while body_stream.hasBytesAvailable() {
sent_form_body.reserve(128);
let p = sent_form_body.as_mut_ptr().add(sent_form_body.len());
let read_length = sent_form_body.capacity() - sent_form_body.len();
let count = body_stream.read_maxLength(NonNull::new(p).unwrap(), read_length);
sent_form_body.set_len(sent_form_body.len() + count as usize);
}
body_stream.close();
}
// Extract all headers fields
let all_headers = request.allHTTPHeaderFields();
// get all our headers values and inject them in our request
if let Some(all_headers) = all_headers {
for current_header in all_headers.allKeys().iter() {
let header_value = all_headers.valueForKey(&current_header).unwrap();
// inject the header into the request
http_request = http_request.header(current_header.to_string(), header_value.to_string());
}
}
let respond_with_404 = || {
let urlresponse = NSHTTPURLResponse::alloc();
let response = NSHTTPURLResponse::initWithURL_statusCode_HTTPVersion_headerFields(
urlresponse,
&url,
StatusCode::NOT_FOUND.as_u16().try_into().unwrap(),
Some(&NSString::from_str(
format!("{:#?}", Version::HTTP_11).as_str(),
)),
None,
)
.unwrap();
task.didReceiveResponse(&response);
// Finish
task.didFinish();
};
fn check_webview_id_valid(webview_id: &str) -> crate::Result<()> {
if !WEBVIEW_STATE.read().unwrap().contains_key(webview_id) {
return Err(crate::Error::CustomProtocolTaskInvalid);
}
Ok(())
}
/// Task may not live longer than async custom protocol handler.
///
/// There are roughly 2 ways to cause segfault:
/// 1. Task has stopped. pointer of the task not valid anymore.
/// 2. Task had stopped, but the pointer of the task has allocated to a new task.
/// Outdated custom handler may call to the new task instance and cause segfault.
fn check_task_is_valid(
webview: &WryWebView,
task_key: usize,
current_uuid: Retained<NSUUID>,
) -> crate::Result<()> {
let latest_task_uuid = webview.get_custom_task_uuid(task_key);
let Some(latest_uuid) = latest_task_uuid else {
return Err(crate::Error::CustomProtocolTaskInvalid);
};
if latest_uuid != current_uuid {
return Err(crate::Error::CustomProtocolTaskInvalid);
}
Ok(())
}
// send response
match http_request.body(sent_form_body) {
Ok(final_request) => {
let webview = webview.retain();
let task = task.retain();
let responder: Box<dyn FnOnce(HttpResponse<Cow<'static, [u8]>>)> =
Box::new(move |sent_response| {
// Consolidate checks before calling into `did*` methods.
let validate = || -> crate::Result<()> {
check_webview_id_valid(webview_id)?;
check_task_is_valid(&webview, task_key, task_uuid.clone())?;
Ok(())
};
// Perform an upfront validation
if let Err(_e) = validate() {
#[cfg(feature = "tracing")]
tracing::warn!("Task invalid before sending response: {:?}", _e);
return; // If invalid, return early without calling task methods.
}
unsafe fn response(
// FIXME: though we give it a static lifetime, it's not guaranteed to be valid.
task: Retained<ProtocolObject<dyn WKURLSchemeTask>>,
// FIXME: though we give it a static lifetime, it's not guaranteed to be valid.
webview: Retained<WryWebView>,
task_key: usize,
task_uuid: Retained<NSUUID>,
webview_id: &str,
url: Retained<NSURL>,
sent_response: HttpResponse<Cow<'_, [u8]>>,
) -> crate::Result<()> {
// Validate
check_webview_id_valid(webview_id)?;
check_task_is_valid(&webview, task_key, task_uuid.clone())?;
let content = sent_response.body();
// default: application/octet-stream, but should be provided by the client
let wanted_mime = sent_response.headers().get(CONTENT_TYPE);
// default to 200
let wanted_status_code = sent_response.status().as_u16() as i32;
// default to HTTP/1.1
let wanted_version = format!("{:#?}", sent_response.version());
let headers = NSMutableDictionary::new();
if let Some(mime) = wanted_mime {
headers.insert(
&*NSString::from_str(CONTENT_TYPE.as_str()),
&*NSString::from_str(mime.to_str().unwrap()),
);
}
headers.insert(
&*NSString::from_str(CONTENT_LENGTH.as_str()),
&*NSString::from_str(&content.len().to_string()),
);
// add headers
for (name, value) in sent_response.headers().iter() {
if let Ok(value) = value.to_str() {
headers.insert(
&*NSString::from_str(name.as_str()),
&*NSString::from_str(value),
);
}
}
let urlresponse = NSHTTPURLResponse::alloc();
let response = NSHTTPURLResponse::initWithURL_statusCode_HTTPVersion_headerFields(
urlresponse,
&url,
wanted_status_code.try_into().unwrap(),
Some(&NSString::from_str(&wanted_version)),
Some(&headers),
)
.unwrap();
// Re-validate before calling didReceiveResponse
check_webview_id_valid(webview_id)?;
check_task_is_valid(&webview, task_key, task_uuid.clone())?;
// Use map_err to convert Option<Retained<Exception>> to crate::Error
objc2::exception::catch(AssertUnwindSafe(|| {
task.didReceiveResponse(&response);
}))
.map_err(|_e| crate::Error::CustomProtocolTaskInvalid)?;
// Send data
let data = NSData::alloc();
// MIGRATE NOTE: we copied the content to the NSData because content will be freed
// when out of scope but NSData will also free the content when it's done and cause doube free.
let data = NSData::initWithBytes_length(
data,
content.as_ptr() as *mut c_void,
content.len(),
);
// Check validity again
check_webview_id_valid(webview_id)?;
check_task_is_valid(&webview, task_key, task_uuid.clone())?;
objc2::exception::catch(AssertUnwindSafe(|| {
task.didReceiveData(&data);
}))
.map_err(|_e| crate::Error::CustomProtocolTaskInvalid)?;
check_webview_id_valid(webview_id)?;
check_task_is_valid(&webview, task_key, task_uuid)?;
objc2::exception::catch(AssertUnwindSafe(|| {
task.didFinish();
}))
.map_err(|_e| crate::Error::CustomProtocolTaskInvalid)?;
if WEBVIEW_STATE.read().unwrap().contains_key(webview_id) {
webview.remove_custom_task_key(task_key);
Ok(())
} else {
Err(crate::Error::CustomProtocolTaskInvalid)
}
}
#[cfg(feature = "tracing")]
let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered();
if let Err(_e) = response(
task,
webview,
task_key,
task_uuid,
webview_id,
url,
sent_response,
) {
#[cfg(feature = "tracing")]
tracing::error!("Error responding to task: {:?}", _e);
}
});
#[cfg(feature = "tracing")]
let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered();
function(
webview_id,
final_request,
RequestAsyncResponder { responder },
);
}
Err(_) => respond_with_404(),
};
} else {
#[cfg(feature = "tracing")]
tracing::warn!(
"Either WebView or WebContext instance is dropped! This handler shouldn't be called."
);
};
}
}
extern "C" fn stop_task(
_this: &ProtocolObject<dyn WKURLSchemeHandler>,
_sel: objc2::runtime::Sel,
webview: &WryWebView,
task: &ProtocolObject<dyn WKURLSchemeTask>,
) {
webview.remove_custom_task_key(task.hash());
}
@@ -0,0 +1,67 @@
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{cell::RefCell, path::PathBuf, rc::Rc};
use objc2::{define_class, msg_send, rc::Retained, runtime::NSObject, MainThreadOnly};
use objc2_foundation::{
MainThreadMarker, NSData, NSError, NSObjectProtocol, NSString, NSURLResponse, NSURL,
};
use objc2_web_kit::{WKDownload, WKDownloadDelegate};
use crate::wkwebview::download::{download_did_fail, download_did_finish, download_policy};
pub struct WryDownloadDelegateIvars {
pub started: Option<RefCell<Box<dyn FnMut(String, &mut PathBuf) -> bool + 'static>>>,
pub completed: Option<Rc<dyn Fn(String, Option<PathBuf>, bool) + 'static>>,
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[ivars = WryDownloadDelegateIvars]
pub struct WryDownloadDelegate;
unsafe impl NSObjectProtocol for WryDownloadDelegate {}
unsafe impl WKDownloadDelegate for WryDownloadDelegate {
#[unsafe(method(download:decideDestinationUsingResponse:suggestedFilename:completionHandler:))]
fn download_policy(
&self,
download: &WKDownload,
response: &NSURLResponse,
suggested_filename: &NSString,
handler: &block2::Block<dyn Fn(*const NSURL)>,
) {
download_policy(self, download, response, suggested_filename, handler);
}
#[unsafe(method(downloadDidFinish:))]
fn download_did_finish(&self, download: &WKDownload) {
download_did_finish(self, download);
}
#[unsafe(method(download:didFailWithError:resumeData:))]
fn download_did_fail(&self, download: &WKDownload, error: &NSError, resume_data: &NSData) {
download_did_fail(self, download, error, resume_data);
}
}
);
impl WryDownloadDelegate {
pub fn new(
download_started_handler: Option<Box<dyn FnMut(String, &mut PathBuf) -> bool + 'static>>,
download_completed_handler: Option<Rc<dyn Fn(String, Option<PathBuf>, bool) + 'static>>,
mtm: MainThreadMarker,
) -> Retained<Self> {
let delegate = mtm
.alloc::<WryDownloadDelegate>()
.set_ivars(WryDownloadDelegateIvars {
started: download_started_handler.map(|handler| RefCell::new(handler)),
completed: download_completed_handler,
});
unsafe { msg_send![super(delegate), init] }
}
}
@@ -0,0 +1,158 @@
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::sync::{Arc, Mutex};
use objc2::{define_class, msg_send, rc::Retained, runtime::NSObject, MainThreadOnly};
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
use objc2_web_kit::{
WKDownload, WKNavigation, WKNavigationAction, WKNavigationActionPolicy, WKNavigationDelegate,
WKNavigationResponse, WKNavigationResponsePolicy,
};
#[cfg(target_os = "ios")]
use crate::wkwebview::ios::WKWebView::WKWebView;
#[cfg(target_os = "macos")]
use objc2_web_kit::WKWebView;
use crate::{
url_from_webview,
wkwebview::{
download::{navigation_download_action, navigation_download_response},
navigation::{
did_commit_navigation, did_finish_navigation, navigation_policy, navigation_policy_response,
web_content_process_did_terminate,
},
},
PageLoadEvent, WryWebView,
};
use super::wry_download_delegate::WryDownloadDelegate;
pub struct WryNavigationDelegateIvars {
pub pending_scripts: Arc<Mutex<Option<Vec<String>>>>,
pub has_download_handler: bool,
pub navigation_policy_function: Box<dyn Fn(String) -> bool>,
pub download_delegate: Option<Retained<WryDownloadDelegate>>,
pub on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent)>>,
pub on_web_content_process_terminate_handler: Option<Box<dyn Fn()>>,
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[ivars = WryNavigationDelegateIvars]
pub struct WryNavigationDelegate;
unsafe impl NSObjectProtocol for WryNavigationDelegate {}
unsafe impl WKNavigationDelegate for WryNavigationDelegate {
#[unsafe(method(webView:decidePolicyForNavigationAction:decisionHandler:))]
fn navigation_policy(
&self,
webview: &WKWebView,
action: &WKNavigationAction,
handler: &block2::Block<dyn Fn(WKNavigationActionPolicy)>,
) {
navigation_policy(self, webview, action, handler);
}
#[unsafe(method(webView:decidePolicyForNavigationResponse:decisionHandler:))]
fn navigation_policy_response(
&self,
webview: &WKWebView,
response: &WKNavigationResponse,
handler: &block2::Block<dyn Fn(WKNavigationResponsePolicy)>,
) {
navigation_policy_response(self, webview, response, handler);
}
#[unsafe(method(webView:didFinishNavigation:))]
fn did_finish_navigation(&self, webview: &WKWebView, navigation: &WKNavigation) {
did_finish_navigation(self, webview, navigation);
}
#[unsafe(method(webView:didCommitNavigation:))]
fn did_commit_navigation(&self, webview: &WKWebView, navigation: &WKNavigation) {
did_commit_navigation(self, webview, navigation);
}
#[unsafe(method(webView:navigationAction:didBecomeDownload:))]
fn navigation_download_action(
&self,
webview: &WKWebView,
action: &WKNavigationAction,
download: &WKDownload,
) {
navigation_download_action(self, webview, action, download);
}
#[unsafe(method(webView:navigationResponse:didBecomeDownload:))]
fn navigation_download_response(
&self,
webview: &WKWebView,
response: &WKNavigationResponse,
download: &WKDownload,
) {
navigation_download_response(self, webview, response, download);
}
#[unsafe(method(webViewWebContentProcessDidTerminate:))]
fn web_content_process_did_terminate(&self, webview: &WKWebView) {
web_content_process_did_terminate(self, webview);
}
}
);
impl WryNavigationDelegate {
#[allow(clippy::too_many_arguments)]
pub fn new(
webview: Retained<WryWebView>,
pending_scripts: Arc<Mutex<Option<Vec<String>>>>,
has_download_handler: bool,
navigation_handler: Option<Box<dyn Fn(String) -> bool>>,
download_delegate: Option<Retained<WryDownloadDelegate>>,
on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent, String)>>,
on_web_content_process_terminate_handler: Option<Box<dyn Fn()>>,
mtm: MainThreadMarker,
) -> Retained<Self> {
let navigation_policy_function = Box::new(move |url: String| -> bool {
navigation_handler
.as_ref()
.map_or(true, |navigation_handler| (navigation_handler)(url))
});
let on_page_load_handler = if let Some(handler) = on_page_load_handler {
let custom_handler = Box::new(move |event| {
handler(event, url_from_webview(&webview).unwrap_or_default());
}) as Box<dyn Fn(PageLoadEvent)>;
Some(custom_handler)
} else {
None
};
let on_web_content_process_terminate_handler =
if let Some(handler) = on_web_content_process_terminate_handler {
let custom_handler = Box::new(move || {
handler();
}) as Box<dyn Fn()>;
Some(custom_handler)
} else {
None
};
let delegate = mtm
.alloc::<WryNavigationDelegate>()
.set_ivars(WryNavigationDelegateIvars {
pending_scripts,
navigation_policy_function,
has_download_handler,
download_delegate,
on_page_load_handler,
on_web_content_process_terminate_handler,
});
unsafe { msg_send![super(delegate), init] }
}
}
+153
View File
@@ -0,0 +1,153 @@
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{collections::HashMap, sync::Mutex};
#[cfg(target_os = "macos")]
use objc2::runtime::ProtocolObject;
use objc2::{define_class, rc::Retained, runtime::Bool, DeclaredClass};
#[cfg(target_os = "macos")]
use objc2_app_kit::{NSDraggingDestination, NSEvent};
use objc2_foundation::{NSObjectProtocol, NSUUID};
#[cfg(target_os = "ios")]
use crate::wkwebview::ios::WKWebView::WKWebView;
#[cfg(target_os = "macos")]
use crate::{
wkwebview::{drag_drop, synthetic_mouse_events},
DragDropEvent,
};
#[cfg(target_os = "ios")]
use objc2_ui_kit::UIEvent as NSEvent;
#[cfg(target_os = "macos")]
use objc2_web_kit::WKWebView;
pub struct WryWebViewIvars {
pub(crate) is_child: bool,
#[cfg(target_os = "macos")]
pub(crate) drag_drop_handler: Box<dyn Fn(DragDropEvent) -> bool>,
#[cfg(target_os = "macos")]
pub(crate) accept_first_mouse: objc2::runtime::Bool,
#[cfg(target_os = "ios")]
pub(crate) input_accessory_view_builder: Option<Box<crate::InputAccessoryViewBuilder>>,
pub(crate) custom_protocol_task_ids: Mutex<HashMap<usize, Retained<NSUUID>>>,
}
define_class!(
#[unsafe(super(WKWebView))]
#[ivars = WryWebViewIvars]
pub struct WryWebView;
/// Overridden NSView methods.
impl WryWebView {
#[unsafe(method(performKeyEquivalent:))]
fn perform_key_equivalent(&self, event: &NSEvent) -> Bool {
// This is a temporary workaround for https://github.com/tauri-apps/tauri/issues/9426
// FIXME: When the webview is a child webview, performKeyEquivalent always return YES
// and stop propagating the event to the window, hence the menu shortcut won't be
// triggered. However, overriding this method also means the cmd+key event won't be
// handled in webview, which means the key cannot be listened by JavaScript.
if self.ivars().is_child {
Bool::NO
} else {
unsafe { objc2::msg_send![super(self), performKeyEquivalent: event] }
}
}
#[cfg(target_os = "macos")]
#[unsafe(method(acceptsFirstMouse:))]
fn accept_first_mouse(&self, _event: &NSEvent) -> Bool {
self.ivars().accept_first_mouse
}
#[cfg(target_os = "ios")]
#[unsafe(method_id(inputAccessoryView))]
fn input_accessory_view(&self) -> Option<Retained<objc2_ui_kit::UIView>> {
if let Some(builder) = &self.ivars().input_accessory_view_builder {
builder(self)
} else {
unsafe { objc2::msg_send![super(self), inputAccessoryView] }
}
}
}
unsafe impl NSObjectProtocol for WryWebView {}
// Drag & Drop
#[cfg(target_os = "macos")]
unsafe impl NSDraggingDestination for WryWebView {
#[unsafe(method(draggingEntered:))]
fn dragging_entered(
&self,
drag_info: &ProtocolObject<dyn objc2_app_kit::NSDraggingInfo>,
) -> objc2_app_kit::NSDragOperation {
drag_drop::dragging_entered(self, drag_info)
}
#[unsafe(method(draggingUpdated:))]
fn dragging_updated(
&self,
drag_info: &ProtocolObject<dyn objc2_app_kit::NSDraggingInfo>,
) -> objc2_app_kit::NSDragOperation {
drag_drop::dragging_updated(self, drag_info)
}
#[unsafe(method(performDragOperation:))]
fn perform_drag_operation(
&self,
drag_info: &ProtocolObject<dyn objc2_app_kit::NSDraggingInfo>,
) -> Bool {
drag_drop::perform_drag_operation(self, drag_info)
}
#[unsafe(method(draggingExited:))]
fn dragging_exited(&self, drag_info: &ProtocolObject<dyn objc2_app_kit::NSDraggingInfo>) {
drag_drop::dragging_exited(self, drag_info)
}
}
// Synthetic mouse events
#[cfg(target_os = "macos")]
impl WryWebView {
#[unsafe(method(otherMouseDown:))]
fn other_mouse_down(&self, event: &NSEvent) {
synthetic_mouse_events::other_mouse_down(self, event)
}
#[unsafe(method(otherMouseUp:))]
fn other_mouse_up(&self, event: &NSEvent) {
synthetic_mouse_events::other_mouse_up(self, event)
}
}
);
// Custom Protocol Task Checker
impl WryWebView {
pub(crate) fn add_custom_task_key(&self, task_id: usize) -> Retained<NSUUID> {
let task_uuid = NSUUID::new();
self
.ivars()
.custom_protocol_task_ids
.lock()
.unwrap()
.insert(task_id, task_uuid.clone());
task_uuid
}
pub(crate) fn remove_custom_task_key(&self, task_id: usize) {
self
.ivars()
.custom_protocol_task_ids
.lock()
.unwrap()
.remove(&task_id);
}
pub(crate) fn get_custom_task_uuid(&self, task_id: usize) -> Option<Retained<NSUUID>> {
self
.ivars()
.custom_protocol_task_ids
.lock()
.unwrap()
.get(&task_id)
.cloned()
}
}
@@ -0,0 +1,105 @@
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{ffi::CStr, panic::AssertUnwindSafe};
use http::Request;
use objc2::{
define_class, msg_send,
rc::Retained,
runtime::{NSObject, ProtocolObject},
DeclaredClass, MainThreadOnly,
};
use objc2_foundation::{ns_string, MainThreadMarker, NSObjectProtocol, NSString};
use objc2_web_kit::{WKScriptMessage, WKScriptMessageHandler, WKUserContentController};
pub const IPC_MESSAGE_HANDLER_NAME: &str = "ipc";
pub struct WryWebViewDelegateIvars {
pub controller: Retained<WKUserContentController>,
pub ipc_handler: Box<dyn Fn(Request<String>)>,
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[ivars = WryWebViewDelegateIvars]
pub struct WryWebViewDelegate;
unsafe impl NSObjectProtocol for WryWebViewDelegate {}
unsafe impl WKScriptMessageHandler for WryWebViewDelegate {
// Function for ipc handler
#[unsafe(method(userContentController:didReceiveScriptMessage:))]
fn did_receive(
this: &WryWebViewDelegate,
_controller: &WKUserContentController,
msg: &WKScriptMessage,
) {
// Safety: objc runtime calls are unsafe
unsafe {
#[cfg(feature = "tracing")]
let _span = tracing::info_span!(parent: None, "wry::ipc::handle").entered();
let ipc_handler = &this.ivars().ipc_handler;
let body = msg.body();
if let Ok(body) = body.downcast::<NSString>() {
let js_utf8 = body.UTF8String();
let frame_info = msg.frameInfo();
let request = frame_info.request();
let url = request.URL().unwrap();
let absolute_url = url.absoluteString().unwrap();
let url_utf8 = absolute_url.UTF8String();
if let (Ok(url), Ok(js)) = (
CStr::from_ptr(url_utf8).to_str(),
CStr::from_ptr(js_utf8).to_str(),
) {
if let Ok(r) = Request::builder().uri(url).body(js.to_string()) {
ipc_handler(r);
} else {
#[cfg(feature = "tracing")]
tracing::warn!("WebView received invalid IPC request: {}", js);
}
return;
}
}
#[cfg(feature = "tracing")]
tracing::warn!("WebView received invalid IPC call.");
}
}
}
);
impl WryWebViewDelegate {
pub fn new(
controller: Retained<WKUserContentController>,
ipc_handler: Box<dyn Fn(Request<String>)>,
mtm: MainThreadMarker,
) -> Retained<Self> {
let delegate = mtm
.alloc::<WryWebViewDelegate>()
.set_ivars(WryWebViewDelegateIvars {
ipc_handler,
controller,
});
let delegate: Retained<Self> = unsafe { msg_send![super(delegate), init] };
let proto_delegate = ProtocolObject::from_ref(&*delegate);
unsafe {
// this will increase the retain count of the delegate
let _res = objc2::exception::catch(AssertUnwindSafe(|| {
delegate
.ivars()
.controller
.addScriptMessageHandler_name(proto_delegate, ns_string!(IPC_MESSAGE_HANDLER_NAME));
}));
}
delegate
}
}
@@ -0,0 +1,110 @@
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#[cfg(target_os = "macos")]
use objc2::DefinedClass;
use objc2::{define_class, msg_send, rc::Retained, MainThreadOnly};
#[cfg(target_os = "macos")]
use objc2_app_kit::{NSApplication, NSEvent, NSView, NSWindow, NSWindowButton};
use objc2_foundation::MainThreadMarker;
#[cfg(target_os = "macos")]
use objc2_foundation::NSRect;
#[cfg(target_os = "ios")]
use objc2_ui_kit::UIView as NSView;
pub struct WryWebViewParentIvars {
#[cfg(target_os = "macos")]
traffic_light_inset: std::cell::Cell<Option<(f64, f64)>>,
}
define_class!(
#[unsafe(super(NSView))]
#[ivars = WryWebViewParentIvars]
pub struct WryWebViewParent;
/// Overridden NSView methods.
impl WryWebViewParent {
#[cfg(target_os = "macos")]
#[unsafe(method(keyDown:))]
fn key_down(&self, event: &NSEvent) {
let mtm = MainThreadMarker::new().unwrap();
let app = NSApplication::sharedApplication(mtm);
unsafe {
if let Some(menu) = app.mainMenu() {
menu.performKeyEquivalent(event);
}
}
}
#[cfg(target_os = "macos")]
#[unsafe(method(drawRect:))]
fn draw(&self, _dirty_rect: NSRect) {
if let Some((x, y)) = self.ivars().traffic_light_inset.get() {
unsafe { inset_traffic_lights(&self.window().unwrap(), x, y) };
}
}
}
);
impl WryWebViewParent {
#[allow(dead_code)]
pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
let delegate = WryWebViewParent::alloc(mtm).set_ivars(WryWebViewParentIvars {
#[cfg(target_os = "macos")]
traffic_light_inset: Default::default(),
});
unsafe { msg_send![super(delegate), init] }
}
#[cfg(target_os = "macos")]
pub fn set_traffic_light_inset(&self, ns_window: &NSWindow, position: dpi::Position) {
let scale_factor = NSWindow::backingScaleFactor(ns_window);
let position = position.to_logical(scale_factor);
self
.ivars()
.traffic_light_inset
.replace(Some((position.x, position.y)));
unsafe {
inset_traffic_lights(ns_window, position.x, position.y);
}
}
}
#[cfg(target_os = "macos")]
pub unsafe fn inset_traffic_lights(window: &NSWindow, x: f64, y: f64) {
let Some(close) = window.standardWindowButton(NSWindowButton::CloseButton) else {
#[cfg(feature = "tracing")]
tracing::warn!("skipping inset_traffic_lights, close button not found");
return;
};
let Some(miniaturize) = window.standardWindowButton(NSWindowButton::MiniaturizeButton) else {
#[cfg(feature = "tracing")]
tracing::warn!("skipping inset_traffic_lights, miniaturize button not found");
return;
};
let zoom = window.standardWindowButton(NSWindowButton::ZoomButton);
let title_bar_container_view = close.superview().unwrap().superview().unwrap();
let close_rect = NSView::frame(&close);
let title_bar_frame_height = close_rect.size.height + y;
let mut title_bar_rect = NSView::frame(&title_bar_container_view);
title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y = window.frame().size.height - title_bar_frame_height;
title_bar_container_view.setFrame(title_bar_rect);
let space_between = NSView::frame(&miniaturize).origin.x - close_rect.origin.x;
let mut window_buttons = vec![close, miniaturize];
if let Some(zoom) = zoom {
window_buttons.push(zoom);
}
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect = NSView::frame(&button);
rect.origin.x = x + (i as f64 * space_between);
button.setFrameOrigin(rect.origin);
}
}
@@ -0,0 +1,282 @@
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#[cfg(target_os = "macos")]
use std::{cell::RefCell, ptr::null_mut, rc::Rc};
use block2::Block;
#[cfg(target_os = "macos")]
use objc2::DefinedClass;
use objc2::{define_class, msg_send, rc::Retained, runtime::NSObject, MainThreadOnly};
#[cfg(target_os = "macos")]
use objc2_app_kit::{NSModalResponse, NSModalResponseOK, NSOpenPanel, NSWindowDelegate};
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
#[cfg(target_os = "macos")]
use objc2_foundation::{NSArray, NSURL};
#[cfg(target_os = "macos")]
use objc2_web_kit::WKOpenPanelParameters;
use objc2_web_kit::{
WKFrameInfo, WKMediaCaptureType, WKPermissionDecision, WKSecurityOrigin, WKUIDelegate,
};
use crate::{NewWindowFeatures, NewWindowResponse, WryWebView};
#[cfg(target_os = "macos")]
struct NewWindow {
#[allow(dead_code)]
ns_window: Retained<objc2_app_kit::NSWindow>,
#[allow(dead_code)]
webview: Retained<objc2_web_kit::WKWebView>,
#[allow(dead_code)]
delegate: Retained<WryNSWindowDelegate>,
}
// SAFETY: we are not using the new window at all, just dropping it on another thread
#[cfg(target_os = "macos")]
unsafe impl Send for NewWindow {}
#[cfg(target_os = "macos")]
impl Drop for NewWindow {
fn drop(&mut self) {
unsafe {
self.webview.removeFromSuperview();
}
}
}
#[cfg(target_os = "macos")]
struct WryNSWindowDelegateIvars {
on_close: Box<dyn Fn()>,
}
#[cfg(target_os = "macos")]
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[ivars = WryNSWindowDelegateIvars]
struct WryNSWindowDelegate;
unsafe impl NSObjectProtocol for WryNSWindowDelegate {}
unsafe impl NSWindowDelegate for WryNSWindowDelegate {
#[unsafe(method(windowWillClose:))]
unsafe fn will_close(&self, _notification: &objc2_foundation::NSNotification) {
let on_close = &self.ivars().on_close;
on_close();
}
}
);
#[cfg(target_os = "macos")]
impl WryNSWindowDelegate {
pub fn new(mtm: MainThreadMarker, on_close: Box<dyn Fn()>) -> Retained<Self> {
let delegate = mtm
.alloc::<WryNSWindowDelegate>()
.set_ivars(WryNSWindowDelegateIvars { on_close });
unsafe { msg_send![super(delegate), init] }
}
}
pub struct WryWebViewUIDelegateIvars {
#[cfg(target_os = "macos")]
new_window_req_handler: Option<Box<dyn Fn(String, NewWindowFeatures) -> NewWindowResponse>>,
#[cfg(target_os = "macos")]
new_windows: Rc<RefCell<Vec<NewWindow>>>,
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[ivars = WryWebViewUIDelegateIvars]
pub struct WryWebViewUIDelegate;
unsafe impl NSObjectProtocol for WryWebViewUIDelegate {}
unsafe impl WKUIDelegate for WryWebViewUIDelegate {
#[cfg(target_os = "macos")]
#[unsafe(method(webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:))]
fn run_file_upload_panel(
&self,
_webview: &WryWebView,
open_panel_params: &WKOpenPanelParameters,
_frame: &WKFrameInfo,
handler: &block2::Block<dyn Fn(*const NSArray<NSURL>)>,
) {
unsafe {
if let Some(mtm) = MainThreadMarker::new() {
let open_panel = NSOpenPanel::openPanel(mtm);
open_panel.setCanChooseFiles(true);
let allow_multi = open_panel_params.allowsMultipleSelection();
open_panel.setAllowsMultipleSelection(allow_multi);
let allow_dir = open_panel_params.allowsDirectories();
open_panel.setCanChooseDirectories(allow_dir);
let ok: NSModalResponse = open_panel.runModal();
if ok == NSModalResponseOK {
let url = open_panel.URLs();
(*handler).call((Retained::as_ptr(&url),));
} else {
(*handler).call((null_mut(),));
}
}
}
}
#[unsafe(method(webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:))]
fn request_media_capture_permission(
&self,
_webview: &WryWebView,
_origin: &WKSecurityOrigin,
_frame: &WKFrameInfo,
_capture_type: WKMediaCaptureType,
decision_handler: &Block<dyn Fn(WKPermissionDecision)>,
) {
//https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc
(*decision_handler).call((WKPermissionDecision::Grant,));
}
#[cfg(target_os = "macos")]
#[unsafe(method_id(webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:))]
unsafe fn create_web_view_for_navigation_action(
&self,
webview: &WryWebView,
configuration: &objc2_web_kit::WKWebViewConfiguration,
action: &objc2_web_kit::WKNavigationAction,
window_features: &objc2_web_kit::WKWindowFeatures,
) -> Option<Retained<objc2_web_kit::WKWebView>> {
if let Some(new_window_req_handler) = &self.ivars().new_window_req_handler {
let request = action.request();
let url = request.URL().unwrap().absoluteString().unwrap();
let current_window = webview.window().unwrap();
let screen = current_window.screen().unwrap();
let screen_frame = screen.frame();
match new_window_req_handler(
url.to_string(),
NewWindowFeatures {
size: if let (Some(width), Some(height)) =
(window_features.width(), window_features.height())
{
Some(dpi::LogicalSize::new(
width.doubleValue(),
height.doubleValue(),
))
} else {
None
},
position: if let (Some(x), Some(y)) = (window_features.x(), window_features.y()) {
Some(dpi::LogicalPosition::new(x.doubleValue(), y.doubleValue()))
} else {
None
},
opener: crate::NewWindowOpener {
webview: webview.into(),
target_configuration: configuration.into(),
},
},
) {
NewWindowResponse::Allow => {
let mtm = MainThreadMarker::new().unwrap();
let defaults = current_window.frame();
let size = objc2_foundation::NSSize::new(
window_features
.width()
.map_or(defaults.size.width, |width| width.doubleValue()),
window_features
.height()
.map_or(defaults.size.height, |height| height.doubleValue()),
);
let position = objc2_foundation::NSPoint::new(
window_features
.x()
.map_or(defaults.origin.x, |x| x.doubleValue()),
window_features.y().map_or(defaults.origin.y, |y| {
screen_frame.size.height - y.doubleValue() - size.height
}),
);
let rect = objc2_foundation::NSRect::new(position, size);
let mut flags = objc2_app_kit::NSWindowStyleMask::Titled
| objc2_app_kit::NSWindowStyleMask::Closable
| objc2_app_kit::NSWindowStyleMask::Miniaturizable;
let resizable = window_features
.allowsResizing()
.map_or(true, |resizable| resizable.boolValue());
if resizable {
flags |= objc2_app_kit::NSWindowStyleMask::Resizable;
}
let window = objc2_app_kit::NSWindow::initWithContentRect_styleMask_backing_defer(
mtm.alloc::<objc2_app_kit::NSWindow>(),
rect,
flags,
objc2_app_kit::NSBackingStoreType::Buffered,
false,
);
// SAFETY: Disable auto-release when closing windows.
// This is required when creating `NSWindow` outside a window
// controller.
window.setReleasedWhenClosed(false);
let webview = objc2_web_kit::WKWebView::initWithFrame_configuration(
mtm.alloc::<objc2_web_kit::WKWebView>(),
window.frame(),
configuration,
);
let new_windows = self.ivars().new_windows.clone();
let window_id = Retained::as_ptr(&window) as usize;
let delegate = WryNSWindowDelegate::new(
mtm,
Box::new(move || {
new_windows
.borrow_mut()
.retain(|window| Retained::as_ptr(&window.ns_window) as usize != window_id);
}),
);
window.setDelegate(Some(objc2::runtime::ProtocolObject::from_ref(&*delegate)));
window.setContentView(Some(&webview));
window.makeKeyAndOrderFront(None);
self.ivars().new_windows.borrow_mut().push(NewWindow {
ns_window: window,
webview: webview.clone(),
delegate,
});
Some(webview)
}
NewWindowResponse::Create { webview } => Some(webview),
NewWindowResponse::Deny => None,
}
} else {
None
}
}
}
);
impl WryWebViewUIDelegate {
pub fn new(
mtm: MainThreadMarker,
new_window_req_handler: Option<Box<dyn Fn(String, NewWindowFeatures) -> NewWindowResponse>>,
) -> Retained<Self> {
#[cfg(target_os = "ios")]
let _new_window_req_handler = new_window_req_handler;
let delegate = mtm
.alloc::<WryWebViewUIDelegate>()
.set_ivars(WryWebViewUIDelegateIvars {
#[cfg(target_os = "macos")]
new_window_req_handler,
#[cfg(target_os = "macos")]
new_windows: Rc::new(RefCell::new(vec![])),
});
unsafe { msg_send![super(delegate), init] }
}
}
+122
View File
@@ -0,0 +1,122 @@
use std::{env::current_dir, ptr::null_mut};
use objc2::{rc::Retained, runtime::ProtocolObject, DeclaredClass};
use objc2_foundation::{NSData, NSError, NSString, NSURLResponse, NSURL};
use objc2_web_kit::{WKDownload, WKNavigationAction, WKNavigationResponse};
#[cfg(target_os = "ios")]
use crate::wkwebview::ios::WKWebView::WKWebView;
#[cfg(target_os = "macos")]
use objc2_web_kit::WKWebView;
use super::class::{
wry_download_delegate::WryDownloadDelegate, wry_navigation_delegate::WryNavigationDelegate,
};
// Download action handler
pub(crate) fn navigation_download_action(
this: &WryNavigationDelegate,
_webview: &WKWebView,
_action: &WKNavigationAction,
download: &WKDownload,
) {
unsafe {
if let Some(delegate) = &this.ivars().download_delegate {
let proto_delegate = ProtocolObject::from_ref(&**delegate);
download.setDelegate(Some(proto_delegate));
}
}
}
// Download response handler
pub(crate) fn navigation_download_response(
this: &WryNavigationDelegate,
_webview: &WKWebView,
_response: &WKNavigationResponse,
download: &WKDownload,
) {
unsafe {
if let Some(delegate) = &this.ivars().download_delegate {
let proto_delegate = ProtocolObject::from_ref(&**delegate);
download.setDelegate(Some(proto_delegate));
}
}
}
pub(crate) fn download_policy(
this: &WryDownloadDelegate,
download: &WKDownload,
_response: &NSURLResponse,
suggested_filename: &NSString,
completion_handler: &block2::Block<dyn Fn(*const NSURL)>,
) {
unsafe {
let request = download.originalRequest().unwrap();
let url = request.URL().unwrap().absoluteString().unwrap();
let suggested_filename = suggested_filename.to_string();
let mut download_destination =
dirs::download_dir().unwrap_or_else(|| current_dir().unwrap_or_default());
download_destination.push(&suggested_filename);
let (suggested_filename, ext) = suggested_filename
.split_once('.')
.map(|(base, ext)| (base, format!(".{ext}")))
.unwrap_or((&suggested_filename, "".to_string()));
// WebView2 does not overwrite files but appends numbers
let mut counter = 1;
while download_destination.exists() {
download_destination.set_file_name(format!("{suggested_filename} ({counter}){ext}"));
counter += 1;
}
let started_fn = &this.ivars().started;
if let Some(started_fn) = started_fn {
let mut started_fn = started_fn.borrow_mut();
match started_fn(url.to_string(), &mut download_destination) {
true => {
let path = NSString::from_str(&download_destination.display().to_string());
let ns_url = NSURL::fileURLWithPath_isDirectory(&path, false);
(*completion_handler).call((Retained::as_ptr(&ns_url),))
}
false => (*completion_handler).call((null_mut(),)),
};
} else {
#[cfg(feature = "tracing")]
tracing::warn!("WebView instance is dropped! This navigation handler shouldn't be called.");
(*completion_handler).call((null_mut(),));
}
}
}
pub(crate) fn download_did_finish(this: &WryDownloadDelegate, download: &WKDownload) {
unsafe {
let original_request = download.originalRequest().unwrap();
let url = original_request.URL().unwrap().absoluteString().unwrap();
if let Some(completed_fn) = this.ivars().completed.clone() {
completed_fn(url.to_string(), None, true);
}
}
}
pub(crate) fn download_did_fail(
this: &WryDownloadDelegate,
download: &WKDownload,
error: &NSError,
_resume_data: &NSData,
) {
unsafe {
#[cfg(debug_assertions)]
{
let description = error.localizedDescription().to_string();
eprintln!("Download failed with error: {description}");
}
let original_request = download.originalRequest().unwrap();
let url = original_request.URL().unwrap().absoluteString().unwrap();
if let Some(completed_fn) = this.ivars().completed.clone() {
completed_fn(url.to_string(), None, false);
}
}
}
+104
View File
@@ -0,0 +1,104 @@
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{ffi::CStr, path::PathBuf};
use objc2::{
runtime::{Bool, ProtocolObject},
DeclaredClass,
};
use objc2_app_kit::{NSDragOperation, NSDraggingInfo, NSFilenamesPboardType};
use objc2_foundation::{NSArray, NSPoint, NSRect, NSString};
use crate::DragDropEvent;
use super::WryWebView;
pub(crate) unsafe fn collect_paths(drag_info: &ProtocolObject<dyn NSDraggingInfo>) -> Vec<PathBuf> {
let pb = drag_info.draggingPasteboard();
let mut drag_drop_paths = Vec::new();
let types = NSArray::arrayWithObject(NSFilenamesPboardType);
if pb.availableTypeFromArray(&types).is_some() {
let paths = pb.propertyListForType(NSFilenamesPboardType).unwrap();
let paths = paths.downcast::<NSArray>().unwrap();
for path in paths {
let path = path.downcast::<NSString>().unwrap();
let path = CStr::from_ptr(path.UTF8String()).to_string_lossy();
drag_drop_paths.push(PathBuf::from(path.into_owned()));
}
}
drag_drop_paths
}
pub(crate) fn dragging_entered(
this: &WryWebView,
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
) -> NSDragOperation {
let paths = unsafe { collect_paths(drag_info) };
let dl: NSPoint = unsafe { drag_info.draggingLocation() };
let frame: NSRect = this.frame();
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);
let listener = &this.ivars().drag_drop_handler;
if !listener(DragDropEvent::Enter { paths, position }) {
// Reject the Wry file drop (invoke the OS default behaviour)
unsafe { objc2::msg_send![super(this), draggingEntered: drag_info] }
} else {
NSDragOperation::Copy
}
}
pub(crate) fn dragging_updated(
this: &WryWebView,
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
) -> NSDragOperation {
let dl: NSPoint = unsafe { drag_info.draggingLocation() };
let frame: NSRect = this.frame();
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);
let listener = &this.ivars().drag_drop_handler;
if !listener(DragDropEvent::Over { position }) {
unsafe {
let os_operation = objc2::msg_send![super(this), draggingUpdated: drag_info];
if os_operation == NSDragOperation::None {
// 0 will be returned for a drop on any arbitrary location on the webview.
// We'll override that with NSDragOperationCopy.
NSDragOperation::Copy
} else {
// A different NSDragOperation is returned when a file is hovered over something like
// a <input type="file">, so we'll make sure to preserve that behaviour.
os_operation
}
}
} else {
NSDragOperation::Copy
}
}
pub(crate) fn perform_drag_operation(
this: &WryWebView,
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
) -> Bool {
let paths = unsafe { collect_paths(drag_info) };
let dl: NSPoint = unsafe { drag_info.draggingLocation() };
let frame: NSRect = this.frame();
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);
let listener = &this.ivars().drag_drop_handler;
if !listener(DragDropEvent::Drop { paths, position }) {
// Reject the Wry drop (invoke the OS default behaviour)
unsafe { objc2::msg_send![super(this), performDragOperation: drag_info] }
} else {
Bool::YES
}
}
pub(crate) fn dragging_exited(this: &WryWebView, drag_info: &ProtocolObject<dyn NSDraggingInfo>) {
let listener = &this.ivars().drag_drop_handler;
if !listener(DragDropEvent::Leave) {
// Reject the Wry drop (invoke the OS default behaviour)
unsafe { objc2::msg_send![super(this), draggingExited: drag_info] }
}
}
+625
View File
@@ -0,0 +1,625 @@
//! This file has been imported from `objc2-web-kit` and modified to match iOS.
#![allow(warnings)]
#![allow(clippy::all)]
use std::{ffi::c_double, ptr::NonNull};
use objc2::{
encode::{Encode, Encoding, RefEncode},
extern_class, extern_methods,
rc::{Allocated, Retained},
runtime::{AnyObject, ProtocolObject},
MainThreadOnly,
};
use objc2_core_foundation::*;
use objc2_foundation::*;
use objc2_ui_kit::*;
use objc2_web_kit::*;
use crate::*;
// NS_ENUM
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WKMediaPlaybackState(pub NSInteger);
impl WKMediaPlaybackState {
#[doc(alias = "WKMediaPlaybackStateNone")]
pub const None: Self = Self(0);
#[doc(alias = "WKMediaPlaybackStatePlaying")]
pub const Playing: Self = Self(1);
#[doc(alias = "WKMediaPlaybackStatePaused")]
pub const Paused: Self = Self(2);
#[doc(alias = "WKMediaPlaybackStateSuspended")]
pub const Suspended: Self = Self(3);
}
unsafe impl Encode for WKMediaPlaybackState {
const ENCODING: Encoding = NSInteger::ENCODING;
}
unsafe impl RefEncode for WKMediaPlaybackState {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
// NS_ENUM
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WKMediaCaptureState(pub NSInteger);
impl WKMediaCaptureState {
#[doc(alias = "WKMediaCaptureStateNone")]
pub const None: Self = Self(0);
#[doc(alias = "WKMediaCaptureStateActive")]
pub const Active: Self = Self(1);
#[doc(alias = "WKMediaCaptureStateMuted")]
pub const Muted: Self = Self(2);
}
unsafe impl Encode for WKMediaCaptureState {
const ENCODING: Encoding = NSInteger::ENCODING;
}
unsafe impl RefEncode for WKMediaCaptureState {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
// NS_ENUM
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WKFullscreenState(pub NSInteger);
impl WKFullscreenState {
#[doc(alias = "WKFullscreenStateNotInFullscreen")]
pub const NotInFullscreen: Self = Self(0);
#[doc(alias = "WKFullscreenStateEnteringFullscreen")]
pub const EnteringFullscreen: Self = Self(1);
#[doc(alias = "WKFullscreenStateInFullscreen")]
pub const InFullscreen: Self = Self(2);
#[doc(alias = "WKFullscreenStateExitingFullscreen")]
pub const ExitingFullscreen: Self = Self(3);
}
unsafe impl Encode for WKFullscreenState {
const ENCODING: Encoding = NSInteger::ENCODING;
}
unsafe impl RefEncode for WKFullscreenState {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
extern_class!(
#[unsafe(super(UIView, UIResponder, NSObject))]
#[thread_kind = MainThreadOnly]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct WKWebView;
);
#[cfg(target_os = "macos")]
unsafe impl NSAccessibility for WKWebView {}
#[cfg(target_os = "macos")]
unsafe impl NSAccessibilityElementProtocol for WKWebView {}
#[cfg(target_os = "macos")]
unsafe impl NSAnimatablePropertyContainer for WKWebView {}
#[cfg(target_os = "macos")]
unsafe impl NSAppearanceCustomization for WKWebView {}
unsafe impl NSCoding for WKWebView {}
#[cfg(target_os = "macos")]
unsafe impl NSDraggingDestination for WKWebView {}
unsafe impl NSObjectProtocol for WKWebView {}
#[cfg(target_os = "macos")]
unsafe impl NSUserInterfaceItemIdentification for WKWebView {}
impl WKWebView {
extern_methods!(
// #[cfg(feature = "WKWebViewConfiguration")]
#[unsafe(method(configuration))]
pub unsafe fn configuration(&self) -> Retained<WKWebViewConfiguration>;
// #[cfg(feature = "WKNavigationDelegate")]
#[unsafe(method(navigationDelegate))]
pub unsafe fn navigationDelegate(
&self,
) -> Option<Retained<ProtocolObject<dyn WKNavigationDelegate>>>;
// #[cfg(feature = "WKNavigationDelegate")]
#[unsafe(method(setNavigationDelegate:))]
pub unsafe fn setNavigationDelegate(
&self,
navigation_delegate: Option<&ProtocolObject<dyn WKNavigationDelegate>>,
);
// #[cfg(feature = "WKUIDelegate")]
#[unsafe(method(UIDelegate))]
pub unsafe fn UIDelegate(&self) -> Option<Retained<ProtocolObject<dyn WKUIDelegate>>>;
// #[cfg(feature = "WKUIDelegate")]
#[unsafe(method(setUIDelegate:))]
pub unsafe fn setUIDelegate(&self, ui_delegate: Option<&ProtocolObject<dyn WKUIDelegate>>);
#[cfg(target_os = "macos")]
// #[cfg(feature = "WKBackForwardList")]
#[unsafe(method(backForwardList))]
pub unsafe fn backForwardList(&self) -> Retained<WKBackForwardList>;
// #[cfg(feature = "WKWebViewConfiguration")]
#[unsafe(method(initWithFrame:configuration:))]
pub unsafe fn initWithFrame_configuration(
this: Allocated<Self>,
frame: CGRect,
configuration: &WKWebViewConfiguration,
) -> Retained<Self>;
#[unsafe(method(initWithCoder:))]
pub unsafe fn initWithCoder(this: Allocated<Self>, coder: &NSCoder) -> Option<Retained<Self>>;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(loadRequest:))]
pub unsafe fn loadRequest(&self, request: &NSURLRequest) -> Option<Retained<WKNavigation>>;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(loadFileURL:allowingReadAccessToURL:))]
pub unsafe fn loadFileURL_allowingReadAccessToURL(
&self,
url: &NSURL,
read_access_url: &NSURL,
) -> Option<Retained<WKNavigation>>;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(loadHTMLString:baseURL:))]
pub unsafe fn loadHTMLString_baseURL(
&self,
string: &NSString,
base_url: Option<&NSURL>,
) -> Option<Retained<WKNavigation>>;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(loadData:MIMEType:characterEncodingName:baseURL:))]
pub unsafe fn loadData_MIMEType_characterEncodingName_baseURL(
&self,
data: &NSData,
mime_type: &NSString,
character_encoding_name: &NSString,
base_url: &NSURL,
) -> Option<Retained<WKNavigation>>;
#[cfg(target_os = "macos")]
// #[cfg(all(feature = "WKBackForwardListItem", feature = "WKNavigation"))]
#[unsafe(method(goToBackForwardListItem:))]
pub unsafe fn goToBackForwardListItem(
&self,
item: &WKBackForwardListItem,
) -> Option<Retained<WKNavigation>>;
#[unsafe(method(title))]
pub unsafe fn title(&self) -> Option<Retained<NSString>>;
#[unsafe(method(URL))]
pub unsafe fn URL(&self) -> Option<Retained<NSURL>>;
#[unsafe(method(isLoading))]
pub unsafe fn isLoading(&self) -> bool;
#[unsafe(method(estimatedProgress))]
pub unsafe fn estimatedProgress(&self) -> c_double;
#[unsafe(method(hasOnlySecureContent))]
pub unsafe fn hasOnlySecureContent(&self) -> bool;
#[unsafe(method(canGoBack))]
pub unsafe fn canGoBack(&self) -> bool;
#[unsafe(method(canGoForward))]
pub unsafe fn canGoForward(&self) -> bool;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(goBack))]
pub unsafe fn goBack(&self) -> Option<Retained<WKNavigation>>;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(goForward))]
pub unsafe fn goForward(&self) -> Option<Retained<WKNavigation>>;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(reload))]
pub unsafe fn reload(&self) -> Option<Retained<WKNavigation>>;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(reloadFromOrigin))]
pub unsafe fn reloadFromOrigin(&self) -> Option<Retained<WKNavigation>>;
#[unsafe(method(stopLoading))]
pub unsafe fn stopLoading(&self);
// #[cfg(feature = "block2")]
#[unsafe(method(evaluateJavaScript:completionHandler:))]
pub unsafe fn evaluateJavaScript_completionHandler(
&self,
java_script_string: &NSString,
completion_handler: Option<&block2::Block<dyn Fn(*mut AnyObject, *mut NSError)>>,
);
#[cfg(target_os = "macos")]
// #[cfg(all(
// feature = "WKContentWorld",
// feature = "WKFrameInfo",
// feature = "block2"
// ))]
#[unsafe(method(evaluateJavaScript:inFrame:inContentWorld:completionHandler:))]
pub unsafe fn evaluateJavaScript_inFrame_inContentWorld_completionHandler(
&self,
java_script_string: &NSString,
frame: Option<&WKFrameInfo>,
content_world: &WKContentWorld,
completion_handler: Option<&block2::Block<dyn Fn(*mut AnyObject, *mut NSError)>>,
);
#[cfg(target_os = "macos")]
// #[cfg(all(
// feature = "WKContentWorld",
// feature = "WKFrameInfo",
// feature = "block2"
// ))]
#[unsafe(method(callAsyncJavaScript:arguments:inFrame:inContentWorld:completionHandler:))]
pub unsafe fn callAsyncJavaScript_arguments_inFrame_inContentWorld_completionHandler(
&self,
function_body: &NSString,
arguments: Option<&NSDictionary<NSString, AnyObject>>,
frame: Option<&WKFrameInfo>,
content_world: &WKContentWorld,
completion_handler: Option<&block2::Block<dyn Fn(*mut AnyObject, *mut NSError)>>,
);
// #[cfg(feature = "block2")]
#[unsafe(method(closeAllMediaPresentationsWithCompletionHandler:))]
pub unsafe fn closeAllMediaPresentationsWithCompletionHandler(
&self,
completion_handler: Option<&block2::Block<dyn Fn()>>,
);
#[deprecated]
#[unsafe(method(closeAllMediaPresentations))]
pub unsafe fn closeAllMediaPresentations(&self);
// #[cfg(feature = "block2")]
#[unsafe(method(pauseAllMediaPlaybackWithCompletionHandler:))]
pub unsafe fn pauseAllMediaPlaybackWithCompletionHandler(
&self,
completion_handler: Option<&block2::Block<dyn Fn()>>,
);
// #[cfg(feature = "block2")]
#[deprecated]
#[unsafe(method(pauseAllMediaPlayback:))]
pub unsafe fn pauseAllMediaPlayback(
&self,
completion_handler: Option<&block2::Block<dyn Fn()>>,
);
// #[cfg(feature = "block2")]
#[unsafe(method(setAllMediaPlaybackSuspended:completionHandler:))]
pub unsafe fn setAllMediaPlaybackSuspended_completionHandler(
&self,
suspended: bool,
completion_handler: Option<&block2::Block<dyn Fn()>>,
);
// #[cfg(feature = "block2")]
#[deprecated]
#[unsafe(method(resumeAllMediaPlayback:))]
pub unsafe fn resumeAllMediaPlayback(
&self,
completion_handler: Option<&block2::Block<dyn Fn()>>,
);
// #[cfg(feature = "block2")]
#[deprecated]
#[unsafe(method(suspendAllMediaPlayback:))]
pub unsafe fn suspendAllMediaPlayback(
&self,
completion_handler: Option<&block2::Block<dyn Fn()>>,
);
// #[cfg(feature = "block2")]
#[unsafe(method(requestMediaPlaybackStateWithCompletionHandler:))]
pub unsafe fn requestMediaPlaybackStateWithCompletionHandler(
&self,
completion_handler: &block2::Block<dyn Fn(WKMediaPlaybackState)>,
);
// #[cfg(feature = "block2")]
#[deprecated]
#[unsafe(method(requestMediaPlaybackState:))]
pub unsafe fn requestMediaPlaybackState(
&self,
completion_handler: &block2::Block<dyn Fn(WKMediaPlaybackState)>,
);
#[unsafe(method(cameraCaptureState))]
pub unsafe fn cameraCaptureState(&self) -> WKMediaCaptureState;
#[unsafe(method(microphoneCaptureState))]
pub unsafe fn microphoneCaptureState(&self) -> WKMediaCaptureState;
// #[cfg(feature = "block2")]
#[unsafe(method(setCameraCaptureState:completionHandler:))]
pub unsafe fn setCameraCaptureState_completionHandler(
&self,
state: WKMediaCaptureState,
completion_handler: Option<&block2::Block<dyn Fn()>>,
);
// #[cfg(feature = "block2")]
#[unsafe(method(setMicrophoneCaptureState:completionHandler:))]
pub unsafe fn setMicrophoneCaptureState_completionHandler(
&self,
state: WKMediaCaptureState,
completion_handler: Option<&block2::Block<dyn Fn()>>,
);
#[cfg(target_os = "macos")]
// #[cfg(all(feature = "WKSnapshotConfiguration", feature = "block2"))]
#[unsafe(method(takeSnapshotWithConfiguration:completionHandler:))]
pub unsafe fn takeSnapshotWithConfiguration_completionHandler(
&self,
snapshot_configuration: Option<&WKSnapshotConfiguration>,
completion_handler: &block2::Block<dyn Fn(*mut NSImage, *mut NSError)>,
);
#[cfg(target_os = "macos")]
// #[cfg(all(feature = "WKPDFConfiguration", feature = "block2"))]
#[unsafe(method(createPDFWithConfiguration:completionHandler:))]
pub unsafe fn createPDFWithConfiguration_completionHandler(
&self,
pdf_configuration: Option<&WKPDFConfiguration>,
completion_handler: &block2::Block<dyn Fn(*mut NSData, *mut NSError)>,
);
// #[cfg(feature = "block2")]
#[unsafe(method(createWebArchiveDataWithCompletionHandler:))]
pub unsafe fn createWebArchiveDataWithCompletionHandler(
&self,
completion_handler: &block2::Block<dyn Fn(NonNull<NSData>, NonNull<NSError>)>,
);
#[unsafe(method(allowsBackForwardNavigationGestures))]
pub unsafe fn allowsBackForwardNavigationGestures(&self) -> bool;
#[unsafe(method(setAllowsBackForwardNavigationGestures:))]
pub unsafe fn setAllowsBackForwardNavigationGestures(
&self,
allows_back_forward_navigation_gestures: bool,
);
#[unsafe(method(customUserAgent))]
pub unsafe fn customUserAgent(&self) -> Option<Retained<NSString>>;
#[unsafe(method(setCustomUserAgent:))]
pub unsafe fn setCustomUserAgent(&self, custom_user_agent: Option<&NSString>);
#[unsafe(method(allowsLinkPreview))]
pub unsafe fn allowsLinkPreview(&self) -> bool;
#[unsafe(method(setAllowsLinkPreview:))]
pub unsafe fn setAllowsLinkPreview(&self, allows_link_preview: bool);
#[unsafe(method(allowsMagnification))]
pub unsafe fn allowsMagnification(&self) -> bool;
#[unsafe(method(setAllowsMagnification:))]
pub unsafe fn setAllowsMagnification(&self, allows_magnification: bool);
#[unsafe(method(magnification))]
pub unsafe fn magnification(&self) -> CGFloat;
#[unsafe(method(setMagnification:))]
pub unsafe fn setMagnification(&self, magnification: CGFloat);
#[unsafe(method(setMagnification:centeredAtPoint:))]
pub unsafe fn setMagnification_centeredAtPoint(&self, magnification: CGFloat, point: CGPoint);
#[unsafe(method(pageZoom))]
pub unsafe fn pageZoom(&self) -> CGFloat;
#[unsafe(method(setPageZoom:))]
pub unsafe fn setPageZoom(&self, page_zoom: CGFloat);
#[cfg(target_os = "macos")]
// #[cfg(all(
// feature = "WKFindConfiguration",
// feature = "WKFindResult",
// feature = "block2"
// ))]
#[unsafe(method(findString:withConfiguration:completionHandler:))]
pub unsafe fn findString_withConfiguration_completionHandler(
&self,
string: &NSString,
configuration: Option<&WKFindConfiguration>,
completion_handler: &block2::Block<dyn Fn(NonNull<WKFindResult>)>,
);
#[unsafe(method(handlesURLScheme:))]
pub unsafe fn handlesURLScheme(url_scheme: &NSString, mtm: MainThreadMarker) -> bool;
// #[cfg(all(feature = "WKDownload", feature = "block2"))]
#[unsafe(method(startDownloadUsingRequest:completionHandler:))]
pub unsafe fn startDownloadUsingRequest_completionHandler(
&self,
request: &NSURLRequest,
completion_handler: &block2::Block<dyn Fn(NonNull<WKDownload>)>,
);
// #[cfg(all(feature = "WKDownload", feature = "block2"))]
#[unsafe(method(resumeDownloadFromResumeData:completionHandler:))]
pub unsafe fn resumeDownloadFromResumeData_completionHandler(
&self,
resume_data: &NSData,
completion_handler: &block2::Block<dyn Fn(NonNull<WKDownload>)>,
);
#[unsafe(method(mediaType))]
pub unsafe fn mediaType(&self) -> Option<Retained<NSString>>;
#[unsafe(method(setMediaType:))]
pub unsafe fn setMediaType(&self, media_type: Option<&NSString>);
#[unsafe(method(interactionState))]
pub unsafe fn interactionState(&self) -> Option<Retained<AnyObject>>;
#[unsafe(method(setInteractionState:))]
pub unsafe fn setInteractionState(&self, interaction_state: Option<&AnyObject>);
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(loadSimulatedRequest:response:responseData:))]
pub unsafe fn loadSimulatedRequest_response_responseData(
&self,
request: &NSURLRequest,
response: &NSURLResponse,
data: &NSData,
) -> Retained<WKNavigation>;
// #[cfg(feature = "WKNavigation")]
#[deprecated]
#[unsafe(method(loadSimulatedRequest:withResponse:responseData:))]
pub unsafe fn loadSimulatedRequest_withResponse_responseData(
&self,
request: &NSURLRequest,
response: &NSURLResponse,
data: &NSData,
) -> Retained<WKNavigation>;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(loadFileRequest:allowingReadAccessToURL:))]
pub unsafe fn loadFileRequest_allowingReadAccessToURL(
&self,
request: &NSURLRequest,
read_access_url: &NSURL,
) -> Retained<WKNavigation>;
// #[cfg(feature = "WKNavigation")]
#[unsafe(method(loadSimulatedRequest:responseHTMLString:))]
pub unsafe fn loadSimulatedRequest_responseHTMLString(
&self,
request: &NSURLRequest,
string: &NSString,
) -> Retained<WKNavigation>;
// #[cfg(feature = "WKNavigation")]
#[deprecated]
#[unsafe(method(loadSimulatedRequest:withResponseHTMLString:))]
pub unsafe fn loadSimulatedRequest_withResponseHTMLString(
&self,
request: &NSURLRequest,
string: &NSString,
) -> Retained<WKNavigation>;
#[cfg(target_os = "macos")]
#[unsafe(method(printOperationWithPrintInfo:))]
pub unsafe fn printOperationWithPrintInfo(
&self,
print_info: &NSPrintInfo,
) -> Retained<NSPrintOperation>;
#[cfg(target_os = "macos")]
#[unsafe(method(themeColor))]
pub unsafe fn themeColor(&self) -> Option<Retained<NSColor>>;
#[cfg(target_os = "macos")]
#[unsafe(method(underPageBackgroundColor))]
pub unsafe fn underPageBackgroundColor(&self) -> Retained<NSColor>;
#[cfg(target_os = "macos")]
#[unsafe(method(setUnderPageBackgroundColor:))]
pub unsafe fn setUnderPageBackgroundColor(&self, under_page_background_color: Option<&NSColor>);
#[unsafe(method(fullscreenState))]
pub unsafe fn fullscreenState(&self) -> WKFullscreenState;
#[unsafe(method(minimumViewportInset))]
pub unsafe fn minimumViewportInset(&self) -> NSEdgeInsets;
#[unsafe(method(maximumViewportInset))]
pub unsafe fn maximumViewportInset(&self) -> NSEdgeInsets;
#[unsafe(method(setMinimumViewportInset:maximumViewportInset:))]
pub unsafe fn setMinimumViewportInset_maximumViewportInset(
&self,
minimum_viewport_inset: NSEdgeInsets,
maximum_viewport_inset: NSEdgeInsets,
);
#[unsafe(method(isInspectable))]
pub unsafe fn isInspectable(&self) -> bool;
#[unsafe(method(setInspectable:))]
pub unsafe fn setInspectable(&self, inspectable: bool);
);
}
/// Methods declared on superclass `UIView`
impl WKWebView {
extern_methods!(
#[unsafe(method(initWithFrame:))]
pub unsafe fn initWithFrame(this: Allocated<Self>, frame_rect: NSRect) -> Retained<Self>;
);
}
/// Methods declared on superclass `UIResponder`
impl WKWebView {
extern_methods!(
#[unsafe(method(init))]
pub unsafe fn init(this: Allocated<Self>) -> Retained<Self>;
);
}
/// Methods declared on superclass `NSObject`
impl WKWebView {
extern_methods!(
#[unsafe(method(new))]
pub unsafe fn new(mtm: MainThreadMarker) -> Retained<Self>;
);
}
/// WKIBActions
impl WKWebView {
extern_methods!(
#[unsafe(method(goBack:))]
pub unsafe fn goBack_(&self, sender: Option<&AnyObject>);
#[unsafe(method(goForward:))]
pub unsafe fn goForward_(&self, sender: Option<&AnyObject>);
#[unsafe(method(reload:))]
pub unsafe fn reload_(&self, sender: Option<&AnyObject>);
#[unsafe(method(reloadFromOrigin:))]
pub unsafe fn reloadFromOrigin_(&self, sender: Option<&AnyObject>);
#[unsafe(method(stopLoading:))]
pub unsafe fn stopLoading_(&self, sender: Option<&AnyObject>);
);
}
#[cfg(target_os = "macos")]
unsafe impl NSUserInterfaceValidations for WKWebView {}
/// WKNSTextFinderClient
impl WKWebView {
extern_methods!();
}
#[cfg(target_os = "macos")]
unsafe impl NSTextFinderClient for WKWebView {}
/// WKDeprecated
impl WKWebView {
extern_methods!(
#[deprecated]
#[unsafe(method(certificateChain))]
pub unsafe fn certificateChain(&self) -> Retained<NSArray>;
);
}
+1
View File
@@ -0,0 +1 @@
pub mod WKWebView;
File diff suppressed because it is too large Load Diff
+116
View File
@@ -0,0 +1,116 @@
use objc2::DeclaredClass;
use objc2_foundation::{NSObjectProtocol, NSString};
use objc2_web_kit::{
WKNavigation, WKNavigationAction, WKNavigationActionPolicy, WKNavigationResponse,
WKNavigationResponsePolicy,
};
#[cfg(target_os = "ios")]
use crate::wkwebview::ios::WKWebView::WKWebView;
#[cfg(target_os = "macos")]
use objc2_web_kit::WKWebView;
use crate::PageLoadEvent;
use super::class::wry_navigation_delegate::WryNavigationDelegate;
pub(crate) fn did_commit_navigation(
this: &WryNavigationDelegate,
webview: &WKWebView,
_navigation: &WKNavigation,
) {
unsafe {
// Call on_load_handler
if let Some(on_page_load) = &this.ivars().on_page_load_handler {
on_page_load(PageLoadEvent::Started);
}
// Inject scripts
let mut pending_scripts = this.ivars().pending_scripts.lock().unwrap();
if let Some(scripts) = &*pending_scripts {
for script in scripts {
webview.evaluateJavaScript_completionHandler(&NSString::from_str(script), None);
}
*pending_scripts = None;
}
}
}
pub(crate) fn did_finish_navigation(
this: &WryNavigationDelegate,
_webview: &WKWebView,
_navigation: &WKNavigation,
) {
if let Some(on_page_load) = &this.ivars().on_page_load_handler {
on_page_load(PageLoadEvent::Finished);
}
}
// Navigation handler
pub(crate) fn navigation_policy(
this: &WryNavigationDelegate,
_webview: &WKWebView,
action: &WKNavigationAction,
handler: &block2::Block<dyn Fn(WKNavigationActionPolicy)>,
) {
unsafe {
// <https://developer.apple.com/documentation/webkit/wknavigationaction/shouldperformdownload>
// Available: macOS 11.3+, iOS 14.5+
let can_download = action.respondsToSelector(objc2::sel!(shouldPerformDownload));
let should_download: bool = if can_download {
action.shouldPerformDownload()
} else {
false
};
let request = action.request();
let url = request.URL().unwrap().absoluteString().unwrap();
if should_download {
let has_download_handler = this.ivars().has_download_handler;
if has_download_handler {
(*handler).call((WKNavigationActionPolicy::Download,));
} else {
(*handler).call((WKNavigationActionPolicy::Cancel,));
}
} else {
let function = &this.ivars().navigation_policy_function;
match function(url.to_string()) {
true => (*handler).call((WKNavigationActionPolicy::Allow,)),
false => (*handler).call((WKNavigationActionPolicy::Cancel,)),
};
}
}
}
// Navigation handler
pub(crate) fn navigation_policy_response(
this: &WryNavigationDelegate,
_webview: &WKWebView,
response: &WKNavigationResponse,
handler: &block2::Block<dyn Fn(WKNavigationResponsePolicy)>,
) {
unsafe {
let can_show_mime_type = response.canShowMIMEType();
if !can_show_mime_type {
let has_download_handler = this.ivars().has_download_handler;
if has_download_handler {
(*handler).call((WKNavigationResponsePolicy::Download,));
return;
}
}
(*handler).call((WKNavigationResponsePolicy::Allow,));
}
}
pub(crate) fn web_content_process_did_terminate(
this: &WryNavigationDelegate,
_webview: &WKWebView,
) {
if let Some(on_web_content_process_terminate) =
&this.ivars().on_web_content_process_terminate_handler
{
on_web_content_process_terminate();
}
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use objc2_foundation::NSObject;
use std::ffi::{c_char, CString};
use crate::{proxy::ProxyEndpoint, Error};
#[allow(non_camel_case_types)]
pub type nw_endpoint_t = *mut NSObject;
#[allow(non_camel_case_types)]
pub type nw_protocol_options_t = *mut NSObject;
#[allow(non_camel_case_types)]
pub type nw_proxy_config_t = *mut NSObject;
#[link(name = "Network", kind = "framework")]
extern "C" {
fn nw_endpoint_create_host(host: *const c_char, port: *const c_char) -> nw_endpoint_t;
pub fn nw_proxy_config_create_socksv5(proxy_endpoint: nw_endpoint_t) -> nw_proxy_config_t;
pub fn nw_proxy_config_create_http_connect(
proxy_endpoint: nw_endpoint_t,
proxy_tls_options: nw_protocol_options_t,
) -> nw_proxy_config_t;
}
impl TryFrom<ProxyEndpoint> for nw_endpoint_t {
type Error = Error;
fn try_from(endpoint: ProxyEndpoint) -> Result<Self, Error> {
unsafe {
let endpoint_host =
CString::new(endpoint.host).map_err(|_| Error::ProxyEndpointCreationFailed)?;
let endpoint_port =
CString::new(endpoint.port).map_err(|_| Error::ProxyEndpointCreationFailed)?;
let endpoint = nw_endpoint_create_host(endpoint_host.as_ptr(), endpoint_port.as_ptr());
if endpoint.is_null() {
Err(Error::ProxyEndpointCreationFailed)
} else {
Ok(endpoint)
}
}
}
}
@@ -0,0 +1,123 @@
use objc2_app_kit::{
NSAlternateKeyMask, NSCommandKeyMask, NSControlKeyMask, NSEvent, NSEventType, NSShiftKeyMask,
NSView,
};
use objc2_foundation::NSString;
use super::WryWebView;
pub(crate) fn other_mouse_down(this: &WryWebView, event: &NSEvent) {
unsafe {
if event.r#type() == NSEventType::OtherMouseDown {
let button_number = event.buttonNumber();
match button_number {
// back button
3 => {
let js = create_js_mouse_event(this, event, true, true);
this.evaluateJavaScript_completionHandler(&NSString::from_str(&js), None);
return;
}
// forward button
4 => {
let js = create_js_mouse_event(this, event, true, false);
this.evaluateJavaScript_completionHandler(&NSString::from_str(&js), None);
return;
}
_ => {}
}
}
this.mouseDown(event);
}
}
pub(crate) fn other_mouse_up(this: &WryWebView, event: &NSEvent) {
unsafe {
if event.r#type() == NSEventType::OtherMouseUp {
let button_number = event.buttonNumber();
match button_number {
// back button
3 => {
let js = create_js_mouse_event(this, event, false, true);
this.evaluateJavaScript_completionHandler(&NSString::from_str(&js), None);
return;
}
// forward button
4 => {
let js = create_js_mouse_event(this, event, false, false);
this.evaluateJavaScript_completionHandler(&NSString::from_str(&js), None);
return;
}
_ => {}
}
}
this.mouseUp(event);
}
}
unsafe fn create_js_mouse_event(
view: &NSView,
event: &NSEvent,
down: bool,
back_button: bool,
) -> String {
let event_name = if down { "mousedown" } else { "mouseup" };
// js equivalent https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
let button = if back_button { 3 } else { 4 };
let mods_flags = event.modifierFlags();
let window_point = event.locationInWindow();
let view_point = view.convertPoint_fromView(window_point, None);
let x = view_point.x as u32;
let y = view_point.y as u32;
// js equivalent https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
let buttons = NSEvent::pressedMouseButtons();
format!(
r#"(() => {{
const el = document.elementFromPoint({x},{y});
const ev = new MouseEvent('{event_name}', {{
view: window,
button: {button},
buttons: {buttons},
x: {x},
y: {y},
bubbles: true,
detail: {detail},
cancelBubble: false,
cancelable: true,
clientX: {x},
clientY: {y},
composed: true,
layerX: {x},
layerY: {y},
pageX: {x},
pageY: {y},
screenX: window.screenX + {x},
screenY: window.screenY + {y},
ctrlKey: {ctrl_key},
metaKey: {meta_key},
shiftKey: {shift_key},
altKey: {alt_key},
}});
el.dispatchEvent(ev)
if (!ev.defaultPrevented && "{event_name}" === "mouseup") {{
if (ev.button === 3) {{
window.history.back();
}}
if (ev.button === 4) {{
window.history.forward();
}}
}}
}})()"#,
event_name = event_name,
x = x,
y = y,
detail = event.clickCount(),
ctrl_key = mods_flags.contains(NSControlKeyMask),
alt_key = mods_flags.contains(NSAlternateKeyMask),
shift_key = mods_flags.contains(NSShiftKeyMask),
meta_key = mods_flags.contains(NSCommandKeyMask),
button = button,
buttons = buttons,
)
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use objc2_foundation::NSProcessInfo;
pub fn operating_system_version() -> (isize, isize, isize) {
let process_info = NSProcessInfo::processInfo();
let version = process_info.operatingSystemVersion();
(
version.majorVersion,
version.minorVersion,
version.patchVersion,
)
}