/* webapp.c – GTK3 + WebKit2GTK-4.1 + WebSocket demo
*
* Build:
* gcc webapp.c $(pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.1 libsoup-2.4 jansson) -o webapp
*
* Runtime tip:
* WebKit sandbox disabled? sudo sysctl -w kernel.unprivileged_userns_clone=1
*/
# include <gtk/gtk.h>
# include <webkit2/webkit2.h>
# include <jsc/jsc.h>
# include <libsoup/soup.h>
# include <jansson.h>
/* Forward declaration for on_websocket */
static void on_websocket ( SoupServer * server ,
SoupServerMessage * msg ,
const char * path ,
SoupWebsocketConnection * conn ,
gpointer user_data ) ;
// Forward declaration for the explicit closed handler
static void on_websocket_closed ( SoupWebsocketConnection * conn , gpointer user_data ) ;
// Structure to hold context for JavaScript execution callbacks
typedef struct {
SoupWebsocketConnection * conn ; // The WebSocket connection to send results back to
} JsExecutionCtx ;
// Callback function for when innerHTML is ready after a JavaScript evaluation (from click handler)
static void
on_inner_html_ready ( GObject * web_view ,
GAsyncResult * res ,
gpointer user_data ) /* user_data = char* selector */
{
GError * error = NULL ;
WebKitJavascriptResult * js_result = webkit_web_view_evaluate_javascript_finish (
WEBKIT_WEB_VIEW ( web_view ) , res , & error ) ;
// CORRECTED: js_result is already declared and assigned correctly above.
// JSCValue *val = NULL; // This line was problematic as it was initializing js_result with a JSCValue type
JSCValue * val ; // Declare val here, assign it below.
if ( ! js_result | | error ) {
g_warning ( " JavaScript eval failed for selector [%s]: %s " ,
( char * ) user_data , error ? error - > message : " unknown " ) ;
g_clear_error ( & error ) ;
g_free ( user_data ) ;
if ( js_result ) g_object_unref ( js_result ) ; // Unref here if we're exiting early
return ;
}
val = webkit_javascript_result_get_js_value ( js_result ) ;
// In this specific callback (on_inner_html_ready), we are expecting a string or null/undefined
// It's not part of the WebSocket JSON fallback, but still needs a valid JSCValue
if ( ! JSC_IS_VALUE ( val ) ) {
g_warning ( " Invalid JSCValue for selector [%s] " , ( char * ) user_data ) ;
g_free ( user_data ) ;
g_object_unref ( js_result ) ; // Unref here as well if val is invalid
return ;
}
gchar * html = jsc_value_is_null ( val ) | | jsc_value_is_undefined ( val )
? NULL : jsc_value_to_string ( val ) ;
g_print ( " \n [C] innerHTML for selector [%s]: \n %s \n \n " ,
( char * ) user_data , html ? html : " (null or undefined) " ) ;
g_free ( html ) ;
g_free ( user_data ) ;
// IMPORTANT: Unref js_result AFTER all processing of 'val'
g_object_unref ( js_result ) ;
}
// Callback function for JavaScript 'click' messages from the WebView
static void
on_js_click ( WebKitUserContentManager * mgr ,
WebKitJavascriptResult * res ,
gpointer user_data ) /* user_data = WebKitWebView* */
{
JSCValue * obj = webkit_javascript_result_get_js_value ( res ) ;
if ( ! jsc_value_is_object ( obj ) ) {
g_warning ( " Invalid JS object received from click handler. " ) ;
return ;
}
JSCValue * sel_val = jsc_value_object_get_property ( obj , " selector " ) ;
JSCValue * x_val = jsc_value_object_get_property ( obj , " x " ) ;
JSCValue * y_val = jsc_value_object_get_property ( obj , " y " ) ;
if ( ! jsc_value_is_string ( sel_val ) | | ! jsc_value_is_number ( x_val ) | | ! jsc_value_is_number ( y_val ) ) {
g_warning ( " Invalid JS property types received from click handler. " ) ;
if ( sel_val ) g_object_unref ( sel_val ) ;
if ( x_val ) g_object_unref ( x_val ) ;
if ( y_val ) g_object_unref ( y_val ) ;
return ;
}
gchar * selector = jsc_value_to_string ( sel_val ) ;
int x = ( int ) jsc_value_to_int32 ( x_val ) ;
int y = ( int ) jsc_value_to_int32 ( y_val ) ;
g_print ( " Clicked selector: %s at (%d,%d) \n " , selector , x , y ) ;
gchar * esc = g_strescape ( selector , NULL ) ;
gchar * script = g_strdup_printf (
" (function(){ "
" try { "
" var e = document.querySelector('%s'); "
" return e && e.innerHTML ? e.innerHTML : null; "
" } catch (err) { "
" return null; "
" } "
" })() " , esc ) ;
g_free ( esc ) ;
WebKitWebView * view = WEBKIT_WEB_VIEW ( user_data ) ;
webkit_web_view_evaluate_javascript ( view ,
script ,
- 1 ,
NULL ,
NULL ,
NULL ,
on_inner_html_ready ,
g_strdup ( selector ) ) ;
g_free ( script ) ;
g_free ( selector ) ;
g_object_unref ( sel_val ) ;
g_object_unref ( x_val ) ;
g_object_unref ( y_val ) ;
}
// Callback function for when JavaScript execution initiated from WebSocket completes
static void
on_js_execution_complete ( GObject * web_view ,
GAsyncResult * res ,
gpointer user_data )
{
JsExecutionCtx * ctx = ( JsExecutionCtx * ) user_data ;
SoupWebsocketConnection * conn = ctx - > conn ;
GError * error = NULL ;
json_t * result_json_root = json_object ( ) ; // The root object for our JSON response
WebKitJavascriptResult * js_result = webkit_web_view_evaluate_javascript_finish (
WEBKIT_WEB_VIEW ( web_view ) , res , & error ) ;
JSCValue * val ; // Declare val here, assign it below.
if ( error ) {
g_warning ( " JavaScript execution failed: %s " , error - > message ) ;
json_object_set_new ( result_json_root , " status " , json_string ( " error " ) ) ;
json_object_set_new ( result_json_root , " message " , json_string ( error - > message ) ) ;
g_clear_error ( & error ) ;
} else if ( ! js_result ) {
g_warning ( " JavaScript execution returned no result object. " ) ;
json_object_set_new ( result_json_root , " status " , json_string ( " error " ) ) ;
json_object_set_new ( result_json_root , " message " , json_string ( " JavaScript execution returned no result. " ) ) ;
} else {
val = webkit_javascript_result_get_js_value ( js_result ) ;
// We expect the result to always be a JSON string due to the wrapper in on_websocket_message
if ( JSC_IS_VALUE ( val ) & & jsc_value_is_string ( val ) ) {
gchar * json_string_from_js = jsc_value_to_string ( val ) ;
g_print ( " DEBUG: Received JSON string from JS: %s \n " , json_string_from_js ? json_string_from_js : " (null) " ) ;
if ( json_string_from_js ) {
json_t * parsed_json_result = json_loads ( json_string_from_js , 0 , NULL ) ;
if ( parsed_json_result ) {
json_object_set_new ( result_json_root , " status " , json_string ( " success " ) ) ;
// Check if it's a function that was returned
if ( json_is_object ( parsed_json_result ) ) {
json_t * type_field = json_object_get ( parsed_json_result , " type " ) ;
if ( type_field & & json_is_string ( type_field ) & &
strcmp ( json_string_value ( type_field ) , " function " ) = = 0 ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " function " ) ) ;
} else if ( json_object_get ( parsed_json_result , " _error " ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " javascript_error " ) ) ;
} else {
json_object_set_new ( result_json_root , " result_type " , json_string ( " object " ) ) ;
}
} else if ( json_is_array ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " array " ) ) ;
} else if ( json_is_string ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " string " ) ) ;
} else if ( json_is_integer ( parsed_json_result ) | | json_is_real ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " number " ) ) ;
} else if ( json_is_boolean ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " boolean " ) ) ;
} else if ( json_is_null ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " null " ) ) ;
} else {
json_object_set_new ( result_json_root , " result_type " , json_string ( " unknown_json_type " ) ) ;
}
json_object_set_new ( result_json_root , " result " , parsed_json_result ) ;
}
if ( parsed_json_result ) {
json_object_set_new ( result_json_root , " status " , json_string ( " success " ) ) ;
// Check if it's our internal error object from JS (e.g., if a JS error occurred)
if ( json_is_object ( parsed_json_result ) & & json_object_get ( parsed_json_result , " _error " ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " javascript_error " ) ) ;
} else if ( json_is_object ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " object " ) ) ;
} else if ( json_is_array ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " array " ) ) ;
} else if ( json_is_string ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " string " ) ) ;
} else if ( json_is_integer ( parsed_json_result ) | | json_is_real ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " number " ) ) ;
} else if ( json_is_boolean ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " boolean " ) ) ;
} else if ( json_is_null ( parsed_json_result ) ) {
json_object_set_new ( result_json_root , " result_type " , json_string ( " null " ) ) ;
} else {
json_object_set_new ( result_json_root , " result_type " , json_string ( " unknown_json_type " ) ) ;
}
json_object_set_new ( result_json_root , " result " , parsed_json_result ) ; // Jansson takes ownership
} else {
// This means JSON.stringify on JS side gave something invalid, or our handling is off
g_warning ( " Failed to parse JSON string from JavaScript: %s " , json_string_from_js ) ;
// CORRECTED: Use g_strdup_printf and json_string
gchar * msg = g_strdup_printf ( " Failed to parse JavaScript result as JSON: %s " , json_string_from_js ) ;
json_object_set_new ( result_json_root , " status " , json_string ( " error " ) ) ;
json_object_set_new ( result_json_root , " message " , json_string ( msg ) ) ;
g_free ( msg ) ;
}
g_free ( json_string_from_js ) ;
} else {
g_warning ( " jsc_value_to_string returned NULL for expected JSON string. " ) ;
json_object_set_new ( result_json_root , " status " , json_string ( " error " ) ) ;
json_object_set_new ( result_json_root , " message " , json_string ( " JavaScript result was not a string or could not be converted to string. " ) ) ;
}
} else {
g_warning ( " JSCValue was not a valid string as expected. Type assertion failed or not string. " ) ;
json_object_set_new ( result_json_root , " status " , json_string ( " error " ) ) ;
gchar * msg = g_strdup_printf ( " Expected JavaScript result to be a JSON string, but it was not a valid JSCValue or not a string type. JSC_IS_VALUE: %d, jsc_value_is_string: %d " ,
( val ! = NULL & & JSC_IS_VALUE ( val ) ) , ( val ! = NULL & & jsc_value_is_string ( val ) ) ) ;
json_object_set_new ( result_json_root , " message " , json_string ( msg ) ) ;
g_free ( msg ) ;
}
// IMPORTANT: Unref js_result AFTER all processing
g_object_unref ( js_result ) ;
}
gchar * json_response_str = json_dumps ( result_json_root , JSON_INDENT ( 2 ) ) ;
if ( json_response_str ) {
soup_websocket_connection_send_text ( conn , json_response_str ) ;
g_free ( json_response_str ) ;
} else {
g_warning ( " Failed to serialize JSON response to string. " ) ;
soup_websocket_connection_send_text ( conn , " { \" status \" : \" error \" , \" message \" : \" Internal server error: Failed to serialize response \" } " ) ;
}
json_decref ( result_json_root ) ;
g_object_unref ( ctx - > conn ) ;
g_free ( ctx ) ;
}
/* WebSocket message handler: Called when a message is received on the WebSocket */
/* WebSocket message handler: Called when a message is received on the WebSocket */
static void
on_websocket_message ( SoupWebsocketConnection * conn ,
SoupWebsocketDataType type ,
GBytes * message ,
gpointer user_data ) /* user_data = WebKitWebView* */
{
gsize size ;
const gchar * data = g_bytes_get_data ( message , & size ) ;
WebKitWebView * view = WEBKIT_WEB_VIEW ( user_data ) ;
if ( type = = SOUP_WEBSOCKET_DATA_TEXT & & data ) {
gchar * incoming_javascript_code = g_strndup ( data , size ) ;
g_print ( " Received raw JavaScript code via WebSocket: \n %s \n " , incoming_javascript_code ) ;
// Improved wrapper that properly handles return statements and expressions
gchar * wrapped_javascript_code = g_strdup_printf (
" (function() { \n "
" try { \n "
" // Wrap user code in eval to handle return statements \n "
" var _userResult_ = eval( \n "
" '(function() { \\ n' + \n "
" %s + \n "
" ' \\ n})()' \n "
" ); \n "
" // Handle different types of results \n "
" if (_userResult_ === undefined) { \n "
" return JSON.stringify(null); \n "
" } else if (typeof _userResult_ === 'function') { \n "
" // Convert function to string representation \n "
" return JSON.stringify({ \n "
" type: 'function', \n "
" value: _userResult_.toString(), \n "
" name: _userResult_.name || 'anonymous' \n "
" }); \n "
" } else { \n "
" return JSON.stringify(_userResult_); \n "
" } \n "
" } catch (e) { \n "
" // Catch JS errors and stringify them in a consistent error format \n "
" return JSON.stringify({ _error: true, message: e.message, stack: e.stack }); \n "
" } \n "
" })() " ,
// Escape the incoming JavaScript code for embedding in a string
incoming_javascript_code
) ;
g_print ( " Executing wrapped JavaScript (forcing JSON string return): \n %s \n " , wrapped_javascript_code ) ;
JsExecutionCtx * ctx = g_new ( JsExecutionCtx , 1 ) ;
ctx - > conn = g_object_ref ( conn ) ;
webkit_web_view_evaluate_javascript ( view ,
wrapped_javascript_code ,
- 1 ,
NULL ,
NULL ,
NULL ,
on_js_execution_complete ,
ctx ) ;
g_free ( incoming_javascript_code ) ;
g_free ( wrapped_javascript_code ) ;
} else {
g_warning ( " Received non-text WebSocket message. Ignoring. " ) ;
}
}
static void
on_websocket_message4 ( SoupWebsocketConnection * conn ,
SoupWebsocketDataType type ,
GBytes * message ,
gpointer user_data ) /* user_data = WebKitWebView* */
{
gsize size ;
const gchar * data = g_bytes_get_data ( message , & size ) ;
WebKitWebView * view = WEBKIT_WEB_VIEW ( user_data ) ;
if ( type = = SOUP_WEBSOCKET_DATA_TEXT & & data ) {
gchar * incoming_javascript_code = g_strndup ( data , size ) ;
g_print ( " Received raw JavaScript code via WebSocket: \n %s \n " , incoming_javascript_code ) ;
// Improved wrapper that properly handles function definitions and expressions
gchar * wrapped_javascript_code = g_strdup_printf (
" (function() { \n "
" %s \n "
" try { \n "
" // Execute the user's code and capture the result \n "
" var _userResult_ = (function() { \n "
" " " \n "
" return \" aaaa \" ; "
" }); \n "
" // If the result is a function, we can optionally call it or just return it \n "
" // For now, we'll return the function itself (not call it) \n "
" if (typeof _userResult_ === 'function') { \n "
" // Convert function to string representation \n "
" return JSON.stringify({ \n "
" type: 'function', \n "
" value: _userResult_().toString(), \n "
" name: _userResult_.name || 'anonymous' \n "
" }); \n "
" } \n "
" return JSON.stringify(_userResult_); \n "
" } catch (e) { \n "
" // Catch JS errors and stringify them in a consistent error format \n "
" return JSON.stringify({ _error: true, message: e.message, stack: e.stack }); \n "
" } \n "
" })() " ,
incoming_javascript_code
) ;
g_print ( " Executing wrapped JavaScript (forcing JSON string return): \n %s \n " , wrapped_javascript_code ) ;
JsExecutionCtx * ctx = g_new ( JsExecutionCtx , 1 ) ;
ctx - > conn = g_object_ref ( conn ) ;
webkit_web_view_evaluate_javascript ( view ,
wrapped_javascript_code ,
- 1 ,
NULL ,
NULL ,
NULL ,
on_js_execution_complete ,
ctx ) ;
g_free ( incoming_javascript_code ) ;
g_free ( wrapped_javascript_code ) ;
} else {
g_warning ( " Received non-text WebSocket message. Ignoring. " ) ;
}
}
static void
on_websocket_message3 ( SoupWebsocketConnection * conn ,
SoupWebsocketDataType type ,
GBytes * message ,
gpointer user_data ) /* user_data = WebKitWebView* */
{
gsize size ;
const gchar * data = g_bytes_get_data ( message , & size ) ;
WebKitWebView * view = WEBKIT_WEB_VIEW ( user_data ) ;
if ( type = = SOUP_WEBSOCKET_DATA_TEXT & & data ) {
gchar * incoming_javascript_code = g_strndup ( data , size ) ;
g_print ( " Received raw JavaScript code via WebSocket: \n %s \n " , incoming_javascript_code ) ;
// New wrapper: always JSON.stringify the result
// The user's code is wrapped in an inner IIFE to prevent variable pollution
// and its return value is then JSON.stringify'd.
// CORRECTED: Multi-line string literal formatting for comments
gchar * wrapped_javascript_code = g_strdup_printf (
" (function() { \n "
" try { \n "
" %s \n "
" const _result_ = function xxx (){ %s }; \n " // Execute user code in a sub-IIFE
" return JSON.stringify(xxx()); \n " // Stringify the result
" } catch (e) { \n "
" // Catch JS errors and stringify them in a consistent error format \n "
" return JSON.stringify({ _error: true, message: e.message, stack: e.stack }); \n "
" } \n "
" })() " ,
incoming_javascript_code ,
incoming_javascript_code
) ;
g_print ( " Executing wrapped JavaScript (forcing JSON string return): \n %s \n " , wrapped_javascript_code ) ;
JsExecutionCtx * ctx = g_new ( JsExecutionCtx , 1 ) ;
ctx - > conn = g_object_ref ( conn ) ;
webkit_web_view_evaluate_javascript ( view ,
wrapped_javascript_code ,
- 1 ,
NULL ,
NULL ,
NULL ,
on_js_execution_complete ,
ctx ) ;
g_free ( incoming_javascript_code ) ;
g_free ( wrapped_javascript_code ) ;
} else {
g_warning ( " Received non-text WebSocket message. Ignoring. " ) ;
}
}
// NEW: Explicit callback function for when the WebSocket connection closes
static void
on_websocket_closed ( SoupWebsocketConnection * conn ,
gpointer user_data )
{
g_print ( " WebSocket connection closed. \n " ) ;
// This balances the g_object_ref done in on_websocket
g_object_unref ( conn ) ;
}
/* WebSocket connection handler: Called when a new WebSocket connection is established */
static void
on_websocket ( SoupServer * server ,
SoupServerMessage * msg ,
const char * path ,
SoupWebsocketConnection * conn ,
gpointer user_data ) /* user_data = WebKitWebView* */
{
g_print ( " WebSocket connected on %s \n " , path ) ;
g_object_ref ( conn ) ; // Increment reference count to keep the connection alive
// Connect the 'message' signal to our handler (on_websocket_message)
g_signal_connect ( G_OBJECT ( conn ) , " message " , G_CALLBACK ( on_websocket_message ) , user_data ) ;
// Connect the 'closed' signal to our explicit handler (on_websocket_closed)
g_signal_connect ( G_OBJECT ( conn ) , " closed " , G_CALLBACK ( on_websocket_closed ) , NULL ) ; // No user_data needed for closed handler
}
// Main activation function for the GTK application
static void
activate ( GtkApplication * app , gpointer unused )
{
GtkWidget * win = gtk_application_window_new ( app ) ;
gtk_window_set_default_size ( GTK_WINDOW ( win ) , 980 , 720 ) ;
gtk_window_set_title ( GTK_WINDOW ( win ) , " JS Remote Execution Demo " ) ;
WebKitUserContentManager * mgr = webkit_user_content_manager_new ( ) ;
WebKitWebView * view = WEBKIT_WEB_VIEW ( webkit_web_view_new_with_user_content_manager ( mgr ) ) ;
/* Set up WebSocket server */
SoupServer * server = soup_server_new ( NULL , NULL ) ;
GError * error = NULL ;
// Attempt to listen on port 8080 locally
if ( ! soup_server_listen_local ( server , 8080 , 0 , & error ) ) {
g_warning ( " Failed to start WebSocket server: %s " , error - > message ) ;
g_clear_error ( & error ) ;
g_object_unref ( server ) ;
// Continue application without WebSocket server if it fails
} else {
g_print ( " WebSocket server listening on ws://localhost:8080/ \n " ) ;
// Add the WebSocket handler for the "/" path.
// The 'on_websocket' callback handles new connections.
soup_server_add_websocket_handler ( server , " / " , NULL , NULL , on_websocket , view , NULL ) ;
g_object_ref ( view ) ; // Keep the WebKitWebView alive for the WebSocket handler's user_data
}
// JavaScript code to be injected into the WebView to capture clicks and get CSS paths
const gchar * click_js =
" (function(){ "
" function getUniqueClass(el){ "
" if(!el.classList||el.classList.length===0) return null; "
" for(let c of el.classList){ "
" if(document.querySelectorAll('.'+CSS.escape(c)).length===1) "
" return c; "
" } return null; "
" } "
" function cssPath(el){ "
" if(el.tagName.toLowerCase()==='html') return 'html'; "
" if(el.id) return '#'+CSS.escape(el.id); "
" const parts=[]; "
" while(el&&el.nodeType===1&&el!==document.body){ "
" let sel=el.nodeName.toLowerCase(); "
" let u=getUniqueClass(el); "
" if(u){ sel='.'+CSS.escape(u); parts.unshift(sel); break; } "
" else if(el.classList.length){ "
" sel+='.'+Array.from(el.classList) "
" .map(c=>CSS.escape(c)).join('.'); "
" } "
" let sibs=Array.from(el.parentNode.children); "
" if(sibs.length>1) "
" sel+=':nth-child('+(sibs.indexOf(el)+1)+')'; "
" parts.unshift(sel); el=el.parentNode; "
" } return parts.join(' > '); "
" } "
" document.addEventListener('click',e=>{ "
" let selector=cssPath(e.target); "
" if(!selector || !document.querySelector(selector)) return; "
" window.webkit.messageHandlers.click.postMessage({ "
" selector: selector, x:e.clientX, y:e.clientY }); "
" }); "
" })(); " ;
// Add the click tracking script as a user script
WebKitUserScript * us = webkit_user_script_new (
click_js ,
WEBKIT_USER_CONTENT_INJECT_TOP_FRAME ,
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END ,
NULL , NULL ) ;
webkit_user_content_manager_add_script ( mgr , us ) ;
webkit_user_script_unref ( us ) ;
/* Load embed.js from file (if it exists) */
GError * error_js = NULL ;
gchar * embed_js_content = NULL ;
if ( ! g_file_get_contents ( " embed.js " , & embed_js_content , NULL , & error_js ) ) {
g_warning ( " Failed to load embed.js: %s " , error_js - > message ) ;
g_clear_error ( & error_js ) ;
} else {
WebKitUserScript * embed_us = webkit_user_script_new (
embed_js_content ,
WEBKIT_USER_CONTENT_INJECT_TOP_FRAME ,
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END ,
NULL , NULL ) ;
webkit_user_content_manager_add_script ( mgr , embed_us ) ;
webkit_user_script_unref ( embed_us ) ;
g_free ( embed_js_content ) ;
}
// Example of another injected script (a simple red bar at the bottom)
const gchar * tools_js =
" let c=document.createElement('div'); "
" c.style.cssText='position:fixed;left:0;bottom:0; "
" height:100px;width:100%;background:#cc0000;opacity:0.4;z-index:9999'; "
" document.body.appendChild(c); "
" " ;
WebKitUserScript * tools_us = webkit_user_script_new (
tools_js ,
WEBKIT_USER_CONTENT_INJECT_TOP_FRAME ,
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END ,
NULL , NULL ) ;
webkit_user_content_manager_add_script ( mgr , tools_us ) ;
webkit_user_script_unref ( tools_us ) ;
// Register a message handler for JavaScript messages sent via 'window.webkit.messageHandlers.click.postMessage'
webkit_user_content_manager_register_script_message_handler ( mgr , " click " ) ;
g_signal_connect ( mgr , " script-message-received::click " ,
G_CALLBACK ( on_js_click ) , view ) ;
// Load an initial URI in the WebView
webkit_web_view_load_uri ( view , " https://google.nl " ) ;
// Add the WebView to the GTK window
gtk_container_add ( GTK_CONTAINER ( win ) , GTK_WIDGET ( view ) ) ;
// Show all widgets
gtk_widget_show_all ( win ) ;
}
// Main function: Initializes GTK application and runs the main loop
int main ( int argc , char * * argv )
{
GtkApplication * app =
gtk_application_new ( " nl.demo.selector2html " ,
# if GLIB_CHECK_VERSION(2,74,0)
G_APPLICATION_DEFAULT_FLAGS ) ;
# else
G_APPLICATION_FLAGS_NONE ) ;
# endif
g_signal_connect ( app , " activate " , G_CALLBACK ( activate ) , NULL ) ;
int status = g_application_run ( G_APPLICATION ( app ) , argc , argv ) ;
g_object_unref ( app ) ;
return status ;
}