forked from mirrors/gecko-dev
This doesn't change behavior on its own, but it's likely we want to make the tab focusability more complicated in bug 1895184, and this will make changes to this area less painful. Differential Revision: https://phabricator.services.mozilla.com/D209525
437 lines
15 KiB
C++
437 lines
15 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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/. */
|
|
|
|
#include "mozilla/dom/HTMLButtonElement.h"
|
|
|
|
#include "HTMLFormSubmissionConstants.h"
|
|
#include "mozilla/dom/FormData.h"
|
|
#include "mozilla/dom/HTMLButtonElementBinding.h"
|
|
#include "nsAttrValueInlines.h"
|
|
#include "nsIContentInlines.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsIFormControl.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsIFormControlFrame.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/ContentEvents.h"
|
|
#include "mozilla/FocusModel.h"
|
|
#include "mozilla/EventDispatcher.h"
|
|
#include "mozilla/EventStateManager.h"
|
|
#include "mozilla/MouseEvents.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/TextEvents.h"
|
|
#include "nsUnicharUtils.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "mozilla/PresState.h"
|
|
#include "nsError.h"
|
|
#include "nsFocusManager.h"
|
|
#include "mozilla/dom/HTMLFormElement.h"
|
|
#include "mozAutoDocUpdate.h"
|
|
|
|
#define NS_IN_SUBMIT_CLICK (1 << 0)
|
|
#define NS_OUTER_ACTIVATE_EVENT (1 << 1)
|
|
|
|
NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Button)
|
|
|
|
namespace mozilla::dom {
|
|
|
|
static const nsAttrValue::EnumTable kButtonTypeTable[] = {
|
|
{"button", FormControlType::ButtonButton},
|
|
{"reset", FormControlType::ButtonReset},
|
|
{"submit", FormControlType::ButtonSubmit},
|
|
{nullptr, 0}};
|
|
|
|
// Default type is 'submit'.
|
|
static const nsAttrValue::EnumTable* kButtonDefaultType = &kButtonTypeTable[2];
|
|
|
|
// Construction, destruction
|
|
HTMLButtonElement::HTMLButtonElement(
|
|
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
|
|
FromParser aFromParser)
|
|
: nsGenericHTMLFormControlElementWithState(
|
|
std::move(aNodeInfo), aFromParser,
|
|
FormControlType(kButtonDefaultType->value)),
|
|
mDisabledChanged(false),
|
|
mInInternalActivate(false),
|
|
mInhibitStateRestoration(aFromParser & FROM_PARSER_FRAGMENT) {
|
|
// Set up our default state: enabled
|
|
AddStatesSilently(ElementState::ENABLED);
|
|
}
|
|
|
|
HTMLButtonElement::~HTMLButtonElement() = default;
|
|
|
|
// nsISupports
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLButtonElement,
|
|
nsGenericHTMLFormControlElementWithState,
|
|
mValidity)
|
|
|
|
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(
|
|
HTMLButtonElement, nsGenericHTMLFormControlElementWithState,
|
|
nsIConstraintValidation)
|
|
|
|
void HTMLButtonElement::SetCustomValidity(const nsAString& aError) {
|
|
ConstraintValidation::SetCustomValidity(aError);
|
|
UpdateValidityElementStates(true);
|
|
}
|
|
|
|
void HTMLButtonElement::UpdateBarredFromConstraintValidation() {
|
|
SetBarredFromConstraintValidation(
|
|
mType == FormControlType::ButtonButton ||
|
|
mType == FormControlType::ButtonReset ||
|
|
HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled());
|
|
}
|
|
|
|
void HTMLButtonElement::FieldSetDisabledChanged(bool aNotify) {
|
|
// FieldSetDisabledChanged *has* to be called *before*
|
|
// UpdateBarredFromConstraintValidation, because the latter depends on our
|
|
// disabled state.
|
|
nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify);
|
|
|
|
UpdateBarredFromConstraintValidation();
|
|
UpdateValidityElementStates(aNotify);
|
|
}
|
|
|
|
NS_IMPL_ELEMENT_CLONE(HTMLButtonElement)
|
|
|
|
void HTMLButtonElement::GetFormEnctype(nsAString& aFormEncType) {
|
|
GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag,
|
|
aFormEncType);
|
|
}
|
|
|
|
void HTMLButtonElement::GetFormMethod(nsAString& aFormMethod) {
|
|
GetEnumAttr(nsGkAtoms::formmethod, "", kFormDefaultMethod->tag, aFormMethod);
|
|
}
|
|
|
|
void HTMLButtonElement::GetType(nsAString& aType) {
|
|
GetEnumAttr(nsGkAtoms::type, kButtonDefaultType->tag, aType);
|
|
}
|
|
|
|
int32_t HTMLButtonElement::TabIndexDefault() { return 0; }
|
|
|
|
bool HTMLButtonElement::IsHTMLFocusable(IsFocusableFlags aFlags,
|
|
bool* aIsFocusable,
|
|
int32_t* aTabIndex) {
|
|
if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
|
|
aFlags, aIsFocusable, aTabIndex)) {
|
|
return true;
|
|
}
|
|
*aIsFocusable = IsFormControlDefaultFocusable(aFlags) && !IsDisabled();
|
|
return false;
|
|
}
|
|
|
|
bool HTMLButtonElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
|
|
const nsAString& aValue,
|
|
nsIPrincipal* aMaybeScriptedPrincipal,
|
|
nsAttrValue& aResult) {
|
|
if (aNamespaceID == kNameSpaceID_None) {
|
|
if (aAttribute == nsGkAtoms::type) {
|
|
return aResult.ParseEnumValue(aValue, kButtonTypeTable, false,
|
|
kButtonDefaultType);
|
|
}
|
|
|
|
if (aAttribute == nsGkAtoms::formmethod) {
|
|
return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
|
|
}
|
|
if (aAttribute == nsGkAtoms::formenctype) {
|
|
return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
|
|
}
|
|
}
|
|
|
|
return nsGenericHTMLFormControlElementWithState::ParseAttribute(
|
|
aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
|
|
}
|
|
|
|
bool HTMLButtonElement::IsDisabledForEvents(WidgetEvent* aEvent) {
|
|
nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
|
|
nsIFrame* formFrame = do_QueryFrame(formControlFrame);
|
|
return IsElementDisabledForEvents(aEvent, formFrame);
|
|
}
|
|
|
|
void HTMLButtonElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
|
|
aVisitor.mCanHandle = false;
|
|
|
|
if (IsDisabledForEvents(aVisitor.mEvent)) {
|
|
return;
|
|
}
|
|
|
|
// Track whether we're in the outermost Dispatch invocation that will
|
|
// cause activation of the input. That is, if we're a click event, or a
|
|
// DOMActivate that was dispatched directly, this will be set, but if we're
|
|
// a DOMActivate dispatched from click handling, it will not be set.
|
|
WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
|
|
bool outerActivateEvent =
|
|
((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
|
|
(aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
|
|
!mInInternalActivate && aVisitor.mEvent->mOriginalTarget == this));
|
|
|
|
if (outerActivateEvent) {
|
|
aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
|
|
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) {
|
|
return rv;
|
|
}
|
|
|
|
if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
|
|
WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
|
|
if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
|
|
OwnerDoc()->MayHaveDOMActivateListeners()) {
|
|
// DOMActive event should be trusted since the activation is actually
|
|
// occurred even if the cause is an untrusted click event.
|
|
InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
|
|
actEvent.mDetail = 1;
|
|
|
|
if (RefPtr<PresShell> presShell = aVisitor.mPresContext->GetPresShell()) {
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
mInInternalActivate = true;
|
|
presShell->HandleDOMEventWithTarget(this, &actEvent, &status);
|
|
mInInternalActivate = false;
|
|
|
|
// If activate is cancelled, we must do the same as when click is
|
|
// cancelled (revert the checkbox to its original value).
|
|
if (status == nsEventStatus_eConsumeNoDefault) {
|
|
aVisitor.mEventStatus = status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
|
|
WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
|
|
if (keyEvent && keyEvent->IsTrusted()) {
|
|
HandleKeyboardActivation(aVisitor);
|
|
}
|
|
|
|
// 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);
|
|
MOZ_ASSERT(form);
|
|
// 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 forgotten.
|
|
form->OnSubmitClickEnd();
|
|
// Tell the form to flush a possible pending submission.
|
|
// the reason is that the script returned false (the event was
|
|
// not ignored) so if there is a stored submission, it needs to
|
|
// be submitted immediatelly.
|
|
// Note, NS_IN_SUBMIT_CLICK is set only when we're in outer activate event.
|
|
form->FlushPendingSubmission();
|
|
}
|
|
}
|
|
|
|
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.
|
|
}
|
|
if (!GetInvokeTargetElement()) {
|
|
HandlePopoverTargetAction();
|
|
} else {
|
|
HandleInvokeTargetAction();
|
|
}
|
|
}
|
|
|
|
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,
|
|
nsINode& aParent) {
|
|
nsresult rv =
|
|
nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
UpdateBarredFromConstraintValidation();
|
|
UpdateValidityElementStates(false);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void HTMLButtonElement::UnbindFromTree(UnbindContext& aContext) {
|
|
nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext);
|
|
|
|
UpdateBarredFromConstraintValidation();
|
|
UpdateValidityElementStates(false);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HTMLButtonElement::Reset() { return NS_OK; }
|
|
|
|
NS_IMETHODIMP
|
|
HTMLButtonElement::SubmitNamesValues(FormData* aFormData) {
|
|
//
|
|
// We only submit if we were the button pressed
|
|
//
|
|
if (aFormData->GetSubmitterElement() != this) {
|
|
return NS_OK;
|
|
}
|
|
|
|
//
|
|
// Get the name (if no name, no submit)
|
|
//
|
|
nsAutoString name;
|
|
GetHTMLAttr(nsGkAtoms::name, name);
|
|
if (name.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
//
|
|
// Get the value
|
|
//
|
|
nsAutoString value;
|
|
GetHTMLAttr(nsGkAtoms::value, value);
|
|
|
|
//
|
|
// Submit
|
|
//
|
|
return aFormData->AddNameValuePair(name, value);
|
|
}
|
|
|
|
void HTMLButtonElement::DoneCreatingElement() {
|
|
if (!mInhibitStateRestoration) {
|
|
GenerateStateKey();
|
|
RestoreFormControlState();
|
|
}
|
|
}
|
|
|
|
void HTMLButtonElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
|
|
const nsAttrValue* aValue, bool aNotify) {
|
|
if (aNotify && aName == nsGkAtoms::disabled &&
|
|
aNameSpaceID == kNameSpaceID_None) {
|
|
mDisabledChanged = true;
|
|
}
|
|
|
|
return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
|
|
aNameSpaceID, aName, aValue, aNotify);
|
|
}
|
|
|
|
void HTMLButtonElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
|
|
const nsAttrValue* aValue,
|
|
const nsAttrValue* aOldValue,
|
|
nsIPrincipal* aSubjectPrincipal,
|
|
bool aNotify) {
|
|
if (aNameSpaceID == kNameSpaceID_None) {
|
|
if (aName == nsGkAtoms::type) {
|
|
if (aValue) {
|
|
mType = FormControlType(aValue->GetEnumValue());
|
|
} else {
|
|
mType = FormControlType(kButtonDefaultType->value);
|
|
}
|
|
}
|
|
|
|
if (aName == nsGkAtoms::type || aName == nsGkAtoms::disabled) {
|
|
if (aName == nsGkAtoms::disabled) {
|
|
// This *has* to be called *before* validity state check because
|
|
// UpdateBarredFromConstraintValidation depends on our disabled state.
|
|
UpdateDisabledState(aNotify);
|
|
}
|
|
|
|
UpdateBarredFromConstraintValidation();
|
|
UpdateValidityElementStates(aNotify);
|
|
}
|
|
}
|
|
|
|
return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
|
|
aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
|
|
}
|
|
|
|
void HTMLButtonElement::SaveState() {
|
|
if (!mDisabledChanged) {
|
|
return;
|
|
}
|
|
|
|
PresState* state = GetPrimaryPresState();
|
|
if (state) {
|
|
// We do not want to save the real disabled state but the disabled
|
|
// attribute.
|
|
state->disabled() = HasAttr(nsGkAtoms::disabled);
|
|
state->disabledSet() = true;
|
|
}
|
|
}
|
|
|
|
bool HTMLButtonElement::RestoreState(PresState* aState) {
|
|
if (aState && aState->disabledSet() && !aState->disabled()) {
|
|
SetDisabled(false, IgnoreErrors());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void HTMLButtonElement::UpdateValidityElementStates(bool aNotify) {
|
|
AutoStateChangeNotifier notifier(*this, aNotify);
|
|
RemoveStatesSilently(ElementState::VALIDITY_STATES);
|
|
if (!IsCandidateForConstraintValidation()) {
|
|
return;
|
|
}
|
|
if (IsValid()) {
|
|
AddStatesSilently(ElementState::VALID | ElementState::USER_VALID);
|
|
} else {
|
|
AddStatesSilently(ElementState::INVALID | ElementState::USER_INVALID);
|
|
}
|
|
}
|
|
|
|
JSObject* HTMLButtonElement::WrapNode(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return HTMLButtonElement_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
} // namespace mozilla::dom
|