Allow for graceful Exit (#107)

* First pass at graceful exit

* Working macos unified exit

* fix example html to work with IE

* terminate > exit

* unified edge, I think

* update gtk c impl

* tag terminate as depricated

* fix old fn name

* remove OleUninitialize from edge

* remove queue_close

* remove terminate

* fix typo in edge cpp file

* inspect return value of graceful exit example

* actually create the variable inspected

* make example run a little faster

* Adjust cocoa webveiw_exit

* add back missing method on edge

* lots of pinting

* fix printf arg

* better even debug printing

* clean up debugging stuff

* remove some unneeded changes

* slow down example

* fix view title

* add comment about posting the event to the application on close

Closes #105 
Fixes #52
This commit is contained in:
Robert Masen
2019-12-28 07:27:12 -06:00
committed by Richard Hozák
parent d3ba96f03e
commit b664974c1b
10 changed files with 132 additions and 37 deletions

View File

@@ -27,7 +27,7 @@ fn main() -> WVResult {
.warning("Warning", "You didn't choose a file."),
}?,
"exit" => {
webview.terminate();
webview.exit();
}
_ => unimplemented!(),
};

32
examples/graceful_exit.rs Normal file
View File

@@ -0,0 +1,32 @@
extern crate web_view;
use web_view::*;
fn main() {
let res = web_view::builder()
.title("Graceful Exit Example")
.content(Content::Html(include_str!("graceful_exit/index.html")))
.size(800, 600)
.resizable(true)
.debug(true)
.user_data(0)
.invoke_handler(invoke_handler)
.run()
.unwrap();
println!("res: {:?}", res)
}
fn invoke_handler(wv: &mut WebView<usize>, arg: &str) -> WVResult {
if arg == "init" {
wv.eval("init()")?;
} else if arg == "update" {
*wv.user_data_mut() += 1;
let js = format!("setCurrentCount({})", wv.user_data());
wv.eval(&js)?;
} else if arg == "exit" {
println!("exiting!");
wv.exit();
}
Ok(())
}

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<style>
#counter-message.preload {
display: none;
}
#counter-message {
display: block;
}
</style>
</head>
<body>
<h1 id="counter-message" class="preload"></h1>
<script>
var t;
function init() {
var h1 = document.getElementById('counter-message');
h1.setAttribute('class', '');
setCurrentCount(0);
t = setTimeout(tick, 500);
}
function tick() {
window.external.invoke('update');
t = setTimeout(tick, 500);
}
function setCurrentCount(count) {
if (count > 10) {
return window.external.invoke('exit');
}
var h1 = document.getElementById('counter-message');
var rem = 11 - count;
var suffix = rem < 2 ? ' second' : ' seconds';
h1.innerText = 'Exiting in ' + rem + suffix;
document.body.appendChild(h1);
}
external.invoke('init');
</script>
</body>
</html>

View File

@@ -29,7 +29,7 @@ fn main() {
render(webview, *counter)?;
}
"exit" => {
webview.terminate();
webview.exit();
}
_ => unimplemented!(),
};

View File

@@ -29,6 +29,7 @@ mod color;
mod dialog;
mod error;
mod escape;
pub use color::Color;
pub use dialog::DialogBuilder;
pub use error::{CustomError, Error, WVResult};
@@ -347,9 +348,14 @@ impl<'a, T> WebView<'a, T> {
&mut self.user_data_wrapper_mut().inner
}
/// Forces the `WebView` instance to end, without dropping.
#[deprecated(note = "Please use exit instead")]
pub fn terminate(&mut self) {
unsafe { webview_terminate(self.inner) }
self.exit();
}
/// Gracefully exits the webview
pub fn exit(&mut self) {
unsafe { webview_exit(self.inner) }
}
/// Executes the provided string as JavaScript code within the `WebView` instance.

View File

@@ -37,7 +37,6 @@ extern {
pub fn webview_free(this: *mut CWebView);
pub fn webview_new(title: *const c_char, url: *const c_char, width: c_int, height: c_int, resizable: c_int, debug: c_int, external_invoke_cb: Option<ErasedExternalInvokeFn>, userdata: *mut c_void) -> *mut CWebView;
pub fn webview_loop(this: *mut CWebView, blocking: c_int) -> c_int;
pub fn webview_terminate(this: *mut CWebView);
pub fn webview_exit(this: *mut CWebView);
pub fn webview_get_user_data(this: *mut CWebView) -> *mut c_void;
pub fn webview_dispatch(this: *mut CWebView, f: Option<ErasedDispatchFn>, arg: *mut c_void);

View File

@@ -40,7 +40,6 @@ WEBVIEW_API void webview_dialog(webview_t w,
char *result, size_t resultsz);
WEBVIEW_API void webview_dispatch(webview_t w, webview_dispatch_fn fn,
void *arg);
WEBVIEW_API void webview_terminate(webview_t w);
WEBVIEW_API void webview_exit(webview_t w);
WEBVIEW_API void webview_debug(const char *format, ...);
WEBVIEW_API void webview_print_log(const char *s);

View File

@@ -71,6 +71,7 @@ WEBVIEW_API webview_t webview_new(const char* title, const char* url, int width,
#define WKNavigationResponsePolicyAllow 1
#define WKUserScriptInjectionTimeAtDocumentStart 0
#define NSApplicationActivationPolicyRegular 0
#define NSApplicationDefinedEvent 15
static id get_nsstring(const char *c_str) {
return objc_msgSend((id)objc_getClass("NSString"),
@@ -88,9 +89,39 @@ static id create_menu_item(id title, const char *action, const char *key) {
}
static void webview_window_will_close(id self, SEL cmd, id notification) {
struct cocoa_webview *w =
struct cocoa_webview *wv =
(struct cocoa_webview *)objc_getAssociatedObject(self, "webview");
webview_terminate(w);
wv->priv.should_exit = 1;
/***
Since by default for `webview_loop` is set to be blocking
we need to somehow signal the application that our
state has changed. The activity in the `invoke_handler` does
not interact with the `webview_loop` at all. This means that
the `exit` wouldn't be recognized by the application until
another event occurs like mouse movement or a key press.
To enable the invoke_handler to notify the application
correctly we need to send a custom event to the application.
We are going to first create an event with the type
NSApplicationDefined, and zero for all the other properties.
***/
id event = objc_msgSend((id)objc_getClass("NSEvent"),
sel_registerName("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"),
NSApplicationDefinedEvent,
(id)objc_getClass("NSZeroPoint"),
0, 0.0, 0, NULL, 0, 0, 0);
id app = objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication"));
/***
With a custom event crated and a pointer to the sharedApplication
we can now send the event. We need to make sure it get's queued as
early as possible, so we will set the argument atStart to
the NSDate distantPast constructor. This will trigger a noop
event on the application allowing the `webview_loop` to continue
its current iteration.
***/
objc_msgSend(app, sel_registerName("postEvent:atStart:"), event,
objc_msgSend((id)objc_getClass("NSDate"),
sel_registerName("distantPast")));
}
static void webview_external_invoke(id self, SEL cmd, id contentController,
@@ -373,7 +404,7 @@ WEBVIEW_API int webview_init(webview_t w) {
objc_msgSend(wv->priv.webview, sel_registerName("setUIDelegate:"), uiDel);
objc_msgSend(wv->priv.webview, sel_registerName("setNavigationDelegate:"),
navDel);
id nsURL = objc_msgSend((id)objc_getClass("NSURL"),
sel_registerName("URLWithString:"),
get_nsstring(webview_check_url(wv->url)));
@@ -463,10 +494,10 @@ WEBVIEW_API int webview_loop(webview_t w, int blocking) {
sel_registerName("distantFuture"))
: objc_msgSend((id)objc_getClass("NSDate"),
sel_registerName("distantPast")));
id app = objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication"));
id event = objc_msgSend(
objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication")),
app,
sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"),
ULONG_MAX, until,
objc_msgSend((id)objc_getClass("NSString"),
@@ -479,7 +510,6 @@ WEBVIEW_API int webview_loop(webview_t w, int blocking) {
sel_registerName("sharedApplication")),
sel_registerName("sendEvent:"), event);
}
return wv->priv.should_exit;
}
@@ -635,16 +665,10 @@ WEBVIEW_API void webview_dispatch(webview_t w, webview_dispatch_fn fn,
dispatch_async_f(dispatch_get_main_queue(), context, webview_dispatch_cb);
}
WEBVIEW_API void webview_terminate(webview_t w) {
struct cocoa_webview* wv = (struct cocoa_webview*)w;
wv->priv.should_exit = 1;
}
WEBVIEW_API void webview_exit(webview_t w) {
struct cocoa_webview* wv = (struct cocoa_webview*)w;
id app = objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication"));
objc_msgSend(app, sel_registerName("terminate:"), app);
wv->external_invoke_cb = NULL;
objc_msgSend(wv->priv.window, sel_registerName("close"));
}
WEBVIEW_API void webview_print_log(const char *s) { printf("%s\n", s); }
WEBVIEW_API void webview_print_log(const char *s) { printf("%s\n", s); }

View File

@@ -146,8 +146,7 @@ public:
return 0;
}
void terminate() { PostQuitMessage(0); }
void exit() { PostQuitMessage(0); }
void dispatch(dispatch_fn_t f)
{
PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
@@ -242,7 +241,7 @@ LRESULT CALLBACK WebviewWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
DestroyWindow(hwnd);
break;
case WM_DESTROY:
w->terminate();
w->exit();
break;
default:
return DefWindowProc(hwnd, msg, wp, lp);
@@ -312,8 +311,7 @@ public:
L"eval", single_threaded_vector<hstring>({ winrt::to_hstring(js) }));
}
void terminate() {
browser_window::terminate();
void exit() {
m_webview.Close();
}
@@ -584,14 +582,10 @@ WEBVIEW_API void webview_dispatch(webview_t w, webview_dispatch_fn fn,
static_cast<webview::webview*>(w)->dispatch([=]() { fn(w, arg); });
}
WEBVIEW_API void webview_terminate(webview_t w)
{
static_cast<webview::webview*>(w)->terminate();
}
WEBVIEW_API void webview_exit(webview_t w)
{
webview_terminate(w);
webview::webview* wv = static_cast<webview::webview*>(w);
DestroyWindow(wv->m_window);
}
WEBVIEW_API void webview_debug(const char *format, ...)

View File

@@ -83,7 +83,7 @@ static void webview_load_changed_cb(WebKitWebView *webview,
static void webview_destroy_cb(GtkWidget *widget, gpointer arg) {
(void)widget;
webview_terminate((webview_t)arg);
webview_exit((webview_t)arg);
}
static gboolean webview_context_menu_cb(WebKitWebView *webview,
@@ -298,12 +298,10 @@ WEBVIEW_API void webview_dispatch(webview_t w, webview_dispatch_fn fn,
g_async_queue_unlock(wv->priv.queue);
}
WEBVIEW_API void webview_terminate(webview_t w) {
WEBVIEW_API void webview_exit(webview_t w) {
struct gtk_webview *wv = (struct webview *)w;
wv->priv.should_exit = 1;
}
WEBVIEW_API void webview_exit(webview_t w) { (void)w; }
WEBVIEW_API void webview_print_log(const char *s) {
fprintf(stderr, "%s\n", s);
}