Bug 1659218 - Prevent activating disabled inputs via dispatchEvent r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D87151
This commit is contained in:
Kagami Sascha Rosylight 2020-10-27 09:17:08 +00:00
Родитель 17accc12da
Коммит 34dff9ebd1
3 изменённых файлов: 91 добавлений и 7 удалений

Просмотреть файл

@ -3107,7 +3107,8 @@ void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
// we've already toggled the state from onclick since the user could
// explicitly dispatch DOMActivate on the element.
//
// This is a compatibility hack.
// These are compatibility hacks and are defined as legacy-pre-activation
// and legacy-canceled-activation behavior in HTML.
//
// Track whether we're in the outermost Dispatch invocation that will
@ -3639,7 +3640,7 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
// tell the form that we are about to exit a click handler
// so the form knows not to defer subsequent submissions
// the pending ones that were created during the handler
// will be flushed or forgoten.
// will be flushed or forgotten.
mForm->OnSubmitClickEnd();
break;
default:
@ -3647,12 +3648,23 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
}
}
// now check to see if the event was "cancelled"
bool preventDefault =
aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault;
if (IsDisabled() && oldType != NS_FORM_INPUT_CHECKBOX &&
oldType != NS_FORM_INPUT_RADIO) {
// Behave as if defaultPrevented when the element becomes disabled by event
// listeners. Checkboxes and radio buttons should still process clicks for
// web compat. See:
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
preventDefault = true;
}
// now check to see if the event was canceled
if (mCheckedIsToggled && outerActivateEvent) {
if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
// if it was cancelled and a radio button, then set the old
if (preventDefault) {
// if it was canceled and a radio button, then set the old
// selected btn to TRUE. if it is a checkbox then set it to its
// original value
// original value (legacy-canceled-activation)
if (oldType == NS_FORM_INPUT_RADIO) {
nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
HTMLInputElement* selectedRadioButton =
@ -3704,7 +3716,7 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
FireChangeEventIfNeeded();
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
} else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
} else if (!preventDefault) {
switch (aVisitor.mEvent->mMessage) {
case eFocus: {
// see if we should select the contents of the textbox. This happens

Просмотреть файл

@ -302,4 +302,50 @@ async_test(function(t) {
assert_false(didSubmit)
t.done()
}, "disconnected form should not submit")
async_test(t => {
const form = document.createElement("form");
form.onsubmit = t.step_func(ev => {
ev.preventDefault();
assert_unreached("The form is unexpectedly submitted.");
});
dump.append(form);
const input = form.appendChild(document.createElement("input"));
input.type = "submit"
input.disabled = true;
input.dispatchEvent(new MouseEvent("click", { cancelable: true }));
t.done();
}, "disabled submit button should not activate");
async_test(t => {
const form = document.createElement("form");
form.onsubmit = t.step_func(ev => {
ev.preventDefault();
assert_unreached("The form is unexpectedly submitted.");
});
dump.append(form);
const input = form.appendChild(document.createElement("input"));
input.onclick = t.step_func(() => {
input.disabled = true;
});
input.type = "submit"
input.dispatchEvent(new MouseEvent("click", { cancelable: true }));
t.done();
}, "submit button should not activate if the event listener disables it");
async_test(t => {
const form = document.createElement("form");
form.onsubmit = t.step_func(ev => {
ev.preventDefault();
assert_unreached("The form is unexpectedly submitted.");
});
dump.append(form);
const input = form.appendChild(document.createElement("input"));
input.onclick = t.step_func(() => {
input.type = "submit"
input.disabled = true;
});
input.click();
t.done();
}, "submit button that morphed from checkbox should not activate");
</script>

Просмотреть файл

@ -49,4 +49,30 @@ test(t => {
label.dispatchEvent(new MouseEvent("click"));
assert_false(input.checked);
}, "disabled radio should not be checked from label click by dispatchEvent");
test(t => {
const checkbox = dump.appendChild(document.createElement("input"));
checkbox.type = "checkbox";
checkbox.onclick = ev => {
checkbox.type = "date";
ev.preventDefault();
};
checkbox.dispatchEvent(new MouseEvent("click", { cancelable: true }));
assert_false(checkbox.checked);
}, "checkbox morphed into another type should not mutate checked state");
test(t => {
const radio1 = dump.appendChild(document.createElement("input"));
const radio2 = dump.appendChild(radio1.cloneNode());
radio1.type = radio2.type = "radio";
radio1.name = radio2.name = "foo";
radio2.checked = true;
radio1.onclick = ev => {
radio1.type = "date";
ev.preventDefault();
};
radio1.dispatchEvent(new MouseEvent("click", { cancelable: true }));
assert_false(radio1.checked);
assert_true(radio2.checked);
}, "radio morphed into another type should not steal the existing checked state");
</script>