diff options
Diffstat (limited to 'dom/html/ElementInternals.cpp')
-rw-r--r-- | dom/html/ElementInternals.cpp | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/dom/html/ElementInternals.cpp b/dom/html/ElementInternals.cpp new file mode 100644 index 0000000000..aaf58f818e --- /dev/null +++ b/dom/html/ElementInternals.cpp @@ -0,0 +1,485 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/ElementInternals.h" + +#include "mozAutoDocUpdate.h" +#include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/dom/CustomEvent.h" +#include "mozilla/dom/CustomStateSet.h" +#include "mozilla/dom/ElementInternalsBinding.h" +#include "mozilla/dom/FormData.h" +#include "mozilla/dom/HTMLElement.h" +#include "mozilla/dom/HTMLFieldSetElement.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "mozilla/dom/MutationObservers.h" +#include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/ValidityState.h" +#include "nsContentUtils.h" +#include "nsDebug.h" +#include "nsGenericHTMLElement.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ElementInternals) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementInternals) + tmp->Unlink(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTarget, mSubmissionValue, mState, mValidity, + mValidationAnchor, mCustomStateSet); + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementInternals) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTarget, mSubmissionValue, mState, + mValidity, mValidationAnchor, + mCustomStateSet); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementInternals) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementInternals) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementInternals) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsIFormControl) + NS_INTERFACE_MAP_ENTRY(nsIConstraintValidation) +NS_INTERFACE_MAP_END + +ElementInternals::ElementInternals(HTMLElement* aTarget) + : nsIFormControl(FormControlType::FormAssociatedCustomElement), + mTarget(aTarget), + mForm(nullptr), + mFieldSet(nullptr), + mControlNumber(-1) {} + +nsISupports* ElementInternals::GetParentObject() { return ToSupports(mTarget); } + +JSObject* ElementInternals::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return ElementInternals_Binding::Wrap(aCx, this, aGivenProto); +} + +// https://html.spec.whatwg.org/#dom-elementinternals-shadowroot +ShadowRoot* ElementInternals::GetShadowRoot() const { + MOZ_ASSERT(mTarget); + + ShadowRoot* shadowRoot = mTarget->GetShadowRoot(); + if (shadowRoot && !shadowRoot->IsAvailableToElementInternals()) { + return nullptr; + } + + return shadowRoot; +} + +// https://html.spec.whatwg.org/commit-snapshots/912a3fe1f29649ccf8229de56f604b3c07ffd242/#dom-elementinternals-setformvalue +void ElementInternals::SetFormValue( + const Nullable<FileOrUSVStringOrFormData>& aValue, + const Optional<Nullable<FileOrUSVStringOrFormData>>& aState, + ErrorResult& aRv) { + MOZ_ASSERT(mTarget); + + /** + * 1. Let element be this's target element. + * 2. If element is not a form-associated custom element, then throw a + * "NotSupportedError" DOMException. + */ + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return; + } + + /** + * 3. Set target element's submission value to value if value is not a + * FormData object, or to a clone of the entry list associated with value + * otherwise. + */ + mSubmissionValue.SetNull(); + if (!aValue.IsNull()) { + const FileOrUSVStringOrFormData& value = aValue.Value(); + OwningFileOrUSVStringOrFormData& owningValue = mSubmissionValue.SetValue(); + if (value.IsFormData()) { + owningValue.SetAsFormData() = value.GetAsFormData().Clone(); + } else if (value.IsFile()) { + owningValue.SetAsFile() = &value.GetAsFile(); + } else { + owningValue.SetAsUSVString() = value.GetAsUSVString(); + } + } + + /** + * 4. If the state argument of the function is omitted, set element's state to + * its submission value. + */ + if (!aState.WasPassed()) { + mState = mSubmissionValue; + return; + } + + /** + * 5. Otherwise, if state is a FormData object, set element's state to clone + * of the entry list associated with state. + * 6. Otherwise, set element's state to state. + */ + mState.SetNull(); + if (!aState.Value().IsNull()) { + const FileOrUSVStringOrFormData& state = aState.Value().Value(); + OwningFileOrUSVStringOrFormData& owningState = mState.SetValue(); + if (state.IsFormData()) { + owningState.SetAsFormData() = state.GetAsFormData().Clone(); + } else if (state.IsFile()) { + owningState.SetAsFile() = &state.GetAsFile(); + } else { + owningState.SetAsUSVString() = state.GetAsUSVString(); + } + } +} + +// https://html.spec.whatwg.org/#dom-elementinternals-form +HTMLFormElement* ElementInternals::GetForm(ErrorResult& aRv) const { + MOZ_ASSERT(mTarget); + + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return nullptr; + } + return GetForm(); +} + +// https://html.spec.whatwg.org/commit-snapshots/3ad5159be8f27e110a70cefadcb50fc45ec21b05/#dom-elementinternals-setvalidity +void ElementInternals::SetValidity( + const ValidityStateFlags& aFlags, const Optional<nsAString>& aMessage, + const Optional<NonNull<nsGenericHTMLElement>>& aAnchor, ErrorResult& aRv) { + MOZ_ASSERT(mTarget); + + /** + * 1. Let element be this's target element. + * 2. If element is not a form-associated custom element, then throw a + * "NotSupportedError" DOMException. + */ + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return; + } + + /** + * 3. If flags contains one or more true values and message is not given or is + * the empty string, then throw a TypeError. + */ + if ((aFlags.mBadInput || aFlags.mCustomError || aFlags.mPatternMismatch || + aFlags.mRangeOverflow || aFlags.mRangeUnderflow || + aFlags.mStepMismatch || aFlags.mTooLong || aFlags.mTooShort || + aFlags.mTypeMismatch || aFlags.mValueMissing) && + (!aMessage.WasPassed() || aMessage.Value().IsEmpty())) { + aRv.ThrowTypeError("Need to provide validation message"); + return; + } + + /** + * 4. For each entry flag → value of flags, set element's validity flag with + * the name flag to value. + */ + SetValidityState(VALIDITY_STATE_VALUE_MISSING, aFlags.mValueMissing); + SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, aFlags.mTypeMismatch); + SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, aFlags.mPatternMismatch); + SetValidityState(VALIDITY_STATE_TOO_LONG, aFlags.mTooLong); + SetValidityState(VALIDITY_STATE_TOO_SHORT, aFlags.mTooShort); + SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, aFlags.mRangeUnderflow); + SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, aFlags.mRangeOverflow); + SetValidityState(VALIDITY_STATE_STEP_MISMATCH, aFlags.mStepMismatch); + SetValidityState(VALIDITY_STATE_BAD_INPUT, aFlags.mBadInput); + SetValidityState(VALIDITY_STATE_CUSTOM_ERROR, aFlags.mCustomError); + mTarget->UpdateValidityElementStates(true); + + /** + * 5. Set element's validation message to the empty string if message is not + * given or all of element's validity flags are false, or to message + * otherwise. + * 6. If element's customError validity flag is true, then set element's + * custom validity error message to element's validation message. + * Otherwise, set element's custom validity error message to the empty + * string. + */ + mValidationMessage = + (!aMessage.WasPassed() || IsValid()) ? EmptyString() : aMessage.Value(); + + /** + * 7. Set element's validation anchor to null if anchor is not given. + * Otherwise, if anchor is not a shadow-including descendant of element, + * then throw a "NotFoundError" DOMException. Otherwise, set element's + * validation anchor to anchor. + */ + nsGenericHTMLElement* anchor = + aAnchor.WasPassed() ? &aAnchor.Value() : nullptr; + // TODO: maybe create something like IsShadowIncludingDescendantOf if there + // are other places also need such check. + if (anchor && (anchor == mTarget || + !anchor->IsShadowIncludingInclusiveDescendantOf(mTarget))) { + aRv.ThrowNotFoundError( + "Validation anchor is not a shadow-including descendant of target" + "element"); + return; + } + mValidationAnchor = anchor; +} + +// https://html.spec.whatwg.org/#dom-elementinternals-willvalidate +bool ElementInternals::GetWillValidate(ErrorResult& aRv) const { + MOZ_ASSERT(mTarget); + + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return false; + } + return WillValidate(); +} + +// https://html.spec.whatwg.org/#dom-elementinternals-validity +ValidityState* ElementInternals::GetValidity(ErrorResult& aRv) { + MOZ_ASSERT(mTarget); + + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return nullptr; + } + return Validity(); +} + +// https://html.spec.whatwg.org/#dom-elementinternals-validationmessage +void ElementInternals::GetValidationMessage(nsAString& aValidationMessage, + ErrorResult& aRv) const { + MOZ_ASSERT(mTarget); + + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return; + } + aValidationMessage = mValidationMessage; +} + +// https://html.spec.whatwg.org/#dom-elementinternals-checkvalidity +bool ElementInternals::CheckValidity(ErrorResult& aRv) { + MOZ_ASSERT(mTarget); + + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return false; + } + return nsIConstraintValidation::CheckValidity(*mTarget); +} + +// https://html.spec.whatwg.org/#dom-elementinternals-reportvalidity +bool ElementInternals::ReportValidity(ErrorResult& aRv) { + MOZ_ASSERT(mTarget); + + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return false; + } + + bool defaultAction = true; + if (nsIConstraintValidation::CheckValidity(*mTarget, &defaultAction)) { + return true; + } + + if (!defaultAction) { + return false; + } + + AutoTArray<RefPtr<Element>, 1> invalidElements; + invalidElements.AppendElement(mTarget); + + AutoJSAPI jsapi; + if (!jsapi.Init(mTarget->GetOwnerGlobal())) { + return false; + } + JS::Rooted<JS::Value> detail(jsapi.cx()); + if (!ToJSValue(jsapi.cx(), invalidElements, &detail)) { + return false; + } + + RefPtr<CustomEvent> event = + NS_NewDOMCustomEvent(mTarget->OwnerDoc(), nullptr, nullptr); + event->InitCustomEvent(jsapi.cx(), u"MozInvalidForm"_ns, + /* CanBubble */ true, + /* Cancelable */ true, detail); + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + mTarget->DispatchEvent(*event); + + return false; +} + +// https://html.spec.whatwg.org/#dom-elementinternals-labels +already_AddRefed<nsINodeList> ElementInternals::GetLabels( + ErrorResult& aRv) const { + MOZ_ASSERT(mTarget); + + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return nullptr; + } + return mTarget->Labels(); +} + +nsGenericHTMLElement* ElementInternals::GetValidationAnchor( + ErrorResult& aRv) const { + MOZ_ASSERT(mTarget); + + if (!mTarget->IsFormAssociatedElement()) { + aRv.ThrowNotSupportedError( + "Target element is not a form-associated custom element"); + return nullptr; + } + return mValidationAnchor; +} + +CustomStateSet* ElementInternals::States() { + if (!mCustomStateSet) { + mCustomStateSet = new CustomStateSet(mTarget); + } + return mCustomStateSet; +} + +void ElementInternals::SetForm(HTMLFormElement* aForm) { mForm = aForm; } + +void ElementInternals::ClearForm(bool aRemoveFromForm, bool aUnbindOrDelete) { + if (mTarget) { + mTarget->ClearForm(aRemoveFromForm, aUnbindOrDelete); + } +} + +NS_IMETHODIMP ElementInternals::Reset() { + if (mTarget) { + MOZ_ASSERT(mTarget->IsFormAssociatedElement()); + nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eFormReset, + mTarget, {}); + } + return NS_OK; +} + +NS_IMETHODIMP ElementInternals::SubmitNamesValues(FormData* aFormData) { + if (!mTarget) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(mTarget->IsFormAssociatedElement()); + + // https://html.spec.whatwg.org/#face-entry-construction + if (!mSubmissionValue.IsNull()) { + if (mSubmissionValue.Value().IsFormData()) { + aFormData->Append(mSubmissionValue.Value().GetAsFormData()); + return NS_OK; + } + + // Get the name + nsAutoString name; + if (!mTarget->GetAttr(nsGkAtoms::name, name) || name.IsEmpty()) { + return NS_OK; + } + + if (mSubmissionValue.Value().IsUSVString()) { + return aFormData->AddNameValuePair( + name, mSubmissionValue.Value().GetAsUSVString()); + } + + return aFormData->AddNameBlobPair(name, + mSubmissionValue.Value().GetAsFile()); + } + return NS_OK; +} + +void ElementInternals::UpdateFormOwner() { + if (mTarget) { + mTarget->UpdateFormOwner(); + } +} + +void ElementInternals::UpdateBarredFromConstraintValidation() { + if (mTarget) { + MOZ_ASSERT(mTarget->IsFormAssociatedElement()); + SetBarredFromConstraintValidation( + mTarget->IsDisabled() || mTarget->HasAttr(nsGkAtoms::readonly) || + mTarget->HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR)); + } +} + +void ElementInternals::Unlink() { + if (mForm) { + // Don't notify, since we're being destroyed in any case. + ClearForm(true, true); + MOZ_DIAGNOSTIC_ASSERT(!mForm); + } + if (mFieldSet) { + mFieldSet->RemoveElement(mTarget); + mFieldSet = nullptr; + } +} + +void ElementInternals::GetAttr(const nsAtom* aName, nsAString& aResult) const { + MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in"); + + const nsAttrValue* val = mAttrs.GetAttr(aName); + if (val) { + val->ToString(aResult); + return; + } + SetDOMStringToNull(aResult); +} + +nsresult ElementInternals::SetAttr(nsAtom* aName, const nsAString& aValue) { + Document* document = mTarget->GetComposedDoc(); + mozAutoDocUpdate updateBatch(document, true); + + uint8_t modType = mAttrs.HasAttr(aName) ? MutationEvent_Binding::MODIFICATION + : MutationEvent_Binding::ADDITION; + + MutationObservers::NotifyARIAAttributeDefaultWillChange(mTarget, aName, + modType); + + bool attrHadValue; + nsAttrValue attrValue(aValue); + nsresult rs = mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue); + nsMutationGuard::DidMutate(); + + MutationObservers::NotifyARIAAttributeDefaultChanged(mTarget, aName, modType); + + return rs; +} + +DocGroup* ElementInternals::GetDocGroup() { + return mTarget->OwnerDoc()->GetDocGroup(); +} + +void ElementInternals::RestoreFormValue( + Nullable<OwningFileOrUSVStringOrFormData>&& aValue, + Nullable<OwningFileOrUSVStringOrFormData>&& aState) { + mSubmissionValue = aValue; + mState = aState; + + if (!mState.IsNull()) { + LifecycleCallbackArgs args; + args.mState = mState; + args.mReason = RestoreReason::Restore; + nsContentUtils::EnqueueLifecycleCallback( + ElementCallbackType::eFormStateRestore, mTarget, args); + } +} + +void ElementInternals::InitializeControlNumber() { + MOZ_ASSERT(mControlNumber == -1, + "FACE control number should only be initialized once!"); + mControlNumber = mTarget->OwnerDoc()->GetNextControlNumber(); +} + +} // namespace mozilla::dom |