summaryrefslogtreecommitdiffstats
path: root/dom/html/HTMLElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/HTMLElement.cpp')
-rw-r--r--dom/html/HTMLElement.cpp467
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);
+}