1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/ElementInternals.h"
9 #include "mozAutoDocUpdate.h"
10 #include "mozilla/dom/CustomElementRegistry.h"
11 #include "mozilla/dom/CustomEvent.h"
12 #include "mozilla/dom/CustomStateSet.h"
13 #include "mozilla/dom/ElementInternalsBinding.h"
14 #include "mozilla/dom/FormData.h"
15 #include "mozilla/dom/HTMLElement.h"
16 #include "mozilla/dom/HTMLFieldSetElement.h"
17 #include "mozilla/dom/MutationEventBinding.h"
18 #include "mozilla/dom/MutationObservers.h"
19 #include "mozilla/dom/ShadowRoot.h"
20 #include "mozilla/dom/ValidityState.h"
21 #include "nsContentUtils.h"
23 #include "nsGenericHTMLElement.h"
25 namespace mozilla::dom
{
27 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ElementInternals
)
29 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementInternals
)
31 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTarget
, mSubmissionValue
, mState
, mValidity
,
32 mValidationAnchor
, mCustomStateSet
);
33 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
34 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementInternals
)
37 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTarget
, mSubmissionValue
, mState
,
38 mValidity
, mValidationAnchor
,
40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
42 NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementInternals
)
43 NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementInternals
)
44 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementInternals
)
45 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
46 NS_INTERFACE_MAP_ENTRY(nsIFormControl
)
47 NS_INTERFACE_MAP_ENTRY(nsIConstraintValidation
)
50 ElementInternals::ElementInternals(HTMLElement
* aTarget
)
51 : nsIFormControl(FormControlType::FormAssociatedCustomElement
),
57 nsISupports
* ElementInternals::GetParentObject() { return ToSupports(mTarget
); }
59 JSObject
* ElementInternals::WrapObject(JSContext
* aCx
,
60 JS::Handle
<JSObject
*> aGivenProto
) {
61 return ElementInternals_Binding::Wrap(aCx
, this, aGivenProto
);
64 // https://html.spec.whatwg.org/#dom-elementinternals-shadowroot
65 ShadowRoot
* ElementInternals::GetShadowRoot() const {
68 ShadowRoot
* shadowRoot
= mTarget
->GetShadowRoot();
69 if (shadowRoot
&& !shadowRoot
->IsAvailableToElementInternals()) {
76 // https://html.spec.whatwg.org/commit-snapshots/912a3fe1f29649ccf8229de56f604b3c07ffd242/#dom-elementinternals-setformvalue
77 void ElementInternals::SetFormValue(
78 const Nullable
<FileOrUSVStringOrFormData
>& aValue
,
79 const Optional
<Nullable
<FileOrUSVStringOrFormData
>>& aState
,
84 * 1. Let element be this's target element.
85 * 2. If element is not a form-associated custom element, then throw a
86 * "NotSupportedError" DOMException.
88 if (!mTarget
->IsFormAssociatedElement()) {
89 aRv
.ThrowNotSupportedError(
90 "Target element is not a form-associated custom element");
95 * 3. Set target element's submission value to value if value is not a
96 * FormData object, or to a clone of the entry list associated with value
99 mSubmissionValue
.SetNull();
100 if (!aValue
.IsNull()) {
101 const FileOrUSVStringOrFormData
& value
= aValue
.Value();
102 OwningFileOrUSVStringOrFormData
& owningValue
= mSubmissionValue
.SetValue();
103 if (value
.IsFormData()) {
104 owningValue
.SetAsFormData() = value
.GetAsFormData().Clone();
105 } else if (value
.IsFile()) {
106 owningValue
.SetAsFile() = &value
.GetAsFile();
108 owningValue
.SetAsUSVString() = value
.GetAsUSVString();
113 * 4. If the state argument of the function is omitted, set element's state to
114 * its submission value.
116 if (!aState
.WasPassed()) {
117 mState
= mSubmissionValue
;
122 * 5. Otherwise, if state is a FormData object, set element's state to clone
123 * of the entry list associated with state.
124 * 6. Otherwise, set element's state to state.
127 if (!aState
.Value().IsNull()) {
128 const FileOrUSVStringOrFormData
& state
= aState
.Value().Value();
129 OwningFileOrUSVStringOrFormData
& owningState
= mState
.SetValue();
130 if (state
.IsFormData()) {
131 owningState
.SetAsFormData() = state
.GetAsFormData().Clone();
132 } else if (state
.IsFile()) {
133 owningState
.SetAsFile() = &state
.GetAsFile();
135 owningState
.SetAsUSVString() = state
.GetAsUSVString();
140 // https://html.spec.whatwg.org/#dom-elementinternals-form
141 HTMLFormElement
* ElementInternals::GetForm(ErrorResult
& aRv
) const {
144 if (!mTarget
->IsFormAssociatedElement()) {
145 aRv
.ThrowNotSupportedError(
146 "Target element is not a form-associated custom element");
152 // https://html.spec.whatwg.org/commit-snapshots/3ad5159be8f27e110a70cefadcb50fc45ec21b05/#dom-elementinternals-setvalidity
153 void ElementInternals::SetValidity(
154 const ValidityStateFlags
& aFlags
, const Optional
<nsAString
>& aMessage
,
155 const Optional
<NonNull
<nsGenericHTMLElement
>>& aAnchor
, ErrorResult
& aRv
) {
159 * 1. Let element be this's target element.
160 * 2. If element is not a form-associated custom element, then throw a
161 * "NotSupportedError" DOMException.
163 if (!mTarget
->IsFormAssociatedElement()) {
164 aRv
.ThrowNotSupportedError(
165 "Target element is not a form-associated custom element");
170 * 3. If flags contains one or more true values and message is not given or is
171 * the empty string, then throw a TypeError.
173 if ((aFlags
.mBadInput
|| aFlags
.mCustomError
|| aFlags
.mPatternMismatch
||
174 aFlags
.mRangeOverflow
|| aFlags
.mRangeUnderflow
||
175 aFlags
.mStepMismatch
|| aFlags
.mTooLong
|| aFlags
.mTooShort
||
176 aFlags
.mTypeMismatch
|| aFlags
.mValueMissing
) &&
177 (!aMessage
.WasPassed() || aMessage
.Value().IsEmpty())) {
178 aRv
.ThrowTypeError("Need to provide validation message");
183 * 4. For each entry flag → value of flags, set element's validity flag with
184 * the name flag to value.
186 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, aFlags
.mValueMissing
);
187 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH
, aFlags
.mTypeMismatch
);
188 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH
, aFlags
.mPatternMismatch
);
189 SetValidityState(VALIDITY_STATE_TOO_LONG
, aFlags
.mTooLong
);
190 SetValidityState(VALIDITY_STATE_TOO_SHORT
, aFlags
.mTooShort
);
191 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
, aFlags
.mRangeUnderflow
);
192 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW
, aFlags
.mRangeOverflow
);
193 SetValidityState(VALIDITY_STATE_STEP_MISMATCH
, aFlags
.mStepMismatch
);
194 SetValidityState(VALIDITY_STATE_BAD_INPUT
, aFlags
.mBadInput
);
195 SetValidityState(VALIDITY_STATE_CUSTOM_ERROR
, aFlags
.mCustomError
);
196 mTarget
->UpdateValidityElementStates(true);
199 * 5. Set element's validation message to the empty string if message is not
200 * given or all of element's validity flags are false, or to message
202 * 6. If element's customError validity flag is true, then set element's
203 * custom validity error message to element's validation message.
204 * Otherwise, set element's custom validity error message to the empty
208 (!aMessage
.WasPassed() || IsValid()) ? EmptyString() : aMessage
.Value();
211 * 7. Set element's validation anchor to null if anchor is not given.
212 * Otherwise, if anchor is not a shadow-including descendant of element,
213 * then throw a "NotFoundError" DOMException. Otherwise, set element's
214 * validation anchor to anchor.
216 nsGenericHTMLElement
* anchor
=
217 aAnchor
.WasPassed() ? &aAnchor
.Value() : nullptr;
218 // TODO: maybe create something like IsShadowIncludingDescendantOf if there
219 // are other places also need such check.
220 if (anchor
&& (anchor
== mTarget
||
221 !anchor
->IsShadowIncludingInclusiveDescendantOf(mTarget
))) {
222 aRv
.ThrowNotFoundError(
223 "Validation anchor is not a shadow-including descendant of target"
227 mValidationAnchor
= anchor
;
230 // https://html.spec.whatwg.org/#dom-elementinternals-willvalidate
231 bool ElementInternals::GetWillValidate(ErrorResult
& aRv
) const {
234 if (!mTarget
->IsFormAssociatedElement()) {
235 aRv
.ThrowNotSupportedError(
236 "Target element is not a form-associated custom element");
239 return WillValidate();
242 // https://html.spec.whatwg.org/#dom-elementinternals-validity
243 ValidityState
* ElementInternals::GetValidity(ErrorResult
& aRv
) {
246 if (!mTarget
->IsFormAssociatedElement()) {
247 aRv
.ThrowNotSupportedError(
248 "Target element is not a form-associated custom element");
254 // https://html.spec.whatwg.org/#dom-elementinternals-validationmessage
255 void ElementInternals::GetValidationMessage(nsAString
& aValidationMessage
,
256 ErrorResult
& aRv
) const {
259 if (!mTarget
->IsFormAssociatedElement()) {
260 aRv
.ThrowNotSupportedError(
261 "Target element is not a form-associated custom element");
264 aValidationMessage
= mValidationMessage
;
267 // https://html.spec.whatwg.org/#dom-elementinternals-checkvalidity
268 bool ElementInternals::CheckValidity(ErrorResult
& aRv
) {
271 if (!mTarget
->IsFormAssociatedElement()) {
272 aRv
.ThrowNotSupportedError(
273 "Target element is not a form-associated custom element");
276 return nsIConstraintValidation::CheckValidity(*mTarget
);
279 // https://html.spec.whatwg.org/#dom-elementinternals-reportvalidity
280 bool ElementInternals::ReportValidity(ErrorResult
& aRv
) {
283 if (!mTarget
->IsFormAssociatedElement()) {
284 aRv
.ThrowNotSupportedError(
285 "Target element is not a form-associated custom element");
289 bool defaultAction
= true;
290 if (nsIConstraintValidation::CheckValidity(*mTarget
, &defaultAction
)) {
294 if (!defaultAction
) {
298 AutoTArray
<RefPtr
<Element
>, 1> invalidElements
;
299 invalidElements
.AppendElement(mTarget
);
302 if (!jsapi
.Init(mTarget
->GetOwnerGlobal())) {
305 JS::Rooted
<JS::Value
> detail(jsapi
.cx());
306 if (!ToJSValue(jsapi
.cx(), invalidElements
, &detail
)) {
310 RefPtr
<CustomEvent
> event
=
311 NS_NewDOMCustomEvent(mTarget
->OwnerDoc(), nullptr, nullptr);
312 event
->InitCustomEvent(jsapi
.cx(), u
"MozInvalidForm"_ns
,
313 /* CanBubble */ true,
314 /* Cancelable */ true, detail
);
315 event
->SetTrusted(true);
316 event
->WidgetEventPtr()->mFlags
.mOnlyChromeDispatch
= true;
317 mTarget
->DispatchEvent(*event
);
322 // https://html.spec.whatwg.org/#dom-elementinternals-labels
323 already_AddRefed
<nsINodeList
> ElementInternals::GetLabels(
324 ErrorResult
& aRv
) const {
327 if (!mTarget
->IsFormAssociatedElement()) {
328 aRv
.ThrowNotSupportedError(
329 "Target element is not a form-associated custom element");
332 return mTarget
->Labels();
335 nsGenericHTMLElement
* ElementInternals::GetValidationAnchor(
336 ErrorResult
& aRv
) const {
339 if (!mTarget
->IsFormAssociatedElement()) {
340 aRv
.ThrowNotSupportedError(
341 "Target element is not a form-associated custom element");
344 return mValidationAnchor
;
347 CustomStateSet
* ElementInternals::States() {
348 if (!mCustomStateSet
) {
349 mCustomStateSet
= new CustomStateSet(mTarget
);
351 return mCustomStateSet
;
354 void ElementInternals::SetForm(HTMLFormElement
* aForm
) { mForm
= aForm
; }
356 void ElementInternals::ClearForm(bool aRemoveFromForm
, bool aUnbindOrDelete
) {
358 mTarget
->ClearForm(aRemoveFromForm
, aUnbindOrDelete
);
362 NS_IMETHODIMP
ElementInternals::Reset() {
364 MOZ_ASSERT(mTarget
->IsFormAssociatedElement());
365 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eFormReset
,
371 NS_IMETHODIMP
ElementInternals::SubmitNamesValues(FormData
* aFormData
) {
373 return NS_ERROR_UNEXPECTED
;
376 MOZ_ASSERT(mTarget
->IsFormAssociatedElement());
378 // https://html.spec.whatwg.org/#face-entry-construction
379 if (!mSubmissionValue
.IsNull()) {
380 if (mSubmissionValue
.Value().IsFormData()) {
381 aFormData
->Append(mSubmissionValue
.Value().GetAsFormData());
387 if (!mTarget
->GetAttr(nsGkAtoms::name
, name
) || name
.IsEmpty()) {
391 if (mSubmissionValue
.Value().IsUSVString()) {
392 return aFormData
->AddNameValuePair(
393 name
, mSubmissionValue
.Value().GetAsUSVString());
396 return aFormData
->AddNameBlobPair(name
,
397 mSubmissionValue
.Value().GetAsFile());
402 void ElementInternals::UpdateFormOwner() {
404 mTarget
->UpdateFormOwner();
408 void ElementInternals::UpdateBarredFromConstraintValidation() {
410 MOZ_ASSERT(mTarget
->IsFormAssociatedElement());
411 SetBarredFromConstraintValidation(
412 mTarget
->IsDisabled() || mTarget
->HasAttr(nsGkAtoms::readonly
) ||
413 mTarget
->HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR
));
417 void ElementInternals::Unlink() {
419 // Don't notify, since we're being destroyed in any case.
420 ClearForm(true, true);
421 MOZ_DIAGNOSTIC_ASSERT(!mForm
);
424 mFieldSet
->RemoveElement(mTarget
);
429 void ElementInternals::GetAttr(const nsAtom
* aName
, nsAString
& aResult
) const {
430 MOZ_ASSERT(aResult
.IsEmpty(), "Should have empty string coming in");
432 const nsAttrValue
* val
= mAttrs
.GetAttr(aName
);
434 val
->ToString(aResult
);
437 SetDOMStringToNull(aResult
);
440 nsresult
ElementInternals::SetAttr(nsAtom
* aName
, const nsAString
& aValue
) {
441 Document
* document
= mTarget
->GetComposedDoc();
442 mozAutoDocUpdate
updateBatch(document
, true);
444 uint8_t modType
= mAttrs
.HasAttr(aName
) ? MutationEvent_Binding::MODIFICATION
445 : MutationEvent_Binding::ADDITION
;
447 MutationObservers::NotifyARIAAttributeDefaultWillChange(mTarget
, aName
,
451 nsAttrValue
attrValue(aValue
);
452 nsresult rs
= mAttrs
.SetAndSwapAttr(aName
, attrValue
, &attrHadValue
);
453 nsMutationGuard::DidMutate();
455 MutationObservers::NotifyARIAAttributeDefaultChanged(mTarget
, aName
, modType
);
460 DocGroup
* ElementInternals::GetDocGroup() {
461 return mTarget
->OwnerDoc()->GetDocGroup();
464 void ElementInternals::RestoreFormValue(
465 Nullable
<OwningFileOrUSVStringOrFormData
>&& aValue
,
466 Nullable
<OwningFileOrUSVStringOrFormData
>&& aState
) {
467 mSubmissionValue
= aValue
;
470 if (!mState
.IsNull()) {
471 LifecycleCallbackArgs args
;
472 args
.mState
= mState
;
473 args
.mReason
= RestoreReason::Restore
;
474 nsContentUtils::EnqueueLifecycleCallback(
475 ElementCallbackType::eFormStateRestore
, mTarget
, args
);
479 void ElementInternals::InitializeControlNumber() {
480 MOZ_ASSERT(mControlNumber
== -1,
481 "FACE control number should only be initialized once!");
482 mControlNumber
= mTarget
->OwnerDoc()->GetNextControlNumber();
485 } // namespace mozilla::dom