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 		byte [] swt = Converter.wcsToMbcs("SWT-GDBusServer-", true);
232 		long swtPtr = OS.g_malloc(swt.length);
233 		C.memmove(swtPtr, swt, swt.length);
234 		long user = OS.g_get_user_name();
235 		byte [] template = Converter.wcsToMbcs("-XXXXXX", true);
236 		long templatePtr = OS.g_malloc(template.length);
237 		C.memmove(templatePtr, template, template.length);
238 		long fileName = OS.g_strconcat(swtPtr, user, templatePtr, 0);
239 		long [] error = new long [1];
240 		long address = OS.g_dir_make_tmp(fileName, error);
241 		if (address == 0) {
242 			System.err.println("SWT WebkitGDBus: error creating temp folder " + Display.extractFreeGError(error[0]));
243 		}
244 		byte [] socket = Converter.wcsToMbcs("unix:tmpdir=", true);
245 		long socketPtr = OS.g_malloc(socket.length);
246 		C.memmove(socketPtr, socket, socket.length);
247 		long finalAddress = OS.g_strconcat(socketPtr, address, 0);
248 
249 		// Clean up temporary string pointers and return result
250 		OS.g_free(swtPtr);
251 		OS.g_free(user);
252 		OS.g_free(templatePtr);
253 		OS.g_free(fileName);
254 		OS.g_free(socketPtr);
255 		return finalAddress;
256 	}
257 
258 	/**
259 	 * This is called when a client call one of the GDBus methods.
260 	 *
261 	 * Developer note:
262 	 * This method can be reached directly from GDBus cmd utility:
263 	 * 	gdbus call --session --dest org.eclipse.swt<UNIQUE_ID> --object-path /org/eclipse/swt/gdbus --method org.eclipse.swt.gdbusInterface.HelloWorld
264 	 * where as you tab complete, you append the UNIQUE_ID.
265 	 *
266 	 * @param connection 	GDBusConnection
267 	 * @param sender 		const gchar
268 	 * @param object_path	const gchar
269 	 * @param interface_name const gchar
270 	 * @param method_name    const gchar
271 	 * @param gvar_parameters	 GVariant
272 	 * @param invocation     GDBusMethodInvocation
273 	 * @param user_data      gpointer
274 	 * @return
275 	 */
276 	@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)277 	private static long handleMethodCB (
278 			long connection, long sender,
279 			long object_path, long interface_name,
280 			long method_name, long gvar_parameters,
281 			long invocation, long user_data) {
282 
283 		String java_method_name = Converter.cCharPtrToJavaString(method_name, false);
284 		Object result = null;
285 		if (java_method_name != null) {
286 			if (java_method_name.equals(webkit2callJava)) {
287 				try {
288 					Object [] java_parameters = (Object []) convertGVariantToJava(gvar_parameters);
289 					result = WebKit.WebKitExtension.webkit2callJavaCallback(java_parameters);
290 				} catch (Exception e) {
291 					// gdbus should always return to prevent extension from hanging.
292 					result = (String) WebBrowser.CreateErrorString (e.getLocalizedMessage ());
293 					System.err.println("SWT WebkitGDBus: Exception occured in Webkit2 callback logic.");
294 				}
295 			} else if (java_method_name.equals(webkitWebExtensionIdentifier)) {
296 				Object [] serverAddress = (Object []) convertGVariantToJava(gvar_parameters);
297 				if (serverAddress[0] != null && serverAddress[0] instanceof String) {
298 					EXTENSION_DBUS_SERVER_CLIENT_ADDRESS = (String) serverAddress[0];
299 					// Connect to the extension's server by creating a connection asynchronously
300 					createConnectionToExtension();
301 					/*
302 					 * Return any pending BrowserFunctions that were created before WebkitGDBus
303 					 * was initialized.
304 					 */
305 					invokeReturnValueExtensionIdentifier(pendingBrowserFunctions, invocation);
306 				} else {
307 					System.err.println("SWT WebkitGDBus: error in web extension identification process."
308 							+ " BrowserFunction may not work.");
309 				}
310 				return 0;
311 			}
312 		} else {
313 			result = (String) "SWT WebkitGDBus: GDBus called an unknown method?";
314 			System.err.println("SWT WebkitGDBus: Received a call from an unknown method: " + java_method_name);
315 		}
316 		invokeReturnValue(result, invocation);
317 		return 0;
318 	}
319 
320 	@SuppressWarnings("unused") // callback not directly called by SWT
callExtensionAsyncCB(long source_object, long result, long user_data)321 	private static long callExtensionAsyncCB (long source_object, long result, long user_data) {
322 		long [] error = new long [1];
323 		long gVariantResult = OS.g_dbus_connection_call_finish (connectionToExtension, result, error);
324 		if (error[0] != 0) {
325 			String msg = Display.extractFreeGError(error[0]);
326 			System.err.println("SWT WebkitGDBus: there was an error executing something asynchronously with the extension (Java callback).");
327 			System.err.println("SWT WebkitGDBus: the error message provided is " + msg);
328 		}
329 		OS.g_variant_unref(gVariantResult);
330 		return 0;
331 	}
332 
333 	@SuppressWarnings("unused") // callback not directly called by SWT
authenticatePeerCB(long observer, long stream, long credentials, long user_data)334 	private static long authenticatePeerCB (long observer, long stream, long credentials, long user_data) {
335 		boolean authorized = false;
336 		if (credentials != 0) {
337 			long error [] = new long [1];
338 			long ownCredentials = OS.g_credentials_new();
339 			authorized = OS.g_credentials_is_same_user(credentials, ownCredentials, error);
340 			if (error[0] != 0) {
341 				String msg = Display.extractFreeGError(error[0]);
342 				System.err.println("SWT WebkitGDBus: error authenticating client connection to server " + msg);
343 			}
344 			OS.g_object_unref(ownCredentials);
345 		}
346 		return authorized ? 1 : 0;
347 	}
348 
349 	@SuppressWarnings("unused") // callback not directly called by SWT
newConnectionCB(long server, long connection, long user_data)350 	private static long newConnectionCB (long server, long connection, long user_data) {
351 		long gdBusNodeInfo;
352 		long [] error = new long [1];
353 		gdBusNodeInfo = OS.g_dbus_node_info_new_for_xml(WEBKITGDBUS_INTROSPECTION_XML, error);
354 		if (gdBusNodeInfo == 0 || error[0] != 0) {
355 			System.err.println("SWT WebkitGDBus: failed to get introspection data");
356 		}
357 		assert gdBusNodeInfo != 0 : "SWT WebKitGDBus: introspection data should not be 0";
358 
359 		long interface_info = OS.g_dbus_node_info_lookup_interface(gdBusNodeInfo, WEBKITGDBUS_INTERFACE_NAME);
360 		long vtable [] = { handleMethodCB.getAddress(), 0, 0 };
361 
362 		OS.g_dbus_connection_register_object(
363 				connection,
364 				WEBKITGDBUS_OBJECT_PATH,
365 				interface_info,
366 				vtable,
367 				0, // user_data
368 				0, // user_data_free_func
369 				error);
370 
371 		if (error[0] != 0) {
372 			System.err.println("SWT WebKitGDBus: failed to register object: " + WEBKITGDBUS_OBJECT_PATH);
373 			return 0;
374 		}
375 
376 		// Ref the connection and add it to the Display's list of connections
377 		connectionFromExtension = OS.g_object_ref(connection);
378 		if (attachedToDisplay && display != null) {
379 			if (!display.dBusConnections.contains(connection)) display.dBusConnections.add(connectionFromExtension);
380 		}
381 		return 1;
382 	}
383 
384 	@SuppressWarnings("unused") // callback not directly called by SWT
newConnectionToExtensionCB(long sourceObject, long result, long user_data)385 	private static long newConnectionToExtensionCB (long sourceObject, long result, long user_data) {
386 		long [] error = new long [1];
387 		connectionToExtension = OS.g_dbus_connection_new_for_address_finish(result, error);
388 		if (error[0] != 0) {
389 			System.err.println("SWT WebKitGDBus: error finishing connection: " + Display.extractFreeGError(error[0]));
390 			return 0;
391 		} else {
392 			connectionToExtensionCreated = true;
393 		}
394 
395 		// Add the connections to the Display's list of connections
396 		if (attachedToDisplay && display != null) {
397 			if (!display.dBusConnections.contains(connectionToExtension)) display.dBusConnections.add(connectionToExtension);
398 		}
399 		return 0;
400 	}
401 
402 	/**
403 	 * Returns a GVariant to the DBus invocation of the extension identifier method. When the extension
404 	 * is initialized it sends a DBus message to the SWT webkit instance. As a return value, the SWT webkit
405 	 * instance sends any BrowserFunctions that have been registered. If no functions have been registered,
406 	 * an "empty" function with a page ID of -1 is sent.
407 	 *
408 	 * @param map the HashMap of BrowserFunctions waiting to be registered in the extension, or null
409 	 * if you'd like to explicitly send an empty function signature
410 	 * @param invocation the GDBus invocation to return the value on
411 	 */
invokeReturnValueExtensionIdentifier(HashMap<Long, ArrayList<ArrayList<String>>> map, long invocation)412 	private static void invokeReturnValueExtensionIdentifier (HashMap<Long, ArrayList<ArrayList<String>>> map,
413 			long invocation) {
414 		long resultGVariant;
415 		long builder;
416 		long type = OS.g_variant_type_new(OS.G_VARIANT_TYPE_ARRAY_BROWSER_FUNCS);
417 		builder = OS.g_variant_builder_new(type);
418 		if (builder == 0) return;
419 		Object [] tupleArray = new Object[3];
420 		boolean sendEmptyFunction;
421 		if (map == null) {
422 			sendEmptyFunction = true;
423 		} else {
424 			sendEmptyFunction = map.isEmpty() && !functionsPending;
425 		}
426 		/*
427 		 * No functions to register, send a page ID of -1 and empty strings.
428 		 */
429 		if (sendEmptyFunction) {
430 			tupleArray[0] = (long)-1;
431 			tupleArray[1] = "";
432 			tupleArray[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 empty BrowserFunction GVariant tuple, skipping.");
438 			}
439 		} else {
440 			for (long id : map.keySet()) {
441 				ArrayList<ArrayList<String>> list = map.get(id);
442 				if (list != null) {
443 					for (ArrayList<String> stringList : list) {
444 						Object [] stringArray = stringList.toArray();
445 						if (stringArray.length > 2) {
446 							System.err.println("SWT WebKitGDBus: String array with BrowserFunction and URL should never have"
447 									+ "more than 2 Strings");
448 						}
449 						tupleArray[0] = id;
450 						System.arraycopy(stringArray, 0, tupleArray, 1, 2);
451 						long tupleGVariant = convertJavaToGVariant(tupleArray);
452 						if (tupleGVariant != 0) {
453 							OS.g_variant_builder_add_value(builder, tupleGVariant);
454 						} else {
455 							System.err.println("SWT WebKitGDBus: error creating BrowserFunction GVariant tuple, skipping.");
456 						}
457 					}
458 				}
459 			}
460 		}
461 		resultGVariant = OS.g_variant_builder_end(builder);
462 		String typeString = Converter.cCharPtrToJavaString(OS.g_variant_get_type_string(resultGVariant), false);
463 		if (!OS.DBUS_TYPE_STRUCT_ARRAY_BROWSER_FUNCS.equals(typeString)) {
464 			System.err.println("SWT WebKitGDBus: an error packaging the GVariant occurred: type mismatch.");
465 		}
466 		long [] variants = {resultGVariant};
467 		long finalGVariant = OS.g_variant_new_tuple(variants, 1);
468 		OS.g_dbus_method_invocation_return_value(invocation, finalGVariant);
469 		OS.g_variant_builder_unref(builder);
470 		OS.g_variant_type_free(type);
471 		return;
472 	}
473 
invokeReturnValue(Object result, long invocation)474 	private static void invokeReturnValue (Object result, long invocation) {
475 		long resultGVariant = 0;
476 		try {
477 			resultGVariant = convertJavaToGVariant(new Object [] {result}); // Result has to be a tuple.
478 		} catch (SWTException e) {
479 			// gdbus should always return to prevent extension from hanging.
480 			String errMsg = (String) WebBrowser.CreateErrorString (e.getLocalizedMessage ());
481 			resultGVariant = convertJavaToGVariant(new Object [] {errMsg});
482 		}
483 		OS.g_dbus_method_invocation_return_value(invocation, resultGVariant);
484 		return; // void return value.
485 	}
486 
487 	/**
488 	 * Asynchronously initializes a GDBusConnection to the web extension. Connection process
489 	 * will be confirmed when the newConnectionToExtension callback is called.
490 	 */
createConnectionToExtension()491 	private static void createConnectionToExtension() {
492 		byte [] address = Converter.javaStringToCString(EXTENSION_DBUS_SERVER_CLIENT_ADDRESS);
493 		long [] error = new long [1];
494 		// Create connection asynchronously to avoid deadlock issues
495 		OS.g_dbus_connection_new_for_address (address, OS.G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
496 				0, 0, newConnectionToExtensionCB.getAddress(), 0);
497 
498 		if (error[0] != 0) {
499 			System.err.println("SWT WebkitGDBus: error creating connection to the extension "
500 					+ Display.extractFreeGError(error[0]));
501 		}
502 	}
503 
504 	/**
505 	 * Calls the web extension synchronously. Returns true if the operation succeeded, and false
506 	 * otherwise (or if the operation times out).
507 	 *
508 	 * @param params a pointer to the GVariant containing the parameters
509 	 * @param methodName a String representing the DBus method name in the extension
510 	 * @return an Object representing the return value from DBus in boolean form
511 	 */
callExtensionSync(long params, String methodName)512 	static Object callExtensionSync (long params, String methodName) {
513 		long [] error = new long [1]; // GError **
514 		long gVariant = OS.g_dbus_connection_call_sync(connectionToExtension, EXTENSION_DBUS_NAME, EXTENSION_OBJECT_PATH,
515 				EXTENSION_INTERFACE_NAME, Converter.javaStringToCString(methodName), params,
516 				0, OS.G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, 0, error);
517 		if (error[0] != 0) {
518 			String msg = Display.extractFreeGError(error[0]);
519 			/*
520 			 * Don't print console warnings for timeout errors, as we can handle these ourselves.
521 			 * Note, most timeout errors happen only when running test cases, not during "normal" use.
522 			 */
523 			if (msg != null && (!msg.contains("Timeout") && !msg.contains("timeout"))) {
524 				System.err.println("SWT WebKitGDBus: there was an error executing something synchronously with the extension.");
525 				System.err.println("SWT WebKitGDBus: the error message is: " + msg);
526 				return (Object) false;
527 			}
528 			return (Object) "timeout";
529 		}
530 		Object resultObject = gVariant != 0 ? convertGVariantToJava(gVariant) : (Object) false;
531 		// Sometimes we get back tuples from GDBus, which get converted into Object arrays. In this case
532 		// we only care about the first value, since the extension never returns anything more than that.
533 		if (resultObject instanceof Object[]) {
534 			return ((Object []) resultObject)[0];
535 		}
536 		return resultObject;
537 	}
538 
539 	/**
540 	 * Calls the web extension asynchronously. Note, this method returning true does not
541 	 * guarantee the operation's success, it only means no errors occurred.
542 	 *
543 	 * @param params a pointer to the GVariant containing the parameters
544 	 * @param methodName a String representing the DBus method name in the extension
545 	 * @return true if the extension was called without errors, false otherwise
546 	 */
callExtensionAsync(long params, String methodName)547 	static boolean callExtensionAsync (long params, String methodName) {
548 		long [] error = new long [1]; // GError **
549 		OS.g_dbus_connection_call(connectionToExtension, EXTENSION_DBUS_NAME, EXTENSION_OBJECT_PATH,
550 				EXTENSION_INTERFACE_NAME, Converter.javaStringToCString(methodName), params,
551 				0, OS.G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, 0, callExtensionAsyncCB.getAddress(), 0);
552 		if (error[0] != 0) {
553 			String msg = Display.extractFreeGError(error[0]);
554 			System.err.println("SWT WebKitGDBus: there was an error executing something asynchronously "
555 					+ "with the extension.");
556 			System.err.println("SWT WebKitGDBus: the error message is: " + msg);
557 			return false;
558 		}
559 		return true;
560 	}
561 
562 	/* TYPE NOTES
563 	 *
564 	 * GDBus doesn't support all the types that we need. I used encoded 'byte' to translate some types.
565 	 *
566 	 * - '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.
567 	 *   so I use 'byte=48' (meaning '0' in ASCII) to denote null.
568 	 *
569 	 * - Empty arrays/structs are not supported by gdbus.
570 	 *    "Container types ... Empty structures are not allowed; there must be at least one type code between the parentheses"
571 	 *	   src: https://dbus.freedesktop.org/doc/dbus-specification.html
572 	 *	I used byte=101  (meaning 'e' in ASCII) to denote empty array.
573 	 *
574 	 * In Javascript all Number types seem to be 'double', (int/float/double/short  -> Double). So we convert everything into double accordingly.
575 	 *
576 	 * DBus Type info:  https://dbus.freedesktop.org/doc/dbus-specification.html#idm423
577 	 * GDBus Type info: https://developer.gnome.org/glib/stable/glib-GVariantType.html
578 	 */
579 
580 	/**
581 	 * Converts the given GVariant to a Java object.
582 	 * (Only subset of types is currently supported).
583 	 *
584 	 * We assume that the given gvariant does not contain errors. (checked by webextension first).
585 	 *
586 	 * @param gVariant a pointer to the native GVariant
587 	 */
convertGVariantToJava(long gVariant)588 	static Object convertGVariantToJava(long gVariant){
589 
590 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_BOOLEAN)){
591 			return OS.g_variant_get_boolean(gVariant);
592 		}
593 
594 		// see: WebKitGTK.java 'TYPE NOTES'
595 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_BYTE)) {
596 			byte byteVal = OS.g_variant_get_byte(gVariant);
597 
598 			switch (byteVal) {
599 			case WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_NULL:
600 				return null;
601 			case WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_EMPTY_ARRAY:
602 				return new Object [0];
603 			default:
604 				System.err.println("SWT WebKitGDBus: received an unsupported byte type via GDBus: " + byteVal);
605 				break;
606 			}
607 		}
608 
609 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_DOUBLE)){
610 			return OS.g_variant_get_double(gVariant);
611 		}
612 
613 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_UINT64)){
614 			return OS.g_variant_get_uint64(gVariant);
615 		}
616 
617 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_STRING)){
618 			return Converter.cCharPtrToJavaString(OS.g_variant_get_string(gVariant, null), false);
619 		}
620 
621 		if (OS.g_variant_is_of_type(gVariant, OS.G_VARIANT_TYPE_TUPLE)){
622 			int length = (int)OS.g_variant_n_children (gVariant);
623 			Object[] result = new Object[length];
624 			for (int i = 0; i < length; i++) {
625 				result[i] = convertGVariantToJava (OS.g_variant_get_child_value(gVariant, i));
626 			}
627 			return result;
628 		}
629 
630 		String typeString = Converter.cCharPtrToJavaString(OS.g_variant_get_type_string(gVariant), false);
631 		SWT.error (SWT.ERROR_INVALID_ARGUMENT, new Throwable("Unhandled variant type " + typeString ));
632 		return null;
633 	}
634 
635 	/**
636 	 * Converts the given Java Object to a GVariant * representation.
637 	 * (Only subset of types is currently supported).
638 	 *
639 	 * We assume that input Object may contain invalid types.
640 	 *
641 	 * @return pointer GVariant *
642 	 */
convertJavaToGVariant(Object javaObject)643 	static long convertJavaToGVariant(Object javaObject) throws SWTException {
644 
645 		if (javaObject == null) {
646 			return OS.g_variant_new_byte(WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_NULL);  // see: WebKitGTK.java 'TYPE NOTES'
647 		}
648 
649 		if (javaObject instanceof Long) {
650 			return OS.g_variant_new_uint64((Long) javaObject);
651 		}
652 
653 		if (javaObject instanceof String) {
654 			return OS.g_variant_new_string (Converter.javaStringToCString((String) javaObject));
655 		}
656 
657 		if (javaObject instanceof Boolean) {
658 			return OS.g_variant_new_boolean((Boolean) javaObject);
659 		}
660 
661 		// We treat Integer, Long, Double, Short as a 'double' because in Javascript these are all 'double'.
662 		// Note,  they all extend 'Number' java type, so they are an instance of it.
663 		if (javaObject instanceof Number) {  // see: WebKitGTK.java 'TYPE NOTES'
664 			return OS.g_variant_new_double (((Number) javaObject).doubleValue());
665 		}
666 
667 		if (javaObject instanceof Object[]) {
668 			Object[] arrayValue = (Object[]) javaObject;
669 			int length = arrayValue.length;
670 
671 			if (length == 0) {
672 				return OS.g_variant_new_byte(WebkitGDBus.SWT_DBUS_MAGIC_NUMBER_EMPTY_ARRAY);  // see: WebKitGTK.java 'TYPE NOTES'
673 			}
674 
675 			long variants[] = new long [length];
676 
677 			for (int i = 0; i < length; i++) {
678 				variants[i] = convertJavaToGVariant(arrayValue[i]);
679 			}
680 
681 			return OS.g_variant_new_tuple(variants, length);
682 		}
683 		System.err.println("SWT WebKitGDBus: invalid object being returned to JavaScript: " + javaObject.toString() + "\n"
684 				+ "Only the following are supported: null, String, Boolean, Number(Long,Integer,Double...), Object[] of basic types");
685 		throw new SWTException(SWT.ERROR_INVALID_ARGUMENT, "Given object is not valid: " + javaObject.toString());
686 	}
687 }
688