Bug 1322947 - Add support of cancel dialog modal with escape key r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D74937
This commit is contained in:
sefeng 2020-05-27 14:37:19 +00:00
parent 72693e39fa
commit 727ca5589e
11 changed files with 290 additions and 0 deletions

View File

@ -117,6 +117,7 @@
#include "mozilla/dom/HTMLAllCollection.h"
#include "mozilla/dom/HTMLMetaElement.h"
#include "mozilla/dom/HTMLSharedElement.h"
#include "mozilla/dom/HTMLDialogElement.h"
#include "mozilla/dom/MutationObservers.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/Performance.h"
@ -12956,6 +12957,18 @@ void Document::SetFullscreenRoot(Document* aRoot) {
mFullscreenRoot = do_GetWeakReference(aRoot);
}
void Document::TryCancelDialog() {
// Check if the document is blocked by modal dialog
for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
if (HTMLDialogElement* dialog =
HTMLDialogElement::FromNodeOrNull(element)) {
dialog->QueueCancelDialog();
break;
}
}
}
already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
RefPtr<Promise> promise = exit->GetPromise();

View File

@ -1880,6 +1880,9 @@ class Document : public nsINode,
// flag.
bool SetFullscreenElement(Element* aElement);
// Cancel the dialog element if the document is blocked by the dialog
void TryCancelDialog();
/**
* Called when a frame in a child process has entered fullscreen or when a
* fullscreen frame in a child process changes to another origin.

View File

@ -167,6 +167,31 @@ void HTMLDialogElement::FocusDialog() {
}
}
void HTMLDialogElement::QueueCancelDialog() {
// queues an element task on the user interaction task source
OwnerDoc()
->EventTargetFor(TaskCategory::UI)
->Dispatch(NewRunnableMethod("HTMLDialogElement::RunCancelDialogSteps",
this,
&HTMLDialogElement::RunCancelDialogSteps));
}
void HTMLDialogElement::RunCancelDialogSteps() {
// 1) Let close be the result of firing an event named cancel at dialog, with
// the cancelable attribute initialized to true.
bool defaultAction = true;
nsContentUtils::DispatchTrustedEvent(
OwnerDoc(), this, NS_LITERAL_STRING("cancel"), CanBubble::eNo,
Cancelable::eYes, &defaultAction);
// 2) If close is true and dialog has an open attribute, then close the dialog
// with no return value.
if (defaultAction) {
Optional<nsAString> retValue;
Close(retValue);
}
}
JSObject* HTMLDialogElement::WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return HTMLDialogElement_Binding::Wrap(aCx, this, aGivenProto);

View File

@ -44,6 +44,8 @@ class HTMLDialogElement final : public nsGenericHTMLElement {
void ShowModal(ErrorResult& aError);
bool IsInTopLayer() const;
void QueueCancelDialog();
void RunCancelDialogSteps();
nsString mReturnValue;

View File

@ -10,6 +10,7 @@ with Files("**"):
DIRS += ['input']
MOCHITEST_MANIFESTS += [
'test/dialog/mochitest.ini',
'test/forms/mochitest.ini',
'test/mochitest.ini',
]

View File

@ -0,0 +1,7 @@
[DEFAULT]
prefs =
dom.dialog_element.enabled=true
[test_cancelDialogByEscape.html]
[test_dialog_cancel_events.html]
[test_dialog_cancel_preventDefault.html]
[test_dialog_keydown_preventDefault.html]

View File

@ -0,0 +1,64 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1322947
-->
<head>
<title>Test for Bug 1322947</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body onload="SimpleTest.waitForFocus(runTest)">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322947"> Test dialog modal is closed by escape key</a>
<p id="display"></p>
<dialog id="dialog">
<p>Hello World</p>
</dialog>
<dialog id="dialogWithAutofocus">
<input autofocus/>
</dialog>
<pre id="test">
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
/* Make sure we still cancel the dialog even if the input element is focused */
function runTestCancelWhenInputFocused() {
const dialog = document.getElementById("dialogWithAutofocus");
const input = document.querySelector("input");
dialog.addEventListener("close", function() {
ok(dialog.close, "dialog with input autofocused is closed");
done();
});
dialog.showModal();
ok(input == document.activeElement, "input element should be focused");
synthesizeKey("VK_ESCAPE", {}, window);
}
function runTest() {
const dialog = document.getElementById("dialog");
dialog.addEventListener("close", function() {
ok(dialog.close, "dialog closed");
setTimeout(function(){
runTestCancelWhenInputFocused();
}, 0);
});
dialog.showModal();
synthesizeKey("VK_ESCAPE", {}, window);
}
function done() {
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1322947
-->
<head>
<title>Test for Bug 1322947</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body onload="runTest()">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322947">Test cancel event
is fired when the dialog is closed by user interaction</a>
<p id="display"></p>
<dialog>
<p>Hello World</p>
</dialog>
<pre id="test">
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
var hasCancelEventFired = false;
var hasCloseEventFired = false;
function runTest() {
const dialog = document.querySelector("dialog");
dialog.addEventListener("cancel", function(event) {
ok(true, "cancel event is fired");
ok(event.cancelable, "cancel event should be cancelable");
ok(!hasCancelEventFired, "cancel event should only be fired once");
ok(!hasCloseEventFired, "close event should be fired after cancel event");
hasCancelEventFired = true;
});
dialog.addEventListener("close", function() {
ok(true, "close event is fired");
ok(!hasCloseEventFired, "close event should only be fired once");
ok(hasCancelEventFired, "cancel event should be fired before close event");
hasCloseEventFired = true;
done();
});
dialog.showModal();
synthesizeKey("VK_ESCAPE", {}, window);
}
function done() {
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1322947
-->
<head>
<title>Test for Bug 1322947</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body onload="SimpleTest.waitForFocus(runTest)">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322947">Test cancel event with preventDefault on cancel event for dialog element</a>
<p id="display"></p>
<dialog>
<p>Hello World</p>
</dialog>
<pre id="test">
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
var hasCancelEventFired = false;
function runTest() {
const dialog = document.querySelector("dialog");
const verify = () => {
ok(hasCancelEventFired, "cancel is fired");
done();
}
dialog.addEventListener("cancel", function(event) {
hasCancelEventFired = true;
event.preventDefault();
setTimeout(function() {
verify();
}, 0)
});
dialog.addEventListener("close", function() {
ok(false, "close event should not be fired");
});
dialog.showModal();
synthesizeKey("VK_ESCAPE", {}, window);
}
function done() {
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1322947
-->
<head>
<title>Test for Bug 1322947</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body onload="SimpleTest.waitForFocus(runTest)">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322947">Test cancel event with preventDefault on keydown event for dialog element</a>
<p id="display"></p>
<dialog>
<p>Hello World</p>
</dialog>
<pre id="test">
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
var hasCancelEventFired = false;
function runTest() {
const dialog = document.querySelector("dialog");
const verify = () => {
ok(!hasCancelEventFired, "cancel should not be fired");
ok(hasKeydownEventFired, "document level keydown event should be fired");
done();
}
dialog.addEventListener("cancel", function(event) {
hasCancelEventFired = true;
});
document.addEventListener("keydown", function(event) {
hasKeydownEventFired = true;
event.preventDefault();
setTimeout(function() {
verify();
}, 0);
});
dialog.showModal();
synthesizeKey("VK_ESCAPE", {}, window);
}
function done() {
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -8194,6 +8194,12 @@ void PresShell::EventHandler::FinalizeHandlingEvent(WidgetEvent* aEvent) {
aEvent->mFlags.mDefaultPreventedByChrome) {
mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = true;
}
if (aEvent->mMessage == eKeyDown &&
!aEvent->mFlags.mDefaultPrevented) {
if (Document* doc = GetDocument()) {
doc->TryCancelDialog();
}
}
}
}
if (aEvent->mMessage == eKeyDown) {