Bug 1629113 - Implement nsIPromptCollection on GeckoView. r=snorp,droeh

Differential Revision: https://phabricator.services.mozilla.com/D72722
This commit is contained in:
Agi Sferro 2020-05-22 23:22:41 +00:00
parent 55a1cf51ab
commit cabca57aba
11 changed files with 196 additions and 2 deletions

View File

@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["PromptCollection"];
const { GeckoViewUtils } = ChromeUtils.import(
"resource://gre/modules/GeckoViewUtils.jsm"
);
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.jsm",
});
const { debug, warn } = GeckoViewUtils.initLogging("PromptCollection"); // eslint-disable-line no-unused-vars
class PromptCollection {
beforeUnloadCheck(browsingContext) {
const msg = {
type: "beforeUnload",
};
const prompter = new GeckoViewPrompter(browsingContext);
const result = prompter.showPrompt(msg);
return !!result?.allow;
}
}
PromptCollection.prototype.classID = Components.ID(
"{3e30d2a0-9934-11ea-bb37-0242ac130002}"
);
PromptCollection.prototype.QueryInterface = ChromeUtils.generateQI([
Ci.nsIPromptCollection,
]);

View File

@ -13,6 +13,12 @@ Classes = [
'headers': ['GeckoViewHistory.h'],
'constructor': 'GeckoViewHistory::GetSingleton',
},
{
'cid': '{3e30d2a0-9934-11ea-bb37-0242ac130002}',
'contract_ids': ['@mozilla.org/embedcomp/prompt-collection;1'],
'jsm': 'resource://gre/modules/PromptCollection.jsm',
'constructor': 'PromptCollection',
},
{
'cid': '{91455c77-64a1-4c37-be00-f94eb9c7b8e1}',
'contract_ids': [

View File

@ -29,6 +29,7 @@ EXTRA_COMPONENTS += [
EXTRA_JS_MODULES += [
'GeckoViewPrompter.jsm',
'PromptCollection.jsm',
]
FINAL_LIBRARY = 'xul'

View File

@ -830,6 +830,7 @@ package org.mozilla.geckoview {
public static interface GeckoSession.PromptDelegate {
method @UiThread @Nullable default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onAlertPrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.AlertPrompt);
method @UiThread @Nullable default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onAuthPrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.AuthPrompt);
method @UiThread @Nullable default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onBeforeUnloadPrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.BeforeUnloadPrompt);
method @UiThread @Nullable default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onButtonPrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.ButtonPrompt);
method @UiThread @Nullable default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onChoicePrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.ChoicePrompt);
method @UiThread @Nullable default public GeckoResult<GeckoSession.PromptDelegate.PromptResponse> onColorPrompt(@NonNull GeckoSession, @NonNull GeckoSession.PromptDelegate.ColorPrompt);
@ -886,6 +887,11 @@ package org.mozilla.geckoview {
field @Nullable public final String title;
}
public static class GeckoSession.PromptDelegate.BeforeUnloadPrompt extends GeckoSession.PromptDelegate.BasePrompt {
ctor protected BeforeUnloadPrompt();
method @UiThread @NonNull public GeckoSession.PromptDelegate.PromptResponse confirm(@Nullable AllowOrDeny);
}
public static class GeckoSession.PromptDelegate.ButtonPrompt extends GeckoSession.PromptDelegate.BasePrompt {
ctor protected ButtonPrompt(@Nullable String, @Nullable String);
method @UiThread @NonNull public GeckoSession.PromptDelegate.PromptResponse confirm(int);

View File

@ -0,0 +1,11 @@
<!DOCTYPE html><html>
<body onbeforeunload="return beforeUnload()">
<a id=navigateAway href="./hello.html">Click Me</a>
<a id=navigateAway2 href="./hello2.html">Click Me</a>
<script>
function beforeUnload() {
return "Please don't leave.";
}
</script>
</body>
</html>

View File

@ -26,6 +26,7 @@ import kotlin.reflect.KClass
*/
open class BaseSessionTest(noErrorCollector: Boolean = false) {
companion object {
const val BEFORE_UNLOAD = "/assets/www/beforeunload.html"
const val CLICK_TO_RELOAD_HTML_PATH = "/assets/www/clickToReload.html"
const val CONTENT_CRASH_URL = "about:crashcontent"
const val DOWNLOAD_HTML_PATH = "/assets/www/download.html"

View File

@ -114,8 +114,10 @@ class PromptDelegateTest : BaseSessionTest() {
})
}
@Ignore // TODO: Reenable when 1501574 is fixed.
@Test fun buttonTest() {
sessionRule.session.loadTestPath(HELLO_HTML_PATH)
sessionRule.waitForPageStop()
sessionRule.delegateDuringNextWait(object : Callbacks.PromptDelegate {
@AssertCalled(count = 1)
override fun onButtonPrompt(session: GeckoSession, prompt: PromptDelegate.ButtonPrompt): GeckoResult<PromptDelegate.PromptResponse> {
@ -141,6 +143,57 @@ class PromptDelegateTest : BaseSessionTest() {
equalTo(false))
}
@Test
fun onBeforeUnloadTest() {
sessionRule.setPrefsUntilTestEnd(mapOf(
"dom.require_user_interaction_for_beforeunload" to false
))
sessionRule.session.loadTestPath(BEFORE_UNLOAD)
sessionRule.waitForPageStop()
val result = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object: Callbacks.ProgressDelegate {
override fun onPageStart(session: GeckoSession, url: String) {
assertThat("Only HELLO2_HTML_PATH should load", url, endsWith(HELLO2_HTML_PATH))
result.complete(null)
}
})
var promptResult = GeckoResult<PromptDelegate.PromptResponse>()
sessionRule.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
override fun onBeforeUnloadPrompt(session: GeckoSession, prompt: PromptDelegate.BeforeUnloadPrompt): GeckoResult<PromptDelegate.PromptResponse>? {
// We have to return something here because otherwise the delegate will be invoked
// before we have a chance to override it in the waitUntilCalled call below
return promptResult
}
})
// This will try to load "hello.html" but will be denied, if the request
// goes through anyway the onLoadRequest delegate above will throw an exception
sessionRule.session.evaluateJS("document.querySelector('#navigateAway').click()")
sessionRule.waitUntilCalled(object : Callbacks.PromptDelegate {
@AssertCalled(count = 1)
override fun onBeforeUnloadPrompt(session: GeckoSession, prompt: PromptDelegate.BeforeUnloadPrompt): GeckoResult<PromptDelegate.PromptResponse>? {
promptResult.complete(prompt.confirm(AllowOrDeny.DENY))
return promptResult
}
})
// This request will go through and end the test. Doing the negative case first will
// ensure that if either of this tests fail the test will fail.
promptResult = GeckoResult()
sessionRule.session.evaluateJS("document.querySelector('#navigateAway2').click()")
sessionRule.waitUntilCalled(object : Callbacks.PromptDelegate {
@AssertCalled(count = 1)
override fun onBeforeUnloadPrompt(session: GeckoSession, prompt: PromptDelegate.BeforeUnloadPrompt): GeckoResult<PromptDelegate.PromptResponse>? {
promptResult.complete(prompt.confirm(AllowOrDeny.ALLOW))
return promptResult
}
})
sessionRule.waitForResult(result)
}
@Test fun textTest() {
sessionRule.session.loadTestPath(HELLO_HTML_PATH)
sessionRule.session.waitForPageStop()

View File

@ -2640,6 +2640,12 @@ public class GeckoSession implements Parcelable {
res = delegate.onAlertPrompt(session, prompt);
break;
}
case "beforeUnload": {
final PromptDelegate.BeforeUnloadPrompt prompt =
new PromptDelegate.BeforeUnloadPrompt();
res = delegate.onBeforeUnloadPrompt(session, prompt);
break;
}
case "button": {
final PromptDelegate.ButtonPrompt prompt =
new PromptDelegate.ButtonPrompt(title, msg);
@ -3872,6 +3878,30 @@ public class GeckoSession implements Parcelable {
}
}
/**
* BeforeUnloadPrompt represents the onbeforeunload prompt.
* See https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
*/
class BeforeUnloadPrompt extends BasePrompt {
protected BeforeUnloadPrompt() {
super(null);
}
/**
* Confirms the prompt.
*
* @param allowOrDeny whether the navigation should be allowed to continue or not.
*
* @return A {@link PromptResponse} which can be used to complete
* the {@link GeckoResult} associated with this prompt.
*/
@UiThread
public @NonNull PromptResponse confirm(final @Nullable AllowOrDeny allowOrDeny) {
ensureResult().putBoolean("allow", allowOrDeny != AllowOrDeny.DENY);
return super.confirm();
}
}
/**
* AlertPrompt contains the information necessary to represent a JavaScript
* alert() call from content; it can only be dismissed, not confirmed.
@ -4832,6 +4862,14 @@ public class GeckoSession implements Parcelable {
return null;
}
@UiThread
default @Nullable GeckoResult<PromptResponse> onBeforeUnloadPrompt(
@NonNull final GeckoSession session,
@NonNull final BeforeUnloadPrompt prompt
) {
return null;
}
/**
* Display a button prompt.
*

View File

@ -27,12 +27,14 @@ exclude: true
([bug 1622500]({{bugzilla}}1622500))
- Added [`NavigationDelegate.onSubframeLoadRequest`][78.5] to allow intercepting
non-top-level navigations.
- Added [`BeforeUnloadPrompt`][78.6] to respond to prompts from onbeforeunload.
[78.1]: {{javadoc_uri}}/WebExtensionController.html#installBuiltIn-java.lang.String-
[78.2]: {{javadoc_uri}}/ContentBlocking.CookieBehavior.html#ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS
[78.3]: {{javadoc_uri}}/WebExtension.CreateTabDetails.html
[78.4]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/contextualIdentities
[78.5]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.html#onSubframeLoadRequest-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest-
[78.6]: {{javadoc_uri}}/GeckoSession.PromptDelegate.BeforeUnloadPrompt.html
## v77
- Added [`GeckoRuntime.appendAppNotesToCrashReport`][77.1] For adding app notes to the crash report.
@ -696,4 +698,4 @@ exclude: true
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: bde8001c948235193636d0d21f684baeb551e739
[api-version]: f23cb7b15d085f0c3a9be06fe2281b0ffd9adf1e

View File

@ -18,6 +18,8 @@ import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.InputType;
import android.text.format.DateFormat;
import android.util.Log;
@ -120,6 +122,37 @@ final class BasicGeckoViewPrompt implements GeckoSession.PromptDelegate {
return GeckoResult.fromValue(prompt.dismiss());
}
@Nullable
@Override
public GeckoResult<PromptResponse> onBeforeUnloadPrompt(final GeckoSession session,
final BeforeUnloadPrompt prompt) {
final Activity activity = mActivity;
if (activity == null) {
return GeckoResult.fromValue(prompt.dismiss());
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
.setTitle(R.string.before_unload_title)
.setMessage(R.string.before_unload_message);
GeckoResult<PromptResponse> res = new GeckoResult<>();
final DialogInterface.OnClickListener listener = (dialog, which) -> {
if (which == DialogInterface.BUTTON_POSITIVE) {
res.complete(prompt.confirm(AllowOrDeny.ALLOW));
} else if (which == DialogInterface.BUTTON_NEGATIVE) {
res.complete(prompt.confirm(AllowOrDeny.DENY));
} else {
res.complete(prompt.dismiss());
}
};
builder.setPositiveButton(R.string.before_unload_leave_page, listener);
builder.setNegativeButton(R.string.before_unload_stay, listener);
createStandardDialog(builder, prompt, res).show();
return res;
}
private int getViewPadding(final AlertDialog.Builder builder) {
final TypedArray attr = builder.getContext().obtainStyledAttributes(
new int[] { android.R.attr.listPreferredItemPaddingLeft });

View File

@ -58,6 +58,10 @@
<string name="key_allow_extensions_in_private_browsing">allow_extensions_in_private_browsing</string>
<string name="key_preferred_color_scheme">preferred_color_scheme</string>
<string name="key_user_agent_override">user_agent_override</string>
<string name="before_unload_message">This page is asking you to confirm that you want to leave - data you have entered may not be saved</string>
<string name="before_unload_title">Are you sure?</string>
<string name="before_unload_leave_page">Leave Page</string>
<string name="before_unload_stay">Stay on Page</string>
<string-array name="pref_preferred_color_scheme_display_names">
<item>Follow System Preference</item>