diff options
Diffstat (limited to 'dom/html/HTMLButtonElement.cpp')
-rw-r--r-- | dom/html/HTMLButtonElement.cpp | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/dom/html/HTMLButtonElement.cpp b/dom/html/HTMLButtonElement.cpp new file mode 100644 index 0000000000..7aa7428a26 --- /dev/null +++ b/dom/html/HTMLButtonElement.cpp @@ -0,0 +1,438 @@ +/* -*- 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 "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsIFormControl.h" +#include "nsIFrame.h" +#include "nsIFormControlFrame.h" +#include "mozilla/dom/Document.h" +#include "mozilla/ContentEvents.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(bool aWithMouse, bool* aIsFocusable, + int32_t* aTabIndex) { + if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable( + aWithMouse, aIsFocusable, aTabIndex)) { + return true; + } + + *aIsFocusable = IsFormControlDefaultFocusable(aWithMouse) && !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(bool aNullParent) { + nsGenericHTMLFormControlElementWithState::UnbindFromTree(aNullParent); + + 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 |