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