/* -*- 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/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); 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); 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) {} nsISupports* ElementInternals::GetParentObject() { return ToSupports(mTarget); } JSObject* ElementInternals::WrapObject(JSContext* aCx, JS::Handle 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& aValue, const Optional>& 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& aMessage, const Optional>& 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->UpdateState(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, 1> invalidElements; invalidElements.AppendElement(mTarget); AutoJSAPI jsapi; if (!jsapi.Init(mTarget->GetOwnerGlobal())) { return false; } JS::Rooted detail(jsapi.cx()); if (!ToJSValue(jsapi.cx(), invalidElements, &detail)) { return false; } mTarget->UpdateState(true); RefPtr 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 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; } 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->HasAttr(nsGkAtoms::readonly) || mTarget->HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || mTarget->IsDisabled()); } } 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(kNameSpaceID_None, 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); mTarget->UpdateState(true); return rs; } DocGroup* ElementInternals::GetDocGroup() { return mTarget->OwnerDoc()->GetDocGroup(); } } // namespace mozilla::dom