Bug 1880558: Allow PnP windows to enter macOS native fullscreen. r=pip-reviewers...
[gecko.git] / dom / html / ElementInternals.cpp
blobaaf58f818ecce00b9a16bb6d5dc3e477904172bf
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 namespace mozilla::dom {
27 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ElementInternals)
29 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ElementInternals)
30 tmp->Unlink();
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,
39 mCustomStateSet);
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)
48 NS_INTERFACE_MAP_END
50 ElementInternals::ElementInternals(HTMLElement* aTarget)
51 : nsIFormControl(FormControlType::FormAssociatedCustomElement),
52 mTarget(aTarget),
53 mForm(nullptr),
54 mFieldSet(nullptr),
55 mControlNumber(-1) {}
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 {
66 MOZ_ASSERT(mTarget);
68 ShadowRoot* shadowRoot = mTarget->GetShadowRoot();
69 if (shadowRoot && !shadowRoot->IsAvailableToElementInternals()) {
70 return nullptr;
73 return shadowRoot;
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,
80 ErrorResult& aRv) {
81 MOZ_ASSERT(mTarget);
83 /**
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");
91 return;
94 /**
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
97 * otherwise.
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();
107 } else {
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;
118 return;
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.
126 mState.SetNull();
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();
134 } else {
135 owningState.SetAsUSVString() = state.GetAsUSVString();
140 // https://html.spec.whatwg.org/#dom-elementinternals-form
141 HTMLFormElement* ElementInternals::GetForm(ErrorResult& aRv) const {
142 MOZ_ASSERT(mTarget);
144 if (!mTarget->IsFormAssociatedElement()) {
145 aRv.ThrowNotSupportedError(
146 "Target element is not a form-associated custom element");
147 return nullptr;
149 return GetForm();
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) {
156 MOZ_ASSERT(mTarget);
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");
166 return;
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");
179 return;
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
201 * otherwise.
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
205 * string.
207 mValidationMessage =
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"
224 "element");
225 return;
227 mValidationAnchor = anchor;
230 // https://html.spec.whatwg.org/#dom-elementinternals-willvalidate
231 bool ElementInternals::GetWillValidate(ErrorResult& aRv) const {
232 MOZ_ASSERT(mTarget);
234 if (!mTarget->IsFormAssociatedElement()) {
235 aRv.ThrowNotSupportedError(
236 "Target element is not a form-associated custom element");
237 return false;
239 return WillValidate();
242 // https://html.spec.whatwg.org/#dom-elementinternals-validity
243 ValidityState* ElementInternals::GetValidity(ErrorResult& aRv) {
244 MOZ_ASSERT(mTarget);
246 if (!mTarget->IsFormAssociatedElement()) {
247 aRv.ThrowNotSupportedError(
248 "Target element is not a form-associated custom element");
249 return nullptr;
251 return Validity();
254 // https://html.spec.whatwg.org/#dom-elementinternals-validationmessage
255 void ElementInternals::GetValidationMessage(nsAString& aValidationMessage,
256 ErrorResult& aRv) const {
257 MOZ_ASSERT(mTarget);
259 if (!mTarget->IsFormAssociatedElement()) {
260 aRv.ThrowNotSupportedError(
261 "Target element is not a form-associated custom element");
262 return;
264 aValidationMessage = mValidationMessage;
267 // https://html.spec.whatwg.org/#dom-elementinternals-checkvalidity
268 bool ElementInternals::CheckValidity(ErrorResult& aRv) {
269 MOZ_ASSERT(mTarget);
271 if (!mTarget->IsFormAssociatedElement()) {
272 aRv.ThrowNotSupportedError(
273 "Target element is not a form-associated custom element");
274 return false;
276 return nsIConstraintValidation::CheckValidity(*mTarget);
279 // https://html.spec.whatwg.org/#dom-elementinternals-reportvalidity
280 bool ElementInternals::ReportValidity(ErrorResult& aRv) {
281 MOZ_ASSERT(mTarget);
283 if (!mTarget->IsFormAssociatedElement()) {
284 aRv.ThrowNotSupportedError(
285 "Target element is not a form-associated custom element");
286 return false;
289 bool defaultAction = true;
290 if (nsIConstraintValidation::CheckValidity(*mTarget, &defaultAction)) {
291 return true;
294 if (!defaultAction) {
295 return false;
298 AutoTArray<RefPtr<Element>, 1> invalidElements;
299 invalidElements.AppendElement(mTarget);
301 AutoJSAPI jsapi;
302 if (!jsapi.Init(mTarget->GetOwnerGlobal())) {
303 return false;
305 JS::Rooted<JS::Value> detail(jsapi.cx());
306 if (!ToJSValue(jsapi.cx(), invalidElements, &detail)) {
307 return false;
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);
319 return false;
322 // https://html.spec.whatwg.org/#dom-elementinternals-labels
323 already_AddRefed<nsINodeList> ElementInternals::GetLabels(
324 ErrorResult& aRv) const {
325 MOZ_ASSERT(mTarget);
327 if (!mTarget->IsFormAssociatedElement()) {
328 aRv.ThrowNotSupportedError(
329 "Target element is not a form-associated custom element");
330 return nullptr;
332 return mTarget->Labels();
335 nsGenericHTMLElement* ElementInternals::GetValidationAnchor(
336 ErrorResult& aRv) const {
337 MOZ_ASSERT(mTarget);
339 if (!mTarget->IsFormAssociatedElement()) {
340 aRv.ThrowNotSupportedError(
341 "Target element is not a form-associated custom element");
342 return nullptr;
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) {
357 if (mTarget) {
358 mTarget->ClearForm(aRemoveFromForm, aUnbindOrDelete);
362 NS_IMETHODIMP ElementInternals::Reset() {
363 if (mTarget) {
364 MOZ_ASSERT(mTarget->IsFormAssociatedElement());
365 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eFormReset,
366 mTarget, {});
368 return NS_OK;
371 NS_IMETHODIMP ElementInternals::SubmitNamesValues(FormData* aFormData) {
372 if (!mTarget) {
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());
382 return NS_OK;
385 // Get the name
386 nsAutoString name;
387 if (!mTarget->GetAttr(nsGkAtoms::name, name) || name.IsEmpty()) {
388 return NS_OK;
391 if (mSubmissionValue.Value().IsUSVString()) {
392 return aFormData->AddNameValuePair(
393 name, mSubmissionValue.Value().GetAsUSVString());
396 return aFormData->AddNameBlobPair(name,
397 mSubmissionValue.Value().GetAsFile());
399 return NS_OK;
402 void ElementInternals::UpdateFormOwner() {
403 if (mTarget) {
404 mTarget->UpdateFormOwner();
408 void ElementInternals::UpdateBarredFromConstraintValidation() {
409 if (mTarget) {
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() {
418 if (mForm) {
419 // Don't notify, since we're being destroyed in any case.
420 ClearForm(true, true);
421 MOZ_DIAGNOSTIC_ASSERT(!mForm);
423 if (mFieldSet) {
424 mFieldSet->RemoveElement(mTarget);
425 mFieldSet = nullptr;
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);
433 if (val) {
434 val->ToString(aResult);
435 return;
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,
448 modType);
450 bool attrHadValue;
451 nsAttrValue attrValue(aValue);
452 nsresult rs = mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue);
453 nsMutationGuard::DidMutate();
455 MutationObservers::NotifyARIAAttributeDefaultChanged(mTarget, aName, modType);
457 return rs;
460 DocGroup* ElementInternals::GetDocGroup() {
461 return mTarget->OwnerDoc()->GetDocGroup();
464 void ElementInternals::RestoreFormValue(
465 Nullable<OwningFileOrUSVStringOrFormData>&& aValue,
466 Nullable<OwningFileOrUSVStringOrFormData>&& aState) {
467 mSubmissionValue = aValue;
468 mState = aState;
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