зеркало из https://github.com/mozilla/gecko-dev.git
477 строки
16 KiB
C++
477 строки
16 KiB
C++
/* -*- 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/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);
|
|
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);
|
|
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;
|
|
}
|
|
|
|
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
|