Warnings/imports/unused code cleanup, rework some enums that seemed a bit wonky in hindsight, set WKWebView download delegate pieces as a feature flag since it's technically a private API

This commit is contained in:
Ryan McGrath 2020-02-29 15:34:07 -08:00
parent dd88beeb10
commit 5cd3a53681
No known key found for this signature in database
GPG key ID: 811674B62B666830
20 changed files with 155 additions and 103 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
target target
Cargo.lock Cargo.lock
placeholder
placeholder2

View file

@ -16,3 +16,6 @@ lazy_static = "1"
objc = "0.2.7" objc = "0.2.7"
objc_id = "0.1.1" objc_id = "0.1.1"
uuid = { version = "0.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
[features]
enable-webview-downloading = []

View file

@ -2,25 +2,13 @@
//! //!
//! Now, I know what you're thinking: this is dumb. //! Now, I know what you're thinking: this is dumb.
//! //!
//! And sure, maybe. But if you've ever opened Xcode and wondered why the hell //! However, there are rare cases where this is beneficial, and by default we're doing nothing...
//! you have a xib/nib in your macOS project, it's (partly) because *that* handles //! so consider this a placeholder that we might use in the future for certain things.
//! the NSMenu architecture for you... an architecture that, supposedly, is one of the
//! last Carbon pieces still laying around.
//!
//! And I gotta be honest, I ain't about the xib/nib life. SwiftUI will hopefully clear
//! that mess up one day, but in the meantime, we'll do this.
//!
//! Now, what we're *actually* doing here is relatively plain - on certain key events,
//! we want to make sure cut/copy/paste/etc are sent down the event chain. Usually, the
//! xib/nib stuff handles this for you... but this'll mostly do the same.
use std::sync::Once; use std::sync::Once;
use cocoa::base::{id, nil};
use objc::declare::ClassDecl; use objc::declare::ClassDecl;
use objc::runtime::Class; use objc::runtime::Class;
use objc::{class, msg_send, sel, sel_impl};
/// Used for injecting a custom NSApplication. Currently does nothing. /// Used for injecting a custom NSApplication. Currently does nothing.
pub(crate) fn register_app_class() -> *const Class { pub(crate) fn register_app_class() -> *const Class {

View file

@ -4,7 +4,6 @@
use std::sync::Once; use std::sync::Once;
use cocoa::base::{id, nil}; use cocoa::base::{id, nil};
use cocoa::foundation::NSString;
use cocoa::appkit::{NSRunningApplication}; use cocoa::appkit::{NSRunningApplication};
use objc_id::Id; use objc_id::Id;
@ -25,7 +24,7 @@ pub trait AppDelegate {
fn did_finish_launching(&self) {} fn did_finish_launching(&self) {}
fn did_become_active(&self) {} fn did_become_active(&self) {}
fn on_message(&self, message: Self::Message) {} fn on_message(&self, _message: Self::Message) {}
} }
/// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime, /// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime,

View file

@ -1,27 +1,41 @@
//! Hoists some type definitions in a way that I personally find cleaner than what's in the Servo //! Hoists some type definitions in a way that I personally find cleaner than what's in the Servo
//! code. //! code.
#[allow(non_upper_case_globals, non_snake_case)]
pub mod NSEventModifierFlag {
use cocoa::foundation::NSUInteger; use cocoa::foundation::NSUInteger;
/// Indicates the Caps Lock key has been pressed. #[derive(Clone, Copy, Debug)]
pub const CapsLock: NSUInteger = 1 << 16; pub enum EventModifierFlag {
CapsLock,
/// Indicates the Control key has been pressed. Control,
pub const Control: NSUInteger = 1 << 18; Option,
Command,
/// Indicates the Option key has been pressed. DeviceIndependentFlagsMask
pub const Option: NSUInteger = 1 << 19;
/// Indicates the Command key has been pressed.
pub const Command: NSUInteger = 1 << 20;
/// Indicates device-independent modifier flags are in play.
pub const DeviceIndependentFlagsMask: NSUInteger = 0xffff0000;
} }
#[allow(non_upper_case_globals, non_snake_case)] impl From<EventModifierFlag> for NSUInteger {
mod NSEventType { fn from(flag: EventModifierFlag) -> NSUInteger {
pub const KeyDown: usize = 10; match flag {
EventModifierFlag::CapsLock => 1 << 16,
EventModifierFlag::Control => 1 << 18,
EventModifierFlag::Option => 1 << 19,
EventModifierFlag::Command => 1 << 20,
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000
}
}
}
impl From<&EventModifierFlag> for NSUInteger {
fn from(flag: &EventModifierFlag) -> NSUInteger {
match flag {
EventModifierFlag::CapsLock => 1 << 16,
EventModifierFlag::Control => 1 << 18,
EventModifierFlag::Option => 1 << 19,
EventModifierFlag::Command => 1 << 20,
EventModifierFlag::DeviceIndependentFlagsMask => 0xffff0000
}
}
}
pub enum EventType {
KeyDown
} }

View file

@ -4,14 +4,13 @@
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::base::{id, nil, YES, NO, BOOL}; use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSInteger, NSUInteger, NSString}; use cocoa::foundation::{NSInteger, NSString};
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;
use objc_id::ShareId; use objc_id::ShareId;
use crate::file_panel::enums::ModalResponse;
use crate::utils::str_from; use crate::utils::str_from;
#[derive(Debug)] #[derive(Debug)]

View file

@ -4,8 +4,8 @@
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::base::{id, nil, YES, NO, BOOL}; use cocoa::base::{id, YES, NO};
use cocoa::foundation::{NSInteger, NSUInteger}; use cocoa::foundation::NSInteger;
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;

View file

@ -4,18 +4,18 @@
pub trait OpenSaveController { pub trait OpenSaveController {
/// Called when the user has entered a filename (typically, during saving). `confirmed` /// Called when the user has entered a filename (typically, during saving). `confirmed`
/// indicates whether or not they hit the save button. /// indicates whether or not they hit the save button.
fn user_entered_filename(&self, filename: &str, confirmed: bool) {} fn user_entered_filename(&self, _filename: &str, _confirmed: bool) {}
/// Notifies you that the panel selection changed. /// Notifies you that the panel selection changed.
fn panel_selection_did_change(&self) {} fn panel_selection_did_change(&self) {}
/// Notifies you that the user changed directories. /// Notifies you that the user changed directories.
fn did_change_to_directory(&self, url: &str) {} fn did_change_to_directory(&self, _url: &str) {}
/// Notifies you that the Save panel is about to expand or collapse because the user /// Notifies you that the Save panel is about to expand or collapse because the user
/// clicked the disclosure triangle that displays or hides the file browser. /// clicked the disclosure triangle that displays or hides the file browser.
fn will_expand(&self, expanding: bool) {} fn will_expand(&self, _expanding: bool) {}
/// Determine whether the specified URL should be enabled in the Open panel. /// Determine whether the specified URL should be enabled in the Open panel.
fn should_enable_url(&self, url: &str) -> bool { true } fn should_enable_url(&self, _url: &str) -> bool { true }
} }

View file

@ -9,10 +9,15 @@ use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::{Object, Sel}; use objc::runtime::{Object, Sel};
use objc_id::ShareId; use objc_id::ShareId;
use crate::events::NSEventModifierFlag; use crate::events::EventModifierFlag;
/// Internal method (shorthand) for generating `NSMenuItem` holders. /// Internal method (shorthand) for generating `NSMenuItem` holders.
fn make_menu_item(title: &str, key: Option<&str>, action: Option<Sel>, modifier: Option<NSUInteger>) -> MenuItem { fn make_menu_item(
title: &str,
key: Option<&str>,
action: Option<Sel>,
modifiers: Option<&[EventModifierFlag]>
) -> MenuItem {
unsafe { unsafe {
let cls = class!(NSMenuItem); let cls = class!(NSMenuItem);
let alloc: id = msg_send![cls, alloc]; let alloc: id = msg_send![cls, alloc];
@ -29,9 +34,16 @@ fn make_menu_item(title: &str, key: Option<&str>, action: Option<Sel>, modifier:
None => msg_send![alloc, initWithTitle:title action:nil keyEquivalent:key] None => msg_send![alloc, initWithTitle:title action:nil keyEquivalent:key]
}); });
if let Some(modifier) = modifier { if let Some(modifiers) = modifiers {
let _: () = msg_send![&*item, setKeyEquivalentModifierMask:modifier]; let mut key_mask: NSUInteger = 0;
};
for modifier in modifiers {
let y: NSUInteger = modifier.into();
key_mask = key_mask | y;
}
let _: () = msg_send![&*item, setKeyEquivalentModifierMask:key_mask];
}
MenuItem::Action(item) MenuItem::Action(item)
} }
@ -108,7 +120,7 @@ impl MenuItem {
"Hide Others", "Hide Others",
Some("h"), Some("h"),
Some(sel!(hide:)), Some(sel!(hide:)),
Some(NSEventModifierFlag::Command | NSEventModifierFlag::Option) Some(&[EventModifierFlag::Command, EventModifierFlag::Option])
) )
} }
@ -143,7 +155,7 @@ impl MenuItem {
"Enter Full Screen", "Enter Full Screen",
Some("f"), Some("f"),
Some(sel!(toggleFullScreen:)), Some(sel!(toggleFullScreen:)),
Some(NSEventModifierFlag::Command | NSEventModifierFlag::Control) Some(&[EventModifierFlag::Command, EventModifierFlag::Control])
) )
} }

View file

@ -1,6 +1,6 @@
//! Wraps NSMenu and handles instrumenting necessary delegate pieces. //! Wraps NSMenu and handles instrumenting necessary delegate pieces.
use cocoa::base::{id, nil, YES}; use cocoa::base::{id, nil};
use cocoa::foundation::NSString; use cocoa::foundation::NSString;
use objc_id::Id; use objc_id::Id;

View file

@ -4,7 +4,7 @@
use cocoa::base::id; use cocoa::base::id;
use objc_id::Id; use objc_id::Id;
use objc::{class, msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;
use crate::utils::str_from; use crate::utils::str_from;

View file

@ -9,7 +9,7 @@
use std::sync::Once; use std::sync::Once;
use cocoa::base::{id, nil, YES, NO}; use cocoa::base::{id, nil, YES};
use objc::declare::ClassDecl; use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL}; use objc::runtime::{Class, Object, Sel, BOOL};

View file

@ -1,10 +1,10 @@
//! Implements wrappers around `WKNavigationAction` and `WKNavigationActionPolicy`. //! Implements wrappers around `WKNavigationAction` and `WKNavigationActionPolicy`.
use cocoa::base::{id, nil, YES, NO}; use cocoa::base::{id, YES, NO};
use cocoa::foundation::NSInteger; use cocoa::foundation::NSInteger;
use objc::runtime::{Class, Object, Sel, BOOL}; use objc::runtime::BOOL;
use objc::{class, msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};
use crate::networking::URLRequest; use crate::networking::URLRequest;
@ -73,8 +73,8 @@ impl NavigationResponse {
pub fn new(response: id) -> Self { pub fn new(response: id) -> Self {
NavigationResponse { NavigationResponse {
can_show_mime_type: unsafe { can_show_mime_type: unsafe {
let canShow: BOOL = msg_send![response, canShowMIMEType]; let can_show: BOOL = msg_send![response, canShowMIMEType];
if canShow == YES { true } else { false } if can_show == YES { true } else { false }
} }
} }
} }

View file

@ -1,15 +1,13 @@
//! A wrapper for `WKWebViewConfiguration`. It aims to (mostly) cover //! A wrapper for `WKWebViewConfiguration`. It aims to (mostly) cover
//! the important pieces of configuring and updating a WebView configuration. //! the important pieces of configuring and updating a WebView configuration.
use cocoa::base::{id, nil, YES, NO}; use cocoa::base::{id, nil, YES};
use cocoa::foundation::NSString; use cocoa::foundation::NSString;
use objc_id::Id; use objc_id::Id;
use objc::runtime::Object; use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use crate::webview::process_pool::register_process_pool;
/// Whether a script should be injected at the start or end of the document load. /// Whether a script should be injected at the start or end of the document load.
pub enum InjectAt { pub enum InjectAt {
Start = 0, Start = 0,

View file

@ -11,12 +11,12 @@ use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSArray, NSInteger}; use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSArray, NSInteger};
use objc::declare::ClassDecl; use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL}; use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use crate::ViewController; use crate::ViewController;
use crate::view::class::register_view_class; use crate::view::class::register_view_class;
use crate::webview::action::{NavigationAction, NavigationResponse, OpenPanelParameters}; use crate::webview::action::{NavigationAction, NavigationResponse};
use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR}; use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR};
use crate::webview::traits::WebViewController; use crate::webview::traits::WebViewController;
use crate::utils::str_from; use crate::utils::str_from;
@ -136,12 +136,9 @@ extern fn decide_policy_for_response<T: WebViewController + 'static>(this: &Obje
}; };
let response = NavigationResponse::new(response); let response = NavigationResponse::new(response);
webview.policy_for_navigation_response(response, |policy| { webview.policy_for_navigation_response(response, |policy| unsafe {
// This is very sketch and should be heavily checked. :|
unsafe {
let handler = handler as *const Block<(NSInteger,), c_void>; let handler = handler as *const Block<(NSInteger,), c_void>;
(*handler).call((policy.into(),)); (*handler).call((policy.into(),));
}
}); });
} }
@ -153,9 +150,7 @@ extern fn run_open_panel<T: WebViewController + 'static>(this: &Object, _: Sel,
&*webview &*webview
}; };
webview.run_open_panel(params.into(), move |urls| { webview.run_open_panel(params.into(), move |urls| unsafe {
// This is very sketch and should be heavily checked. :|
unsafe {
let handler = handler as *const Block<(id,), c_void>; let handler = handler as *const Block<(id,), c_void>;
match urls { match urls {
@ -171,10 +166,13 @@ extern fn run_open_panel<T: WebViewController + 'static>(this: &Object, _: Sel,
None => { (*handler).call((nil,)); } None => { (*handler).call((nil,)); }
} }
}
}); });
} }
/// Called when a download has been initiated in the WebView, and when the navigation policy
/// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private
/// API.
#[cfg(feature = "enable-webview-downloading")]
extern fn handle_download<T: WebViewController + 'static>(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) { extern fn handle_download<T: WebViewController + 'static>(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) {
let webview = unsafe { let webview = unsafe {
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR);
@ -182,7 +180,7 @@ extern fn handle_download<T: WebViewController + 'static>(this: &Object, _: Sel,
&*webview &*webview
}; };
let handler = handler as *const Block<(BOOL, id), c_void>; let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>;
let filename = str_from(suggested_filename); let filename = str_from(suggested_filename);
webview.run_save_panel(filename, move |can_overwrite, path| unsafe { webview.run_save_panel(filename, move |can_overwrite, path| unsafe {
@ -190,7 +188,6 @@ extern fn handle_download<T: WebViewController + 'static>(this: &Object, _: Sel,
let _: () = msg_send![download, cancel]; let _: () = msg_send![download, cancel];
} }
println!("Saving to Path: {:?}", path);
let path = NSString::alloc(nil).init_str(&path.unwrap()); let path = NSString::alloc(nil).init_str(&path.unwrap());
(*handler).call((match can_overwrite { (*handler).call((match can_overwrite {
@ -236,6 +233,7 @@ pub fn register_controller_class<
// WKDownloadDelegate is a private class on macOS that handles downloading (saving) files. // WKDownloadDelegate is a private class on macOS that handles downloading (saving) files.
// It's absurd that this is still private in 2020. This probably couldn't get into the app // It's absurd that this is still private in 2020. This probably couldn't get into the app
// store, so... screw it, fine for now. // store, so... screw it, fine for now.
#[cfg(feature = "enable-webview-downloading")]
decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download::<T> as extern fn(&Object, _, id, id, usize)); decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download::<T> as extern fn(&Object, _, id, id, usize));
VIEW_CLASS = decl.register(); VIEW_CLASS = decl.register();

View file

@ -7,7 +7,7 @@ pub(crate) static WEBVIEW_CONTROLLER_PTR: &str = "rstWebViewControllerPtr";
pub mod action; pub mod action;
pub(crate) mod controller; pub(crate) mod controller;
pub(crate) mod process_pool; //pub(crate) mod process_pool;
pub mod traits; pub mod traits;
pub use traits::{WebViewController}; pub use traits::{WebViewController};

View file

@ -1,22 +1,52 @@
//! WebViewController is a combination of various delegates and helpers for an underlying
//! `WKWebView`. It allows you to do things such as handle opening a file (for uploads or
//! in-browser-processing), handling navigation actions or JS message callbacks, and so on.
use crate::webview::action::{NavigationAction, NavigationPolicy, NavigationResponse, NavigationResponsePolicy, OpenPanelParameters}; use crate::webview::action::{
NavigationAction, NavigationPolicy,
NavigationResponse, NavigationResponsePolicy,
OpenPanelParameters
};
/// You can implement this on structs to handle callbacks from the underlying `WKWebView`.
pub trait WebViewController { pub trait WebViewController {
fn on_message(&self, name: &str, body: &str) {} /// Called when a JS message is passed by the browser process. For instance, if you added
/// `notify` as a callback, and in the browser you called
/// `webkit.messageHandlers.notify.postMessage({...})` it would wind up here, with `name` being
/// `notify` and `body` being your arguments.
///
/// Note that at the moment, you really should handle bridging JSON/stringification yourself.
fn on_message(&self, _name: &str, _body: &str) {}
fn policy_for_navigation_action<F: Fn(NavigationPolicy)>(&self, action: NavigationAction, handler: F) { /// Given a callback handler, you can decide what policy should be taken for a given browser
/// action. By default, this is `NavigationPolicy::Allow`.
fn policy_for_navigation_action<F: Fn(NavigationPolicy)>(&self, _action: NavigationAction, handler: F) {
handler(NavigationPolicy::Allow); handler(NavigationPolicy::Allow);
} }
fn policy_for_navigation_response<F: Fn(NavigationResponsePolicy)>(&self, response: NavigationResponse, handler: F) { /// Given a callback handler, you can decide what policy should be taken for a given browser
/// response. By default, this is `NavigationResponsePolicy::Allow`.
fn policy_for_navigation_response<F: Fn(NavigationResponsePolicy)>(&self, _response: NavigationResponse, handler: F) {
handler(NavigationResponsePolicy::Allow); handler(NavigationResponsePolicy::Allow);
} }
fn run_open_panel<F: Fn(Option<Vec<String>>) + 'static>(&self, parameters: OpenPanelParameters, handler: F) { /// Given a callback handler and some open panel parameters (e.g, if the user is clicking an
/// upload field that pre-specifies supported options), you should create a `FileSelectPanel`
/// and thread the callbacks accordingly.
fn run_open_panel<F: Fn(Option<Vec<String>>) + 'static>(&self, _parameters: OpenPanelParameters, handler: F) {
handler(None); handler(None);
} }
fn run_save_panel<F: Fn(bool, Option<String>) + 'static>(&self, suggested_filename: &str, handler: F) { /// Given a callback handler and a suggested filename, you should create a `FileSavePanel`
/// and thread the callbacks accordingly.
///
/// Note that this specific callback is only
/// automatically fired if you're linking in to the `enable_webview_downloads` feature, which
/// is not guaranteed to be App Store compatible. If you want a version that can go in the App
/// Store, you'll likely need to write some JS in the webview to handle triggering
/// downloading/saving. This is due to Apple not allowing the private methods on `WKWebView` to
/// be open, which... well, complain to them, not me. :)
fn run_save_panel<F: Fn(bool, Option<String>) + 'static>(&self, _suggested_filename: &str, handler: F) {
handler(false, None); handler(false, None);
} }
} }

View file

@ -12,10 +12,10 @@ use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use cocoa::base::{id, nil, YES, NO}; use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString}; use cocoa::foundation::NSString;
use objc_id::ShareId; use objc_id::ShareId;
use objc::runtime::{Class, Object, Sel, BOOL}; use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use crate::ViewController; use crate::ViewController;

View file

@ -3,11 +3,11 @@
use std::sync::Once; use std::sync::Once;
use cocoa::base::{id, nil, YES, NO}; use cocoa::base::id;
use objc::declare::ClassDecl; use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel}; use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, sel, sel_impl};
use crate::window::WindowController; use crate::window::WindowController;
@ -30,7 +30,7 @@ pub(crate) fn register_window_controller_class<T: WindowController + 'static>()
static INIT: Once = Once::new(); static INIT: Once = Once::new();
INIT.call_once(|| unsafe { INIT.call_once(|| unsafe {
let superclass = Class::get("NSWindowController").unwrap(); let superclass = class!(NSWindowController);
let mut decl = ClassDecl::new("RSTWindowController", superclass).unwrap(); let mut decl = ClassDecl::new("RSTWindowController", superclass).unwrap();
decl.add_ivar::<usize>(WINDOW_CONTROLLER_PTR); decl.add_ivar::<usize>(WINDOW_CONTROLLER_PTR);

View file

@ -26,9 +26,18 @@ pub struct WindowInner {
pub toolbar: Option<Toolbar> pub toolbar: Option<Toolbar>
} }
pub mod WindowTitleVisibility { pub enum WindowTitleVisibility {
pub const Visible: usize = 0; Visible,
pub const Hidden: usize = 1; Hidden
}
impl From<WindowTitleVisibility> for usize {
fn from(visibility: WindowTitleVisibility) -> usize {
match visibility {
WindowTitleVisibility::Visible => 0,
WindowTitleVisibility::Hidden => 1
}
}
} }
impl WindowInner { impl WindowInner {