1 /*******************************************************************************
2  * Copyright (c) 2017 Red Hat and others. All rights reserved.
3  * The contents of this file are made available under the terms
4  * of the GNU Lesser General Public License (LGPL) Version 2.1 that
5  * accompanies this distribution (lgpl-v21.txt).  The LGPL is also
6  * available at http://www.gnu.org/licenses/lgpl.html.  If the version
7  * of the LGPL at http://www.gnu.org is different to the version of
8  * the LGPL accompanying this distribution and there is any conflict
9  * between the two license versions, the terms of the LGPL accompanying
10  * this distribution shall govern.
11  *
12  * Contributors:
13  *     Red Hat - initial API and implementation
14  *******************************************************************************/
15 
16 package org.eclipse.swt.browser;
17 
18 import java.util.*;
19 
20 import org.eclipse.swt.*;
21 import org.eclipse.swt.internal.*;
22 import org.eclipse.swt.internal.gtk.*;
23 import org.eclipse.swt.widgets.*;
24 
25 /**
26  * Logic for Webkit to interact with it's Webkit extension via GDBus.
27  *
28  * While this class supports quite a bit of GDBus and gvariant support, it is by no means a complete
29  * implementation and it's tailored to support Java to Javascript conversion. (E.g all Numbers are converted to Double).
30  * If this is ever to be used outside of Webkit, then care must be taken to deal with
31  * cases that are not currently implemented/used. See: WebKitGTK.java 'TYPE NOTES'
32  *
33  * For hygiene purposes, GVariant types should not be leaving this class. Convert on the way in/out.
34  *
35  * @category gdbus
36  */
37 class WebkitGDBus {
38 	/*
39 	 * If any of the GDBus names/paths/interfaces need to be changed, then they also need to be changed
40 	 * in the extension (webkitgtk_extension.c).
41 	 */
42 
43 	/* WEBKITGDBUS_DBUS_NAME isn't actually used but is here for informative purposes */
44 	@SuppressWarnings("unused")
45 	private static final String WEBKITGDBUS_DBUS_NAME = "org.eclipse.swt";
46 	private static final byte [] WEBKITGDBUS_OBJECT_PATH = Converter.javaStringToCString("/org/eclipse/swt/gdbus");
47 	private static final String WEBKITGDBUS_INTERFACE_NAME_JAVA = "org.eclipse.swt.gdbusInterface";
48 	private static final byte [] WEBKITGDBUS_INTERFACE_NAME = Converter.javaStringToCString("org.eclipse.swt.gdbusInterface");
49 
50 	/* Extension connection details, in byte [] form */
51 	private static final byte [] EXTENSION_DBUS_NAME = Converter.javaStringToCString("org.eclipse.swt.webkitgtk_extension");
52 	private static final byte [] EXTENSION_OBJECT_PATH = Converter.javaStringToCString("/org/eclipse/swt/webkitgtk_extension/gdbus");
53 	private static final byte [] EXTENSION_INTERFACE_NAME = Converter.javaStringToCString("org.eclipse.swt.webkitgtk_extension.gdbusInterface");
54 
55 	/** Extension GDBusServer client address */
56 	private static String EXTENSION_DBUS_SERVER_CLIENT_ADDRESS;
57 
58 	/* Accepted GDBus methods */
59 	private static final String webkit2callJava = WebKit.WebKitExtension.getJavaScriptFunctionName();
60 	private static final String webkitWebExtensionIdentifier = WebKit.WebKitExtension.getWebExtensionIdentifier();
61 
62 	/* Connections */
63 	/** GDBusConnection from the web extension */
64 	static long connectionFromExtension;
65 	/** GDBusConnection to the web extension */
66 	static long connectionToExtension;
67 	/** A field that is set to true if the proxy connection has been established, false otherwise. */
68 	static boolean connectionToExtensionCreated;
69 
70 	/* Server related objects */
71 	/** GDBusServer for the main SWT process */
72 	private static long gDBusServer = 0;
73 	/** GDBusAuthObserver for the server */
74 	private static long authObserver = 0;
75 	/** GUID of the GDBusServer */
76 	private static long guid = 0;
77 
78 	/** Display this GDBus class is "attached" to */
79 	static Display display;
80 
81 
82 	// BrowserFunction logic
83 	/** Set to true if there are <code>BrowserFunction</code> objects waiting to be registered with the web extension.*/
84 	static boolean functionsPending;
85 	/**
86 	 * HashMap that stores any BrowserFunctions which have been created but not yet registered with the web extension.
87 	 * These functions will be registered with the web extension as soon as the proxy to the extension is set up.
88 	 *
89 	 * The format of the HashMap is (page ID, list of function string and URL).
90 	 */
91 	static HashMap<Long, ArrayList<ArrayList<String>>> pendingBrowserFunctions = new HashMap<>();
92 
93 
94 	/**
95 	 * Interface is read/parsed at run time. No compilation with gdbus-code-gen necessary.
96 	 *
97 	 * Note,
98 	 * - When calling a method via g_dbus_proxy_call_sync(..g_variant params..),
99 	 *   the g_variant that describes parameters should only mirror incoming parameters.
100 	 *   Each type is a separate argument.
101 	 *   e.g:
102 	 *    g_variant                 xml:
103 	 *   "(si)", "string", 42 		   .. arg type='s'
104 	 *   								   .. arg type='i'
105 	 *
106 	 * - Nested parameters need to have a 2nd bracket around them.
107 	 *   e.g:
108 	 *    g_variant                    xml:
109 	 *    "((r)i)", *gvariant, 42			.. arg type='r'
110 	 *    									.. arg type='i'
111 	 *
112 	 * - '@' is a pointer to a gvariant. so '@r' is a pointer to nested type, i.e *gvariant
113 	 *
114 	 * To understand the mappings, it's good to understand DBus and GVariant's syntax:
115 	 * https://dbus.freedesktop.org/doc/dbus-specification.html#idm423
116 	 * https://developer.gnome.org/glib/stable/glib-GVariantType.html
117 	 *
118 	 * Be mindful about only using supported DBUS_TYPE_* , as convert* methods might fail otherwise.
119 	 * Alternatively, modify convert* methods.
120 	 */
121 	private static final byte [] WEBKITGDBUS_INTROSPECTION_XML = Converter.javaStringToCString(
122 			"<node>"
123 			+  "  <interface name='" + WEBKITGDBUS_INTERFACE_NAME_JAVA + "'>"
124 			+  "    <method name='" + webkit2callJava + "'>"
125 			+  "      <arg type='"+ OS.DBUS_TYPE_STRING + "' name='webViewPtr' direction='in'/>"
126 			+  "      <arg type='"+ OS.DBUS_TYPE_DOUBLE + "' name='index' direction='in'/>"
127 			+  "      <arg type='"+ OS.DBUS_TYPE_STRING + "' name='token' direction='in'/>"
128 			+  "      <arg type='" + OS.DBUS_TYPE_SINGLE_COMPLETE + "' name='arguments' direction='in'/>"
129 			+  "      <arg type='" + OS.DBUS_TYPE_SINGLE_COMPLETE + "' name='result' direction='out'/>"
130 			+  "    </method>"
131 			+  "	<method name='" + webkitWebExtensionIdentifier + "'>"
132 			+  "      <arg type='"+ OS.DBUS_TYPE_STRING + "' name='webExtensionServerAddress' direction='in'/>"
133 			+  "      <arg type='"+ OS.DBUS_TYPE_STRUCT_ARRAY_BROWSER_FUNCS + "' name='result' direction='out'/>"
134 			+  "    </method>"
135 			+  "  </interface>"
136 			+  "</node>");
137 
138 	/**
139 	 * GDBus/DBus doesn't have a notion of Null.
140 	 * To get around this, we use magic numbers to represent special cases.
141 	 * Currently this is specific to Webkit to deal with Javascript data type conversions.
142 	 * @category gdbus */
143 	private static final byte SWT_DBUS_MAGIC_NUMBER_EMPTY_ARRAY = 101;
144 	/** @category gdbus */
145 	private static final byte SWT_DBUS_MAGIC_NUMBER_NULL = 48;
146 
147 
148 	/** Callback for GDBus method handling */
149 	private static Callback handleMethodCB;
150 	/** Callback for incoming connections to WebkitGDBus' server */
151 	private static Callback newConnectionCB;
152 	/** Callback for creating a new connection to the web extension */
153 	private static Callback newConnectionToExtensionCB;
154 	/** Callback for authenticating connections to WebkitGDBus' server */
155 	private static Callback authenticatePeerCB;
156 	/** Callback for asynchronous proxy calls to the extension */
157 	private static Callback callExtensionAsyncCB;
158 
159 	static {
160 		handleMethodCB = new Callback (WebkitGDBus.class, "handleMethodCB", 8); //$NON-NLS-1$
161 		callExtensionAsyncCB = new Callback (WebkitGDBus.class, "callExtensionAsyncCB", 3); //$NON-NLS-1$
162 		newConnectionCB = new Callback (WebkitGDBus.class, "newConnectionCB", 3); //$NON-NLS-1$
163 		newConnectionToExtensionCB = new Callback (WebkitGDBus.class, "newConnectionToExtensionCB", 3); //$NON-NLS-1$
164 		authenticatePeerCB = new Callback (WebkitGDBus.class, "authenticatePeerCB", 4); //$NON-NLS-1$
165 	}
166 
167 	/** True iff the GDBusServer has been initialized */
168 	static boolean initialized;
169 	/** True iff this class has been attached to a Display */
170 	static boolean attachedToDisplay;
171 
172 	/** This method is in an internal class and is not intended to be referenced by clients. */
init()173 	static long init () {
174 		if (initialized) {
175 			return gDBusServer;
176 		}
177 		initialized = true;
178 
179 		// Generate address and GUID
180 		long address = construct_server_address();
181 		authObserver = OS.g_dbus_auth_observer_new();
182 		guid = OS.g_dbus_generate_guid();
183 
184 		// Create server
185 		long [] error = new long [1];
186 		gDBusServer = OS.g_dbus_server_new_sync(address, OS.G_DBUS_SERVER_FLAGS_NONE, guid, authObserver, 0, error);
187 
188 		// Connect authentication and incoming connections signals to the newly created server, and start it
189 		if (gDBusServer != 0) {
190 			OS.g_signal_connect(gDBusServer, OS.new_connection, newConnectionCB.getAddress(), 0);
191 			OS.g_signal_connect(authObserver, OS.authorize_authenticated_peer, authenticatePeerCB.getAddress(), 0);
192 			OS.g_dbus_server_start(gDBusServer);
193 		} else {
194 			System.err.println("SWT WebKitGDBus: error creating DBus server " + Display.extractFreeGError(error[0]));
195 		}
196 		return gDBusServer;
197 	}
198 
199 	/**
200 	 * Sets the Display for this class. This allows WebkitGDBus to attach its servers and related objects
201 	 * to the provided Display. When the Display is disposed, it will will release the GDBus related
202 	 * resources.
203 	 *
204 	 * @param displayToSet the Display to set
205 	 */
setDisplay(Display displayToSet)206 	static void setDisplay (Display displayToSet) {
207 		if (attachedToDisplay) {
208 			return;
209 		} else {
210 			display = displayToSet;
211 
212 			/*
213 			 * Add the GDBusServer, GDBusAuthObserver, and GUID to the Display.
214 			 * Note that we don't add the connections yet, because they likely
215 			 * don't exist. Those are added in callbacks as they come in.
216 			 */
217 			if (gDBusServer != 0) display.dBusServers.add(gDBusServer);
218 			if (authObserver != 0) display.dBusAuthObservers.add(authObserver);
219 			if (guid != 0) display.dBusGUIDS.add(guid);
220 			attachedToDisplay = true;
221 		}
222 	}
223 
224 	/**
225 	 * Constructs an address at which the GDBus server for the SWT main process
226 	 * can be reached.
227 	 *
228 	 * @return a pointer to the address
229 	 */
construct_server_address()230 	private static long construct_server_address () {
231 		// On Linux, the address required is used for as an abstract path. If abstract path is not supported
232 		// one must create & manage temporary directories. See Bug562443.
233 		byte [] address = Converter.wcsToMbcs("unix:tmpdir=/tmp/SWT-GDBusServer", true);
234 		long addressPtr = OS.g_malloc(address.length);
235 		C.memmove(addressPtr, address, address.length);
236 
237 		return addressPtr;
238 	}
239 
240 	/**
241 	 * This is called when a client call one of the GDBus methods.
242 	 *
243 	 * Developer note:
244 	 * This method can be reached directly from GDBus cmd utility:
245 	 * 	gdbus call --session --dest org.eclipse.swt<UNIQUE_ID> --object-path /org/eclipse/swt/gdbus --method org.eclipse.swt.gdbusInterface.HelloWorld
246 	 * where as you tab complete, you append the UNIQUE_ID.
247 	 *
248 	 * @param connection 	GDBusConnection
249 	 * @param sender 		const gchar
250 	 * @param object_path	const gchar
251 	 * @param interface_name const gchar
252 	 * @param method_name    const gchar
253 	 * @param gvar_parameters	 GVariant
254 	 * @param invocation     GDBusMethodInvocation
255 	 * @param user_data      gpointer
256 	 * @return
257 	 */
258 	@SuppressWarnings("unused") // callback not directly called by SWT
handleMethodCB( long connection, long sender, long object_path, long interface_name, long method_name, long gvar_parameters, long invocation, long user_data)259 	private static long handleMethodCB (
260 			long connection, long sender,
261 			long object_path, long interface_name,
262 			long method_name, long gvar_parameters,
263 			long invocation, long user_data) {
264 
265 		String java_method_name = Converter.cCharPtrToJavaString(method_name, false);
266 		Object result = null;
267 		if (java_method_name != null) {
268 			if (java_method_name.equals(webkit2callJava)) {
269 				try {
270 					Object [] java_parameters = (Object []) convertGVariantToJava(gvar_parameters);
271 					result = WebKit.WebKitExtension.webkit2callJavaCallback(java_parameters);
272 				} catch (Exception e) {
273 					// gdbus should always return to prevent extension from hanging.
274 					result = (String) WebBrowser.CreateErrorString (e.getLocalizedMessage ());
275 					System.err.println("SWT WebkitGDBus: Exception occured in Webkit2 callback logic.");
276 				}
277 			} else if (java_method_name.equals(webkitWebExtensionIdentifier)) {
278 				Object [] serverAddress = (Object []) convertGVariantToJava(gvar_parameters);
279 				if (serverAddress[0] != null && serverAddress[0] instanceof String) {
280 					EXTENSION_DBUS_SERVER_CLIENT_ADDRESS = (String) serverAddress[0];
281 					// Connect to the extension's server by creating a connection asynchronously
282 					createConnectionToExtension();
283 					/*
284 					 * Return any pending BrowserFunctions that were created before WebkitGDBus
285 					 * was initialized.
286 					 */
287 					invokeReturnValueExtensionIdentifier(pendingBrowserFunctions, invocation);
288 				} else {
289 					System.err.println("SWT WebkitGDBus: error in web extension identification process."
290 							+ " BrowserFunction may not work.");
291 				}
292 				return 0;
293 			}
294 		} else {
295 			result = (String) "SWT WebkitGDBus: GDBus called an unknown method?";
296 			System.err.println("SWT WebkitGDBus: Received a call from an unknown method: " + java_method_name);
297 		}
298 		invokeReturnValue(result, invocation);
299 		return 0;
300 	}
301 
302 	@SuppressWarnings("unused") // callback not directly called by SWT
callExtensionAsyncCB(long source_object, long result, long user_data)303 	private static long callExtensionAsyncCB (long source_object, long result, long user_data) {
304 		long [] error = new long [1];
305 		long gVariantResult = OS.g_dbus_connection_call_finish (connectionToExtension, result, error);
306 		if (error[0] != 0) {
307 			String msg = Display.extractFreeGError(error[0]);
308 			System.err.println("SWT WebkitGDBus: there was an error executing something asynchronously with the extension (Java callback).");
309 			System.err.println("SWT WebkitGDBus: the error message provided is " + msg);
310 		}
311 		OS.g_variant_unref(gVariantResult);
312 		return 0;
313 	}
314 
315 	@SuppressWarnings("unused") // callback not directly called by SWT
authenticatePeerCB(long observer, long stream, long credentials, long user_data)316 	private static long authenticatePeerCB (long observer, long stream, long credentials, long user_data) {
317 		boolean authorized = false;
318 		if (credentials != 0) {
319 			long error [] = new long [1];
320 			long ownCredentials = OS.g_credentials_new();
321 			authorized = OS.g_credentials_is_same_user(credentials, ownCredentials, error);
322 			if (error[0] != 0) {
323 				String msg = Display.extractFreeGError(error[0]);
324 				System.err.println("SWT WebkitGDBus: error authenticating client connection to server " + msg);
325 			}
326 			OS.g_object_unref(ownCredentials);
327 		}
328 		return authorized ? 1 : 0;
329 	}
330 
331 	@SuppressWarnings("unused") // callback not directly called by SWT
newConnectionCB(long server, long connection, long user_data)332 	private static long newConnectionCB (long server, long connection, long user_data) {
333 		long gdBusNodeInfo;
334 		long [] error = new long [1];
335 		gdBusNodeInfo = OS.g_dbus_node_info_new_for_xml(WEBKITGDBUS_INTROSPECTION_XML, error);
336 		if (gdBusNodeInfo == 0 || error[0] != 0) {
337 			System.err.println("SWT WebkitGDBus: failed to get introspection data");
338 		}
339 		assert gdBusNodeInfo != 0 : "SWT WebKitGDBus: introspection data should not be 0";
340 
341 		long interface_info = OS.g_dbus_node_info_lookup_interface(gdBusNodeInfo, WEBKITGDBUS_INTERFACE_NAME);
342 		long vtable [] = { handleMethodCB.getAddress(), 0, 0 };
343 
344 		OS.g_dbus_connection_register_object(
345 				connection,
346 				WEBKITGDBUS_OBJECT_PATH,
347 				interface_info,
348 				vtable,
349 				0, // user_data
350 				0, // user_data_free_func
351 				error);
352 
353 		if (error[0] != 0) {
354 			System.err.println("SWT WebKitGDBus: failed to register object: " + WEBKITGDBUS_OBJECT_PATH);
355 			return 0;
356 		}
357 
358 		// Ref the connection and add it to the Display's list of connections
359 		connectionFromExtension = OS.g_object_ref(connection);
360 		if (attachedToDisplay && display != null) {
361 			if (!display.dBusConnections.contains(connection)) display.dBusConnections.add(connectionFromExtension);
362 		}
363 		return 1;
364 	}
365 
366 	@SuppressWarnings("unused") // callback not directly called by SWT
newConnectionToExtensionCB(long sourceObject, long result, long user_data)367 	private static long newConnectionToExtensionCB (long sourceObject, long result, long user_data) {
368 		long [] error = new long [1];
369 		connectionToExtension = OS.g_dbus_connection_new_for_address_finish(result, error);
370 		if (error[0] != 0) {
371 			System.err.println("SWT WebKitGDBus: error finishing connection: " + Display.extractFreeGError(error[0]));
372 			return 0;
373 		} else {
374 			connectionToExtensionCreated = true;
375 		}
376 
377 		// Add the connections to the Display's list of connections
378 		if (attachedToDisplay && display != null) {
379 			if (!display.dBusConnections.contains(connectionToExtension)) display.dBusConnections.add(connectionToExtension);
380 		}
381 		return 0;
382 	}
383 
384 	/**
385 	 * Returns a GVariant to the DBus invocation of the extension identifier method. When the extension
386 	 * is initialized it sends a DBus message to the SWT webkit instance. As a return value, the SWT webkit
387 	 * instance sends any BrowserFunctions that have been registered. If no functions have been registered,
388 	 * an "empty" function with a page ID of -1 is sent.
389 	 *
390 	 * @param map the HashMap of BrowserFunctions waiting to be registered in the extension, or null
391 	 * if you'd like to explicitly send an empty function signature
392 	 * @param invocation the GDBus invocation to return the value on
393 	 */
invokeReturnValueExtensionIdentifier(HashMap<Long, ArrayList<ArrayList<String>>> map, long invocation)394 	private static void invokeReturnValueExtensionIdentifier (HashMap<Long, ArrayList<ArrayList<String>>> map,
395 			long invocation) {
396 		long resultGVariant;
397 		long builder;
398 		long type = OS.g_variant_type_new(OS.G_VARIANT_TYPE_ARRAY_BROWSER_FUNCS);
399 		builder = OS.g_variant_builder_new(type);
400 		if (builder == 0) return;
401 		Object [] tupleArray = new Object[3];
402 		boolean sendEmptyFunction;
403 		if (map == null) {
404 			sendEmptyFunction = true;
405 		} else {
406 			sendEmptyFunction = map.isEmpty() && !functionsPending;
407 		}
408 		/*
409 		 * No functions to register, send a page ID of -1 and empty strings.
410 		 */
411 		if (sendEmptyFunction) {
412 			tupleArray[0] = (long)-1;
413 			tupleArray[1] = "";
414 			tupleArray[2] = "";
415 			long tupleGVariant = convertJavaToGVariant(tupleArray);
416 			if (tupleGVariant != 0) {
417 				OS.g_variant_builder_add_value(builder, tupleGVariant);
418 			} else {
419 				System.err.println("SWT WebKitGDBus: error creating empty BrowserFunction GVariant tuple, skipping.");
420 			}
421 		} else {
422 			for (long id : map.keySet()) {
423 				ArrayList<ArrayList<String>> list = map.get(id);
424 				if (list != null) {
425 					for (ArrayList<String> stringList : list) {
426 						Object [] stringArray = stringList.toArray();
427 						if (stringArray.length > 2) {
428 							System.err.println("SWT WebKitGDBus: String array with BrowserFunction and URL should never have"
429 									+ "more than 2 Strings");
430 						}
431 						tupleArray[0] = id;
432 						System.arraycopy(stringArray, 0, tupleArray, 1, 2);
433 						long tupleGVariant = convertJavaToGVariant(tupleArray);
434 						if (tupleGVariant != 0) {
435 							OS.g_variant_builder_add_value(builder, tupleGVariant);
436 						} else {
437 							System.err.println("SWT WebKitGDBus: error creating BrowserFunction GVariant tuple, skipping.");
438 						}
439 					}
440 				}
441 			}
442 		}
443 		resultGVariant = OS.g_variant_builder_end(builder);
444 		String typeString = Converter.cCharPtrToJavaString(OS.g_variant_get_type_string(resultGVariant), false);
445 		if (!OS.DBUS_TYPE_STRUCT_ARRAY_BROWSER_FUNCS.equals(typeString)) {
446 			System.err.println("SWT WebKitGDBus: an error packaging the GVariant occurred: type mismatch.");
447 		}
448 		long [] variants = {resultGVariant};
449 		long finalGVariant = OS.g_variant_new_tuple(variants, 1);
450 		OS.g_dbus_method_invocation_return_value(invocation, finalGVariant);
451 		OS.g_variant_builder_unref(builder);
452 		OS.g_variant_type_free(type);
453 		return;
454 	}
455 
invokeReturnValue(Object result, long invocation)456 	private static void invokeReturnValue (Object result, long invocation) {
457 		long resultGVariant = 0;
458 		try {
459 			resultGVariant = convertJavaToGVariant(new Object [] {result}); // Result has to be a tuple.
460 		} catch (SWTException e) {
461 			// gdbus should always return to prevent extension from hanging.
462 			String errMsg = (String) WebBrowser.CreateErrorString (e.getLocalizedMessage ());
463 			resultGVariant = convertJavaToGVariant(new Object [] {errMsg});
464 		}
465 		OS.g_dbus_method_invocation_return_value(invocation, resultGVariant);
466 		return; // void return value.
467 	}
468 
469 	/**
470 	 * Asynchronously initializes a GDBusConnection to the web extension. Connection process
471 	 * will be confirmed when the newConnectionToExtension callback is called.
472 	 */
createConnectionToExtension()473 	private static void createConnectionToExtension() {
474 		byte [] address = Converter.javaStringToCString(EXTENSION_DBUS_SERVER_CLIENT_ADDRESS);
475 		long [] error = new long [1];
476 		// Create connection asynchronously to avoid deadlock issues
477 		OS.g_dbus_connection_new_for_address (address, OS.G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
478 				0, 0, newConnectionToExtensionCB.getAddress(), 0);
479 
480 		if (error[0] != 0) {
481 			System.err.println("SWT WebkitGDBus: error creating connection to the extension "
482 					+ Display.extractFreeGError(error[0]));
483 		}
484 	}
485 
486 	/**
487 	 * Calls the web extension synchronously. Returns true if the operation succeeded, and false
488 	 * otherwise (or if the operation times out).
489 	 *
490 	 * @param params a pointer to the GVariant containing the parameters
491 	 * @param methodName a String representing the DBus method name in the extension
492 	 * @return an Object representing the return value from DBus in boolean form
493 	 */
callExtensionSync(long params, String methodName)494 	static Object callExtensionSync (long params, String methodName) {
495 		long [] error = new long [1]; // GError **
496 		long gVariant = OS.g_dbus_connection_call_sync(connectionToExtension, EXTENSION_DBUS_NAME, EXTENSION_OBJECT_PATH,
497 				EXTENSION_INTERFACE_NAME, Converter.javaStringToCString(methodName), params,
498 				0, OS.G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, 0, error);
499 		if (error[0] != 0) {
500 			String msg = Display.extractFreeGError(error[0]);
501 			/*
502 			 * Don't print console warnings for timeout errors, as we can handle these ourselves.
503 			 * Note, most timeout errors happen only when running test cases, not during "normal" use.
504 			 */
505 			if (msg != null && (!msg.contains("Timeout") && !msg.contains("timeout"))) {
506 				System.err.println("SWT WebKitGDBus: there was an error executing something synchronously with the extension.");
507 				System.err.println("SWT WebKitGDBus: the error message is: " + msg);
508 				return (Object) false;
509 			}
510 			return (Object) "timeout";
511 		}
512 		Object resultObject = gVariant != 0 ? convertGVariantToJava(gVariant) : (Object) false;
513 		// Sometimes we get back tuples from GDBus, which get converted into Object arrays. In this case
514 		// we only care about the first value, since the extension never returns anything more than that.
515 		if (resultObject instanceof Object[]) {
516 			return ((Object []) resultObject)[0];
517 		}
518 		return resultObject;
519 	}
520 
521 	/**
522 	 * Calls the web extension asynchronously. Note, this method returning true does not
523 	 * guarantee the operation's success, it only means no errors occurred.
524 	 *
525 	 * @param params a pointer to the GVariant containing the parameters
526 	 * @param methodName a String representing the DBus method name in the extension
527 	 * @return true if the extension was called without errors, false otherwise
528 	 */
callExtensionAsync(long params, String methodName)529 	static boolean callExtensionAsync (long params, String methodName) {
530 		long [] error = new long [1]; // GError **
531 		OS.g_dbus_connection_call(connectionToExtension, EXTENSION_DBUS_NAME, EXTENSION_OBJECT_PATH,
532 				EXTENSION_INTERFACE_NAME, Converter.javaStringToCString(methodName), params,
533 				0, OS.G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, 0, callExtensionAsyncCB.getAddress(), 0);
534 		if (error[0] != 0) {
535 			String msg = Display.extractFreeGError(error[0]);
536 			System.err.println("SWT WebKitGDBus: there was an error executing something asynchronously "
537 					+ "with the extension.");
538 			System.err.println("SWT WebKitGDBus: the error message is: " + msg);
539 			return false;
540 		}
541 		return true;
542 	}
543 
544 	/* TYPE NOTES
545 	 *
546 	 * GDBus doesn't support all the types that we need. I used encoded 'byte' to translate some types.
547 	 *
548 	 * - 'null' is not supported. I thought to potentially use  'maybe' types, but they imply a possible NULL of a certain type, but not null itself.
549 	 *   so I use 'byte=48' (meaning '0' in ASCII) to denote null.
550 	 *
551 	 * - Empty arrays/structs are not supported by gdbus.
552 	 *    "Container types ... Empty structures are not allowed; there must be at least one type code between the parentheses"
553 	 *	   src: https://dbus.freedesktop.org/doc/dbus-specification.html
554 	 *	I used byte=101  (meaning 'e' in ASCII) to denote empty array.
555 	 *
556 	 * In Javascript all Number types seem to be 'double', (int/float/double/short  -> Double). So we convert everything into double accordingly.
557 	 *
558 	 * DBus Type info:  https://dbus.freedesktop.org/doc/dbus-specification.html#idm423
559 	 * GDBus Type info: https://developer.gnome.org/glib/stable/glib-GVariantType.html
560 	 */
561 
562 	/**
563 	 * Converts the given GVariant to a Java object.
564 	 * (Only subset of types is currently supported).
565 	 *
566 	 * We assume that the given gvariant does not contain errors. (checked by webextension first).
567 	 *
568 	 * @param gVariant a pointer to the native GVariant
569 	 */
convertGVariantToJava(long gVariant)570 	static Object convertGVariantToJava(long gVariant){
571 
572 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_BOOLEAN)){
573 			return OS.g_variant_get_boolean(gVariant);
574 		}
575 
576 		// see: WebKitGTK.java 'TYPE NOTES'
577 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_BYTE)) {
578 			byte byteVal = OS.g_variant_get_byte(gVariant);
579 
580 			switch (byteVal) {
581 			case WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_NULL:
582 				return null;
583 			case WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_EMPTY_ARRAY:
584 				return new Object [0];
585 			default:
586 				System.err.println("SWT WebKitGDBus: received an unsupported byte type via GDBus: " + byteVal);
587 				break;
588 			}
589 		}
590 
591 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_DOUBLE)){
592 			return OS.g_variant_get_double(gVariant);
593 		}
594 
595 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_UINT64)){
596 			return OS.g_variant_get_uint64(gVariant);
597 		}
598 
599 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_STRING)){
600 			return Converter.cCharPtrToJavaString(OS.g_variant_get_string(gVariant, null), false);
601 		}
602 
603 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_TUPLE)){
604 			int length = (int)OS.g_variant_n_children (gVariant);
605 			Object[] result = new Object[length];
606 			for (int i = 0; i < length; i++) {
607 				result[i] = convertGVariantToJava (OS.g_variant_get_child_value(gVariant, i));
608 			}
609 			return result;
610 		}
611 
612 		String typeString = Converter.cCharPtrToJavaString(OS.g_variant_get_type_string(gVariant), false);
613 		SWT.error (SWT.ERROR_INVALID_ARGUMENT, new Throwable("Unhandled variant type " + typeString ));
614 		return null;
615 	}
616 
617 	/**
618 	 * Converts the given Java Object to a GVariant * representation.
619 	 * (Only subset of types is currently supported).
620 	 *
621 	 * We assume that input Object may contain invalid types.
622 	 *
623 	 * @return pointer GVariant *
624 	 */
convertJavaToGVariant(Object javaObject)625 	static long convertJavaToGVariant(Object javaObject) throws SWTException {
626 
627 		if (javaObject == null) {
628 			return OS.g_variant_new_byte(WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_NULL);  // see: WebKitGTK.java 'TYPE NOTES'
629 		}
630 
631 		if (javaObject instanceof Long) {
632 			return OS.g_variant_new_uint64((Long) javaObject);
633 		}
634 
635 		if (javaObject instanceof String) {
636 			return OS.g_variant_new_string (Converter.javaStringToCString((String) javaObject));
637 		}
638 
639 		if (javaObject instanceof Boolean) {
640 			return OS.g_variant_new_boolean((Boolean) javaObject);
641 		}
642 
643 		// We treat Integer, Long, Double, Short as a 'double' because in Javascript these are all 'double'.
644 		// Note,  they all extend 'Number' java type, so they are an instance of it.
645 		if (javaObject instanceof Number) {  // see: WebKitGTK.java 'TYPE NOTES'
646 			return OS.g_variant_new_double (((Number) javaObject).doubleValue());
647 		}
648 
649 		if (javaObject instanceof Object[]) {
650 			Object[] arrayValue = (Object[]) javaObject;
651 			int length = arrayValue.length;
652 
653 			if (length == 0) {
654 				return OS.g_variant_new_byte(WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_EMPTY_ARRAY);  // see: WebKitGTK.java 'TYPE NOTES'
655 			}
656 
657 			long variants[] = new long [length];
658 
659 			for (int i = 0; i < length; i++) {
660 				variants[i] = convertJavaToGVariant(arrayValue[i]);
661 			}
662 
663 			return OS.g_variant_new_tuple(variants, length);
664 		}
665 		System.err.println("SWT WebKitGDBus: invalid object being returned to JavaScript: " + javaObject.toString() + "\n"
666 				+ "Only the following are supported: null, String, Boolean, Number(Long,Integer,Double...), Object[] of basic types");
667 		throw new SWTException(SWT.ERROR_INVALID_ARGUMENT, "Given object is not valid: " + javaObject.toString());
668 	}
669 }
670