Skip to content

Commit

Permalink
[wpe] Add support for Alert and Confirm dialogs
Browse files Browse the repository at this point in the history
  • Loading branch information
zhani committed Jul 29, 2024
1 parent 7edf64d commit a779ecb
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 9 deletions.
37 changes: 36 additions & 1 deletion wpe/src/main/cpp/Browser/Page.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ class JNIPageCache final : public JNI::TypedClass<JNIPage> {
static_cast<jboolean>(webkit_web_view_can_go_forward(webView)));
}

static gboolean onScriptDialog(Page* page, WebKitScriptDialog* dialog, WebKitWebView* webView)
{
auto dialogPtr = static_cast<jlong>(webkit_script_dialog_ref(dialog));
auto jActiveURL = JNI::String(webkit_web_view_get_uri(webView));
auto jMessage = JNI::String(webkit_script_dialog_get_message(dialog));
callJavaMethod(getJNIPageCache().m_onScriptDialog, page->m_pageJavaInstance.get(), dialogPtr,
webkit_script_dialog_get_dialog_type(dialog), static_cast<jstring>(jActiveURL),
static_cast<jstring>(jMessage));

return TRUE;
}

static bool onFullscreenRequest(Page* page, bool fullscreen) noexcept
{
if (page->m_viewBackend != nullptr) {
Expand Down Expand Up @@ -154,6 +166,7 @@ class JNIPageCache final : public JNI::TypedClass<JNIPage> {
const JNI::Method<void(jdouble)> m_onLoadProgress;
const JNI::Method<void(jstring)> m_onUriChanged;
const JNI::Method<void(jstring, jboolean, jboolean)> m_onTitleChanged;
const JNI::Method<jboolean(jlong, jint, jstring, jstring)> m_onScriptDialog;
const JNI::Method<void()> m_onInputMethodContextIn;
const JNI::Method<void()> m_onInputMethodContextOut;
const JNI::Method<void()> m_onEnterFullscreenMode;
Expand Down Expand Up @@ -183,6 +196,8 @@ class JNIPageCache final : public JNI::TypedClass<JNIPage> {
static void nativeRequestExitFullscreenMode(JNIEnv* env, jobject obj, jlong pagePtr) noexcept;
static void nativeEvaluateJavascript(
JNIEnv* env, jobject obj, jlong pagePtr, jstring script, JNIWKCallback callback) noexcept;
static void nativeScriptDialogClose(JNIEnv* env, jobject obj, jlong dialogPtr) noexcept;
static void nativeScriptDialogConfirm(JNIEnv* env, jobject obj, jlong dialogPtr, jboolean confirm) noexcept;
};

const JNIPageCache& getJNIPageCache()
Expand All @@ -198,6 +213,7 @@ JNIPageCache::JNIPageCache()
, m_onLoadProgress(getMethod<void(jdouble)>("onLoadProgress"))
, m_onUriChanged(getMethod<void(jstring)>("onUriChanged"))
, m_onTitleChanged(getMethod<void(jstring, jboolean, jboolean)>("onTitleChanged"))
, m_onScriptDialog(getMethod<jboolean(jlong, jint, jstring, jstring)>("onScriptDialog"))
, m_onInputMethodContextIn(getMethod<void()>("onInputMethodContextIn"))
, m_onInputMethodContextOut(getMethod<void()>("onInputMethodContextOut"))
, m_onEnterFullscreenMode(getMethod<void()>("onEnterFullscreenMode"))
Expand Down Expand Up @@ -226,7 +242,9 @@ JNIPageCache::JNIPageCache()
JNI::NativeMethod<void(jlong)>(
"nativeRequestExitFullscreenMode", JNIPageCache::nativeRequestExitFullscreenMode),
JNI::NativeMethod<void(jlong, jstring, JNIWKCallback)>(
"nativeEvaluateJavascript", JNIPageCache::nativeEvaluateJavascript));
"nativeEvaluateJavascript", JNIPageCache::nativeEvaluateJavascript),
JNI::NativeMethod<void(jlong)>("nativeScriptDialogClose", JNIPageCache::nativeScriptDialogClose),
JNI::NativeMethod<void(jlong, jboolean)>("nativeScriptDialogConfirm", JNIPageCache::nativeScriptDialogConfirm));
}

jlong JNIPageCache::nativeInit(
Expand Down Expand Up @@ -438,6 +456,21 @@ void JNIPageCache::nativeEvaluateJavascript(
}
}

void JNIPageCache::nativeScriptDialogClose(JNIEnv* /*env*/, jobject /*obj*/, jlong dialogPtr) noexcept
{
Logging::logDebug("Page::nativeScriptDialogClose() [tid %d]", gettid());
auto* dialog = reinterpret_cast<WebKitScriptDialog*>(dialogPtr); // NOLINT(performance-no-int-to-ptr)
webkit_script_dialog_close(dialog);
}

void JNIPageCache::nativeScriptDialogConfirm(
JNIEnv* /*env*/, jobject /*obj*/, jlong dialogPtr, jboolean confirm) noexcept
{
Logging::logDebug("Page::nativeScriptDialogConfirm() [tid %d]", gettid());
auto* dialog = reinterpret_cast<WebKitScriptDialog*>(dialogPtr); // NOLINT(performance-no-int-to-ptr)
webkit_script_dialog_confirm_set_confirmed(dialog, static_cast<gboolean>(confirm));
}

/***********************************************************************************************************************
* Native Page class implementation
**********************************************************************************************************************/
Expand Down Expand Up @@ -478,6 +511,8 @@ Page::Page(
g_signal_connect_swapped(m_webView, "notify::uri", G_CALLBACK(JNIPageCache::onUriChanged), this));
m_signalHandlers.push_back(
g_signal_connect_swapped(m_webView, "notify::title", G_CALLBACK(JNIPageCache::onTitleChanged), this));
m_signalHandlers.push_back(
g_signal_connect_swapped(m_webView, "script-dialog", G_CALLBACK(JNIPageCache::onScriptDialog), this));

wpe_view_backend_set_fullscreen_handler(
wpeBackend, reinterpret_cast<wpe_view_backend_fullscreen_handler>(JNIPageCache::onFullscreenRequest), this);
Expand Down
85 changes: 85 additions & 0 deletions wpe/src/main/java/com/wpe/wpe/Page.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
package com.wpe.wpe;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
Expand All @@ -41,9 +43,12 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;

import com.wpe.wpeview.WPEJsResult;
import com.wpe.wpeview.WPEView;

import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;

/**
* A Page roughly corresponds with a tab in a regular browser UI.
Expand All @@ -61,6 +66,11 @@ public final class Page {
public static final int LOAD_COMMITTED = 2;
public static final int LOAD_FINISHED = 3;

public static final int WEBKIT_SCRIPT_DIALOG_ALERT = 0;
public static final int WEBKIT_SCRIPT_DIALOG_CONFIRM = 1;
public static final int WEBKIT_SCRIPT_DIALOG_PROMPT = 2;
public static final int WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM = 3;

protected long nativePtr = 0;
public long getNativePtr() { return nativePtr; }

Expand All @@ -83,6 +93,8 @@ public final class Page {
private native void nativeDeleteInputMethodContent(long nativePtr, int offset);
private native void nativeRequestExitFullscreenMode(long nativePtr);
private native void nativeEvaluateJavascript(long nativePtr, String script, WKCallback<String> callback);
private native void nativeScriptDialogClose(long nativeDialogPtr);
private native void nativeScriptDialogConfirm(long nativeDialogPtr, boolean confirm);

private final WPEView wpeView;
private final PageSurfaceView surfaceView;
Expand Down Expand Up @@ -199,6 +211,79 @@ public void onTitleChanged(@NonNull String title, boolean canGoBack, boolean can
wpeView.onTitleChanged(title);
}

private class ScriptDialogResult implements WPEJsResult {

private final long nativeScriptDialogPtr;

public ScriptDialogResult(long nativeScriptDialogPtr) { this.nativeScriptDialogPtr = nativeScriptDialogPtr; }
@Override
@SuppressWarnings("SyntheticAccessor")
public void cancel() {
nativeScriptDialogConfirm(nativeScriptDialogPtr, false);
nativeScriptDialogClose(nativeScriptDialogPtr);
}
@Override
@SuppressWarnings("SyntheticAccessor")
public void confirm() {
nativeScriptDialogConfirm(nativeScriptDialogPtr, true);
nativeScriptDialogClose(nativeScriptDialogPtr);
}
}

private static class ScriptDialogCancelListener
implements DialogInterface.OnCancelListener, DialogInterface.OnClickListener {
private final WPEJsResult result;

public ScriptDialogCancelListener(@NonNull WPEJsResult result) { this.result = result; }
@Override
public void onCancel(DialogInterface dialogInterface) {
result.cancel();
}
@Override
public void onClick(DialogInterface dialogInterface, int which) {
result.cancel();
}
}

private static class ScriptDialogPositiveListener implements DialogInterface.OnClickListener {
private final WPEJsResult result;

public ScriptDialogPositiveListener(@NonNull WPEJsResult result) { this.result = result; }

@Override
public void onClick(DialogInterface dialogInterface, int which) {
result.confirm();
}
}

@Keep
public boolean onScriptDialog(long nativeDialogPtr, int dialogType, @NonNull String url, @NonNull String message) {
ScriptDialogResult result = new ScriptDialogResult(nativeDialogPtr);
if (!wpeView.onDialogScript(dialogType, url, message, result)) {
if (dialogType == Page.WEBKIT_SCRIPT_DIALOG_ALERT || dialogType == Page.WEBKIT_SCRIPT_DIALOG_CONFIRM) {
final AlertDialog.Builder builder = new AlertDialog.Builder(wpeView.getContext());
String title = url;
try {
URL alertUrl = new URL(url);
title = "The page at " + alertUrl.getProtocol() + "://" + alertUrl.getHost() + " says";
} catch (MalformedURLException ex) {
// NOOP
}
builder.setTitle(title);
builder.setMessage(message);
builder.setOnCancelListener(new ScriptDialogCancelListener(result));
builder.setPositiveButton("Yes", new ScriptDialogPositiveListener(result));
if (dialogType != Page.WEBKIT_SCRIPT_DIALOG_ALERT) {
builder.setNegativeButton("No", new ScriptDialogCancelListener(result));
}
builder.show();
return true;
}
}

return false;
}

@Keep
public void onInputMethodContextIn() {
WeakReference<WPEView> weakRefecence = new WeakReference<>(wpeView);
Expand Down
86 changes: 78 additions & 8 deletions wpe/src/main/java/com/wpe/wpeview/WPEChromeClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ default void onProgressChanged(@NonNull WPEView view, int progress) {}
*/
default void onReceivedTitle(@NonNull WPEView view, @NonNull String title) {}

/**
* A callback interface used by the host application to notify
* the current page that its custom view has been dismissed.
*/
interface CustomViewCallback {
/**
* Invoked when the host application dismisses the
* custom view.
*/
void onCustomViewHidden();
}

/**
* Notify the host application that the current page has entered full screen mode.
* @param view is the View object to be shown.
Expand All @@ -67,14 +79,72 @@ default void onShowCustomView(@NonNull View view, @NonNull WPEChromeClient.Custo
default void onHideCustomView() {}

/**
* A callback interface used by the host application to notify
* the current page that its custom view has been dismissed.
* Notify the host application that the web page wants to display a
* JavaScript {@code alert()} dialog.
* <p>The default behavior if this method returns {@code false} or is not
* overridden is to show a dialog containing the alert message and suspend
* JavaScript execution until the dialog is dismissed.
* <p>To show a custom dialog, the app should return {@code true} from this
* method, in which case the default dialog will not be shown and JavaScript
* execution will be suspended. The app should call
* {@code WPEJsResult.confirm()} when the custom dialog is dismissed such that
* JavaScript execution can be resumed.
* <p>To suppress the dialog and allow JavaScript execution to
* continue, call {@code WPEJsResult.confirm()} immediately and then return
* {@code true}.
* <p>Note that if the {@link WPEChromeClient} is set to be {@code null},
* or if {@link WPEChromeClient} is not set at all, the default dialog will
* be suppressed and Javascript execution will continue immediately.
* <p>Note that the default dialog does not inherit the {@link
* android.view.Display#FLAG_SECURE} flag from the parent window.
*
* @param view The WPEView that initiated the callback.
* @param url The url of the page requesting the dialog.
* @param message Message to be displayed in the window.
* @param result A WPEJsResult to confirm that the user closed the window.
* @return boolean {@code true} if the request is handled or ignored.
* {@code false} if WPEView needs to show the default dialog.
*/
interface CustomViewCallback {
/**
* Invoked when the host application dismisses the
* custom view.
*/
void onCustomViewHidden();
default boolean onJsAlert(@NonNull WPEView view, @NonNull String url, @NonNull String message,
@NonNull WPEJsResult result) {
return false;
}

/**
* Notify the host application that the web page wants to display a
* JavaScript {@code confirm()} dialog.
* <p>The default behavior if this method returns {@code false} or is not
* overridden is to show a dialog containing the message and suspend
* JavaScript execution until the dialog is dismissed. The default dialog
* will return {@code true} to the JavaScript {@code confirm()} code when
* the user presses the 'confirm' button, and will return {@code false} to
* the JavaScript code when the user presses the 'cancel' button or
* dismisses the dialog.
* <p>To show a custom dialog, the app should return {@code true} from this
* method, in which case the default dialog will not be shown and JavaScript
* execution will be suspended. The app should call
* {@code WPEJsResult.confirm()} or {@code WPEJsResult.cancel()} when the custom
* dialog is dismissed.
* <p>To suppress the dialog and allow JavaScript execution to continue,
* call {@code WPEJsResult.confirm()} or {@code WPEJsResult.cancel()} immediately
* and then return {@code true}.
* <p>Note that if the {@link WPEChromeClient} is set to be {@code null},
* or if {@link WPEChromeClient} is not set at all, the default dialog will
* be suppressed and the default value of {@code false} will be returned to
* the JavaScript code immediately.
* <p>Note that the default dialog does not inherit the {@link
* android.view.Display#FLAG_SECURE} flag from the parent window.
*
* @param view The WPEView that initiated the callback.
* @param url The url of the page requesting the dialog.
* @param message Message to be displayed in the window.
* @param result A WPEJsResult used to send the user's response to
* javascript.
* @return boolean {@code true} if the request is handled or ignored.
* {@code false} if WPEView needs to show the default dialog.
*/
default boolean onJsConfirm(@NonNull WPEView view, @NonNull String url, @NonNull String message,
@NonNull WPEJsResult result) {
return false;
}
}
7 changes: 7 additions & 0 deletions wpe/src/main/java/com/wpe/wpeview/WPEJsResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.wpe.wpeview;

public interface WPEJsResult {
void cancel();

void confirm();
}
12 changes: 12 additions & 0 deletions wpe/src/main/java/com/wpe/wpeview/WPEView.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ public void onTitleChanged(@NonNull String title) {
wpeChromeClient.onReceivedTitle(this, title);
}

public boolean onDialogScript(int dialogType, @NonNull String url, @NonNull String message,
@NonNull WPEJsResult result) {
if (wpeChromeClient != null) {
if (dialogType == Page.WEBKIT_SCRIPT_DIALOG_ALERT) {
return wpeChromeClient.onJsAlert(this, url, message, result);
} else if (dialogType == Page.WEBKIT_SCRIPT_DIALOG_CONFIRM) {
return wpeChromeClient.onJsConfirm(this, url, message, result);
}
}
return false;
}

public void onEnterFullscreenMode() {
if ((surfaceView != null) && (wpeChromeClient != null)) {
removeView(surfaceView);
Expand Down

0 comments on commit a779ecb

Please sign in to comment.