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