Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / html / ElementInternals.cpp
blob9a19744603b671be1390740912656d3cf8734c80
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"
22 #include "nsDebug.h"
23 #include "nsGenericHTMLElement.h"
25 #ifdef ACCESSIBILITY
26 # include "nsAccessibilityService.h"
27 #endif
29 namespace mozilla::dom {
31 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ElementInternals)
33 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementInternals)
34 tmp->Unlink();
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,
43 mCustomStateSet);
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)
52 NS_INTERFACE_MAP_END
54 ElementInternals::ElementInternals(HTMLElement* aTarget)
55 : nsIFormControl(FormControlType::FormAssociatedCustomElement),
56 mTarget(aTarget),
57 mForm(nullptr),
58 mFieldSet(nullptr),
59 mControlNumber(-1) {}
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 {
70 MOZ_ASSERT(mTarget);
72 ShadowRoot* shadowRoot = mTarget->GetShadowRoot();
73 if (shadowRoot && !shadowRoot->IsAvailableToElementInternals()) {
74 return nullptr;
77 return shadowRoot;
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,
84 ErrorResult& aRv) {
85 MOZ_ASSERT(mTarget);
87 /**
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");
95 return;
98 /**
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
101 * otherwise.
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();
111 } else {
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;
122 return;
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.
130 mState.SetNull();
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();
138 } else {
139 owningState.SetAsUSVString() = state.GetAsUSVString();
144 // https://html.spec.whatwg.org/#dom-elementinternals-form
145 HTMLFormElement* ElementInternals::GetForm(ErrorResult& aRv) const {
146 MOZ_ASSERT(mTarget);
148 if (!mTarget->IsFormAssociatedElement()) {
149 aRv.ThrowNotSupportedError(
150 "Target element is not a form-associated custom element");
151 return nullptr;
153 return GetForm();
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) {
160 MOZ_ASSERT(mTarget);
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");
170 return;
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");
183 return;
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
205 * otherwise.
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
209 * string.
211 mValidationMessage =
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"
228 "element");
229 return;
231 mValidationAnchor = anchor;
234 // https://html.spec.whatwg.org/#dom-elementinternals-willvalidate
235 bool ElementInternals::GetWillValidate(ErrorResult& aRv) const {
236 MOZ_ASSERT(mTarget);
238 if (!mTarget->IsFormAssociatedElement()) {
239 aRv.ThrowNotSupportedError(
240 "Target element is not a form-associated custom element");
241 return false;
243 return WillValidate();
246 // https://html.spec.whatwg.org/#dom-elementinternals-validity
247 ValidityState* ElementInternals::GetValidity(ErrorResult& aRv) {
248 MOZ_ASSERT(mTarget);
250 if (!mTarget->IsFormAssociatedElement()) {
251 aRv.ThrowNotSupportedError(
252 "Target element is not a form-associated custom element");
253 return nullptr;
255 return Validity();
258 // https://html.spec.whatwg.org/#dom-elementinternals-validationmessage
259 void ElementInternals::GetValidationMessage(nsAString& aValidationMessage,
260 ErrorResult& aRv) const {
261 MOZ_ASSERT(mTarget);
263 if (!mTarget->IsFormAssociatedElement()) {
264 aRv.ThrowNotSupportedError(
265 "Target element is not a form-associated custom element");
266 return;
268 aValidationMessage = mValidationMessage;
271 // https://html.spec.whatwg.org/#dom-elementinternals-checkvalidity
272 bool ElementInternals::CheckValidity(ErrorResult& aRv) {
273 MOZ_ASSERT(mTarget);
275 if (!mTarget->IsFormAssociatedElement()) {
276 aRv.ThrowNotSupportedError(
277 "Target element is not a form-associated custom element");
278 return false;
280 return nsIConstraintValidation::CheckValidity(*mTarget);
283 // https://html.spec.whatwg.org/#dom-elementinternals-reportvalidity
284 bool ElementInternals::ReportValidity(ErrorResult& aRv) {
285 MOZ_ASSERT(mTarget);
287 if (!mTarget->IsFormAssociatedElement()) {
288 aRv.ThrowNotSupportedError(
289 "Target element is not a form-associated custom element");
290 return false;
293 bool defaultAction = true;
294 if (nsIConstraintValidation::CheckValidity(*mTarget, &defaultAction)) {
295 return true;
298 if (!defaultAction) {
299 return false;
302 AutoTArray<RefPtr<Element>, 1> invalidElements;
303 invalidElements.AppendElement(mTarget);
305 AutoJSAPI jsapi;
306 if (!jsapi.Init(mTarget->GetOwnerGlobal())) {
307 return false;
309 JS::Rooted<JS::Value> detail(jsapi.cx());
310 if (!ToJSValue(jsapi.cx(), invalidElements, &detail)) {
311 return false;
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);
323 return false;
326 // https://html.spec.whatwg.org/#dom-elementinternals-labels
327 already_AddRefed<nsINodeList> ElementInternals::GetLabels(
328 ErrorResult& aRv) const {
329 MOZ_ASSERT(mTarget);
331 if (!mTarget->IsFormAssociatedElement()) {
332 aRv.ThrowNotSupportedError(
333 "Target element is not a form-associated custom element");
334 return nullptr;
336 return mTarget->Labels();
339 nsGenericHTMLElement* ElementInternals::GetValidationAnchor(
340 ErrorResult& aRv) const {
341 MOZ_ASSERT(mTarget);
343 if (!mTarget->IsFormAssociatedElement()) {
344 aRv.ThrowNotSupportedError(
345 "Target element is not a form-associated custom element");
346 return nullptr;
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) {
361 if (mTarget) {
362 mTarget->ClearForm(aRemoveFromForm, aUnbindOrDelete);
366 NS_IMETHODIMP ElementInternals::Reset() {
367 if (mTarget) {
368 MOZ_ASSERT(mTarget->IsFormAssociatedElement());
369 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eFormReset,
370 mTarget, {});
372 return NS_OK;
375 NS_IMETHODIMP ElementInternals::SubmitNamesValues(FormData* aFormData) {
376 if (!mTarget) {
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());
386 return NS_OK;
389 // Get the name
390 nsAutoString name;
391 if (!mTarget->GetAttr(nsGkAtoms::name, name) || name.IsEmpty()) {
392 return NS_OK;
395 if (mSubmissionValue.Value().IsUSVString()) {
396 return aFormData->AddNameValuePair(
397 name, mSubmissionValue.Value().GetAsUSVString());
400 return aFormData->AddNameBlobPair(name,
401 mSubmissionValue.Value().GetAsFile());
403 return NS_OK;
406 void ElementInternals::UpdateFormOwner() {
407 if (mTarget) {
408 mTarget->UpdateFormOwner();
412 void ElementInternals::UpdateBarredFromConstraintValidation() {
413 if (mTarget) {
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() {
422 if (mForm) {
423 // Don't notify, since we're being destroyed in any case.
424 ClearForm(true, true);
425 MOZ_DIAGNOSTIC_ASSERT(!mForm);
427 if (mFieldSet) {
428 mFieldSet->RemoveElement(mTarget);
429 mFieldSet = nullptr;
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);
437 if (val) {
438 val->ToString(aResult);
439 return;
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,
452 modType);
454 bool attrHadValue;
455 nsAttrValue attrValue(aValue);
456 nsresult rs = mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue);
457 nsMutationGuard::DidMutate();
459 MutationObservers::NotifyARIAAttributeDefaultChanged(mTarget, aName, modType);
461 return rs;
464 DocGroup* ElementInternals::GetDocGroup() {
465 return mTarget->OwnerDoc()->GetDocGroup();
468 void ElementInternals::RestoreFormValue(
469 Nullable<OwningFileOrUSVStringOrFormData>&& aValue,
470 Nullable<OwningFileOrUSVStringOrFormData>&& aState) {
471 mSubmissionValue = aValue;
472 mState = aState;
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;
498 #ifdef ACCESSIBILITY
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;
505 if (accService) {
506 accService->NotifyAttrElementWillChange(mTarget, aAttr);
508 #endif
510 if (aElement) {
511 mAttrElements.InsertOrUpdate(aAttr, do_GetWeakReference(aElement));
512 } else {
513 mAttrElements.Remove(aAttr);
516 #ifdef ACCESSIBILITY
517 if (accService) {
518 accService->NotifyAttrElementChanged(mTarget, aAttr);
520 #endif
523 Element* ElementInternals::GetAttrElement(nsAtom* aAttr) const {
524 nsWeakPtr weakAttrEl = mAttrElements.Get(aAttr);
525 nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl);
526 return attrEl;
529 } // namespace mozilla::dom