summaryrefslogtreecommitdiffstats
path: root/dom/html/ElementInternals.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/ElementInternals.cpp')
-rw-r--r--dom/html/ElementInternals.cpp485
1 files changed, 485 insertions, 0 deletions
diff --git a/dom/html/ElementInternals.cpp b/dom/html/ElementInternals.cpp
new file mode 100644
index 0000000000..aaf58f818e
--- /dev/null
+++ b/dom/html/ElementInternals.cpp
@@ -0,0 +1,485 @@
+/* -*- 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/CustomStateSet.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, mCustomStateSet);
+ 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,
+ mCustomStateSet);
+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),
+ mControlNumber(-1) {}
+
+nsISupports* ElementInternals::GetParentObject() { return ToSupports(mTarget); }
+
+JSObject* ElementInternals::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> 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<FileOrUSVStringOrFormData>& aValue,
+ const Optional<Nullable<FileOrUSVStringOrFormData>>& 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<nsAString>& aMessage,
+ const Optional<NonNull<nsGenericHTMLElement>>& 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->UpdateValidityElementStates(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<RefPtr<Element>, 1> invalidElements;
+ invalidElements.AppendElement(mTarget);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mTarget->GetOwnerGlobal())) {
+ return false;
+ }
+ JS::Rooted<JS::Value> detail(jsapi.cx());
+ if (!ToJSValue(jsapi.cx(), invalidElements, &detail)) {
+ return false;
+ }
+
+ RefPtr<CustomEvent> 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<nsINodeList> 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;
+}
+
+CustomStateSet* ElementInternals::States() {
+ if (!mCustomStateSet) {
+ mCustomStateSet = new CustomStateSet(mTarget);
+ }
+ return mCustomStateSet;
+}
+
+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->IsDisabled() || mTarget->HasAttr(nsGkAtoms::readonly) ||
+ mTarget->HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR));
+ }
+}
+
+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(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);
+
+ return rs;
+}
+
+DocGroup* ElementInternals::GetDocGroup() {
+ return mTarget->OwnerDoc()->GetDocGroup();
+}
+
+void ElementInternals::RestoreFormValue(
+ Nullable<OwningFileOrUSVStringOrFormData>&& aValue,
+ Nullable<OwningFileOrUSVStringOrFormData>&& aState) {
+ mSubmissionValue = aValue;
+ mState = aState;
+
+ if (!mState.IsNull()) {
+ LifecycleCallbackArgs args;
+ args.mState = mState;
+ args.mReason = RestoreReason::Restore;
+ nsContentUtils::EnqueueLifecycleCallback(
+ ElementCallbackType::eFormStateRestore, mTarget, args);
+ }
+}
+
+void ElementInternals::InitializeControlNumber() {
+ MOZ_ASSERT(mControlNumber == -1,
+ "FACE control number should only be initialized once!");
+ mControlNumber = mTarget->OwnerDoc()->GetNextControlNumber();
+}
+
+} // namespace mozilla::dom