Bug 573616 - [GTK] BrowserFunction only work in first browser instance (Part 2)

Implement BrowserFunction calls with synchronous XMLHttpRequests
to a custom protocol handler.

SWT registers a custom protocol handler (swt://) and BrowserFunction
calls from JavaScript to Java are performed by issuing HTTP requests
using this protocol. The function to call and its arguments are JSON
encoded in the request URI. Function return value is JSON encoded
in the response body.

JSON codec created for the Edge integration on Windows is reused.

For early and reliable JavaScript injection after page navigation
and reload, BrowserFunction scripts are registered with the user
content manager API. This prevents race conditions compared to using
execute() from the LocationListener.changing or ProgressListener.completed.

An automated test (test_BrowserFunction_multiprocess) is included
to verify correct BrowserFunction operation across multiple Browser
instances and page reloads.

Change-Id: I33f97ee5710b561b4c3a71e1567ae21814ac43ee
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.swt/+/191032
Tested-by: Alexander Kurtakov <akurtako@redhat.com>
Reviewed-by: Alexander Kurtakov <akurtako@redhat.com>
diff --git a/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/JSON.java b/bundles/org.eclipse.swt/Eclipse SWT Browser/common/org/eclipse/swt/browser/JSON.java
similarity index 100%
rename from bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/JSON.java
rename to bundles/org.eclipse.swt/Eclipse SWT Browser/common/org/eclipse/swt/browser/JSON.java
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
index 6765f62..cea7169 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
@@ -10851,6 +10851,18 @@
 }
 #endif
 
+#ifndef NO_addressof_1g_1free
+JNIEXPORT jlong JNICALL OS_NATIVE(addressof_1g_1free)
+	(JNIEnv *env, jclass that)
+{
+	jlong rc = 0;
+	OS_NATIVE_ENTER(env, that, addressof_1g_1free_FUNC);
+	rc = (jlong)&g_free;
+	OS_NATIVE_EXIT(env, that, addressof_1g_1free_FUNC);
+	return rc;
+}
+#endif
+
 #ifndef NO_call__JJJJJ
 JNIEXPORT jlong JNICALL OS_NATIVE(call__JJJJJ)
 	(JNIEnv *env, jclass that, jlong arg0, jlong arg1, jlong arg2, jlong arg3, jlong arg4)
@@ -11986,6 +11998,18 @@
 }
 #endif
 
+#ifndef NO_g_1memory_1input_1stream_1new_1from_1data
+JNIEXPORT jlong JNICALL OS_NATIVE(g_1memory_1input_1stream_1new_1from_1data)
+	(JNIEnv *env, jclass that, jlong arg0, jlong arg1, jlong arg2)
+{
+	jlong rc = 0;
+	OS_NATIVE_ENTER(env, that, g_1memory_1input_1stream_1new_1from_1data_FUNC);
+	rc = (jlong)g_memory_input_stream_new_from_data((const void *)arg0, (gssize)arg1, (GDestroyNotify)arg2);
+	OS_NATIVE_EXIT(env, that, g_1memory_1input_1stream_1new_1from_1data_FUNC);
+	return rc;
+}
+#endif
+
 #ifndef NO_g_1menu_1insert_1item
 JNIEXPORT void JNICALL OS_NATIVE(g_1menu_1insert_1item)
 	(JNIEnv *env, jclass that, jlong arg0, jint arg1, jlong arg2)
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c
index bd377ce..0ab5dc8 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.c
@@ -936,6 +936,7 @@
 	"XSynchronize",
 	"X_1EVENT_1TYPE",
 	"X_1EVENT_1WINDOW",
+	"addressof_1g_1free",
 	"call__JJJJJ",
 	"call__JJJJJJJ",
 	"call__JJJJJJJJ",
@@ -1019,6 +1020,7 @@
 	"g_1main_1context_1release",
 	"g_1main_1context_1wakeup",
 	"g_1malloc",
+	"g_1memory_1input_1stream_1new_1from_1data",
 	"g_1menu_1insert_1item",
 	"g_1menu_1item_1new",
 	"g_1menu_1item_1new_1section",
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h
index d48b327..057048b 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h
@@ -910,6 +910,7 @@
 	XSynchronize_FUNC,
 	X_1EVENT_1TYPE_FUNC,
 	X_1EVENT_1WINDOW_FUNC,
+	addressof_1g_1free_FUNC,
 	call__JJJJJ_FUNC,
 	call__JJJJJJJ_FUNC,
 	call__JJJJJJJJ_FUNC,
@@ -993,6 +994,7 @@
 	g_1main_1context_1release_FUNC,
 	g_1main_1context_1wakeup_FUNC,
 	g_1malloc_FUNC,
+	g_1memory_1input_1stream_1new_1from_1data_FUNC,
 	g_1menu_1insert_1item_FUNC,
 	g_1menu_1item_1new_FUNC,
 	g_1menu_1item_1new_1section_FUNC,
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
index ca9b6b1..64d9ac9 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
@@ -1037,6 +1037,8 @@
 public static final native long g_filename_from_uri(long uri, long [] hostname, long [] error);
 /** @param mem cast=(gpointer) */
 public static final native void g_free(long mem);
+/** @method accessor=g_free,flags=const address */
+public static final native long addressof_g_free();
 /**
  * @param variable cast=(const gchar *),flags=no_out
  */
@@ -2277,4 +2279,13 @@
  * @param position cast=(guint)
  */
 public static final native long g_list_model_get_item(long list, int position);
+
+/* GMemoryInputStream */
+/**
+ * @param data cast=(const void *)
+ * @param len cast=(gssize)
+ * @param destroy cast=(GDestroyNotify)
+ */
+public static final native long g_memory_input_stream_new_from_data(long data, long len, long destroy);
+
 }
diff --git a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk.c b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk.c
index f8782de..b278236 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk.c
@@ -1214,6 +1214,28 @@
 }
 #endif
 
+#ifndef NO_webkit_1security_1manager_1register_1uri_1scheme_1as_1secure
+JNIEXPORT void JNICALL WebKitGTK_NATIVE(webkit_1security_1manager_1register_1uri_1scheme_1as_1secure)
+	(JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1)
+{
+	jbyte *lparg1=NULL;
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1security_1manager_1register_1uri_1scheme_1as_1secure_FUNC);
+	if (arg1) if ((lparg1 = (*env)->GetByteArrayElements(env, arg1, NULL)) == NULL) goto fail;
+/*
+	webkit_security_manager_register_uri_scheme_as_secure(arg0, lparg1);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_security_manager_register_uri_scheme_as_secure)
+		if (fp) {
+			((void (CALLING_CONVENTION*)(jlong, jbyte *))fp)(arg0, lparg1);
+		}
+	}
+fail:
+	if (arg1 && lparg1) (*env)->ReleaseByteArrayElements(env, arg1, lparg1, 0);
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1security_1manager_1register_1uri_1scheme_1as_1secure_FUNC);
+}
+#endif
+
 #ifndef NO_webkit_1uri_1request_1get_1http_1headers
 JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1uri_1request_1get_1http_1headers)
 	(JNIEnv *env, jclass that, jlong arg0)
@@ -1318,6 +1340,148 @@
 }
 #endif
 
+#ifndef NO_webkit_1uri_1scheme_1request_1finish
+JNIEXPORT void JNICALL WebKitGTK_NATIVE(webkit_1uri_1scheme_1request_1finish)
+	(JNIEnv *env, jclass that, jlong arg0, jlong arg1, jlong arg2, jbyteArray arg3)
+{
+	jbyte *lparg3=NULL;
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1uri_1scheme_1request_1finish_FUNC);
+	if (arg3) if ((lparg3 = (*env)->GetByteArrayElements(env, arg3, NULL)) == NULL) goto fail;
+/*
+	webkit_uri_scheme_request_finish(arg0, arg1, arg2, lparg3);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_uri_scheme_request_finish)
+		if (fp) {
+			((void (CALLING_CONVENTION*)(jlong, jlong, jlong, jbyte *))fp)(arg0, arg1, arg2, lparg3);
+		}
+	}
+fail:
+	if (arg3 && lparg3) (*env)->ReleaseByteArrayElements(env, arg3, lparg3, JNI_ABORT);
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1uri_1scheme_1request_1finish_FUNC);
+}
+#endif
+
+#ifndef NO_webkit_1uri_1scheme_1request_1get_1uri
+JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1uri_1scheme_1request_1get_1uri)
+	(JNIEnv *env, jclass that, jlong arg0)
+{
+	jlong rc = 0;
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1uri_1scheme_1request_1get_1uri_FUNC);
+/*
+	rc = (jlong)webkit_uri_scheme_request_get_uri(arg0);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_uri_scheme_request_get_uri)
+		if (fp) {
+			rc = (jlong)((jlong (CALLING_CONVENTION*)(jlong))fp)(arg0);
+		}
+	}
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1uri_1scheme_1request_1get_1uri_FUNC);
+	return rc;
+}
+#endif
+
+#ifndef NO_webkit_1uri_1scheme_1request_1get_1web_1view
+JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1uri_1scheme_1request_1get_1web_1view)
+	(JNIEnv *env, jclass that, jlong arg0)
+{
+	jlong rc = 0;
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1uri_1scheme_1request_1get_1web_1view_FUNC);
+/*
+	rc = (jlong)webkit_uri_scheme_request_get_web_view(arg0);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_uri_scheme_request_get_web_view)
+		if (fp) {
+			rc = (jlong)((jlong (CALLING_CONVENTION*)(jlong))fp)(arg0);
+		}
+	}
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1uri_1scheme_1request_1get_1web_1view_FUNC);
+	return rc;
+}
+#endif
+
+#ifndef NO_webkit_1user_1content_1manager_1add_1script
+JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1user_1content_1manager_1add_1script)
+	(JNIEnv *env, jclass that, jlong arg0, jlong arg1)
+{
+	jlong rc = 0;
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1user_1content_1manager_1add_1script_FUNC);
+/*
+	rc = (jlong)webkit_user_content_manager_add_script(arg0, arg1);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_user_content_manager_add_script)
+		if (fp) {
+			rc = (jlong)((jlong (CALLING_CONVENTION*)(jlong, jlong))fp)(arg0, arg1);
+		}
+	}
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1user_1content_1manager_1add_1script_FUNC);
+	return rc;
+}
+#endif
+
+#ifndef NO_webkit_1user_1content_1manager_1remove_1all_1scripts
+JNIEXPORT void JNICALL WebKitGTK_NATIVE(webkit_1user_1content_1manager_1remove_1all_1scripts)
+	(JNIEnv *env, jclass that, jlong arg0)
+{
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1user_1content_1manager_1remove_1all_1scripts_FUNC);
+/*
+	webkit_user_content_manager_remove_all_scripts(arg0);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_user_content_manager_remove_all_scripts)
+		if (fp) {
+			((void (CALLING_CONVENTION*)(jlong))fp)(arg0);
+		}
+	}
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1user_1content_1manager_1remove_1all_1scripts_FUNC);
+}
+#endif
+
+#ifndef NO_webkit_1user_1script_1new
+JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1user_1script_1new)
+	(JNIEnv *env, jclass that, jbyteArray arg0, jint arg1, jint arg2, jlong arg3, jlong arg4)
+{
+	jbyte *lparg0=NULL;
+	jlong rc = 0;
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1user_1script_1new_FUNC);
+	if (arg0) if ((lparg0 = (*env)->GetByteArrayElements(env, arg0, NULL)) == NULL) goto fail;
+/*
+	rc = (jlong)webkit_user_script_new(lparg0, arg1, arg2, arg3, arg4);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_user_script_new)
+		if (fp) {
+			rc = (jlong)((jlong (CALLING_CONVENTION*)(jbyte *, jint, jint, jlong, jlong))fp)(lparg0, arg1, arg2, arg3, arg4);
+		}
+	}
+fail:
+	if (arg0 && lparg0) (*env)->ReleaseByteArrayElements(env, arg0, lparg0, JNI_ABORT);
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1user_1script_1new_FUNC);
+	return rc;
+}
+#endif
+
+#ifndef NO_webkit_1user_1script_1unref
+JNIEXPORT void JNICALL WebKitGTK_NATIVE(webkit_1user_1script_1unref)
+	(JNIEnv *env, jclass that, jlong arg0)
+{
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1user_1script_1unref_FUNC);
+/*
+	webkit_user_script_unref(arg0);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_user_script_unref)
+		if (fp) {
+			((void (CALLING_CONVENTION*)(jlong))fp)(arg0);
+		}
+	}
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1user_1script_1unref_FUNC);
+}
+#endif
+
 #ifndef NO_webkit_1web_1context_1allow_1tls_1certificate_1for_1host
 JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1web_1context_1allow_1tls_1certificate_1for_1host)
 	(JNIEnv *env, jclass that, jlong arg0, jlong arg1, jbyteArray arg2)
@@ -1382,6 +1546,26 @@
 }
 #endif
 
+#ifndef NO_webkit_1web_1context_1get_1security_1manager
+JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1web_1context_1get_1security_1manager)
+	(JNIEnv *env, jclass that, jlong arg0)
+{
+	jlong rc = 0;
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1web_1context_1get_1security_1manager_FUNC);
+/*
+	rc = (jlong)webkit_web_context_get_security_manager(arg0);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_web_context_get_security_manager)
+		if (fp) {
+			rc = (jlong)((jlong (CALLING_CONVENTION*)(jlong))fp)(arg0);
+		}
+	}
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1web_1context_1get_1security_1manager_FUNC);
+	return rc;
+}
+#endif
+
 #ifndef NO_webkit_1web_1context_1get_1type
 JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1web_1context_1get_1type)
 	(JNIEnv *env, jclass that)
@@ -1422,6 +1606,28 @@
 }
 #endif
 
+#ifndef NO_webkit_1web_1context_1register_1uri_1scheme
+JNIEXPORT void JNICALL WebKitGTK_NATIVE(webkit_1web_1context_1register_1uri_1scheme)
+	(JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1, jlong arg2, jlong arg3, jlong arg4)
+{
+	jbyte *lparg1=NULL;
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1web_1context_1register_1uri_1scheme_FUNC);
+	if (arg1) if ((lparg1 = (*env)->GetByteArrayElements(env, arg1, NULL)) == NULL) goto fail;
+/*
+	webkit_web_context_register_uri_scheme(arg0, lparg1, arg2, arg3, arg4);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_web_context_register_uri_scheme)
+		if (fp) {
+			((void (CALLING_CONVENTION*)(jlong, jbyte *, jlong, jlong, jlong))fp)(arg0, lparg1, arg2, arg3, arg4);
+		}
+	}
+fail:
+	if (arg1 && lparg1) (*env)->ReleaseByteArrayElements(env, arg1, lparg1, 0);
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1web_1context_1register_1uri_1scheme_FUNC);
+}
+#endif
+
 #ifndef NO_webkit_1web_1context_1set_1tls_1errors_1policy
 JNIEXPORT void JNICALL WebKitGTK_NATIVE(webkit_1web_1context_1set_1tls_1errors_1policy)
 	(JNIEnv *env, jclass that, jlong arg0, jint arg1)
@@ -1685,6 +1891,26 @@
 }
 #endif
 
+#ifndef NO_webkit_1web_1view_1get_1user_1content_1manager
+JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1web_1view_1get_1user_1content_1manager)
+	(JNIEnv *env, jclass that, jlong arg0)
+{
+	jlong rc = 0;
+	WebKitGTK_NATIVE_ENTER(env, that, webkit_1web_1view_1get_1user_1content_1manager_FUNC);
+/*
+	rc = (jlong)webkit_web_view_get_user_content_manager(arg0);
+*/
+	{
+		WebKitGTK_LOAD_FUNCTION(fp, webkit_web_view_get_user_content_manager)
+		if (fp) {
+			rc = (jlong)((jlong (CALLING_CONVENTION*)(jlong))fp)(arg0);
+		}
+	}
+	WebKitGTK_NATIVE_EXIT(env, that, webkit_1web_1view_1get_1user_1content_1manager_FUNC);
+	return rc;
+}
+#endif
+
 #ifndef NO_webkit_1web_1view_1get_1window_1properties
 JNIEXPORT jlong JNICALL WebKitGTK_NATIVE(webkit_1web_1view_1get_1window_1properties)
 	(JNIEnv *env, jclass that, jlong arg0)
diff --git a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_stats.c b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_stats.c
index 774b722..cc0c92c 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_stats.c
+++ b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_stats.c
@@ -80,16 +80,26 @@
 	"webkit_1policy_1decision_1ignore",
 	"webkit_1response_1policy_1decision_1get_1request",
 	"webkit_1response_1policy_1decision_1get_1response",
+	"webkit_1security_1manager_1register_1uri_1scheme_1as_1secure",
 	"webkit_1uri_1request_1get_1http_1headers",
 	"webkit_1uri_1request_1get_1uri",
 	"webkit_1uri_1request_1new",
 	"webkit_1uri_1response_1get_1content_1length",
 	"webkit_1uri_1response_1get_1mime_1type",
+	"webkit_1uri_1scheme_1request_1finish",
+	"webkit_1uri_1scheme_1request_1get_1uri",
+	"webkit_1uri_1scheme_1request_1get_1web_1view",
+	"webkit_1user_1content_1manager_1add_1script",
+	"webkit_1user_1content_1manager_1remove_1all_1scripts",
+	"webkit_1user_1script_1new",
+	"webkit_1user_1script_1unref",
 	"webkit_1web_1context_1allow_1tls_1certificate_1for_1host",
 	"webkit_1web_1context_1get_1cookie_1manager",
 	"webkit_1web_1context_1get_1default",
+	"webkit_1web_1context_1get_1security_1manager",
 	"webkit_1web_1context_1get_1type",
 	"webkit_1web_1context_1get_1website_1data_1manager",
+	"webkit_1web_1context_1register_1uri_1scheme",
 	"webkit_1web_1context_1set_1tls_1errors_1policy",
 	"webkit_1web_1resource_1get_1data",
 	"webkit_1web_1resource_1get_1data_1finish",
@@ -103,6 +113,7 @@
 	"webkit_1web_1view_1get_1settings",
 	"webkit_1web_1view_1get_1title",
 	"webkit_1web_1view_1get_1uri",
+	"webkit_1web_1view_1get_1user_1content_1manager",
 	"webkit_1web_1view_1get_1window_1properties",
 	"webkit_1web_1view_1go_1back",
 	"webkit_1web_1view_1go_1forward",
diff --git a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_stats.h b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_stats.h
index 9ce6e0c..c189b55 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_stats.h
+++ b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_stats.h
@@ -90,16 +90,26 @@
 	webkit_1policy_1decision_1ignore_FUNC,
 	webkit_1response_1policy_1decision_1get_1request_FUNC,
 	webkit_1response_1policy_1decision_1get_1response_FUNC,
+	webkit_1security_1manager_1register_1uri_1scheme_1as_1secure_FUNC,
 	webkit_1uri_1request_1get_1http_1headers_FUNC,
 	webkit_1uri_1request_1get_1uri_FUNC,
 	webkit_1uri_1request_1new_FUNC,
 	webkit_1uri_1response_1get_1content_1length_FUNC,
 	webkit_1uri_1response_1get_1mime_1type_FUNC,
+	webkit_1uri_1scheme_1request_1finish_FUNC,
+	webkit_1uri_1scheme_1request_1get_1uri_FUNC,
+	webkit_1uri_1scheme_1request_1get_1web_1view_FUNC,
+	webkit_1user_1content_1manager_1add_1script_FUNC,
+	webkit_1user_1content_1manager_1remove_1all_1scripts_FUNC,
+	webkit_1user_1script_1new_FUNC,
+	webkit_1user_1script_1unref_FUNC,
 	webkit_1web_1context_1allow_1tls_1certificate_1for_1host_FUNC,
 	webkit_1web_1context_1get_1cookie_1manager_FUNC,
 	webkit_1web_1context_1get_1default_FUNC,
+	webkit_1web_1context_1get_1security_1manager_FUNC,
 	webkit_1web_1context_1get_1type_FUNC,
 	webkit_1web_1context_1get_1website_1data_1manager_FUNC,
+	webkit_1web_1context_1register_1uri_1scheme_FUNC,
 	webkit_1web_1context_1set_1tls_1errors_1policy_FUNC,
 	webkit_1web_1resource_1get_1data_FUNC,
 	webkit_1web_1resource_1get_1data_1finish_FUNC,
@@ -113,6 +123,7 @@
 	webkit_1web_1view_1get_1settings_FUNC,
 	webkit_1web_1view_1get_1title_FUNC,
 	webkit_1web_1view_1get_1uri_FUNC,
+	webkit_1web_1view_1get_1user_1content_1manager_FUNC,
 	webkit_1web_1view_1get_1window_1properties_FUNC,
 	webkit_1web_1view_1go_1back_FUNC,
 	webkit_1web_1view_1go_1forward_FUNC,
diff --git a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/browser/WebKit.java b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/browser/WebKit.java
index b12ec6f..e797734 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/browser/WebKit.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/browser/WebKit.java
@@ -96,6 +96,7 @@
 	String tlsErrorType;
 
 	boolean firstLoad = true;
+	static boolean FirstCreate = true;
 
 	/**
 	 * Stores the browser which is opening a new browser window,
@@ -168,6 +169,9 @@
 	static final String DOMEVENT_MOUSEOVER = "mouseover"; //$NON-NLS-1$
 	static final String DOMEVENT_MOUSEWHEEL = "mousewheel"; //$NON-NLS-1$
 
+	static final byte[] SWT_PROTOCOL = Converter.wcsToMbcs("swt", true); // $NON-NLS-1$
+	static final byte[] JSON_MIME_TYPE = Converter.wcsToMbcs("application/json", true); // $NON-NLS-1$
+
 	/* WebKit signal data */
 	static final int NOTIFY_PROGRESS = 1;
 	static final int NOTIFY_TITLE = 2;
@@ -191,10 +195,7 @@
 	static final String SWT_WEBKITGTK_VERSION = "org.eclipse.swt.internal.webkitgtk.version"; //$NON-NLS-1$
 
 	/* the following Callbacks are never freed */
-	static Callback Proc2, Proc3, Proc4, Proc5;
-
-	/** Process key/mouse events from javascript. */
-	static Callback JSDOMEventProc;
+	static Callback Proc2, Proc3, Proc4, Proc5, JSDOMEventProc, RequestProc;
 
 	/** Flag indicating whether TLS errors (like self-signed certificates) are to be ignored. */
 	static final boolean ignoreTls;
@@ -207,6 +208,7 @@
 			new Webkit2AsyncToSync();
 
 			JSDOMEventProc = new Callback (WebKit.class, "JSDOMEventProc", 3); //$NON-NLS-1$
+			RequestProc = new Callback (WebKit.class, "RequestProc", 2); //$NON-NLS-1$
 
 			NativeClearSessions = () -> {
 				if (!WebKitGTK.LibraryLoaded) return;
@@ -250,11 +252,31 @@
 	@Override
 	public void createFunction(BrowserFunction function) {
 		super.createFunction(function);
+		updateUserScript();
 	}
 
 	@Override
 	public void destroyFunction (BrowserFunction function) {
 		super.destroyFunction(function);
+		updateUserScript();
+	}
+
+	void updateUserScript() {
+		// Maintain a script bundle of BrowserFunctions to be injected on page navigation or reload.
+		long manager = WebKitGTK.webkit_web_view_get_user_content_manager(webView);
+		WebKitGTK.webkit_user_content_manager_remove_all_scripts(manager);
+		if (!functions.isEmpty()) {
+			StringBuilder sb = new StringBuilder();
+			for (BrowserFunction function : functions.values()) {
+				sb.append(function.functionString);
+			}
+			sb.append('\0');
+			byte[] scriptData = sb.toString().getBytes(StandardCharsets.UTF_8);
+			long script = WebKitGTK.webkit_user_script_new(
+					scriptData, WebKitGTK.WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, WebKitGTK.WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, 0, 0);
+			WebKitGTK.webkit_user_content_manager_add_script(manager, script);
+			WebKitGTK.webkit_user_script_unref(script);
+		}
 	}
 
 	private static String getInternalErrorMsg () {
@@ -280,7 +302,14 @@
 
 	@Override
 	String getJavaCallDeclaration() {
-		return super.getJavaCallDeclaration();
+		// callJava does a synchronous XMLHttpRequest, which is handled by RequestProc.
+		return "if (!window.callJava) { window.callJava = function(index, token, args) {\n"
+				+ "var xhr = new XMLHttpRequest();\n"
+				+ "var uri = 'swt://browserfunction/' + index + '/' + token + '?' + encodeURIComponent(JSON.stringify(args));\n"
+				+ "xhr.open('POST', uri, false);\n"
+				+ "xhr.send(null);\n"
+				+ "return JSON.parse(xhr.responseText);\n"
+				+ "}}\n";
 	}
 
 	/**
@@ -397,6 +426,54 @@
 	return 0;
 }
 
+static long RequestProc (long request, long user_data) {
+	// Custom protocol handler (swt://) for BrowserFunction callbacks.
+	// Note that a response must be sent regardless of any errors, otherwise the caller will hang.
+	String response = "null";
+
+	long webView = WebKitGTK.webkit_uri_scheme_request_get_web_view(request);
+	Browser browser = FindBrowser(webView);
+	if (browser != null) {
+		BrowserFunction function = null;
+		Object[] args = null;
+
+		long uriPtr = WebKitGTK.webkit_uri_scheme_request_get_uri(request);
+		String uriStr = Converter.cCharPtrToJavaString(uriPtr, false);
+		try {
+			URI uri = new URI(uriStr);
+			String[] parts = uri.getPath().split("/");
+			int index = Integer.parseInt(parts[1]);
+			String token = parts[2];
+
+			WebKit webkit = (WebKit)browser.webBrowser;
+			function = webkit.functions.get(index);
+			if (function != null && !function.token.equals(token)) {
+				function = null;
+			}
+
+			args = (Object[]) JSON.parse(uri.getQuery());
+		} catch (URISyntaxException | IllegalArgumentException | IndexOutOfBoundsException | ClassCastException e) {
+		}
+
+		if (function != null) {
+			Object result;
+			try {
+				result = function.function(args);
+			} catch (Exception e) {
+				result = WebBrowser.CreateErrorString(e.getLocalizedMessage());
+			}
+			response = JSON.stringify(result);
+		}
+	}
+
+	long[] outBytes = new long[1];
+	long dataPtr = OS.g_utf16_to_utf8(response.toCharArray(), response.length(), null, outBytes, null);
+	long stream = OS.g_memory_input_stream_new_from_data(dataPtr, outBytes[0], OS.addressof_g_free());
+	WebKitGTK.webkit_uri_scheme_request_finish(request, stream, outBytes[0], JSON_MIME_TYPE);
+	OS.g_object_unref(stream);
+	return 0;
+}
+
 static long Proc (long handle, long user_data) {
 	long webView  = handle;
 
@@ -563,6 +640,14 @@
 	 */
 	Webkit2AsyncToSync.setCookieBrowser(browser);
 
+	if (FirstCreate) {
+		FirstCreate = false;
+		// Register the swt:// custom protocol for BrowserFunction calls via XMLHttpRequest
+		long context = WebKitGTK.webkit_web_context_get_default();
+		WebKitGTK.webkit_web_context_register_uri_scheme(context, SWT_PROTOCOL, RequestProc.getAddress(), 0, 0);
+		long security = WebKitGTK.webkit_web_context_get_security_manager(context);
+		WebKitGTK.webkit_security_manager_register_uri_scheme_as_secure(security, SWT_PROTOCOL);
+	}
 
 	Composite parentShell = parent.getParent();
 	Browser parentBrowser = WebKit.parentBrowser;
diff --git a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/internal/webkit/WebKitGTK.java b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/internal/webkit/WebKitGTK.java
index b87c383..a49a689 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/internal/webkit/WebKitGTK.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/internal/webkit/WebKitGTK.java
@@ -87,6 +87,8 @@
 
 	public static final int WEBKIT_WEBSITE_DATA_COOKIES = 1 << 8;
 
+	public static final int WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START = 0;
+	public static final int WEBKIT_USER_CONTENT_INJECT_TOP_FRAME = 1;
 
 	/** Signals */
 
@@ -359,9 +361,15 @@
 public static final native long webkit_web_context_get_website_data_manager(long context);
 
 /** @method flags=dynamic */
+public static final native long webkit_web_context_get_security_manager(long context);
+
+/** @method flags=dynamic */
 public static final native void webkit_web_context_set_tls_errors_policy(long context, int policy);
 
 /** @method flags=dynamic */
+public static final native void webkit_web_context_register_uri_scheme(long context, byte[] scheme, long callback, long user_data, long user_data_destroy_func);
+
+/** @method flags=dynamic */
 public static final native int webkit_web_view_can_go_back(long web_view);
 
 /** @method flags=dynamic */
@@ -386,6 +394,9 @@
 public static final native long webkit_web_view_get_settings(long web_view);
 
 /** @method flags=dynamic */
+public static final native long webkit_web_view_get_user_content_manager(long web_view);
+
+/** @method flags=dynamic */
 public static final native long webkit_web_view_get_title(long web_view);
 
 /** @method flags=dynamic */
@@ -486,6 +497,36 @@
 /** @method flags=dynamic */
 public static final native long webkit_uri_response_get_mime_type(long responce);
 
+/**
+ * @method flags=dynamic
+ * @param content_type flags=no_out
+ */
+public static final native void webkit_uri_scheme_request_finish(long request, long stream, long stream_length, byte[] content_type);
+
+/** @method flags=dynamic */
+public static final native long webkit_uri_scheme_request_get_uri (long request);
+
+/** @method flags=dynamic */
+public static final native long webkit_uri_scheme_request_get_web_view(long request);
+
+/** @method flags=dynamic */
+public static final native long webkit_user_content_manager_add_script(long manager, long script);
+
+/** @method flags=dynamic */
+public static final native void webkit_user_content_manager_remove_all_scripts(long manager);
+
+/** @method flags=dynamic */
+public static final native void webkit_security_manager_register_uri_scheme_as_secure(long security_manager, byte[] scheme);
+
+/**
+ * @method flags=dynamic
+ * @param source flags=no_out
+ */
+public static final native long webkit_user_script_new (byte[] source, int injected_frames, int injection_time, long allow_list, long block_list);
+
+/** @method flags=dynamic */
+public static final native void webkit_user_script_unref (long user_script);
+
 /* --------------------- start SWT natives --------------------- */
 public static final native int GdkRectangle_sizeof();
 
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java
index 3bbc19a..4daf9d8 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java
@@ -2162,6 +2162,47 @@
 	assertTrue(message, passed);
 }
 
+@Test
+public void test_BrowserFunction_multiprocess() {
+	// Test that BrowserFunctions work in multiple Browser instances simultaneously.
+	Browser browser1 = new Browser(shell, SWT.NONE);
+	Browser browser2 = new Browser(shell, SWT.NONE);
+
+	class JavaFunc extends BrowserFunction {
+		JavaFunc(Browser browser) {
+			super(browser, "javaFunc");
+		}
+
+		@Override
+		public Object function(Object[] arguments) {
+			return arguments[0];
+		}
+	}
+	new JavaFunc(browser1);
+	new JavaFunc(browser2);
+	assertEquals("value1", browser1.evaluate("return javaFunc('value1')"));
+	assertEquals("value2", browser2.evaluate("return javaFunc('value2')"));
+
+	// Ensure that navigation to a different page preserves BrowserFunctions.
+	int[] completed = new int[1];
+	ProgressListener listener = new ProgressAdapter() {
+		@Override
+		public void completed(ProgressEvent event) {
+			completed[0]++;
+		}
+	};
+	browser1.addProgressListener(listener);
+	browser2.addProgressListener(listener);
+	browser1.setText("<body>new_page1");
+	browser2.setText("<body>new_page2");
+	waitForPassCondition(() -> completed[0] == 2);
+	assertEquals("value1", browser1.evaluate("return javaFunc('value1')"));
+	assertEquals("value2", browser2.evaluate("return javaFunc('value2')"));
+
+	browser1.dispose();
+	browser2.dispose();
+}
+
 
 /* custom */
 /**