Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions bundles/org.eclipse.rap.rwt/js/rwt/client/JavaScriptExecutor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*
* Contributors:
* EclipseSource - initial API and implementation
* Kyle Smith - Add evaluate method
******************************************************************************/

namespace( "rwt.client" );
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*
* Contributors:
* EclipseSource - initial API and implementation
* Kyle Smith - Add evaluate method
******************************************************************************/

rwt.remote.HandlerRegistry.add( "rwt.client.JavaScriptExecutor", {
Expand All @@ -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 );
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
* <p>
* 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}.
* <p>
* <b>Important:</b> 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}.
* <p>
* 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<String> evaluate( String code );
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,71 @@
*
* 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<String, CompletableFuture<String>> futures;

public JavaScriptExecutorImpl() {
ConnectionImpl connection = ( ConnectionImpl )RWT.getUISession().getConnection();
futures = new HashMap<>();
remoteObject = connection.createServiceObject( REMOTE_ID );
remoteObject.setHandler( new EvaluateHandler() );
}

@Override
public void execute( String code ) {
remoteObject.call( "execute", new JsonObject().add( "content", code.trim() ) );
}

@Override
public CompletableFuture<String> evaluate( String code ) {
CompletableFuture<String> 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<String> future = futures.get( idJson.asString() );
if( future != null ) {
if( retvalJson != null ) {
future.complete( retvalJson.toString() );
} else {
future.complete( null );
}
futures.remove( idJson.asString() );
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -75,12 +77,38 @@ 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<String> 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<String> future = executor.evaluate( "5 + 6" );
CompletableFuture<String> 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 );
when( connection.createServiceObject( anyString() ) ).thenReturn( remoteObject );
Fixture.fakeConnection( connection );
return connection;
}

}