diff --git a/bundles/org.eclipse.rap.rwt/js/rwt/client/JavaScriptExecutor.js b/bundles/org.eclipse.rap.rwt/js/rwt/client/JavaScriptExecutor.js
index 204f3c0253..3cf6a505b2 100644
--- a/bundles/org.eclipse.rap.rwt/js/rwt/client/JavaScriptExecutor.js
+++ b/bundles/org.eclipse.rap.rwt/js/rwt/client/JavaScriptExecutor.js
@@ -7,6 +7,7 @@
*
* Contributors:
* EclipseSource - initial API and implementation
+ * Kyle Smith - Add evaluate method
******************************************************************************/
namespace( "rwt.client" );
@@ -17,6 +18,15 @@ rwt.client.JavaScriptExecutor = function() {
eval( code );
};
+ this.evaluate = function( futureId, code ) {
+ const remote = rwt.remote.Connection.getInstance().getRemoteObject( this );
+ const retval = eval( code );
+ remote.call( "complete", {
+ futureId : futureId,
+ retval: retval
+ } );
+ };
+
};
rwt.client.JavaScriptExecutor.getInstance = function() {
diff --git a/bundles/org.eclipse.rap.rwt/js/rwt/remote/handler/JavaScriptExecutorHandler.js b/bundles/org.eclipse.rap.rwt/js/rwt/remote/handler/JavaScriptExecutorHandler.js
index bb17e5e847..9f5cee588e 100644
--- a/bundles/org.eclipse.rap.rwt/js/rwt/remote/handler/JavaScriptExecutorHandler.js
+++ b/bundles/org.eclipse.rap.rwt/js/rwt/remote/handler/JavaScriptExecutorHandler.js
@@ -7,6 +7,7 @@
*
* Contributors:
* EclipseSource - initial API and implementation
+ * Kyle Smith - Add evaluate method
******************************************************************************/
rwt.remote.HandlerRegistry.add( "rwt.client.JavaScriptExecutor", {
@@ -20,12 +21,16 @@ rwt.remote.HandlerRegistry.add( "rwt.client.JavaScriptExecutor", {
destructor : rwt.util.Functions.returnTrue,
methods : [
- "execute"
+ "execute",
+ "evaluate"
],
methodHandler : {
"execute" : function( object, args ) {
object.execute( args.content );
+ },
+ "evaluate" : function( object, args ) {
+ object.evaluate( args.futureId, args.content );
}
}
diff --git a/bundles/org.eclipse.rap.rwt/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java b/bundles/org.eclipse.rap.rwt/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java
index df282b9a81..093fe93474 100644
--- a/bundles/org.eclipse.rap.rwt/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java
+++ b/bundles/org.eclipse.rap.rwt/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java
@@ -7,9 +7,12 @@
*
* Contributors:
* EclipseSource - initial API and implementation
+ * Kyle Smith - Add evalute method
******************************************************************************/
package org.eclipse.rap.rwt.client.service;
+import java.util.concurrent.CompletableFuture;
+
/**
* The JavaScriptExecuter service allows executing JavaScript code on the client.
@@ -20,13 +23,35 @@
public interface JavaScriptExecutor extends ClientService {
/**
- * Evaluate the JavaScript code on the client
- *
- * If the code throws an error, it will crash the web client.
- * Accessing internals of the web client is strongly discouraged.
+ * Executes the JavaScript code on the client.
+ *
+ * If the code throws an error, it will crash the web client. Accessing internals of the web
+ * client is strongly discouraged.
*
* @param code the JavaScript code to evaluate
*/
public void execute( String code );
+ /**
+ * Evaluates the JavaScript code on the client. Unlike {@link #execute(String)}, this method
+ * returns a value (asynchronously) if the specified JavaScript code returns a value. The returned
+ * {@link CompletableFuture} is completed when the client object sends the return value, if any,
+ * in the next remote call, which may never happen. If the JavaScript code does not return a
+ * value, the future is completed with {@code null}.
+ *
+ * Important: Do not wait for the future to complete in the UI thread or it will cause a
+ * deadlock. The code to execute is only sent to the client once the UI thread is available, which
+ * will not happen if the future is blocking it. Instead, use another thread or
+ * {@link java.util.concurrent.ExecutorService ExecutorService}.
+ *
+ * If the code throws an error, it will crash the web client. Accessing internals of the web
+ * client is strongly discouraged.
+ *
+ * @param code the JavaScript code to evaluate
+ * @return a future that will be completed with the result of the JavaScript code, or a future
+ * that will be completed with {@code null} if the given JavaScript code does not return a
+ * value (i.e., returns {@code undefined})
+ * @since 4.3
+ */
+ public CompletableFuture evaluate( String code );
}
diff --git a/bundles/org.eclipse.rap.rwt/src/org/eclipse/rap/rwt/internal/client/JavaScriptExecutorImpl.java b/bundles/org.eclipse.rap.rwt/src/org/eclipse/rap/rwt/internal/client/JavaScriptExecutorImpl.java
index bee6d8ed61..e45ad109bc 100644
--- a/bundles/org.eclipse.rap.rwt/src/org/eclipse/rap/rwt/internal/client/JavaScriptExecutorImpl.java
+++ b/bundles/org.eclipse.rap.rwt/src/org/eclipse/rap/rwt/internal/client/JavaScriptExecutorImpl.java
@@ -7,24 +7,35 @@
*
* Contributors:
* EclipseSource - initial API and implementation
+ * Kyle Smith - Add evalute method
******************************************************************************/
package org.eclipse.rap.rwt.internal.client;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
import org.eclipse.rap.json.JsonObject;
+import org.eclipse.rap.json.JsonValue;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.rap.rwt.internal.remote.ConnectionImpl;
+import org.eclipse.rap.rwt.remote.AbstractOperationHandler;
import org.eclipse.rap.rwt.remote.RemoteObject;
+import org.eclipse.swt.internal.widgets.IdGenerator;
public final class JavaScriptExecutorImpl implements JavaScriptExecutor {
private static final String REMOTE_ID = "rwt.client.JavaScriptExecutor";
private final RemoteObject remoteObject;
+ private final Map> futures;
public JavaScriptExecutorImpl() {
ConnectionImpl connection = ( ConnectionImpl )RWT.getUISession().getConnection();
+ futures = new HashMap<>();
remoteObject = connection.createServiceObject( REMOTE_ID );
+ remoteObject.setHandler( new EvaluateHandler() );
}
@Override
@@ -32,4 +43,35 @@ public void execute( String code ) {
remoteObject.call( "execute", new JsonObject().add( "content", code.trim() ) );
}
+ @Override
+ public CompletableFuture evaluate( String code ) {
+ CompletableFuture future = new CompletableFuture<>();
+ String id = IdGenerator.getInstance( RWT.getUISession() ).createId( future );
+ futures.put( id, future );
+ remoteObject.call( "evaluate",
+ new JsonObject().add( "futureId", id ).add( "content", code.trim() ) );
+ return future;
+ }
+
+ private final class EvaluateHandler extends AbstractOperationHandler {
+
+ @Override
+ public void handleCall( String method, JsonObject properties ) {
+ if( "complete".equals( method ) ) {
+ JsonValue idJson = properties.get( "futureId" );
+ JsonValue retvalJson = properties.get( "retval" );
+ if( idJson != null && idJson.isString() ) {
+ CompletableFuture future = futures.get( idJson.asString() );
+ if( future != null ) {
+ if( retvalJson != null ) {
+ future.complete( retvalJson.toString() );
+ } else {
+ future.complete( null );
+ }
+ futures.remove( idJson.asString() );
+ }
+ }
+ }
+ }
+ }
}
diff --git a/tests/org.eclipse.rap.rwt.test/src/org/eclipse/rap/rwt/internal/client/JavaScriptExecutorImpl_Test.java b/tests/org.eclipse.rap.rwt.test/src/org/eclipse/rap/rwt/internal/client/JavaScriptExecutorImpl_Test.java
index 77d60e94b0..1d6d289236 100644
--- a/tests/org.eclipse.rap.rwt.test/src/org/eclipse/rap/rwt/internal/client/JavaScriptExecutorImpl_Test.java
+++ b/tests/org.eclipse.rap.rwt.test/src/org/eclipse/rap/rwt/internal/client/JavaScriptExecutorImpl_Test.java
@@ -18,6 +18,8 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import java.util.concurrent.CompletableFuture;
+
import org.eclipse.rap.json.JsonObject;
import org.eclipse.rap.rwt.internal.remote.ConnectionImpl;
import org.eclipse.rap.rwt.remote.RemoteObject;
@@ -75,6 +77,33 @@ public void testExecute_createsSeparateOperationForEveryCall() {
verify( remoteObject ).call( eq( "execute" ), eq( new JsonObject().add( "content", "code 2" ) ) );
verifyNoMoreInteractions( remoteObject );
}
+
+ @Test
+ public void testEvaluate_createsCallOperation() {
+ RemoteObject remoteObject = mock( RemoteObject.class );
+ fakeConnection( remoteObject );
+ JavaScriptExecutorImpl executor = new JavaScriptExecutorImpl();
+
+ CompletableFuture future = executor.evaluate( "5 + 6" );
+
+ verify( remoteObject ).call( eq( "evaluate" ), eq( new JsonObject().add( "content", "5+6" ) ) );
+ verifyNoMoreInteractions( remoteObject );
+ }
+
+ @Test
+ public void testEvaluate_createsSeparateOperationForEveryCall() {
+ RemoteObject remoteObject = mock( RemoteObject.class );
+ fakeConnection( remoteObject );
+ JavaScriptExecutorImpl executor = new JavaScriptExecutorImpl();
+
+ CompletableFuture future = executor.evaluate( "5 + 6" );
+ CompletableFuture future2 = executor.evaluate( "Date.now()" );
+
+ verify( remoteObject ).call( eq( "evaluate" ), eq( new JsonObject().add( "content", "5+6" ) ) );
+ verify( remoteObject ).call( eq( "evaluate" ), eq( new JsonObject().add( "content", "Date.now()" ) ) );
+
+ verifyNoMoreInteractions( remoteObject );
+ }
private static ConnectionImpl fakeConnection( RemoteObject remoteObject ) {
ConnectionImpl connection = mock( ConnectionImpl.class );
@@ -82,5 +111,4 @@ private static ConnectionImpl fakeConnection( RemoteObject remoteObject ) {
Fixture.fakeConnection( connection );
return connection;
}
-
}