diff --git a/java/org/cef/callback/CefQueryCallback.java b/java/org/cef/callback/CefQueryCallback.java index 0972bf2e..fe54ce71 100644 --- a/java/org/cef/callback/CefQueryCallback.java +++ b/java/org/cef/callback/CefQueryCallback.java @@ -4,6 +4,8 @@ package org.cef.callback; +import java.nio.ByteBuffer; + /** * Interface representing a query callback. */ @@ -11,10 +13,17 @@ public interface CefQueryCallback { /** * Notify the associated JavaScript onSuccess callback that the query has * completed successfully. - * @param response Response passed to JavaScript. + * @param response Response text passed to JavaScript. */ public void success(String response); + /** + * Notify the associated JavaScript onSuccess callback that the query has + * completed successfully. + * @param response Response buffer passed to JavaScript. May be null. + */ + public void success(ByteBuffer response); + /** * Notify the associated JavaScript onFailure callback that the query has * failed. @@ -22,4 +31,12 @@ public interface CefQueryCallback { * @param error_message Error message passed to JavaScript. */ public void failure(int error_code, String error_message); + + /** + * Returns whether this callback is persistent and therefore whether + * {@link CefQueryCallback#success(String)} may be called multiple times. + * Persistent queries must be explicitly canceled, either from the browser + * or via {@link CefQueryCallback#failure(int, String)}. + */ + public boolean isPersistent(); } diff --git a/java/org/cef/callback/CefQueryCallback_N.java b/java/org/cef/callback/CefQueryCallback_N.java index 14b32e3a..e62e0b15 100644 --- a/java/org/cef/callback/CefQueryCallback_N.java +++ b/java/org/cef/callback/CefQueryCallback_N.java @@ -4,6 +4,8 @@ package org.cef.callback; +import java.nio.ByteBuffer; + class CefQueryCallback_N extends CefNativeAdapter implements CefQueryCallback { CefQueryCallback_N() {} @@ -16,7 +18,16 @@ protected void finalize() throws Throwable { @Override public void success(String response) { try { - N_Success(getNativeRef(null), response); + N_Success(getNativeRef(null), response, isPersistent()); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void success(ByteBuffer response) { + try { + N_SuccessBinary(getNativeRef(null), response, isPersistent()); } catch (UnsatisfiedLinkError ule) { ule.printStackTrace(); } @@ -31,6 +42,21 @@ public void failure(int error_code, String error_message) { } } - private final native void N_Success(long self, String response); + @Override + public boolean isPersistent() { + return false; + } + + private final native void N_Success(long self, String response, boolean persistent); + private final native void N_SuccessBinary(long self, ByteBuffer response, boolean persistent); private final native void N_Failure(long self, int error_code, String error_message); } + +class CefQueryCallback_N_Persistent extends CefQueryCallback_N { + CefQueryCallback_N_Persistent() {} + + @Override + public boolean isPersistent() { + return true; + } +} diff --git a/java/org/cef/handler/CefMessageRouterHandler.java b/java/org/cef/handler/CefMessageRouterHandler.java index 4827a476..53413a8f 100644 --- a/java/org/cef/handler/CefMessageRouterHandler.java +++ b/java/org/cef/handler/CefMessageRouterHandler.java @@ -9,18 +9,21 @@ import org.cef.callback.CefNative; import org.cef.callback.CefQueryCallback; +import java.nio.ByteBuffer; + /** * Implement this interface to handle queries. All methods will be executed on the browser process * UI thread. */ public interface CefMessageRouterHandler extends CefNative { /** - * Called when the browser receives a JavaScript query. + * Called when the browser receives a JavaScript text query. * * @param browser The corresponding browser. * @param frame The frame generating the event. Instance only valid within the scope of this * method. * @param queryId The unique ID for the query. + * @param request The query text. * @param persistent True if the query is persistent. * @param callback Object used to continue or cancel the query asynchronously. * @return True to handle the query or false to propagate the query to other registered @@ -31,6 +34,24 @@ public interface CefMessageRouterHandler extends CefNative { public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, String request, boolean persistent, CefQueryCallback callback); + /** + * Called when the browser receives a JavaScript binary query. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param queryId The unique ID for the query. + * @param request The query direct buffer. Valid only for the scope of this method. + * @param persistent True if the query is persistent. + * @param callback Object used to continue or cancel the query asynchronously. + * @return True to handle the query or false to propagate the query to other registered + * handlers, if any. If no handlers return true from this method then the query will be + * automatically canceled with an error code of -1 delivered to the JavaScript onFailure + * callback. + */ + public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, ByteBuffer request, + boolean persistent, CefQueryCallback callback); + /** * Called when a pending JavaScript query is canceled. * diff --git a/java/org/cef/handler/CefMessageRouterHandlerAdapter.java b/java/org/cef/handler/CefMessageRouterHandlerAdapter.java index c924dbfe..a91ba622 100644 --- a/java/org/cef/handler/CefMessageRouterHandlerAdapter.java +++ b/java/org/cef/handler/CefMessageRouterHandlerAdapter.java @@ -9,6 +9,8 @@ import org.cef.callback.CefNativeAdapter; import org.cef.callback.CefQueryCallback; +import java.nio.ByteBuffer; + /** * An abstract adapter class for receiving message router events. * The methods in this class are empty. @@ -22,6 +24,12 @@ public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, String return false; } + @Override + public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, ByteBuffer request, + boolean persistent, CefQueryCallback callback) { + return false; + } + @Override public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) { return; diff --git a/java/tests/detailed/handler/MessageRouterHandlerEx.java b/java/tests/detailed/handler/MessageRouterHandlerEx.java index bc4b780b..f534f31b 100644 --- a/java/tests/detailed/handler/MessageRouterHandlerEx.java +++ b/java/tests/detailed/handler/MessageRouterHandlerEx.java @@ -12,11 +12,14 @@ import org.cef.callback.CefQueryCallback; import org.cef.handler.CefMessageRouterHandlerAdapter; +import java.nio.ByteBuffer; + public class MessageRouterHandlerEx extends CefMessageRouterHandlerAdapter { private final CefClient client_; private final CefMessageRouterConfig config_ = new CefMessageRouterConfig("myQuery", "myQueryAbort"); private CefMessageRouter router_ = null; + private boolean binary_direct_ = false; public MessageRouterHandlerEx(final CefClient client) { client_ = client; @@ -47,6 +50,39 @@ public boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String router_ = null; callback.success(""); } + } else if (request.startsWith("doPersistent")) { + if (persistent) { + callback.success("Hello,"); + callback.success("World!"); + callback.failure(0, "Finished"); + } else { + callback.failure(-1, "Request not marked as persistent"); + } + } else { + // not handled + return false; + } + return true; + } + + @Override + public boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, ByteBuffer request, + boolean persistent, CefQueryCallback callback) { + if (request != null) { + int size = request.capacity(); + boolean direct = (binary_direct_ = !binary_direct_); + ByteBuffer response = direct ? ByteBuffer.allocateDirect(size) + : ByteBuffer.allocate(size); + // reverse bytes + for (int i = 0; i < size; i++) { + byte b = request.get(i); + response.put(size - i - 1, b); + } + callback.success(response); + if (persistent) { + callback.failure(0, direct ? "Finished (direct)" + : "Finished (array)"); + } } else { // not handled return false; diff --git a/java/tests/detailed/handler/binding_test2.html b/java/tests/detailed/handler/binding_test2.html index 8a61a96f..866c2cd9 100644 --- a/java/tests/detailed/handler/binding_test2.html +++ b/java/tests/detailed/handler/binding_test2.html @@ -17,6 +17,44 @@ }); } +function doPersistent() { + document.getElementById('persist').value = ''; + window.cefQuery({ + request: 'doPersistent', + persistent: true, + onSuccess: function(response) { + document.getElementById('persist').value += ' MESSAGE: [ ' + response + '];'; + }, + onFailure: function(error_code, error_message) { + document.getElementById('persist').value += ' FINISHED: ' + error_code + ' ' + error_message; + } + }); +} + +function doBinary() { + document.getElementById('binary').value = 'ORIGINAL: ['; + const binaryData = new Uint8Array([10, 20, 30, 40, 50]); + for (let i = 0; i < binaryData.length; i++) { + document.getElementById('binary').value += ' ' + binaryData.at(i); + } + document.getElementById('binary').value += ' ];'; + window.cefQuery({ + request: binaryData.buffer, + persistent: true, + onSuccess: function(response) { + document.getElementById('binary').value += ' RESPONSE: ['; + const echoedBinaryData = new Uint8Array(response); + for (let i = 0; i < echoedBinaryData.length; i++) { + document.getElementById('binary').value += ' ' + echoedBinaryData.at(i); + } + document.getElementById('binary').value += ' ];'; + }, + onFailure: function(error_code, error_message) { + document.getElementById('binary').value += ' FINISHED: ' + error_code + ' ' + error_message; + } + }); +} + function execute(cmd) { window.cefQuery({ request: cmd, @@ -75,6 +113,18 @@

JavaScript Binding Test - Part 2


+

This button tests persistent queries. When the button is pressed, it clears the output, +and when it receives a success message, it appends it to the output. When it finally receives +a failure message, it appends the error_code followed by the error_message.

+ + +

This button tests binary queries. When the button is pressed, it clears the output and +replaces it with a representation of the binary data sent in the query. When it receives a +success message, it appends a similar representation of the binary data received (which has +been reversed by Java). When it finally receives a failure message, it appends the error_code +followed by the error_message.

+ + diff --git a/native/CefQueryCallback_N.cpp b/native/CefQueryCallback_N.cpp index 017ff1aa..232e1b82 100644 --- a/native/CefQueryCallback_N.cpp +++ b/native/CefQueryCallback_N.cpp @@ -26,11 +26,55 @@ JNIEXPORT void JNICALL Java_org_cef_callback_CefQueryCallback_1N_N_1Success(JNIEnv* env, jobject obj, jlong self, - jstring response) { + jstring response, + jboolean persistent) { CefRefPtr callback = GetSelf(self); if (!callback) return; callback->Success(GetJNIString(env, response)); + if (persistent) + return; + ClearSelf(env, obj); +} + +JNIEXPORT void JNICALL +Java_org_cef_callback_CefQueryCallback_1N_N_1SuccessBinary( + JNIEnv* env, + jobject obj, + jlong self, + jobject response, + jboolean persistent) { + CefRefPtr callback = GetSelf(self); + if (!callback) + return; + jboolean isDirect = JNI_TRUE; + if (response) + JNI_CALL_BOOLEAN_METHOD(isDirect, env, response, "isDirect", "()Z"); + if (isDirect != JNI_FALSE) { + callback->Success(GetJNIByteBufferData(env, response), + GetJNIByteBufferLength(env, response)); + } else { + jobject responseArrayObj = nullptr; + jint responseOffset = 0; + jint responseLength = 0; + JNI_CALL_METHOD(env, response, "array", "()[B", Object, responseArrayObj); + JNI_CALL_METHOD(env, response, "arrayOffset", "()I", Int, responseOffset); + JNI_CALL_METHOD(env, response, "capacity", "()I", Int, responseLength); + jbyteArray responseArray = static_cast(responseArrayObj); + jsize responseArrayLength = env->GetArrayLength(responseArray); + if (responseLength >= 0 && responseLength <= responseArrayLength && + responseOffset >= 0 && responseOffset < responseLength) { + // isCopy is ignored + jboolean isCopy = JNI_FALSE; + jbyte *responseBytes = env->GetByteArrayElements(responseArray, &isCopy); + callback->Success(static_cast(responseBytes + responseOffset), + static_cast(responseLength)); + // JNI_ABORT: no need to copy back the data + env->ReleaseByteArrayElements(responseArray, responseBytes, JNI_ABORT); + } + } + if (persistent) + return; ClearSelf(env, obj); } diff --git a/native/CefQueryCallback_N.h b/native/CefQueryCallback_N.h index f6ba930f..35aa34dd 100644 --- a/native/CefQueryCallback_N.h +++ b/native/CefQueryCallback_N.h @@ -10,13 +10,26 @@ extern "C" { /* * Class: org_cef_callback_CefQueryCallback_N * Method: N_Success - * Signature: (JLjava/lang/String;)V + * Signature: (JLjava/lang/String;Z)V */ JNIEXPORT void JNICALL Java_org_cef_callback_CefQueryCallback_1N_N_1Success(JNIEnv*, jobject, jlong, - jstring); + jstring, + jboolean); + +/* + * Class: org_cef_callback_CefQueryCallback_N + * Method: N_Success + * Signature: (JLjava/nio/ByteBuffer;Z)V + */ +JNIEXPORT void JNICALL +Java_org_cef_callback_CefQueryCallback_1N_N_1SuccessBinary(JNIEnv*, + jobject, + jlong, + jobject, + jboolean); /* * Class: org_cef_callback_CefQueryCallback_N diff --git a/native/message_router_handler.cpp b/native/message_router_handler.cpp index 793ccf7c..f5b94243 100644 --- a/native/message_router_handler.cpp +++ b/native/message_router_handler.cpp @@ -13,11 +13,16 @@ using CefQueryCallback = CefMessageRouterBrowserSide::Callback; // JNI CefQueryCallback object. class ScopedJNIQueryCallback : public ScopedJNIObject { public: - ScopedJNIQueryCallback(JNIEnv* env, CefRefPtr obj) - : ScopedJNIObject(env, - obj, - "org/cef/callback/CefQueryCallback_N", - "CefQueryCallback") {} + ScopedJNIQueryCallback(JNIEnv* env, + CefRefPtr obj, + bool persistent) + : ScopedJNIObject( + env, + obj, + persistent ? + "org/cef/callback/CefQueryCallback_N_Persistent" : + "org/cef/callback/CefQueryCallback_N", + "CefQueryCallback") {} }; } // namespace @@ -40,7 +45,11 @@ bool MessageRouterHandler::OnQuery( ScopedJNIFrame jframe(env, frame); jframe.SetTemporary(); ScopedJNIString jrequest(env, request); - ScopedJNIQueryCallback jcallback(env, callback); + + // Uses a subclass of CefQueryCallback_N for persistent queries, as otherwise + // the reference to the CefMessageRouterBrowserSide::Callback gets cleared + // immediately when N_Success is called + ScopedJNIQueryCallback jcallback(env, callback, persistent); jboolean jresult = JNI_FALSE; @@ -61,6 +70,49 @@ bool MessageRouterHandler::OnQuery( return jresult != JNI_FALSE; } +bool MessageRouterHandler::OnQuery( + CefRefPtr browser, + CefRefPtr frame, + int64_t query_id, + CefRefPtr request, + bool persistent, + CefRefPtr callback) { + ScopedJNIEnv env; + if (!env) + return false; + + ScopedJNIBrowser jbrowser(env, browser); + ScopedJNIFrame jframe(env, frame); + jframe.SetTemporary(); + ScopedJNIObjectLocal jrequest( + env, + env->NewDirectByteBuffer(const_cast(request->GetData()), + (jlong)request->GetSize())); + + // Uses a subclass of CefQueryCallback_N for persistent queries, as otherwise + // the reference to the CefMessageRouterBrowserSide::Callback gets cleared + // immediately when N_Success is called + ScopedJNIQueryCallback jcallback(env, callback, persistent); + + jboolean jresult = JNI_FALSE; + + JNI_CALL_METHOD(env, handle_, "onQuery", + "(Lorg/cef/browser/CefBrowser;Lorg/cef/browser/" + "CefFrame;JLjava/nio/ByteBuffer;ZLorg/cef/" + "callback/CefQueryCallback;)Z", + Boolean, jresult, jbrowser.get(), jframe.get(), + (jlong)query_id, jrequest.get(), + persistent ? JNI_TRUE : JNI_FALSE, jcallback.get()); + + if (jresult == JNI_FALSE) { + // If the Java method returns "false" the callback won't be used and + // the reference can therefore be removed. + jcallback.SetTemporary(); + } + + return jresult != JNI_FALSE; +} + void MessageRouterHandler::OnQueryCanceled(CefRefPtr browser, CefRefPtr frame, int64_t query_id) { diff --git a/native/message_router_handler.h b/native/message_router_handler.h index 90b4ae77..d1701186 100644 --- a/native/message_router_handler.h +++ b/native/message_router_handler.h @@ -25,6 +25,12 @@ class MessageRouterHandler : public CefMessageRouterBrowserSide::Handler, const CefString& request, bool persistent, CefRefPtr callback) override; + virtual bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64_t query_id, + CefRefPtr request, + bool persistent, + CefRefPtr callback) override; virtual void OnQueryCanceled(CefRefPtr browser, CefRefPtr frame, int64_t query_id) override;