mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 16:55:40 +00:00
Bug 1629113 - Implement nsIPromptCollection on GeckoView. r=snorp,droeh
Differential Revision: https://phabricator.services.mozilla.com/D72722
This commit is contained in:
parent
55a1cf51ab
commit
cabca57aba
39
mobile/android/components/geckoview/PromptCollection.jsm
Normal file
39
mobile/android/components/geckoview/PromptCollection.jsm
Normal 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,
|
||||
]);
|
@ -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': [
|
||||
|
@ -29,6 +29,7 @@ EXTRA_COMPONENTS += [
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'GeckoViewPrompter.jsm',
|
||||
'PromptCollection.jsm',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
@ -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);
|
||||
|
@ -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>
|
@ -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"
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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 });
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user