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"
26 # include "nsAccessibilityService.h"
29 namespace mozilla::dom
{
31 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ElementInternals
)
33 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementInternals
)
35 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTarget
, mSubmissionValue
, mState
, mValidity
,
36 mValidationAnchor
, mCustomStateSet
);
37 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
38 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementInternals
)
41 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTarget
, mSubmissionValue
, mState
,
42 mValidity
, mValidationAnchor
,
44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
46 NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementInternals
)
47 NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementInternals
)
48 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementInternals
)
49 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
50 NS_INTERFACE_MAP_ENTRY(nsIFormControl
)
51 NS_INTERFACE_MAP_ENTRY(nsIConstraintValidation
)
54 ElementInternals::ElementInternals(HTMLElement
* aTarget
)
55 : nsIFormControl(FormControlType::FormAssociatedCustomElement
),
61 nsISupports
* ElementInternals::GetParentObject() { return ToSupports(mTarget
); }
63 JSObject
* ElementInternals::WrapObject(JSContext
* aCx
,
64 JS::Handle
<JSObject
*> aGivenProto
) {
65 return ElementInternals_Binding::Wrap(aCx
, this, aGivenProto
);
68 // https://html.spec.whatwg.org/#dom-elementinternals-shadowroot
69 ShadowRoot
* ElementInternals::GetShadowRoot() const {
72 ShadowRoot
* shadowRoot
= mTarget
->GetShadowRoot();
73 if (shadowRoot
&& !shadowRoot
->IsAvailableToElementInternals()) {
80 // https://html.spec.whatwg.org/commit-snapshots/912a3fe1f29649ccf8229de56f604b3c07ffd242/#dom-elementinternals-setformvalue
81 void ElementInternals::SetFormValue(
82 const Nullable
<FileOrUSVStringOrFormData
>& aValue
,
83 const Optional
<Nullable
<FileOrUSVStringOrFormData
>>& aState
,
88 * 1. Let element be this's target element.
89 * 2. If element is not a form-associated custom element, then throw a
90 * "NotSupportedError" DOMException.
92 if (!mTarget
->IsFormAssociatedElement()) {
93 aRv
.ThrowNotSupportedError(
94 "Target element is not a form-associated custom element");
99 * 3. Set target element's submission value to value if value is not a
100 * FormData object, or to a clone of the entry list associated with value
103 mSubmissionValue
.SetNull();
104 if (!aValue
.IsNull()) {
105 const FileOrUSVStringOrFormData
& value
= aValue
.Value();
106 OwningFileOrUSVStringOrFormData
& owningValue
= mSubmissionValue
.SetValue();
107 if (value
.IsFormData()) {
108 owningValue
.SetAsFormData() = value
.GetAsFormData().Clone();
109 } else if (value
.IsFile()) {
110 owningValue
.SetAsFile() = &value
.GetAsFile();
112 owningValue
.SetAsUSVString() = value
.GetAsUSVString();
117 * 4. If the state argument of the function is omitted, set element's state to
118 * its submission value.
120 if (!aState
.WasPassed()) {
121 mState
= mSubmissionValue
;
126 * 5. Otherwise, if state is a FormData object, set element's state to clone
127 * of the entry list associated with state.
128 * 6. Otherwise, set element's state to state.
131 if (!aState
.Value().IsNull()) {
132 const FileOrUSVStringOrFormData
& state
= aState
.Value().Value();
133 OwningFileOrUSVStringOrFormData
& owningState
= mState
.SetValue();
134 if (state
.IsFormData()) {
135 owningState
.SetAsFormData() = state
.GetAsFormData().Clone();
136 } else if (state
.IsFile()) {
137 owningState
.SetAsFile() = &state
.GetAsFile();
139 owningState
.SetAsUSVString() = state
.GetAsUSVString();
144 // https://html.spec.whatwg.org/#dom-elementinternals-form
145 HTMLFormElement
* ElementInternals::GetForm(ErrorResult
& aRv
) const {
148 if (!mTarget
->IsFormAssociatedElement()) {
149 aRv
.ThrowNotSupportedError(
150 "Target element is not a form-associated custom element");
156 // https://html.spec.whatwg.org/commit-snapshots/3ad5159be8f27e110a70cefadcb50fc45ec21b05/#dom-elementinternals-setvalidity
157 void ElementInternals::SetValidity(
158 const ValidityStateFlags
& aFlags
, const Optional
<nsAString
>& aMessage
,
159 const Optional
<NonNull
<nsGenericHTMLElement
>>& aAnchor
, ErrorResult
& aRv
) {
163 * 1. Let element be this's target element.
164 * 2. If element is not a form-associated custom element, then throw a
165 * "NotSupportedError" DOMException.
167 if (!mTarget
->IsFormAssociatedElement()) {
168 aRv
.ThrowNotSupportedError(
169 "Target element is not a form-associated custom element");
174 * 3. If flags contains one or more true values and message is not given or is
175 * the empty string, then throw a TypeError.
177 if ((aFlags
.mBadInput
|| aFlags
.mCustomError
|| aFlags
.mPatternMismatch
||
178 aFlags
.mRangeOverflow
|| aFlags
.mRangeUnderflow
||
179 aFlags
.mStepMismatch
|| aFlags
.mTooLong
|| aFlags
.mTooShort
||
180 aFlags
.mTypeMismatch
|| aFlags
.mValueMissing
) &&
181 (!aMessage
.WasPassed() || aMessage
.Value().IsEmpty())) {
182 aRv
.ThrowTypeError("Need to provide validation message");
187 * 4. For each entry flag → value of flags, set element's validity flag with
188 * the name flag to value.
190 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, aFlags
.mValueMissing
);
191 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH
, aFlags
.mTypeMismatch
);
192 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH
, aFlags
.mPatternMismatch
);
193 SetValidityState(VALIDITY_STATE_TOO_LONG
, aFlags
.mTooLong
);
194 SetValidityState(VALIDITY_STATE_TOO_SHORT
, aFlags
.mTooShort
);
195 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
, aFlags
.mRangeUnderflow
);
196 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW
, aFlags
.mRangeOverflow
);
197 SetValidityState(VALIDITY_STATE_STEP_MISMATCH
, aFlags
.mStepMismatch
);
198 SetValidityState(VALIDITY_STATE_BAD_INPUT
, aFlags
.mBadInput
);
199 SetValidityState(VALIDITY_STATE_CUSTOM_ERROR
, aFlags
.mCustomError
);
200 mTarget
->UpdateValidityElementStates(true);
203 * 5. Set element's validation message to the empty string if message is not
204 * given or all of element's validity flags are false, or to message
206 * 6. If element's customError validity flag is true, then set element's
207 * custom validity error message to element's validation message.
208 * Otherwise, set element's custom validity error message to the empty
212 (!aMessage
.WasPassed() || IsValid()) ? EmptyString() : aMessage
.Value();
215 * 7. Set element's validation anchor to null if anchor is not given.
216 * Otherwise, if anchor is not a shadow-including descendant of element,
217 * then throw a "NotFoundError" DOMException. Otherwise, set element's
218 * validation anchor to anchor.
220 nsGenericHTMLElement
* anchor
=
221 aAnchor
.WasPassed() ? &aAnchor
.Value() : nullptr;
222 // TODO: maybe create something like IsShadowIncludingDescendantOf if there
223 // are other places also need such check.
224 if (anchor
&& (anchor
== mTarget
||
225 !anchor
->IsShadowIncludingInclusiveDescendantOf(mTarget
))) {
226 aRv
.ThrowNotFoundError(
227 "Validation anchor is not a shadow-including descendant of target"
231 mValidationAnchor
= anchor
;
234 // https://html.spec.whatwg.org/#dom-elementinternals-willvalidate
235 bool ElementInternals::GetWillValidate(ErrorResult
& aRv
) const {
238 if (!mTarget
->IsFormAssociatedElement()) {
239 aRv
.ThrowNotSupportedError(
240 "Target element is not a form-associated custom element");
243 return WillValidate();
246 // https://html.spec.whatwg.org/#dom-elementinternals-validity
247 ValidityState
* ElementInternals::GetValidity(ErrorResult
& aRv
) {
250 if (!mTarget
->IsFormAssociatedElement()) {
251 aRv
.ThrowNotSupportedError(
252 "Target element is not a form-associated custom element");
258 // https://html.spec.whatwg.org/#dom-elementinternals-validationmessage
259 void ElementInternals::GetValidationMessage(nsAString
& aValidationMessage
,
260 ErrorResult
& aRv
) const {
263 if (!mTarget
->IsFormAssociatedElement()) {
264 aRv
.ThrowNotSupportedError(
265 "Target element is not a form-associated custom element");
268 aValidationMessage
= mValidationMessage
;
271 // https://html.spec.whatwg.org/#dom-elementinternals-checkvalidity
272 bool ElementInternals::CheckValidity(ErrorResult
& aRv
) {
275 if (!mTarget
->IsFormAssociatedElement()) {
276 aRv
.ThrowNotSupportedError(
277 "Target element is not a form-associated custom element");
280 return nsIConstraintValidation::CheckValidity(*mTarget
);
283 // https://html.spec.whatwg.org/#dom-elementinternals-reportvalidity
284 bool ElementInternals::ReportValidity(ErrorResult
& aRv
) {
287 if (!mTarget
->IsFormAssociatedElement()) {
288 aRv
.ThrowNotSupportedError(
289 "Target element is not a form-associated custom element");
293 bool defaultAction
= true;
294 if (nsIConstraintValidation::CheckValidity(*mTarget
, &defaultAction
)) {
298 if (!defaultAction
) {
302 AutoTArray
<RefPtr
<Element
>, 1> invalidElements
;
303 invalidElements
.AppendElement(mTarget
);
306 if (!jsapi
.Init(mTarget
->GetOwnerGlobal())) {
309 JS::Rooted
<JS::Value
> detail(jsapi
.cx());
310 if (!ToJSValue(jsapi
.cx(), invalidElements
, &detail
)) {
314 RefPtr
<CustomEvent
> event
=
315 NS_NewDOMCustomEvent(mTarget
->OwnerDoc(), nullptr, nullptr);
316 event
->InitCustomEvent(jsapi
.cx(), u
"MozInvalidForm"_ns
,
317 /* CanBubble */ true,
318 /* Cancelable */ true, detail
);
319 event
->SetTrusted(true);
320 event
->WidgetEventPtr()->mFlags
.mOnlyChromeDispatch
= true;
321 mTarget
->DispatchEvent(*event
);
326 // https://html.spec.whatwg.org/#dom-elementinternals-labels
327 already_AddRefed
<nsINodeList
> ElementInternals::GetLabels(
328 ErrorResult
& aRv
) const {
331 if (!mTarget
->IsFormAssociatedElement()) {
332 aRv
.ThrowNotSupportedError(
333 "Target element is not a form-associated custom element");
336 return mTarget
->Labels();
339 nsGenericHTMLElement
* ElementInternals::GetValidationAnchor(
340 ErrorResult
& aRv
) const {
343 if (!mTarget
->IsFormAssociatedElement()) {
344 aRv
.ThrowNotSupportedError(
345 "Target element is not a form-associated custom element");
348 return mValidationAnchor
;
351 CustomStateSet
* ElementInternals::States() {
352 if (!mCustomStateSet
) {
353 mCustomStateSet
= new CustomStateSet(mTarget
);
355 return mCustomStateSet
;
358 void ElementInternals::SetForm(HTMLFormElement
* aForm
) { mForm
= aForm
; }
360 void ElementInternals::ClearForm(bool aRemoveFromForm
, bool aUnbindOrDelete
) {
362 mTarget
->ClearForm(aRemoveFromForm
, aUnbindOrDelete
);
366 NS_IMETHODIMP
ElementInternals::Reset() {
368 MOZ_ASSERT(mTarget
->IsFormAssociatedElement());
369 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eFormReset
,
375 NS_IMETHODIMP
ElementInternals::SubmitNamesValues(FormData
* aFormData
) {
377 return NS_ERROR_UNEXPECTED
;
380 MOZ_ASSERT(mTarget
->IsFormAssociatedElement());
382 // https://html.spec.whatwg.org/#face-entry-construction
383 if (!mSubmissionValue
.IsNull()) {
384 if (mSubmissionValue
.Value().IsFormData()) {
385 aFormData
->Append(mSubmissionValue
.Value().GetAsFormData());
391 if (!mTarget
->GetAttr(nsGkAtoms::name
, name
) || name
.IsEmpty()) {
395 if (mSubmissionValue
.Value().IsUSVString()) {
396 return aFormData
->AddNameValuePair(
397 name
, mSubmissionValue
.Value().GetAsUSVString());
400 return aFormData
->AddNameBlobPair(name
,
401 mSubmissionValue
.Value().GetAsFile());
406 void ElementInternals::UpdateFormOwner() {
408 mTarget
->UpdateFormOwner();
412 void ElementInternals::UpdateBarredFromConstraintValidation() {
414 MOZ_ASSERT(mTarget
->IsFormAssociatedElement());
415 SetBarredFromConstraintValidation(
416 mTarget
->IsDisabled() || mTarget
->HasAttr(nsGkAtoms::readonly
) ||
417 mTarget
->HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR
));
421 void ElementInternals::Unlink() {
423 // Don't notify, since we're being destroyed in any case.
424 ClearForm(true, true);
425 MOZ_DIAGNOSTIC_ASSERT(!mForm
);
428 mFieldSet
->RemoveElement(mTarget
);
433 void ElementInternals::GetAttr(const nsAtom
* aName
, nsAString
& aResult
) const {
434 MOZ_ASSERT(aResult
.IsEmpty(), "Should have empty string coming in");
436 const nsAttrValue
* val
= mAttrs
.GetAttr(aName
);
438 val
->ToString(aResult
);
441 SetDOMStringToNull(aResult
);
444 nsresult
ElementInternals::SetAttr(nsAtom
* aName
, const nsAString
& aValue
) {
445 Document
* document
= mTarget
->GetComposedDoc();
446 mozAutoDocUpdate
updateBatch(document
, true);
448 uint8_t modType
= mAttrs
.HasAttr(aName
) ? MutationEvent_Binding::MODIFICATION
449 : MutationEvent_Binding::ADDITION
;
451 MutationObservers::NotifyARIAAttributeDefaultWillChange(mTarget
, aName
,
455 nsAttrValue
attrValue(aValue
);
456 nsresult rs
= mAttrs
.SetAndSwapAttr(aName
, attrValue
, &attrHadValue
);
457 nsMutationGuard::DidMutate();
459 MutationObservers::NotifyARIAAttributeDefaultChanged(mTarget
, aName
, modType
);
464 DocGroup
* ElementInternals::GetDocGroup() {
465 return mTarget
->OwnerDoc()->GetDocGroup();
468 void ElementInternals::RestoreFormValue(
469 Nullable
<OwningFileOrUSVStringOrFormData
>&& aValue
,
470 Nullable
<OwningFileOrUSVStringOrFormData
>&& aState
) {
471 mSubmissionValue
= aValue
;
474 if (!mState
.IsNull()) {
475 LifecycleCallbackArgs args
;
476 args
.mState
= mState
;
477 args
.mReason
= RestoreReason::Restore
;
478 nsContentUtils::EnqueueLifecycleCallback(
479 ElementCallbackType::eFormStateRestore
, mTarget
, args
);
483 void ElementInternals::InitializeControlNumber() {
484 MOZ_ASSERT(mControlNumber
== -1,
485 "FACE control number should only be initialized once!");
486 mControlNumber
= mTarget
->OwnerDoc()->GetNextControlNumber();
489 void ElementInternals::SetAttrElement(nsAtom
* aAttr
, Element
* aElement
) {
490 // Accessibility requires that no other attribute changes occur between
491 // AttrElementWillChange and AttrElementChanged. Scripts could cause
492 // this, so don't let them run here. We do this even if accessibility isn't
493 // running so that the JS behavior is consistent regardless of accessibility.
494 // Otherwise, JS might be able to use this difference to determine whether
495 // accessibility is running, which would be a privacy concern.
496 nsAutoScriptBlocker scriptBlocker
;
499 // If the target has this attribute defined then it overrides the defaults
500 // defined here in the Internals instance. In that case we don't need to
501 // notify the change to a11y since the attribute hasn't changed, just the
502 // underlying default. We can set accService to null and not notify.
503 nsAccessibilityService
* accService
=
504 !mTarget
->HasAttr(aAttr
) ? GetAccService() : nullptr;
506 accService
->NotifyAttrElementWillChange(mTarget
, aAttr
);
511 mAttrElements
.InsertOrUpdate(aAttr
, do_GetWeakReference(aElement
));
513 mAttrElements
.Remove(aAttr
);
518 accService
->NotifyAttrElementChanged(mTarget
, aAttr
);
523 Element
* ElementInternals::GetAttrElement(nsAtom
* aAttr
) const {
524 nsWeakPtr weakAttrEl
= mAttrElements
.Get(aAttr
);
525 nsCOMPtr
<Element
> attrEl
= do_QueryReferent(weakAttrEl
);
529 } // namespace mozilla::dom