feat: 大屏桌面壳(Tauri 2)
This commit is contained in:
@@ -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
@@ -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(¤t_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] }
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>;
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod WKWebView;
|
||||
+1478
File diff suppressed because it is too large
Load Diff
+116
@@ -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
@@ -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
@@ -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,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user