diff options
Diffstat (limited to 'dom/html/HTMLElement.cpp')
-rw-r--r-- | dom/html/HTMLElement.cpp | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/dom/html/HTMLElement.cpp b/dom/html/HTMLElement.cpp new file mode 100644 index 0000000000..ff9c786c55 --- /dev/null +++ b/dom/html/HTMLElement.cpp @@ -0,0 +1,467 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/HTMLElement.h" + +#include "mozilla/EventDispatcher.h" +#include "mozilla/PresState.h" +#include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/dom/ElementInternalsBinding.h" +#include "mozilla/dom/FormData.h" +#include "mozilla/dom/FromParser.h" +#include "mozilla/dom/HTMLElementBinding.h" +#include "nsContentUtils.h" +#include "nsGenericHTMLElement.h" +#include "nsILayoutHistoryState.h" + +namespace mozilla::dom { + +HTMLElement::HTMLElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser) + : nsGenericHTMLFormElement(std::move(aNodeInfo)) { + if (NodeInfo()->Equals(nsGkAtoms::bdi)) { + AddStatesSilently(ElementState::HAS_DIR_ATTR_LIKE_AUTO); + } + + InhibitRestoration(!(aFromParser & FROM_PARSER_NETWORK)); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLElement, nsGenericHTMLFormElement) + +// QueryInterface implementation for HTMLElement + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLElement) + NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIFormControl, GetElementInternals()) + NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIConstraintValidation, GetElementInternals()) +NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLFormElement) + +NS_IMPL_ADDREF_INHERITED(HTMLElement, nsGenericHTMLFormElement) +NS_IMPL_RELEASE_INHERITED(HTMLElement, nsGenericHTMLFormElement) + +NS_IMPL_ELEMENT_CLONE(HTMLElement) + +JSObject* HTMLElement::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return dom::HTMLElement_Binding::Wrap(aCx, this, aGivenProto); +} + +void HTMLElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { + if (IsDisabledForEvents(aVisitor.mEvent)) { + // Do not process any DOM events if the element is disabled + aVisitor.mCanHandle = false; + return; + } + + nsGenericHTMLFormElement::GetEventTargetParent(aVisitor); +} + +nsINode* HTMLElement::GetScopeChainParent() const { + if (IsFormAssociatedCustomElements()) { + auto* form = GetFormInternal(); + if (form) { + return form; + } + } + return nsGenericHTMLFormElement::GetScopeChainParent(); +} + +nsresult HTMLElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = nsGenericHTMLFormElement::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + UpdateBarredFromConstraintValidation(); + UpdateValidityElementStates(false); + return rv; +} + +void HTMLElement::UnbindFromTree(bool aNullParent) { + nsGenericHTMLFormElement::UnbindFromTree(aNullParent); + + UpdateBarredFromConstraintValidation(); + UpdateValidityElementStates(false); +} + +void HTMLElement::DoneCreatingElement() { + if (MOZ_UNLIKELY(IsFormAssociatedElement())) { + MaybeRestoreFormAssociatedCustomElementState(); + } +} + +void HTMLElement::SaveState() { + if (MOZ_LIKELY(!IsFormAssociatedElement())) { + return; + } + + auto* internals = GetElementInternals(); + + nsCString stateKey = internals->GetStateKey(); + if (stateKey.IsEmpty()) { + return; + } + + nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false); + if (!history) { + return; + } + + // Get the pres state for this key, if it doesn't exist, create one. + PresState* result = history->GetState(stateKey); + if (!result) { + UniquePtr<PresState> newState = NewPresState(); + result = newState.get(); + history->AddState(stateKey, std::move(newState)); + } + + const auto& state = internals->GetFormState(); + const auto& value = internals->GetFormSubmissionValue(); + result->contentData() = CustomElementTuple( + nsContentUtils::ConvertToCustomElementFormValue(value), + nsContentUtils::ConvertToCustomElementFormValue(state)); +} + +void HTMLElement::MaybeRestoreFormAssociatedCustomElementState() { + MOZ_ASSERT(IsFormAssociatedElement()); + + if (HasFlag(HTML_ELEMENT_INHIBIT_RESTORATION)) { + return; + } + + auto* internals = GetElementInternals(); + if (internals->GetStateKey().IsEmpty()) { + Document* doc = GetUncomposedDoc(); + nsCString stateKey; + nsContentUtils::GenerateStateKey(this, doc, stateKey); + internals->SetStateKey(std::move(stateKey)); + + RestoreFormAssociatedCustomElementState(); + } +} + +void HTMLElement::RestoreFormAssociatedCustomElementState() { + MOZ_ASSERT(IsFormAssociatedElement()); + + auto* internals = GetElementInternals(); + + const nsCString& stateKey = internals->GetStateKey(); + if (stateKey.IsEmpty()) { + return; + } + nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true); + if (!history) { + return; + } + PresState* result = history->GetState(stateKey); + if (!result) { + return; + } + auto& content = result->contentData(); + if (content.type() != PresContentData::TCustomElementTuple) { + return; + } + + auto& ce = content.get_CustomElementTuple(); + nsCOMPtr<nsIGlobalObject> global = GetOwnerDocument()->GetOwnerGlobal(); + internals->RestoreFormValue( + nsContentUtils::ExtractFormAssociatedCustomElementValue(global, + ce.value()), + nsContentUtils::ExtractFormAssociatedCustomElementValue(global, + ce.state())); +} + +void HTMLElement::InhibitRestoration(bool aShouldInhibit) { + if (aShouldInhibit) { + SetFlags(HTML_ELEMENT_INHIBIT_RESTORATION); + } else { + UnsetFlags(HTML_ELEMENT_INHIBIT_RESTORATION); + } +} + +void HTMLElement::SetCustomElementDefinition( + CustomElementDefinition* aDefinition) { + nsGenericHTMLFormElement::SetCustomElementDefinition(aDefinition); + // Always create an ElementInternal for form-associated custom element as the + // Form related implementation lives in ElementInternal which implements + // nsIFormControl. It is okay for the attachElementInternal API as there is a + // separated flag for whether attachElementInternal is called. + if (aDefinition && !aDefinition->IsCustomBuiltIn() && + aDefinition->mFormAssociated) { + CustomElementData* data = GetCustomElementData(); + MOZ_ASSERT(data); + auto* internals = data->GetOrCreateElementInternals(this); + + // This is for the case that script constructs a custom element directly, + // e.g. via new MyCustomElement(), where the upgrade steps won't be ran to + // update the disabled state in UpdateFormOwner(). + if (data->mState == CustomElementData::State::eCustom) { + UpdateDisabledState(true); + } else if (!HasFlag(HTML_ELEMENT_INHIBIT_RESTORATION)) { + internals->InitializeControlNumber(); + } + } +} + +// https://html.spec.whatwg.org/commit-snapshots/53bc3803433e1c817918b83e8a84f3db900031dd/#dom-attachinternals +already_AddRefed<ElementInternals> HTMLElement::AttachInternals( + ErrorResult& aRv) { + CustomElementData* ceData = GetCustomElementData(); + + // 1. If element's is value is not null, then throw a "NotSupportedError" + // DOMException. + if (nsAtom* isAtom = ceData ? ceData->GetIs(this) : nullptr) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Cannot attach ElementInternals to a customized built-in element " + "'%s'", + NS_ConvertUTF16toUTF8(isAtom->GetUTF16String()).get())); + return nullptr; + } + + // 2. Let definition be the result of looking up a custom element definition + // given element's node document, its namespace, its local name, and null + // as is value. + nsAtom* nameAtom = NodeInfo()->NameAtom(); + CustomElementDefinition* definition = nullptr; + if (ceData) { + definition = ceData->GetCustomElementDefinition(); + + // If the definition is null, the element possible hasn't yet upgraded. + // Fallback to use LookupCustomElementDefinition to find its definition. + if (!definition) { + definition = nsContentUtils::LookupCustomElementDefinition( + NodeInfo()->GetDocument(), nameAtom, NodeInfo()->NamespaceID(), + ceData->GetCustomElementType()); + } + } + + // 3. If definition is null, then throw an "NotSupportedError" DOMException. + if (!definition) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Cannot attach ElementInternals to a non-custom element '%s'", + NS_ConvertUTF16toUTF8(nameAtom->GetUTF16String()).get())); + return nullptr; + } + + // 4. If definition's disable internals is true, then throw a + // "NotSupportedError" DOMException. + if (definition->mDisableInternals) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "AttachInternal() to '%s' is disabled by disabledFeatures", + NS_ConvertUTF16toUTF8(nameAtom->GetUTF16String()).get())); + return nullptr; + } + + // If this is not a custom element, i.e. ceData is nullptr, we are unable to + // find a definition and should return earlier above. + MOZ_ASSERT(ceData); + + // 5. If element's attached internals is true, then throw an + // "NotSupportedError" DOMException. + if (ceData->HasAttachedInternals()) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "AttachInternals() has already been called from '%s'", + NS_ConvertUTF16toUTF8(nameAtom->GetUTF16String()).get())); + return nullptr; + } + + // 6. If element's custom element state is not "precustomized" or "custom", + // then throw a "NotSupportedError" DOMException. + if (ceData->mState != CustomElementData::State::ePrecustomized && + ceData->mState != CustomElementData::State::eCustom) { + aRv.ThrowNotSupportedError( + R"(Custom element state is not "precustomized" or "custom".)"); + return nullptr; + } + + // 7. Set element's attached internals to true. + ceData->AttachedInternals(); + + // 8. Create a new ElementInternals instance targeting element, and return it. + return do_AddRef(ceData->GetOrCreateElementInternals(this)); +} + +void HTMLElement::AfterClearForm(bool aUnbindOrDelete) { + // No need to enqueue formAssociated callback if we aren't releasing or + // unbinding from tree, UpdateFormOwner() will handle it. + if (aUnbindOrDelete) { + MOZ_ASSERT(IsFormAssociatedElement()); + nsContentUtils::EnqueueLifecycleCallback( + ElementCallbackType::eFormAssociated, this, {}); + } +} + +void HTMLElement::UpdateFormOwner() { + MOZ_ASSERT(IsFormAssociatedElement()); + + // If @form is set, the element *has* to be in a composed document, + // otherwise it wouldn't be possible to find an element with the + // corresponding id. If @form isn't set, the element *has* to have a parent, + // otherwise it wouldn't be possible to find a form ancestor. We should not + // call UpdateFormOwner if none of these conditions are fulfilled. + if (HasAttr(nsGkAtoms::form) ? IsInComposedDoc() : !!GetParent()) { + UpdateFormOwner(true, nullptr); + } + UpdateFieldSet(true); + UpdateDisabledState(true); + UpdateBarredFromConstraintValidation(); + UpdateValidityElementStates(true); + + MaybeRestoreFormAssociatedCustomElementState(); +} + +bool HTMLElement::IsDisabledForEvents(WidgetEvent* aEvent) { + if (IsFormAssociatedElement()) { + return IsElementDisabledForEvents(aEvent, GetPrimaryFrame()); + } + + return false; +} + +void HTMLElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) { + if (aNameSpaceID == kNameSpaceID_None && + (aName == nsGkAtoms::disabled || aName == nsGkAtoms::readonly)) { + if (aName == nsGkAtoms::disabled) { + // This *has* to be called *before* validity state check because + // UpdateBarredFromConstraintValidation depend on our disabled state. + UpdateDisabledState(aNotify); + } + if (aName == nsGkAtoms::readonly && !!aValue != !!aOldValue) { + UpdateReadOnlyState(aNotify); + } + UpdateBarredFromConstraintValidation(); + UpdateValidityElementStates(aNotify); + } + + return nsGenericHTMLFormElement::AfterSetAttr( + aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); +} + +void HTMLElement::UpdateValidityElementStates(bool aNotify) { + AutoStateChangeNotifier notifier(*this, aNotify); + RemoveStatesSilently(ElementState::VALIDITY_STATES); + ElementInternals* internals = GetElementInternals(); + if (!internals || !internals->IsCandidateForConstraintValidation()) { + return; + } + if (internals->IsValid()) { + AddStatesSilently(ElementState::VALID | ElementState::USER_VALID); + } else { + AddStatesSilently(ElementState::INVALID | ElementState::USER_INVALID); + } +} + +void HTMLElement::SetFormInternal(HTMLFormElement* aForm, bool aBindToTree) { + ElementInternals* internals = GetElementInternals(); + MOZ_ASSERT(internals); + internals->SetForm(aForm); +} + +HTMLFormElement* HTMLElement::GetFormInternal() const { + ElementInternals* internals = GetElementInternals(); + MOZ_ASSERT(internals); + return internals->GetForm(); +} + +void HTMLElement::SetFieldSetInternal(HTMLFieldSetElement* aFieldset) { + ElementInternals* internals = GetElementInternals(); + MOZ_ASSERT(internals); + internals->SetFieldSet(aFieldset); +} + +HTMLFieldSetElement* HTMLElement::GetFieldSetInternal() const { + ElementInternals* internals = GetElementInternals(); + MOZ_ASSERT(internals); + return internals->GetFieldSet(); +} + +bool HTMLElement::CanBeDisabled() const { return IsFormAssociatedElement(); } + +bool HTMLElement::DoesReadOnlyApply() const { + return IsFormAssociatedElement(); +} + +void HTMLElement::UpdateDisabledState(bool aNotify) { + bool oldState = IsDisabled(); + nsGenericHTMLFormElement::UpdateDisabledState(aNotify); + if (oldState != IsDisabled()) { + MOZ_ASSERT(IsFormAssociatedElement()); + LifecycleCallbackArgs args; + args.mDisabled = !oldState; + nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eFormDisabled, + this, args); + } +} + +void HTMLElement::UpdateFormOwner(bool aBindToTree, Element* aFormIdElement) { + HTMLFormElement* oldForm = GetFormInternal(); + nsGenericHTMLFormElement::UpdateFormOwner(aBindToTree, aFormIdElement); + HTMLFormElement* newForm = GetFormInternal(); + if (newForm != oldForm) { + LifecycleCallbackArgs args; + args.mForm = newForm; + nsContentUtils::EnqueueLifecycleCallback( + ElementCallbackType::eFormAssociated, this, args); + } +} + +bool HTMLElement::IsFormAssociatedElement() const { + CustomElementData* data = GetCustomElementData(); + return data && data->IsFormAssociated(); +} + +void HTMLElement::FieldSetDisabledChanged(bool aNotify) { + // This *has* to be called *before* UpdateBarredFromConstraintValidation + // because this function depend on our disabled state. + nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify); + + UpdateBarredFromConstraintValidation(); + UpdateValidityElementStates(aNotify); +} + +ElementInternals* HTMLElement::GetElementInternals() const { + CustomElementData* data = GetCustomElementData(); + if (!data || !data->IsFormAssociated()) { + // If the element is not a form associated custom element, it should not be + // able to be QueryInterfaced to nsIFormControl and could not perform + // the form operation, either, so we return nullptr here. + return nullptr; + } + + return data->GetElementInternals(); +} + +void HTMLElement::UpdateBarredFromConstraintValidation() { + CustomElementData* data = GetCustomElementData(); + if (data && data->IsFormAssociated()) { + ElementInternals* internals = data->GetElementInternals(); + MOZ_ASSERT(internals); + internals->UpdateBarredFromConstraintValidation(); + } +} + +} // namespace mozilla::dom + +// Here, we expand 'NS_IMPL_NS_NEW_HTML_ELEMENT()' by hand. +// (Calling the macro directly (with no args) produces compiler warnings.) +nsGenericHTMLElement* NS_NewHTMLElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser) { + RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); + auto* nim = nodeInfo->NodeInfoManager(); + return new (nim) mozilla::dom::HTMLElement(nodeInfo.forget(), aFromParser); +} + +// Distinct from the above in order to have function pointer that compared +// unequal to a function pointer to the above. +nsGenericHTMLElement* NS_NewCustomElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser) { + RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); + auto* nim = nodeInfo->NodeInfoManager(); + return new (nim) mozilla::dom::HTMLElement(nodeInfo.forget(), aFromParser); +} |