diff options
Diffstat (limited to 'bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk')
8 files changed, 632 insertions, 59 deletions
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 de6febe682..686437b6e5 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 @@ -2921,6 +2921,26 @@ JNIEXPORT jintLong JNICALL WebKitGTK_NATIVE(_1webkit_1web_1view_1get_1main_1reso } #endif +#ifndef NO__1webkit_1web_1view_1get_1page_1id +JNIEXPORT jintLong JNICALL WebKitGTK_NATIVE(_1webkit_1web_1view_1get_1page_1id) + (JNIEnv *env, jclass that, jintLong arg0) +{ + jintLong rc = 0; + WebKitGTK_NATIVE_ENTER(env, that, _1webkit_1web_1view_1get_1page_1id_FUNC); +/* + rc = (jintLong)webkit_web_view_get_page_id(arg0); +*/ + { + WebKitGTK_LOAD_FUNCTION(fp, webkit_web_view_get_page_id) + if (fp) { + rc = (jintLong)((jintLong (CALLING_CONVENTION*)(jintLong))fp)(arg0); + } + } + WebKitGTK_NATIVE_EXIT(env, that, _1webkit_1web_1view_1get_1page_1id_FUNC); + return rc; +} +#endif + #ifndef NO__1webkit_1web_1view_1get_1progress JNIEXPORT jdouble JNICALL WebKitGTK_NATIVE(_1webkit_1web_1view_1get_1progress) (JNIEnv *env, jclass that, jintLong arg0) diff --git a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_extension.c b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_extension.c index 2dbc28c16c..fd958309a8 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_extension.c +++ b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_extension.c @@ -13,7 +13,6 @@ gint32 parentUniqueId = 0; guchar SWT_DBUS_MAGIC_NUMBER_EMPTY_ARRAY = 101; guchar SWT_DBUS_MAGIC_NUMBER_NULL = 48; - // +-------------+---------------------------------------------------------------- // | Misc Helpers| // +-------------+ @@ -35,6 +34,13 @@ static const gchar base_service_name[] = "org.eclipse.swt"; // Base name. Full n static const gchar object_name[] = "/org/eclipse/swt/gdbus"; static const gchar interface[] = "org.eclipse.swt.gdbusInterface"; +typedef struct { + guint64 page_id; + const gchar *function; + const gchar *url; +} BrowserFunction; + +GSList *function_list = NULL; GDBusProxy *proxy = NULL; // The proxy that we work with void proxy_init () { @@ -72,10 +78,12 @@ GVariant * callMainProc(char * methodName, GVariant * params) { // Error checking. if (result == NULL) { - if (error != NULL) - g_error("SWT Webextension: Call failed because '%s.' Probably didn't handle type properly, could be an SWT bug. Signature: %s\n", error->message, g_variant_get_type_string(params)); - else - g_error("SWT Webextension: Call failed for an unknown reason.\n"); + if (error != NULL) { + g_error("SWT web extension: Call failed because '%s.'\n", error->message); + } + else { + g_error("SWT web extension: Call failed for an unknown reason.\n"); + } return NULL; } @@ -289,17 +297,124 @@ static JSValueRef webkit2callJava (JSContextRef context, return retVal; } +static void web_page_created_callback(WebKitWebExtension *extension, WebKitWebPage *web_page, gpointer user_data) { + // Observation. This seems to be called only once. +} + +/** + * Returns the main frame of the WebPage with the given ID + */ +static WebKitFrame *webkitgtk_extension_get_main_frame (const guint64 id) { + WebKitWebPage *web_page = webkit_web_extension_get_page (this_extension, id); + return webkit_web_page_get_main_frame (web_page); +} + +/* + * Execute the Javascript for the given page and URL. + */ +static gboolean webkitgtk_extension_execute_script (const guint64 page_id, const gchar* script, const gchar* url) { + WebKitFrame *main_frame = webkitgtk_extension_get_main_frame (page_id); + + JSStringRef url_string = JSStringCreateWithUTF8CString (url); + JSStringRef script_string = JSStringCreateWithUTF8CString (script); + + JSGlobalContextRef context = webkit_frame_get_javascript_global_context (main_frame); + + JSValueRef exception; + JSValueRef result = JSEvaluateScript(context, script_string, NULL, url_string, 0, &exception); + if (!result) { + JSStringRef exceptionIString = JSValueToStringCopy(context, exception, NULL); + size_t exceptionUTF8Size = JSStringGetMaximumUTF8CStringSize(exceptionIString); + char* exceptionUTF8 = (char*)malloc(exceptionUTF8Size); + JSStringGetUTF8CString(exceptionIString, exceptionUTF8, exceptionUTF8Size); + g_error("SWT web extension: failed to execute script exception: %s\n", exceptionUTF8); + free(exceptionUTF8); + JSStringRelease(exceptionIString); + } + + JSStringRelease (url_string); + JSStringRelease (script_string); + + return result != NULL; +} + +void execute_browser_functions(gconstpointer item, gpointer page) { + BrowserFunction *function = (BrowserFunction *) item; + if (function != NULL && function->page_id == GPOINTER_TO_UINT(page)) { + webkitgtk_extension_execute_script(function->page_id, function->function, function->url); + } + return; +} + +gint find_browser_function (gconstpointer item, gconstpointer target) { + BrowserFunction *element = (BrowserFunction *) item; + BrowserFunction *remove = (BrowserFunction *) target; + if (element->page_id == remove->page_id && g_strcmp0(element->function, remove->function) == 0 && + g_strcmp0(element->url, remove->url) == 0) { + return 0; + } + return 1; +} + +void add_browser_function(guint64 page_id, const gchar *function, const gchar *url) { + BrowserFunction *func = g_slice_new0(BrowserFunction); + func->page_id = page_id; + func->function = function; + func->url = url; + function_list = g_slist_append(function_list, func); +} + +void remove_browser_function(guint64 page_id, const gchar *function, const gchar *url) { + BrowserFunction *func = g_slice_new0(BrowserFunction); + func->page_id = page_id; + func->function = function; + func->url = url; + GSList *to_remove = g_slist_find_custom(function_list, func, find_browser_function); + if (to_remove != NULL) { + function_list = g_slist_delete_link(function_list, to_remove); + } + g_slice_free(BrowserFunction, func); +} + +void unpack_browser_function_array(GVariant *array) { + GVariantIter iter; + GVariant *child; + + g_variant_iter_init (&iter, array); + while ((child = g_variant_iter_next_value (&iter))) { + gsize length = (int)g_variant_n_children (child); + if (length > 3) { + // If the length is longer than three, something went wrong and this tuple should be skipped + g_warning("SWT web extension: there was an error unpacking the GVariant tuple for a BrowserFunction in the web extension.\n"); + continue; + } + guint64 page = g_variant_get_uint64(g_variant_get_child_value(child, 0)); + if (page == -1) { + // Empty or malformed BrowserFunction, skip this one + continue; + } else { + const gchar *function = g_variant_get_string(g_variant_get_child_value(child, 1), NULL); + const gchar *url = g_variant_get_string(g_variant_get_child_value(child, 2), NULL); + if (function != NULL && url != NULL) { + add_browser_function(page, function, url); + } else { + g_warning("SWT web extension: there was an error unpacking the function string or URL.\n"); + } + } + g_variant_unref (child); + } +} /* - * Everytime a webpage is loaded, we should re-register the 'webkit2callJava' function. + * Every time a webpage is loaded, we should re-register the 'webkit2callJava' function. + * Additionally, we re-register all BrowserFunctions that are stored in the function_list + * GSList. */ -static void window_object_cleared_callback (WebKitScriptWorld *world, - WebKitWebPage *web_page, +static void window_object_cleared_callback (WebKitScriptWorld *world, WebKitWebPage *web_page, WebKitFrame *frame, - gpointer user_data) -{ - // Observation: This is called everytime a webpage is loaded. - JSGlobalContextRef jsContext; + gpointer user_data) { + // Observation: This is called every time a webpage is loaded. + JSGlobalContextRef jsContext; JSObjectRef globalObject; JSValueRef exception = 0; @@ -314,23 +429,113 @@ static void window_object_cleared_callback (WebKitScriptWorld *world, if (exception) { g_print("OJSObjectSetProperty exception occurred"); } + + /* + * Iterate over the list of BrowserFunctions and execute each one of them for the current page. + * This ensures that BrowserFunctions are not lost on page reloads. See bug 536141. + */ + if (function_list != NULL) { + guint64 page_id = webkit_web_page_get_id (web_page); + if (page_id != -1) { + g_slist_foreach(function_list, (GFunc)execute_browser_functions, GUINT_TO_POINTER(page_id)); + } else { + g_warning("SWT web extension: there was an error fetching the page ID in the object_cleared callback.\n"); + } + } } -static void web_page_created_callback(WebKitWebExtension *extension, WebKitWebPage *web_page, gpointer user_data) { - // Observation. This seems to be called only once. +static void +webkitgtk_extension_handle_method_call (GDBusConnection *connection, const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) { + gboolean result = FALSE; + const gchar *script; + const gchar *url; + guint64 page_id; + // Check method names + if (g_strcmp0(method_name, "webkitgtk_extension_register_function") == 0) { + g_variant_get(parameters, "(t&s&s)", &page_id, &script, &url); + if (page_id != -1) { + result = TRUE; + // Return before processing the linked list, to prevent DBus from hanging + g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", result)); + add_browser_function(page_id, script, url); + return; + } + g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", result)); + return; + } + if (g_strcmp0(method_name, "webkitgtk_extension_deregister_function") == 0) { + g_variant_get(parameters, "(t&s&s)", &page_id, &script, &url); + if (page_id != -1) { + result = TRUE; + // Return before processing the linked list, to prevent DBus from hanging + g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", result)); + remove_browser_function(page_id, script, url); + return; + } + g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", result)); + return; + } + g_error ("UNKNOWN method %s\n", method_name); } -G_MODULE_EXPORT void -webkit_web_extension_initialize_with_user_data(WebKitWebExtension *extension, GVariant *user_data) -{ +static const GDBusInterfaceVTable interface_vtable = {webkitgtk_extension_handle_method_call, NULL, NULL}; + +static void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { + dbus_interface = g_dbus_node_info_lookup_interface(dbus_node, WEBKITGTK_EXTENSION_DBUS_INTERFACE); + guint registration_id = g_dbus_connection_register_object(connection, + webkitgtk_extension_dbus_path, + dbus_interface, + &interface_vtable, NULL, /* user_data */ + NULL, /* user_data_free_func */ + NULL); /* GError** */ + g_assert(registration_id > 0); + + GVariant *g_var_result = callMainProc("webkitWebExtensionIdentifer", g_variant_new ("(ss)", + webkitgtk_extension_dbus_name, webkitgtk_extension_dbus_path)); + if (g_variant_is_of_type(g_var_result, G_VARIANT_TYPE_TUPLE)) { + unpack_browser_function_array(g_variant_get_child_value(g_var_result, 0)); + } else { + g_warning("SWT web extension: on_bus_acquired return value from SWT was an unexpected type (not a tuple).\n"); + } + return; +} + +G_MODULE_EXPORT void webkit_web_extension_initialize_with_user_data(WebKitWebExtension *extension, GVariant *user_data) { // To debug this extension: // - ensure this is build with debug flags (look for '-g*' in make_linux, or 'SWT_LIB_DEBUG' macro. // - connect to WebKitWebProcess with pid of this extension. Use below to print it: - // g_print("Webext pid: %d (To debug, attach to WebKitWebProcess with this pid)\n", getpid()); + // g_print("Webext pid: %d (To debug, attach to WebKitWebProcess with this pid)\n", getpid()); + this_extension = extension; parentUniqueId = g_variant_get_int32(user_data); g_signal_connect(extension, "page-created", G_CALLBACK(web_page_created_callback), NULL); // To hook into javascript execution: g_signal_connect (webkit_script_world_get_default (), "window-object-cleared", G_CALLBACK (window_object_cleared_callback), NULL); + + // Create DBus server for this web extension + webkitgtk_extension_dbus_name = combineStrInt((char *) WEBKITGTK_EXTENSION_DBUS_NAME_PREFIX, (gint32) getpid()); + webkitgtk_extension_dbus_path = combineStrInt((char *) WEBKITGTK_EXTENSION_DBUS_PATH_PREFIX, (gint32) getpid()); + + dbus_introspection_xml = g_new (gchar, strlen(dbus_introspection_xml_template) + strlen(WEBKITGTK_EXTENSION_DBUS_INTERFACE) + 1); + g_sprintf (dbus_introspection_xml, dbus_introspection_xml_template, WEBKITGTK_EXTENSION_DBUS_INTERFACE); + dbus_node = g_dbus_node_info_new_for_xml (dbus_introspection_xml, NULL); + g_assert (dbus_node != NULL); + + guint owner_id; + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + webkitgtk_extension_dbus_name, + G_BUS_NAME_OWNER_FLAGS_REPLACE | G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, + on_bus_acquired, + NULL, /* on_name_acquired */ + NULL, /* on_name_lost */ + NULL, + NULL); + g_assert (owner_id != 0); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_extension.h b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_extension.h index 72a507ba81..4d2713cd76 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_extension.h +++ b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/library/webkitgtk_extension.h @@ -1,4 +1,4 @@ -/******************************************************************************* + /******************************************************************************* * Copyright (c) 2017 Red Hat and others. All rights reserved. * The contents of this file are made available under the terms * of the GNU Lesser General Public License (LGPL) Version 2.1 that @@ -12,12 +12,9 @@ * Contributors: * Red Hat - initial API and implementation *******************************************************************************/ - - #ifndef INC_webkit_extension_H #define INC_webkit_extension_H - #include <string.h> #include <glib.h> @@ -29,12 +26,10 @@ #include <unistd.h> #include <stdio.h> - // These 2 are only for getpid(); #include <sys/types.h> #include <unistd.h> - #include <webkit2/webkit-web-extension.h> #include <JavaScriptCore/JavaScript.h> @@ -42,22 +37,40 @@ #include <JavaScriptCore/JSObjectRef.h> #include <JavaScriptCore/JSStringRef.h> - - - - - - - - - - - - - - - - - +#define WEBKITGTK_EXTENSION_DBUS_NAME_PREFIX "org.eclipse.swt.webkitgtk_extension" +#define WEBKITGTK_EXTENSION_DBUS_PATH_PREFIX "/org/eclipse/swt/webkitgtk_extension/gdbus/" +#define WEBKITGTK_EXTENSION_DBUS_INTERFACE "org.eclipse.swt.webkitgtk_extension.gdbusInterface" + +#define WEBKIT_MAIN_PROCESS_DBUS_NAME_PREFIX "org.eclipse.swt" +#define WEBKIT_MAIN_PROCESS_DBUS_PATH_PREFIX "/org/eclipse/swt/gdbus/" + +static gchar* webkitgtk_extension_dbus_name; +static gchar* webkitgtk_extension_dbus_path; + +static WebKitWebExtension *this_extension; + +static GDBusNodeInfo *dbus_node; +static GDBusInterfaceInfo *dbus_interface; +static gchar* dbus_introspection_xml; +static gchar* dbus_introspection_xml_template = +"<node>" + "<interface name='%s'>" + + "<method name='webkitgtk_extension_register_function'>" + "<arg type='t' name='page_id' direction='in'/>" + "<arg type='s' name='script' direction='in'/>" + "<arg type='s' name='url' direction='in'/>" + "<arg type='b' name='result' direction='out'/>" + "</method>" + + "<method name='webkitgtk_extension_deregister_function'>" + "<arg type='t' name='page_id' direction='in'/>" + "<arg type='s' name='script' direction='in'/>" + "<arg type='s' name='url' direction='in'/>" + "<arg type='b' name='result' direction='out'/>" + "</method>" + + "</interface>" +"</node>"; #endif /*INC_webkit_extension_H*/ 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 32ad50e13e..84ee7e587d 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 @@ -167,6 +167,7 @@ char * WebKitGTK_nativeFunctionNames[] = { "_1webkit_1web_1view_1get_1load_1status", "_1webkit_1web_1view_1get_1main_1frame", "_1webkit_1web_1view_1get_1main_1resource", + "_1webkit_1web_1view_1get_1page_1id", "_1webkit_1web_1view_1get_1progress", "_1webkit_1web_1view_1get_1settings", "_1webkit_1web_1view_1get_1title", 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 dab44c08fe..22196ffafa 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 @@ -177,6 +177,7 @@ typedef enum { _1webkit_1web_1view_1get_1load_1status_FUNC, _1webkit_1web_1view_1get_1main_1frame_FUNC, _1webkit_1web_1view_1get_1main_1resource_FUNC, + _1webkit_1web_1view_1get_1page_1id_FUNC, _1webkit_1web_1view_1get_1progress_FUNC, _1webkit_1web_1view_1get_1settings_FUNC, _1webkit_1web_1view_1get_1title_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 81b218af3c..470ae9959f 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 @@ -127,6 +127,7 @@ class WebKit extends WebBrowser { */ long /*int*/ webView; long /*int*/ scrolledWindow; + long pageId; /** Webkit1 only. Used by the externalObject for javascript callback to java. */ long /*int*/ webViewData; @@ -419,12 +420,52 @@ class WebKit extends WebBrowser { @Override public void createFunction(BrowserFunction function) { if (WEBKIT2) { - if (!Webkit2Extension.gdbus_init()) { - System.err.println("SWT Webkit Warning: Webkit extension failed to initialize. BrowserFunction will not work.: " + function.name); + if (!WebkitGDBus.initialized) { + System.err.println("SWT webkit: WebkitGDBus and/or Webkit2Extension not loaded, BrowserFunction will not work." + + "Tried to create "+ function.name); return; } } super.createFunction(function); + if (WEBKIT2) { + String url = this.getUrl().isEmpty() ? "nullURL" : this.getUrl(); + /* + * If the proxy to the extension has not yet been loaded, store the BrowserFunction page ID, + * function string, and URL in a HashMap. Once the proxy to the extension is loaded, these + * functions will be sent to and registered in the extension. + */ + if (!WebkitGDBus.proxyToExtension) { + WebkitGDBus.functionsPending = true; + ArrayList<ArrayList<String>> list = new ArrayList<>(); + ArrayList<String> functionAndUrl = new ArrayList<>(); + functionAndUrl.add(0, function.functionString); + functionAndUrl.add(1, url); + list.add(functionAndUrl); + ArrayList<ArrayList<String>> existing = WebkitGDBus.pendingBrowserFunctions.putIfAbsent(this.pageId, list); + if (existing != null) { + existing.add(functionAndUrl); + } + } else { + // If the proxy to the extension is already loaded, register the function in the extension via DBus + boolean successful = webkit_extension_modify_function(this.pageId, function.functionString, url, "register"); + if (!successful) { + System.err.println("SWT webkit: failure registering BrowserFunction " + function.name); + } + } + } + } + + @Override + public void destroyFunction (BrowserFunction function) { + // Only deregister functions if the proxy to the extension has been loaded + if (WebkitGDBus.proxyToExtension && WEBKIT2) { + String url = this.getUrl().isEmpty() ? "nullURL" : this.getUrl(); + boolean successful = webkit_extension_modify_function(this.pageId, function.functionString, url, "deregister"); + if (!successful) { + System.err.println("SWT webkit: failure deregistering BrowserFunction from extension " + function.name); + } + } + super.destroyFunction(function); } private static String getInternalErrorMsg () { @@ -458,6 +499,7 @@ class WebKit extends WebBrowser { static class Webkit2Extension { /** Note, if updating this, you need to change it also in webkitgtk_extension.c */ private static final String javaScriptFunctionName = "webkit2callJava"; // $NON-NLS-1$ + private static final String webkitWebExtensionIdentifier = "webkitWebExtensionIdentifer"; // $NON-NLS-1$ private static Callback initializeWebExtensions_callback; private static int uniqueID = OS.getpid(); @@ -470,6 +512,9 @@ class WebKit extends WebBrowser { static String getJavaScriptFunctionName() { return javaScriptFunctionName; } + static String getWebExtensionIdentifer() { + return webkitWebExtensionIdentifier; + } static String getJavaScriptFunctionDeclaration(long /*int*/ webView) { return "if (!window.callJava) {\n" + " window.callJava = function callJava(index, token, args) {\n" @@ -479,6 +524,12 @@ class WebKit extends WebBrowser { } static void init() { + /* + * Initialize GDBus before the extension, as the extension initialization callback at the C level + * sends data back to SWT via GDBus. Failure to load GDBus here will result in crashes. + * See bug 536141. + */ + gdbus_init(); initializeWebExtensions_callback = new Callback(Webkit2Extension.class, "initializeWebExtensions_callback", void.class, new Type [] {long.class, long.class}); if (initializeWebExtensions_callback.getAddress() == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS); if (WebKitGTK.webkit_get_minor_version() >= 4) { // Callback exists only since 2.04 @@ -1199,6 +1250,7 @@ public void create (Composite parent, int style) { OS.g_signal_connect (webView, OS.focus_out_event, JSDOMEventProc.getAddress (), WIDGET_EVENT); // if connecting any other special gtk event to webkit, add SWT.* to w2_passThroughSwtEvents above. } + this.pageId = WebKitGTK.webkit_web_view_get_page_id (webView); if (WEBKIT1) { OS.g_signal_connect (webView, OS.button_press_event, JSDOMEventProc.getAddress (), 0); OS.g_signal_connect (webView, OS.button_release_event, JSDOMEventProc.getAddress (), 0); @@ -1522,6 +1574,46 @@ void nonBlockingExecute(String script) { } } +/** + * Modifies a BrowserFunction in the web extension. This method can be used to register/deregister BrowserFunctions + * in the web extension, so that those BrowserFunctions are executed upon triggering of the object_cleared callback (in + * the extension, not in Java). + * + * This function will return true if: the operation succeeds synchronously, or if the synchronous call timed out and an + * asynchronous call was performed instead. All other cases will return false. + * + * Supported actions: "register" and "deregister" + * + * @param pageId the page ID of the WebKit instance/web page + * @param function the function string + * @param url the URL + * @param action the action being performed on the function, which will be used to form the DBus method name. + * @return true if the action succeeded (or was performed asynchronously), false if it failed + */ +private boolean webkit_extension_modify_function (long pageId, String function, String url, String action){ + long /*int*/ args[] = { OS.g_variant_new_uint64(pageId), + OS.g_variant_new_string (Converter.javaStringToCString(function)), + OS.g_variant_new_string (Converter.javaStringToCString(url))}; + final long /*int*/ argsTuple = OS.g_variant_new_tuple(args, args.length); + if (argsTuple == 0) return false; + String dbusMethodName = "webkitgtk_extension_" + action + "_function"; + Object returnVal = WebkitGDBus.callExtensionSync(argsTuple, dbusMethodName); + if (returnVal instanceof Boolean) { + return (Boolean) returnVal; + } else if (returnVal instanceof String) { + String returnString = (String) returnVal; + /* + * Call the extension asynchronously if a synchronous call times out. + * Note: this is a pretty rare case, and usually only happens when running test cases. + * See bug 536141. + */ + if ("timeout".equals(returnString)) { + return WebkitGDBus.callExtensionAsync(argsTuple, dbusMethodName); + } + } + return false; +} + @Override public boolean execute (String script) { if (WEBKIT2){ @@ -3284,7 +3376,6 @@ long /*int*/ webkit_load_changed (long /*int*/ web_view, int status, long user_d return handleLoadCommitted (uri, true); } case WebKitGTK.WEBKIT2_LOAD_FINISHED: { - registerBrowserFunctions(); // Bug 508217 addEventHandlers (web_view, true); long /*int*/ title = WebKitGTK.webkit_web_view_get_title (webView); diff --git a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/browser/WebkitGDBus.java b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/browser/WebkitGDBus.java index de2dc641ea..d7ec16e391 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/browser/WebkitGDBus.java +++ b/bundles/org.eclipse.swt/Eclipse SWT WebKit/gtk/org/eclipse/swt/browser/WebkitGDBus.java @@ -15,6 +15,8 @@ package org.eclipse.swt.browser; +import java.util.*; + import org.eclipse.swt.*; import org.eclipse.swt.internal.*; import org.eclipse.swt.internal.gtk.*; @@ -33,11 +35,29 @@ import org.eclipse.swt.internal.gtk.*; */ class WebkitGDBus { private static String DBUS_SERVICE_NAME; - private static final String DBUS_OBJECT_NAME = "/org/eclipse/swt/gdbus"; + private static final String DBUS_OBJECT_PATH = "/org/eclipse/swt/gdbus"; private static final String INTERFACE_NAME = "org.eclipse.swt.gdbusInterface"; + private static final String EXTENSION_INTERFACE_NAME = "org.eclipse.swt.webkitgtk_extension.gdbusInterface"; + private static String EXTENSION_DBUS_NAME; + private static String EXTENSION_DBUS_PATH; /** Accepted methods over gdbus */ private static final String webkit2callJava = WebKit.Webkit2Extension.getJavaScriptFunctionName(); + private static final String webkitWebExtensionIdentifier = WebKit.Webkit2Extension.getWebExtensionIdentifer(); + + /** Proxy connection to the web extension.*/ + static long /*int*/ proxy; + /** A field that is set to true if the proxy connection has been established, false otherwise */ + static boolean proxyToExtension; + /** Set to true if there are <code>BrowserFunction</code> objects waiting to be registered with the web extension.*/ + static boolean functionsPending; + /** + * HashMap that stores any BrowserFunctions which have been created but not yet registered with the web extension. + * These functions will be registered with the web extension as soon as the proxy to the extension is set up. + * + * The format of the HashMap is (page ID, list of function string and URL). + */ + static HashMap<Long, ArrayList<ArrayList<String>>> pendingBrowserFunctions = new HashMap<>(); /** @@ -77,6 +97,11 @@ class WebkitGDBus { + " <arg type='" + OS.DBUS_TYPE_SINGLE_COMPLETE + "' name='arguments' direction='in'/>" + " <arg type='" + OS.DBUS_TYPE_SINGLE_COMPLETE + "' name='result' direction='out'/>" + " </method>" + + " <method name='" + webkitWebExtensionIdentifier + "'>" + + " <arg type='"+ OS.DBUS_TYPE_STRING + "' name='webExtensionDbusName' direction='in'/>" + + " <arg type='"+ OS.DBUS_TYPE_STRING + "' name='webExtensionDbusPath' direction='in'/>" + + " <arg type='"+ OS.DBUS_TYPE_STRUCT_ARRAY_BROWSER_FUNCS + "' name='result' direction='out'/>" + + " </method>" + " </interface>" + "</node>"; @@ -96,6 +121,9 @@ class WebkitGDBus { private static Callback onNameLostCallback; private static Callback handleMethodCallback; + /** Callback for asynchronous proxy calls to the extension */ + private static Callback callExtensionAsyncCallback; + static { onBusAcquiredCallback = new Callback (WebkitGDBus.class, "onBusAcquiredCallback", 3); //$NON-NLS-1$ if (onBusAcquiredCallback.getAddress () == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS); @@ -108,9 +136,12 @@ class WebkitGDBus { handleMethodCallback = new Callback (WebkitGDBus.class, "handleMethodCallback", 8); //$NON-NLS-1$ if (handleMethodCallback.getAddress () == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS); + + callExtensionAsyncCallback = new Callback (WebkitGDBus.class, "callExtensionAsyncCallback", 3); //$NON-NLS-1$ + if (callExtensionAsyncCallback.getAddress () == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS); } - static private boolean initialized; + static boolean initialized; /** This method is not intended to be referenced by clients. Internal class. */ static void init(String uniqueId) { @@ -169,7 +200,7 @@ class WebkitGDBus { // Other times it validates it fine. We ignore for now as 32bit will be dropped anyway. OS.g_dbus_connection_register_object( connection, - Converter.javaStringToCString(DBUS_OBJECT_NAME), + Converter.javaStringToCString(DBUS_OBJECT_PATH), interface_info, vtable, 0, // user_data @@ -177,7 +208,7 @@ class WebkitGDBus { error); if (error[0] != 0) { - System.err.println("SWT WebkitGDBus: Failed to register object: " + DBUS_OBJECT_NAME); + System.err.println("SWT WebkitGDBus: Failed to register object: " + DBUS_OBJECT_PATH); return 0; } } @@ -236,19 +267,123 @@ class WebkitGDBus { String java_method_name = Converter.cCharPtrToJavaString(method_name, false); Object result = null; - if (java_method_name != null && java_method_name.equals(webkit2callJava)) { - try { - Object [] java_parameters = (Object []) convertGVariantToJava(gvar_parameters); - result = WebKit.Webkit2Extension.webkit2callJavaCallback(java_parameters); - } catch (Exception e) { - // gdbus should always return to prevent extension from hanging. - result = (String) WebBrowser.CreateErrorString (e.getLocalizedMessage ()); - System.err.println("SWT Webkit: Exception occured in Webkit2 callback logic. Bug?"); + if (java_method_name != null) { + if (java_method_name.equals(webkit2callJava)) { + try { + Object [] java_parameters = (Object []) convertGVariantToJava(gvar_parameters); + result = WebKit.Webkit2Extension.webkit2callJavaCallback(java_parameters); + } catch (Exception e) { + // gdbus should always return to prevent extension from hanging. + result = (String) WebBrowser.CreateErrorString (e.getLocalizedMessage ()); + System.err.println("SWT Webkit: Exception occured in Webkit2 callback logic. Bug?"); + } + } else if (java_method_name.equals(webkitWebExtensionIdentifier)) { + Object [] nameArray = (Object []) convertGVariantToJava(gvar_parameters); + if (nameArray [0] != null && nameArray[0] instanceof String) EXTENSION_DBUS_NAME = (String) nameArray[0]; + if (nameArray [1] != null && nameArray[1] instanceof String) EXTENSION_DBUS_PATH = (String) nameArray[1]; + proxyToExtension = proxyToExtensionInit(); + if (proxyToExtension) { + invokeReturnValueExtensionIdentifier(pendingBrowserFunctions, invocation); + } else { + invokeReturnValueExtensionIdentifier(null, invocation); + System.err.println("SWT webkit: proxy to web extension failed to load, BrowserFunction may not work."); + } + return 0; + } + } else { + result = (String) "SWT webkit: GDBus called an unknown method?"; + System.err.println("SWT webkit: Received a call from an unknown method: " + java_method_name); + } + invokeReturnValue(result, invocation); + return 0; + } + + @SuppressWarnings("unused") + private static long /*int*/ callExtensionAsyncCallback (long /*int*/ source_object, long /*int*/ res, long /*int*/ user_data) { + long /*int*/[] gerror = new long /*int*/[1]; + long /*int*/ result = OS.g_dbus_proxy_call_finish (proxy, res, gerror); + if (gerror[0] != 0){ + long /*int*/ errMsg = OS.g_error_get_message(gerror[0]); + String msg = Converter.cCharPtrToJavaString(errMsg, false); + System.err.println("SWT webkit: There was an error executing something asynchronously with the extension (Java callback)."); + System.err.println("SWT webkit: the error message provided is " + msg); + OS.g_error_free(gerror[0]); + } + return 0; + } + + /** + * Returns a GVariant to the DBus invocation of the extension identifier method. When the extension + * is initialized it sends a DBus message to the SWT webkit instance. As a return value, the SWT webkit + * instance sends any BrowserFunctions that have been registered. If no functions have been registered, + * an "empty" function with a page ID of -1 is sent. + * + * @param map the HashMap of BrowserFunctions waiting to be registered in the extension, or null + * if you'd like to explicitly send an empty function signature + * @param invocation the GDBus invocation to return the value on + */ + private static void invokeReturnValueExtensionIdentifier (HashMap<Long, ArrayList<ArrayList<String>>> map, + long /*int*/ invocation) { + long /*int*/ resultGVariant; + long /*int*/ builder; + long /*int*/ type = OS.g_variant_type_new(OS.G_VARIANT_TYPE_ARRAY_BROWSER_FUNCS); + builder = OS.g_variant_builder_new(type); + if (builder == 0) return; + Object [] tupleArray = new Object[3]; + boolean sendEmptyFunction; + if (map == null) { + sendEmptyFunction = true; + } else { + sendEmptyFunction = map.isEmpty() && !functionsPending; + } + /* + * No functions to register, send a page ID of -1 and empty strings. + */ + if (sendEmptyFunction) { + tupleArray[0] = (long)-1; + tupleArray[1] = ""; + tupleArray[2] = ""; + long /*int*/ tupleGVariant = convertJavaToGVariant(tupleArray); + if (tupleGVariant != 0) { + OS.g_variant_builder_add_value(builder, tupleGVariant); + } else { + System.err.println("SWT webkit: error creating empty BrowserFunction GVariant tuple, skipping."); } } else { - result = (String) "SWT Webkit: Gdbus called an unknown method?"; - System.err.println("SWT WebkitGDBus: Received a call from an unknown method: " + java_method_name); + for (long id : map.keySet()) { + ArrayList<ArrayList<String>> list = map.get(id); + if (list != null) { + for (ArrayList<String> stringList : list) { + Object [] stringArray = stringList.toArray(); + if (stringArray.length > 2) { + System.err.println("SWT webkit: String array with BrowserFunction and URL should never have" + + "more than 2 Strings"); + } + tupleArray[0] = id; + System.arraycopy(stringArray, 0, tupleArray, 1, 2); + long /*int*/ tupleGVariant = convertJavaToGVariant(tupleArray); + if (tupleGVariant != 0) { + OS.g_variant_builder_add_value(builder, tupleGVariant); + } else { + System.err.println("SWT webkit: error creating BrowserFunction GVariant tuple, skipping."); + } + } + } + } + } + resultGVariant = OS.g_variant_builder_end(builder); + String typeString = Converter.cCharPtrToJavaString(OS.g_variant_get_type_string(resultGVariant), false); + if (!OS.DBUS_TYPE_STRUCT_ARRAY_BROWSER_FUNCS.equals(typeString)) { + System.err.println("An error packaging the GVariant occurred: type mismatch."); } + long /*int*/ [] variants = {resultGVariant}; + long /*int*/ finalGVariant = OS.g_variant_new_tuple(variants, 1); + OS.g_dbus_method_invocation_return_value(invocation, finalGVariant); + OS.g_variant_builder_unref(builder); + return; + } + + private static void invokeReturnValue (Object result, long /*int*/ invocation) { long /*int*/ resultGVariant = 0; try { resultGVariant = convertJavaToGVariant(new Object [] {result}); // Result has to be a tuple. @@ -258,10 +393,98 @@ class WebkitGDBus { resultGVariant = convertJavaToGVariant(new Object [] {errMsg}); } OS.g_dbus_method_invocation_return_value(invocation, resultGVariant); - return 0; // void return value. + return; // void return value. } + /** + * Initializes the proxy connection to the web extension. + * + * @return true if establishing the proxy connections succeeded, + * false otherwise + */ + private static boolean proxyToExtensionInit() { + if (proxy != 0) { + return true; + } else { + if (EXTENSION_DBUS_NAME != null && EXTENSION_DBUS_PATH != null) { + long /*int*/ [] error = new long /*int*/ [1]; + byte [] name = Converter.javaStringToCString(EXTENSION_DBUS_NAME); + byte [] path = Converter.javaStringToCString(EXTENSION_DBUS_PATH); + byte [] interfaceName = Converter.javaStringToCString(EXTENSION_INTERFACE_NAME); + proxy = OS.g_dbus_proxy_new_for_bus_sync(OS.G_BUS_TYPE_SESSION, OS.G_DBUS_PROXY_FLAGS_NONE, 0, name, path, interfaceName, 0, error); + if (error[0] != 0) { + long /*int*/ errMsg = OS.g_error_get_message(error[0]); + String msg = Converter.cCharPtrToJavaString(errMsg, false); + OS.g_error_free(error[0]); + System.err.println("SWT webkit: there was an error establishing the proxy connection to the extension. " + + " The error is " + msg); + return false; + } else { + return true; + } + } + } + return false; + } + + /** + * Calls the web extension synchronously. Returns true if the operation succeeded, and false + * otherwise (or if the operation times out). + * + * @param params a pointer to the GVariant containing the parameters + * @param methodName a String representing the DBus method name in the extension + * @return an Object representing the return value from DBus in boolean form + */ + static Object callExtensionSync (long /*int*/ params, String methodName) { + long /*int*/[] gerror = new long /*int*/ [1]; // GError ** + long /*int*/ gVariant = OS.g_dbus_proxy_call_sync(proxy, Converter.javaStringToCString(methodName), + params, OS.G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, 0, gerror); + if (gerror[0] != 0) { + long /*int*/ errMsg = OS.g_error_get_message(gerror[0]); + String msg = Converter.cCharPtrToJavaString(errMsg, false); + /* + * Don't print console warnings for timeout errors, as we can handle these ourselves. + * Note, most timeout errors happen only when running test cases, not during "normal" use. + */ + if (msg != null && (!msg.contains("Timeout") && !msg.contains("timeout"))) { + System.err.println("SWT webkit: There was an error executing something synchronously with the extension."); + System.err.println("SWT webkit: The error message is: " + msg); + return (Object) false; + } + OS.g_error_free(gerror[0]); + return (Object) "timeout"; + } + Object resultObject = gVariant != 0 ? convertGVariantToJava(gVariant) : (Object) false; + // Sometimes we get back tuples from GDBus, which get converted into Object arrays. In this case + // we only care about the first value, since the extension never returns anything more than that. + if (resultObject instanceof Object[]) { + return ((Object []) resultObject)[0]; + } + return resultObject; + } + /** + * Calls the web extension asynchronously. Note, this method returning true does not + * guarantee the operation's success, it only means no errors occurred. + * + * @param params a pointer to the GVariant containing the parameters + * @param methodName a String representing the DBus method name in the extension + * @return true if the extension was called without errors, false otherwise + */ + static boolean callExtensionAsync (long /*int*/ params, String methodName) { + long /*int*/[] gerror = new long /*int*/ [1]; // GError ** + OS.g_dbus_proxy_call(proxy, Converter.javaStringToCString(methodName), + params, OS.G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, 0, callExtensionAsyncCallback.getAddress(), gerror); + if (gerror[0] != 0) { + long /*int*/ errMsg = OS.g_error_get_message(gerror[0]); + String msg = Converter.cCharPtrToJavaString(errMsg, false); + System.err.println("SWT webkit: There was an error executing something asynchronously with the extension."); + System.err.println("SWT webkit: The error message is: " + msg); + OS.g_error_free(gerror[0]); + return false; + } + return true; + } /* TYPE NOTES * @@ -314,6 +537,10 @@ class WebkitGDBus { return new Double(OS.g_variant_get_double(gVariant)); } + if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_UINT64)){ + return new Long(OS.g_variant_get_uint64(gVariant)); + } + if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_STRING)){ return Converter.cCharPtrToJavaString(OS.g_variant_get_string(gVariant, null), false); } @@ -346,6 +573,10 @@ class WebkitGDBus { return OS.g_variant_new_byte(WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_NULL); // see: WebKitGTK.java 'TYPE NOTES' } + if (javaObject instanceof Long) { + return OS.g_variant_new_uint64((Long) javaObject); + } + if (javaObject instanceof String) { return OS.g_variant_new_string (Converter.javaStringToCString((String) javaObject)); } 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 11ccd2261f..c1df6956a2 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 @@ -1722,6 +1722,17 @@ public static final long /*int*/ webkit_web_view_get_main_frame (long /*int*/ we } /** @method flags=dynamic */ +public static final native long /*int*/ _webkit_web_view_get_page_id (long /*int*/ web_view); +public static final long /*int*/ webkit_web_view_get_page_id (long /*int*/ web_view) { + lock.lock(); + try { + return _webkit_web_view_get_page_id (web_view); + } finally { + lock.unlock(); + } +} + +/** @method flags=dynamic */ public static final native double _webkit_web_view_get_progress (long /*int*/ web_view); public static final double webkit_web_view_get_progress (long /*int*/ web_view) { assert WEBKIT1 : Webkit1AssertMsg; |