Bug 1855633 - Part 2: Activation behavior method for button element. r=edgar

Differential Revision: https://phabricator.services.mozilla.com/D183989
This commit is contained in:
Vincent Hilla 2023-10-30 09:39:27 +00:00
parent b88dc29857
commit 3f852adf17
8 changed files with 115 additions and 83 deletions

View File

@ -995,7 +995,6 @@ nsresult EventDispatcher::Dispatch(EventTarget* aTarget,
if (preVisitor.mWantsActivationBehavior) {
MOZ_ASSERT(&chain[0] == targetEtci);
activationTargetItemIndex.emplace(0);
preVisitor.mEvent->mFlags.mMultiplePreActionsPrevented = true;
}
if (!preVisitor.mCanHandle) {
@ -1065,7 +1064,6 @@ nsresult EventDispatcher::Dispatch(EventTarget* aTarget,
activationTargetItemIndex.isNothing() && aEvent->mFlags.mBubbles) {
MOZ_ASSERT(&chain.LastElement() == parentEtci);
activationTargetItemIndex.emplace(chain.Length() - 1);
preVisitor.mEvent->mFlags.mMultiplePreActionsPrevented = true;
}
if (preVisitor.mCanHandle) {

View File

@ -172,21 +172,25 @@ void HTMLButtonElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
if (outerActivateEvent) {
aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
if (mType == FormControlType::ButtonSubmit && mForm &&
!aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented) {
aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented = true;
aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
aVisitor.mItemData = static_cast<Element*>(mForm);
// tell the form that we are about to enter a click handler.
// that means that if there are scripted submissions, the
// latest one will be deferred until after the exit point of the handler.
mForm->OnSubmitClickBegin(this);
}
aVisitor.mWantsActivationBehavior = true;
}
nsGenericHTMLElement::GetEventTargetParent(aVisitor);
}
void HTMLButtonElement::LegacyPreActivationBehavior(
EventChainVisitor& aVisitor) {
// out-of-spec legacy pre-activation behavior needed because of bug 1803805
if (mType == FormControlType::ButtonSubmit && mForm) {
aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
aVisitor.mItemData = static_cast<Element*>(mForm);
// tell the form that we are about to enter a click handler.
// that means that if there are scripted submissions, the
// latest one will be deferred until after the exit point of the handler.
mForm->OnSubmitClickBegin(this);
}
}
nsresult HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
nsresult rv = NS_OK;
if (!aVisitor.mPresContext) {
@ -223,24 +227,19 @@ nsresult HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
HandleKeyboardActivation(aVisitor);
}
if (aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT) {
if (mForm) {
// Hold a strong ref while dispatching
RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
if (mType == FormControlType::ButtonReset) {
form->MaybeReset(this);
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
} else if (mType == FormControlType::ButtonSubmit) {
form->MaybeSubmit(this);
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
}
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type-button-state
// NS_FORM_BUTTON_BUTTON do nothing.
}
HandlePopoverTargetAction();
// Bug 1459231: Temporarily needed till links respect activation target
// Then also remove NS_OUTER_ACTIVATE_EVENT
if ((aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT) && mForm &&
(mType == FormControlType::ButtonReset ||
mType == FormControlType::ButtonSubmit)) {
aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
}
}
return rv;
}
void EndSubmitClick(EventChainVisitor& aVisitor) {
if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK)) {
nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mItemData));
RefPtr<HTMLFormElement> form = HTMLFormElement::FromNodeOrNull(content);
@ -257,8 +256,40 @@ nsresult HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
// Note, NS_IN_SUBMIT_CLICK is set only when we're in outer activate event.
form->FlushPendingSubmission();
}
}
return rv;
void HTMLButtonElement::ActivationBehavior(EventChainPostVisitor& aVisitor) {
if (!aVisitor.mPresContext) {
// Should check whether EndSubmitClick is needed here.
return;
}
if (!IsDisabled()) {
if (mForm) {
// Hold a strong ref while dispatching
RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
if (mType == FormControlType::ButtonReset) {
form->MaybeReset(this);
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
} else if (mType == FormControlType::ButtonSubmit) {
form->MaybeSubmit(this);
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
}
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type-button-state
// NS_FORM_BUTTON_BUTTON do nothing.
}
HandlePopoverTargetAction();
}
EndSubmitClick(aVisitor);
}
void HTMLButtonElement::LegacyCanceledActivationBehavior(
EventChainPostVisitor& aVisitor) {
// still need to end submission, see bug 1803805
// e.g. when parent element of button has event handler preventing default
// legacy canceled instead of activation behavior will be run
EndSubmitClick(aVisitor);
}
nsresult HTMLButtonElement::BindToTree(BindContext& aContext,

View File

@ -53,6 +53,11 @@ class HTMLButtonElement final : public nsGenericHTMLFormControlElementWithState,
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY
nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
void LegacyPreActivationBehavior(EventChainVisitor& aVisitor) override;
MOZ_CAN_RUN_SCRIPT
void ActivationBehavior(EventChainPostVisitor& aVisitor) override;
void LegacyCanceledActivationBehavior(
EventChainPostVisitor& aVisitor) override;
// nsINode
nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;

View File

@ -3125,8 +3125,7 @@ bool HTMLInputElement::CheckActivationBehaviorPreconditions(
if (outerActivateEvent) {
aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
}
return outerActivateEvent &&
!aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented;
return outerActivateEvent;
}
default:
return false;

View File

@ -1,10 +1,4 @@
[Event-dispatch-single-activation-behavior.html]
[When clicking child <FORM><BUTTON type=reset></BUTTON></FORM> of parent <INPUT type=checkbox></INPUT>, only child should be activated.]
expected: FAIL
[When clicking child <FORM><BUTTON type=reset></BUTTON></FORM> of parent <INPUT type=radio></INPUT>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><INPUT type=checkbox></INPUT><SPAN></SPAN></LABEL> of parent <INPUT type=checkbox></INPUT>, only child should be activated.]
expected: FAIL
@ -38,47 +32,8 @@
[When clicking child <LABEL><INPUT type=checkbox></INPUT><SPAN></SPAN></LABEL> of parent <FORM><BUTTON type=reset></BUTTON></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><BUTTON type=button></BUTTON></LABEL> of parent <INPUT type=checkbox></INPUT>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><BUTTON type=button></BUTTON></LABEL> of parent <FORM><INPUT type=submit></INPUT></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><BUTTON type=button></BUTTON></LABEL> of parent <FORM><INPUT type=image></INPUT></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><BUTTON type=button></BUTTON></LABEL> of parent <FORM><INPUT type=reset></INPUT></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><BUTTON type=button></BUTTON></LABEL> of parent <FORM><BUTTON type=submit></BUTTON></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><BUTTON type=button></BUTTON></LABEL> of parent <FORM><BUTTON type=reset></BUTTON></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><BUTTON type=button></BUTTON></LABEL> of parent <A></A>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><BUTTON type=button></BUTTON></LABEL> of parent <AREA></AREA>, only child should be activated.]
expected: FAIL
[When clicking child <LABEL><BUTTON type=button></BUTTON></LABEL> of parent <INPUT type=radio></INPUT>, only child should be activated.]
expected: FAIL
[When clicking child <FORM><INPUT type=submit></INPUT></FORM> of parent <FORM><BUTTON type=submit></BUTTON></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <FORM><INPUT type=submit></INPUT></FORM> of parent <FORM><BUTTON type=reset></BUTTON></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <FORM><INPUT type=image></INPUT></FORM> of parent <FORM><BUTTON type=submit></BUTTON></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <FORM><INPUT type=image></INPUT></FORM> of parent <FORM><BUTTON type=reset></BUTTON></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <FORM><INPUT type=reset></INPUT></FORM> of parent <FORM><BUTTON type=submit></BUTTON></FORM>, only child should be activated.]
expected: FAIL
[When clicking child <FORM><INPUT type=reset></INPUT></FORM> of parent <FORM><BUTTON type=reset></BUTTON></FORM>, only child should be activated.]
expected: FAIL

View File

@ -198,7 +198,6 @@ async_test(t => {
}));
span.addEventListener("click", t.step_func(ev => {
ev.preventDefault();
t.step_timeout(() => t.done(), 500);
}));
@ -207,4 +206,27 @@ async_test(t => {
}, "clicking the child of a button by dispatching a non-bubbling event should not trigger submit");
async_test(t => {
const form = document.createElement("form");
const button = document.createElement("button");
button.disabled = true;
const span = document.createElement("span");
button.appendChild(span);
form.appendChild(button);
document.body.appendChild(form);
form.addEventListener("submit", t.step_func_done(ev => {
ev.preventDefault();
assert_unreached("Form should not be submitted");
}));
span.addEventListener("click", t.step_func(ev => {
assert_true(true, "span was clicked");
t.step_timeout(() => t.done(), 500);
}));
span.click();
}, "clicking the child of a disabled button with .click() should not trigger submit");
</script>

View File

@ -7,11 +7,13 @@
<iframe name=frame1 id=frame1></iframe>
<form id=form1 target=frame1 action="does_not_exist.html">
<button id=submitbutton type=submit>
<div id=buttonchilddiv>
button child div text
</div>
</button>
<div id=parentdiv>
<button id=submitbutton type=submit>
<div id=buttonchilddiv>
button child div text
</div>
</button>
</div>
</form>
<script>
@ -31,4 +33,26 @@ async_test(t => {
buttonChildDiv.click();
});
}, 'This test will pass if a form navigation successfully occurs when clicking a child element of a <button type=submit> element with a onclick event handler which prevents the default form submission and manually calls form.submit() instead.');
async_test(t => {
window.addEventListener('load', () => {
const frame1 = document.getElementById('frame1');
frame1.addEventListener('load', t.step_func_done(() => {}));
const submitButton = document.getElementById('submitbutton');
submitButton.addEventListener('click', event => {
const form = document.getElementById('form1');
form.submit();
});
const parentDiv = document.getElementById("parentdiv");
parentDiv.addEventListener("click", event => {
// event was already handled for the button
// but it's activation behavior won't have run yet and we prevent that now
event.preventDefault();
})
submitButton.click();
});
}, "clicking a submit button, which calls form.submit and has a parent calling e.prevenDefault() should still submit the form");
</script>

View File

@ -99,8 +99,6 @@ struct BaseEventFlags {
// the first <label> element is clicked, that one may set this true.
// Then, the second <label> element won't handle the event.
bool mMultipleActionsPrevented : 1;
// Similar to above but expected to be used during PreHandleEvent phase.
bool mMultiplePreActionsPrevented : 1;
// If mIsBeingDispatched is true, the DOM event created from the event is
// dispatching into the DOM tree and not completed.
bool mIsBeingDispatched : 1;