From aac249c4f0a3e0f21aae81e0a26c362b67336af2 Mon Sep 17 00:00:00 2001 From: Qiantan Hong Date: Fri, 14 Oct 2022 14:08:40 -0700 Subject: [PATCH] Implment some user content APIs for WebKit Xwidgets Implement WebKit user scripts and script message handlers. * src/xwidget.h (store_xwidget_script_message_event): store script message event into event queue * src/xwidget.c (store_xwidget_script_message_event, make-xwidget, webkit_script_message_cb, xwidget-webkit-add-user-script, xwidget-webkit-remove-all-user-scripts, xwidget-webkit-register-script-message, xwidget-webkit-unregister-script-message): Implement user script and script message handler primitives. * src/nsxwidget.c (nsxwidget_webkit_add_user_script, nsxwidget_webkit_remove_all_user_scripts, nsxwidget_webkit_register_script_message, nsxwidget_webkit_unregister_script_message, initWithFrame, initialize, userContentController): NS implementation. Changed naming of a previous used script message handler to avoid namespace pollution. * src/nsxwidget.h (nsxwidget_webkit_add_user_script, nsxwidget_webkit_remove_all_user_scripts, nsxwidget_webkit_register_script_message, nsxwidget_webkit_unregister_script_message): NS implementation * lisp/xwidget.el (xwidget-webkit-callback, xwidget-webkit-push-script-message-handler, xwidget-webkit-pop-script-message-handler): let lisp recognize and dispatch script message events Acked-by: Qiantan Hong --- lisp/xwidget.el | 32 +++++++++ src/nsxwidget.h | 5 ++ src/nsxwidget.m | 80 +++++++++++++++++++-- src/xwidget.c | 181 ++++++++++++++++++++++++++++++++++++++++++++++++ src/xwidget.h | 4 ++ 5 files changed, 295 insertions(+), 7 deletions(-) diff --git a/lisp/xwidget.el b/lisp/xwidget.el index 41a1190c64..79c9cc2afd 100644 --- a/lisp/xwidget.el +++ b/lisp/xwidget.el @@ -485,8 +485,40 @@ xwidget-webkit-callback (let ((proc (nth 3 last-input-event)) (arg (nth 4 last-input-event))) (funcall proc arg))) + ((eq xwidget-event-type 'script-message) + (let ((name (nth 3 last-input-event)) + (value (nth 4 last-input-event))) + (let ((handler-pair (assq name (xwidget-get xwidget 'script-message-handlers)))) + (if handler-pair + (funcall (cdr handler-pair) xwidget value) + (xwidget-log "unhandled script message:%s" name))))) (t (xwidget-log "unhandled event:%s" xwidget-event-type))))) +(defun xwidget-webkit-push-script-message-handler (xwidget name handler) + "Associate HANDLER with script messages under symbol NAME for Webkit XWIDGET. + +HANDLER will be called with two arguments: the Webkit XWIDGET and +the javascript object passed from the script message converted to +a Lisp object." + (xwidget-webkit-register-script-message (xwidget-webkit-current-session) name) + (xwidget-put xwidget 'script-message-handlers + (cons (cons name handler) (xwidget-get xwidget 'script-message-handlers))) + name) + +(defun xwidget-webkit-pop-script-message-handler (xwidget name) + "Remove a handler associated with symbol NAME for Webkit XWIDGET. + +Returns the removed handler function, or NIL if such handler is +not found." + (let* ((old-alist (xwidget-get xwidget 'script-message-handlers)) + (handler-pair (assq name old-alist))) + (when handler-pair + (let ((new-alist (delq handler-pair old-alist))) + (xwidget-put xwidget 'script-message-handlers new-alist) + (unless (assq name new-alist) + (xwidget-webkit-unregister-script-message (xwidget-webkit-current-session) name))) + (cdr handler-pair)))) + (defvar bookmark-make-record-function) (when (memq window-system '(mac ns)) (defcustom xwidget-webkit-enable-plugins nil diff --git a/src/nsxwidget.h b/src/nsxwidget.h index 666509744a..cd1dc1bc2c 100644 --- a/src/nsxwidget.h +++ b/src/nsxwidget.h @@ -40,6 +40,11 @@ #define NSXWIDGET_H_INCLUDED void nsxwidget_webkit_execute_script (struct xwidget *xw, const char *script, Lisp_Object fun); +void nsxwidget_webkit_add_user_script (struct xwidget *xw, const char *script, + int injection_time_start, int main_frame_only); +void nsxwidget_webkit_remove_all_user_scripts (struct xwidget *xw); +Lisp_Object nsxwidget_webkit_register_script_message (struct xwidget *xw, const char *name); +void nsxwidget_webkit_unregister_script_message (struct xwidget *xw, const char *name); /* Functions for xwidget model. */ #ifdef __OBJC__ diff --git a/src/nsxwidget.m b/src/nsxwidget.m index be0eba0bcb..e09759f754 100644 --- a/src/nsxwidget.m +++ b/src/nsxwidget.m @@ -88,7 +88,7 @@ - (id)initWithFrame:(CGRect)frame @"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)" @" AppleWebKit/603.3.8 (KHTML, like Gecko)" @" Version/11.0.1 Safari/603.3.8"; - [scriptor addScriptMessageHandler:self name:@"keyDown"]; + [scriptor addScriptMessageHandler:self name:@"__xwidget_internal_keyDown"]; [scriptor addUserScript:[[WKUserScript alloc] initWithSource:xwScript injectionTime: @@ -275,23 +275,34 @@ + (void)initialize @"}" @"function xwKeyDown(event) {" @" if (event.ctrlKey && event.key == 'g') {" - @" window.webkit.messageHandlers.keyDown.postMessage('C-g');" + @" window.webkit.messageHandlers.__xwidget_internal_keyDown.postMessage('C-g');" @" }" @"}" @"document.addEventListener('keydown', xwKeyDown);" ; } +static Lisp_Object js_to_lisp (id value); + /* Confirming to WKScriptMessageHandler, listens concerning keyDown in webkit. Currently 'C-g'. */ - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { - if ([message.body isEqualToString:@"C-g"]) + if ([message.name isEqualToString:@"__xwidget_internal_keyDown"]) { - /* Just give up focus, no relay "C-g" to emacs, another "C-g" - follows will be handled by emacs. */ - [self.window makeFirstResponder:self.xw->xv->emacswindow]; + if ([message.body isEqualToString:@"C-g"]) + { + /* Just give up focus, no relay "C-g" to emacs, another "C-g" + follows will be handled by emacs. */ + [self.window makeFirstResponder:self.xw->xv->emacswindow]; + } + } + else + { + store_xwidget_script_message_event (self.xw, + message.name.UTF8String, + js_to_lisp (message.body)); } } @@ -437,6 +448,61 @@ - (void)userContentController:(WKUserContentController *)userContentController }]; } +void +nsxwidget_webkit_add_user_script (struct xwidget *xw, const char *script, + int injection_time_start, int main_frame_only) +{ + XwWebView *xwWebView = (XwWebView *) xw->xwWidget; + WKUserContentController *scriptor = xwWebView.configuration.userContentController; + + NSString *javascriptString = [NSString stringWithUTF8String:script]; + WKUserScriptInjectionTime injectionTime = injection_time_start? + WKUserScriptInjectionTimeAtDocumentStart : WKUserScriptInjectionTimeAtDocumentEnd; + WKUserScript *userScript = [[WKUserScript alloc] + initWithSource: javascriptString + injectionTime: injectionTime + forMainFrameOnly: main_frame_only]; + [scriptor addUserScript: userScript]; +} + +void +nsxwidget_webkit_remove_all_user_scripts (struct xwidget *xw) +{ + XwWebView *xwWebView = (XwWebView *) xw->xwWidget; + WKUserContentController *scriptor = xwWebView.configuration.userContentController; + + [scriptor removeAllUserScripts]; +} + +Lisp_Object +nsxwidget_webkit_register_script_message (struct xwidget *xw, const char *name) +{ + XwWebView *xwWebView = (XwWebView *) xw->xwWidget; + WKUserContentController *scriptor = xwWebView.configuration.userContentController; + + NSString *messageName = [NSString stringWithUTF8String:name]; + + @try + { + [scriptor addScriptMessageHandler:xwWebView name:messageName]; + } + @catch (NSException *e) + { + return Qnil; + } + return Qt; +} + +void +nsxwidget_webkit_unregister_script_message (struct xwidget *xw, const char *name) +{ + XwWebView *xwWebView = (XwWebView *) xw->xwWidget; + WKUserContentController *scriptor = xwWebView.configuration.userContentController; + + NSString *messageName = [NSString stringWithUTF8String:name]; + [scriptor removeScriptMessageHandlerForName:messageName]; +} + /* Window containing an xwidget. */ @implementation XwWindow @@ -469,7 +535,7 @@ - (BOOL)isFlipped { return YES; } WKUserContentController *scriptor = ((XwWebView *) xw->xwWidget).configuration.userContentController; [scriptor removeAllUserScripts]; - [scriptor removeScriptMessageHandlerForName:@"keyDown"]; + [scriptor removeScriptMessageHandlerForName:@"__xwidget_internal_keyDown"]; [scriptor release]; if (xw->xv) xw->xv->model = Qnil; /* Make sure related view stale. */ diff --git a/src/xwidget.c b/src/xwidget.c index 8bdfab02fd..e4250065b9 100644 --- a/src/xwidget.c +++ b/src/xwidget.c @@ -126,6 +126,16 @@ webkit_decide_policy_cb (WebKitWebView *, }; static void find_widget (GtkWidget *t, struct widget_search_data *); + +struct webkit_script_message_cb_data +{ + struct xwidget *xw; + char name[0]; +}; +static void webkit_script_message_cb (WebKitUserContentManager *, + WebKitJavascriptResult *, + gpointer); + #endif #ifdef HAVE_PGTK @@ -380,6 +390,8 @@ DEFUN ("make-xwidget", settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (xw->widget_osr)); g_object_set (G_OBJECT (settings), "enable-developer-extras", TRUE, NULL); } + WebKitUserContentManager *scriptor = webkit_user_content_manager_new (); + xw->widget_osr = webkit_web_view_new_with_user_content_manager (scriptor); gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width, xw->height); @@ -2308,6 +2320,21 @@ store_xwidget_js_callback_event (struct xwidget *xw, kbd_buffer_store_event (&event); } +void +store_xwidget_script_message_event (struct xwidget *xw, + const char *name, + Lisp_Object body) +{ + struct input_event event; + Lisp_Object xwl; + XSETXWIDGET (xwl, xw); + EVENT_INIT (event); + event.kind = XWIDGET_EVENT; + event.frame_or_window = Qnil; + event.arg = list4 (intern ("script-message"), xwl, intern (name), body); + kbd_buffer_store_event (&event); +} + #ifdef USE_GTK static void @@ -2622,6 +2649,17 @@ webkit_decide_policy_cb (WebKitWebView *webView, } } +static void webkit_script_message_cb (WebKitUserContentManager *scriptor, + WebKitJavascriptResult *js_result, + gpointer data) +{ + JSCValue *value = webkit_javascript_result_get_js_value (js_result); + struct webkit_script_message_cb_data *arg = data; + + Lisp_Object lisp_value = webkit_js_to_lisp (value); + store_xwidget_script_message_event (arg->xw, arg->name, lisp_value); +} + static gboolean webkit_script_dialog_cb (WebKitWebView *webview, WebKitScriptDialog *script_dialog, @@ -2717,6 +2755,7 @@ xwidget_init_view (struct xwidget *xww, XSETWINDOW (xv->w, s->w); XSETXWIDGET (xv->model, xww); + #ifdef HAVE_X_WINDOWS xv->dpy = FRAME_X_DISPLAY (s->f); @@ -3198,6 +3237,140 @@ DEFUN ("xwidget-webkit-execute-script", return Qnil; } +DEFUN ("xwidget-webkit-add-user-script", + Fxwidget_webkit_add_user_script, Sxwidget_webkit_add_user_script, + 4, 4, 0, + doc: /* Add user SCRIPT to the Webkit XWIDGET. +INJECTION-TIME is a symbol which can take one of the following values: + +- start: SCRIPT is injected when document starts loading +- end: SCRIPT is injected when document finishes loading + +If MAIN_FRAME_ONLY is nil, SCRIPT is injected to all frames. +Otherwise, SCRIPT is only injected to top frames.*/) + (Lisp_Object xwidget, Lisp_Object script, + Lisp_Object injection_time, Lisp_Object main_frame_only) +{ + WEBKIT_FN_INIT (); + CHECK_STRING (script); + CHECK_SYMBOL (injection_time); + + script = ENCODE_SYSTEM(script); + + int injection_time_start, mfo; + mfo = !NILP (main_frame_only); + if (EQ (injection_time, Qstart)) + injection_time_start = 1; + else if (EQ (injection_time, Qend)) + injection_time_start = 0; + else + error ("Unknown Xwidget Webkit user script injection time: %s", + SDATA (SYMBOL_NAME (injection_time))); + +#ifdef USE_GTK + WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr); + WebKitUserContentManager *scriptor = webkit_web_view_get_user_content_manager (wkwv); + + int webkit_injected_frames = mfo? + WEBKIT_USER_CONTENT_INJECT_TOP_FRAME : WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES; + int webkit_injection_time = injection_time_start? + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START : WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END; + WebKitUserScript *userScript = webkit_user_script_new (SSDATA (script), + webkit_injected_frames, + webkit_injection_time, + NULL, NULL); + webkit_user_content_manager_add_script (scriptor, userScript); + webkit_user_script_unref (userScript); +#elif defined NS_IMPL_COCOA + nsxwidget_webkit_add_user_script (xw, SSDATA (script), injection_time_start, mfo); +#endif + return Qnil; +} + +DEFUN ("xwidget-webkit-remove-all-user-scripts", + Fxwidget_webkit_remove_all_user_scripts, Sxwidget_webkit_remove_all_user_scripts, + 1, 1, 0, + doc: /* Remove all user scripts from XWIDGET. */) + (Lisp_Object xwidget) +{ + WEBKIT_FN_INIT (); + +#ifdef USE_GTK + WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr); + WebKitUserContentManager *scriptor = webkit_web_view_get_user_content_manager (wkwv); + + webkit_user_content_manager_remove_all_scripts (scriptor); +#elif defined NS_IMPL_COCOA + nsxwidget_webkit_remove_all_user_scripts(xw); +#endif + return Qnil; +} + +DEFUN ("xwidget-webkit-register-script-message", + Fxwidget_webkit_register_script_message, Sxwidget_webkit_register_script_message, + 2, 2, 0, + doc: /* Register script message with symbol NAME in Webkit XWIDGET. +Returns T if the operation is successful, NIL otherwise. +The cause of failure is usually that NAME has already been registered for XWIDGET. */) + (Lisp_Object xwidget, Lisp_Object name) +{ + WEBKIT_FN_INIT (); + CHECK_SYMBOL (name); + const char *sname = SSDATA( SYMBOL_NAME (name)); + +#ifdef USE_GTK + WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr); + WebKitUserContentManager *scriptor = webkit_web_view_get_user_content_manager (wkwv); + + gchar *signal_name = g_strconcat ("script-message-received::", sname, NULL); + size_t name_length = strlen (sname) + 1; + struct webkit_script_message_cb_data *arg = malloc (sizeof *arg + name_length); + arg->xw = xw; + g_strlcpy (arg->name, sname, name_length); + g_signal_connect_data(scriptor, signal_name, G_CALLBACK (webkit_script_message_cb), + arg, (GClosureNotify)free, 0); + g_free (signal_name); + if (webkit_user_content_manager_register_script_message_handler (scriptor, sname)) + { + return Qt; + } + else + { + g_signal_handlers_disconnect_matched (scriptor, + G_SIGNAL_MATCH_DATA, + 0, 0, 0, 0, arg); + return Qnil; + } +#elif defined NS_IMPL_COCOA + return nsxwidget_webkit_register_script_message(xw, sname); +#endif +} + +DEFUN ("xwidget-webkit-unregister-script-message", + Fxwidget_webkit_unregister_script_message, Sxwidget_webkit_unregister_script_message, + 2, 2, 0, + doc: /* Unregister script message with symbol NAME in Webkit XWIDGET. */) + (Lisp_Object xwidget, Lisp_Object name) +{ + WEBKIT_FN_INIT (); + CHECK_SYMBOL (name); + const char *sname = SSDATA( SYMBOL_NAME (name)); + +#ifdef USE_GTK + WebKitWebView *wkwv = WEBKIT_WEB_VIEW (xw->widget_osr); + WebKitUserContentManager *scriptor = webkit_web_view_get_user_content_manager (wkwv); + + webkit_user_content_manager_unregister_script_message_handler (scriptor, sname); + g_signal_handlers_disconnect_matched (scriptor, + G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DETAIL, + 0, g_quark_from_string (sname), 0, + G_CALLBACK (webkit_script_message_cb), 0); +#elif defined NS_IMPL_COCOA + nsxwidget_webkit_unregister_script_message(xw, sname); +#endif + return Qnil; +} + DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0, doc: /* Resize XWIDGET to NEW_WIDTH, NEW_HEIGHT. */ ) (Lisp_Object xwidget, Lisp_Object new_width, Lisp_Object new_height) @@ -3919,6 +4092,14 @@ syms_of_xwidget (void) defsubr (&Sxwidget_webkit_execute_script); DEFSYM (Qwebkit, "webkit"); + defsubr (&Sxwidget_webkit_add_user_script); + DEFSYM (Qstart, "start"); + DEFSYM (Qend, "end"); + defsubr (&Sxwidget_webkit_remove_all_user_scripts); + + defsubr (&Sxwidget_webkit_register_script_message); + defsubr (&Sxwidget_webkit_unregister_script_message); + defsubr (&Sxwidget_size_request); defsubr (&Sdelete_xwidget_view); diff --git a/src/xwidget.h b/src/xwidget.h index 502beb6765..e835f700bc 100644 --- a/src/xwidget.h +++ b/src/xwidget.h @@ -203,6 +203,10 @@ #define XG_XWIDGET_VIEW "emacs_xwidget_view" Lisp_Object proc, Lisp_Object argument); +void store_xwidget_script_message_event (struct xwidget *xw, + const char *name, + Lisp_Object value); + extern struct xwidget *xwidget_from_id (uint32_t id); #ifdef HAVE_X_WINDOWS -- 2.26.2