1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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/HTMLElement.h"
9 #include "mozilla/EventDispatcher.h"
10 #include "mozilla/PresState.h"
11 #include "mozilla/dom/CustomElementRegistry.h"
12 #include "mozilla/dom/ElementInternalsBinding.h"
13 #include "mozilla/dom/FormData.h"
14 #include "mozilla/dom/FromParser.h"
15 #include "mozilla/dom/HTMLElementBinding.h"
16 #include "nsContentUtils.h"
17 #include "nsGenericHTMLElement.h"
18 #include "nsILayoutHistoryState.h"
20 namespace mozilla::dom
{
22 HTMLElement::HTMLElement(already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
23 FromParser aFromParser
)
24 : nsGenericHTMLFormElement(std::move(aNodeInfo
)) {
25 if (NodeInfo()->Equals(nsGkAtoms::bdi
)) {
26 AddStatesSilently(ElementState::HAS_DIR_ATTR_LIKE_AUTO
);
29 InhibitRestoration(!(aFromParser
& FROM_PARSER_NETWORK
));
32 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLElement
, nsGenericHTMLFormElement
)
34 // QueryInterface implementation for HTMLElement
36 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLElement
)
37 NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIFormControl
, GetElementInternals())
38 NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIConstraintValidation
, GetElementInternals())
39 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLFormElement
)
41 NS_IMPL_ADDREF_INHERITED(HTMLElement
, nsGenericHTMLFormElement
)
42 NS_IMPL_RELEASE_INHERITED(HTMLElement
, nsGenericHTMLFormElement
)
44 NS_IMPL_ELEMENT_CLONE(HTMLElement
)
46 JSObject
* HTMLElement::WrapNode(JSContext
* aCx
,
47 JS::Handle
<JSObject
*> aGivenProto
) {
48 return dom::HTMLElement_Binding::Wrap(aCx
, this, aGivenProto
);
51 void HTMLElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
52 if (IsDisabledForEvents(aVisitor
.mEvent
)) {
53 // Do not process any DOM events if the element is disabled
54 aVisitor
.mCanHandle
= false;
58 nsGenericHTMLFormElement::GetEventTargetParent(aVisitor
);
61 nsINode
* HTMLElement::GetScopeChainParent() const {
62 if (IsFormAssociatedCustomElements()) {
63 auto* form
= GetFormInternal();
68 return nsGenericHTMLFormElement::GetScopeChainParent();
71 nsresult
HTMLElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
72 nsresult rv
= nsGenericHTMLFormElement::BindToTree(aContext
, aParent
);
73 NS_ENSURE_SUCCESS(rv
, rv
);
75 UpdateBarredFromConstraintValidation();
76 UpdateValidityElementStates(false);
80 void HTMLElement::UnbindFromTree(UnbindContext
& aContext
) {
81 nsGenericHTMLFormElement::UnbindFromTree(aContext
);
83 UpdateBarredFromConstraintValidation();
84 UpdateValidityElementStates(false);
87 void HTMLElement::DoneCreatingElement() {
88 if (MOZ_UNLIKELY(IsFormAssociatedElement())) {
89 MaybeRestoreFormAssociatedCustomElementState();
93 void HTMLElement::SaveState() {
94 if (MOZ_LIKELY(!IsFormAssociatedElement())) {
98 auto* internals
= GetElementInternals();
100 nsCString stateKey
= internals
->GetStateKey();
101 if (stateKey
.IsEmpty()) {
105 nsCOMPtr
<nsILayoutHistoryState
> history
= GetLayoutHistory(false);
110 // Get the pres state for this key, if it doesn't exist, create one.
111 PresState
* result
= history
->GetState(stateKey
);
113 UniquePtr
<PresState
> newState
= NewPresState();
114 result
= newState
.get();
115 history
->AddState(stateKey
, std::move(newState
));
118 const auto& state
= internals
->GetFormState();
119 const auto& value
= internals
->GetFormSubmissionValue();
120 result
->contentData() = CustomElementTuple(
121 nsContentUtils::ConvertToCustomElementFormValue(value
),
122 nsContentUtils::ConvertToCustomElementFormValue(state
));
125 void HTMLElement::MaybeRestoreFormAssociatedCustomElementState() {
126 MOZ_ASSERT(IsFormAssociatedElement());
128 if (HasFlag(HTML_ELEMENT_INHIBIT_RESTORATION
)) {
132 auto* internals
= GetElementInternals();
133 if (internals
->GetStateKey().IsEmpty()) {
134 Document
* doc
= GetUncomposedDoc();
136 nsContentUtils::GenerateStateKey(this, doc
, stateKey
);
137 internals
->SetStateKey(std::move(stateKey
));
139 RestoreFormAssociatedCustomElementState();
143 void HTMLElement::RestoreFormAssociatedCustomElementState() {
144 MOZ_ASSERT(IsFormAssociatedElement());
146 auto* internals
= GetElementInternals();
148 const nsCString
& stateKey
= internals
->GetStateKey();
149 if (stateKey
.IsEmpty()) {
152 nsCOMPtr
<nsILayoutHistoryState
> history
= GetLayoutHistory(true);
156 PresState
* result
= history
->GetState(stateKey
);
160 auto& content
= result
->contentData();
161 if (content
.type() != PresContentData::TCustomElementTuple
) {
165 auto& ce
= content
.get_CustomElementTuple();
166 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerDocument()->GetOwnerGlobal();
167 internals
->RestoreFormValue(
168 nsContentUtils::ExtractFormAssociatedCustomElementValue(global
,
170 nsContentUtils::ExtractFormAssociatedCustomElementValue(global
,
174 void HTMLElement::InhibitRestoration(bool aShouldInhibit
) {
175 if (aShouldInhibit
) {
176 SetFlags(HTML_ELEMENT_INHIBIT_RESTORATION
);
178 UnsetFlags(HTML_ELEMENT_INHIBIT_RESTORATION
);
182 void HTMLElement::SetCustomElementDefinition(
183 CustomElementDefinition
* aDefinition
) {
184 nsGenericHTMLFormElement::SetCustomElementDefinition(aDefinition
);
185 // Always create an ElementInternal for form-associated custom element as the
186 // Form related implementation lives in ElementInternal which implements
187 // nsIFormControl. It is okay for the attachElementInternal API as there is a
188 // separated flag for whether attachElementInternal is called.
189 if (aDefinition
&& !aDefinition
->IsCustomBuiltIn() &&
190 aDefinition
->mFormAssociated
) {
191 CustomElementData
* data
= GetCustomElementData();
193 auto* internals
= data
->GetOrCreateElementInternals(this);
195 // This is for the case that script constructs a custom element directly,
196 // e.g. via new MyCustomElement(), where the upgrade steps won't be ran to
197 // update the disabled state in UpdateFormOwner().
198 if (data
->mState
== CustomElementData::State::eCustom
) {
199 UpdateDisabledState(true);
200 } else if (!HasFlag(HTML_ELEMENT_INHIBIT_RESTORATION
)) {
201 internals
->InitializeControlNumber();
206 // https://html.spec.whatwg.org/commit-snapshots/53bc3803433e1c817918b83e8a84f3db900031dd/#dom-attachinternals
207 already_AddRefed
<ElementInternals
> HTMLElement::AttachInternals(
209 CustomElementData
* ceData
= GetCustomElementData();
211 // 1. If element's is value is not null, then throw a "NotSupportedError"
213 if (nsAtom
* isAtom
= ceData
? ceData
->GetIs(this) : nullptr) {
214 aRv
.ThrowNotSupportedError(nsPrintfCString(
215 "Cannot attach ElementInternals to a customized built-in element "
217 NS_ConvertUTF16toUTF8(isAtom
->GetUTF16String()).get()));
221 // 2. Let definition be the result of looking up a custom element definition
222 // given element's node document, its namespace, its local name, and null
224 nsAtom
* nameAtom
= NodeInfo()->NameAtom();
225 CustomElementDefinition
* definition
= nullptr;
227 definition
= ceData
->GetCustomElementDefinition();
229 // If the definition is null, the element possible hasn't yet upgraded.
230 // Fallback to use LookupCustomElementDefinition to find its definition.
232 definition
= nsContentUtils::LookupCustomElementDefinition(
233 NodeInfo()->GetDocument(), nameAtom
, NodeInfo()->NamespaceID(),
234 ceData
->GetCustomElementType());
238 // 3. If definition is null, then throw an "NotSupportedError" DOMException.
240 aRv
.ThrowNotSupportedError(nsPrintfCString(
241 "Cannot attach ElementInternals to a non-custom element '%s'",
242 NS_ConvertUTF16toUTF8(nameAtom
->GetUTF16String()).get()));
246 // 4. If definition's disable internals is true, then throw a
247 // "NotSupportedError" DOMException.
248 if (definition
->mDisableInternals
) {
249 aRv
.ThrowNotSupportedError(nsPrintfCString(
250 "AttachInternal() to '%s' is disabled by disabledFeatures",
251 NS_ConvertUTF16toUTF8(nameAtom
->GetUTF16String()).get()));
255 // If this is not a custom element, i.e. ceData is nullptr, we are unable to
256 // find a definition and should return earlier above.
259 // 5. If element's attached internals is true, then throw an
260 // "NotSupportedError" DOMException.
261 if (ceData
->HasAttachedInternals()) {
262 aRv
.ThrowNotSupportedError(nsPrintfCString(
263 "AttachInternals() has already been called from '%s'",
264 NS_ConvertUTF16toUTF8(nameAtom
->GetUTF16String()).get()));
268 // 6. If element's custom element state is not "precustomized" or "custom",
269 // then throw a "NotSupportedError" DOMException.
270 if (ceData
->mState
!= CustomElementData::State::ePrecustomized
&&
271 ceData
->mState
!= CustomElementData::State::eCustom
) {
272 aRv
.ThrowNotSupportedError(
273 R
"(Custom element state is not "precustomized
" or "custom
".)");
277 // 7. Set element's attached internals to true.
278 ceData
->AttachedInternals();
280 // 8. Create a new ElementInternals instance targeting element, and return it.
281 return do_AddRef(ceData
->GetOrCreateElementInternals(this));
284 void HTMLElement::AfterClearForm(bool aUnbindOrDelete
) {
285 // No need to enqueue formAssociated callback if we aren't releasing or
286 // unbinding from tree, UpdateFormOwner() will handle it.
287 if (aUnbindOrDelete
) {
288 MOZ_ASSERT(IsFormAssociatedElement());
289 nsContentUtils::EnqueueLifecycleCallback(
290 ElementCallbackType::eFormAssociated
, this, {});
294 void HTMLElement::UpdateFormOwner() {
295 MOZ_ASSERT(IsFormAssociatedElement());
297 // If @form is set, the element *has* to be in a composed document,
298 // otherwise it wouldn't be possible to find an element with the
299 // corresponding id. If @form isn't set, the element *has* to have a parent,
300 // otherwise it wouldn't be possible to find a form ancestor. We should not
301 // call UpdateFormOwner if none of these conditions are fulfilled.
302 if (HasAttr(nsGkAtoms::form
) ? IsInComposedDoc() : !!GetParent()) {
303 UpdateFormOwner(true, nullptr);
305 UpdateFieldSet(true);
306 UpdateDisabledState(true);
307 UpdateBarredFromConstraintValidation();
308 UpdateValidityElementStates(true);
310 MaybeRestoreFormAssociatedCustomElementState();
313 bool HTMLElement::IsDisabledForEvents(WidgetEvent
* aEvent
) {
314 if (IsFormAssociatedElement()) {
315 return IsElementDisabledForEvents(aEvent
, GetPrimaryFrame());
321 void HTMLElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
322 const nsAttrValue
* aValue
,
323 const nsAttrValue
* aOldValue
,
324 nsIPrincipal
* aMaybeScriptedPrincipal
,
326 if (aNameSpaceID
== kNameSpaceID_None
&&
327 (aName
== nsGkAtoms::disabled
|| aName
== nsGkAtoms::readonly
)) {
328 if (aName
== nsGkAtoms::disabled
) {
329 // This *has* to be called *before* validity state check because
330 // UpdateBarredFromConstraintValidation depend on our disabled state.
331 UpdateDisabledState(aNotify
);
333 if (aName
== nsGkAtoms::readonly
&& !!aValue
!= !!aOldValue
) {
334 UpdateReadOnlyState(aNotify
);
336 UpdateBarredFromConstraintValidation();
337 UpdateValidityElementStates(aNotify
);
340 return nsGenericHTMLFormElement::AfterSetAttr(
341 aNameSpaceID
, aName
, aValue
, aOldValue
, aMaybeScriptedPrincipal
, aNotify
);
344 void HTMLElement::UpdateValidityElementStates(bool aNotify
) {
345 AutoStateChangeNotifier
notifier(*this, aNotify
);
346 RemoveStatesSilently(ElementState::VALIDITY_STATES
);
347 ElementInternals
* internals
= GetElementInternals();
348 if (!internals
|| !internals
->IsCandidateForConstraintValidation()) {
351 if (internals
->IsValid()) {
352 AddStatesSilently(ElementState::VALID
| ElementState::USER_VALID
);
354 AddStatesSilently(ElementState::INVALID
| ElementState::USER_INVALID
);
358 void HTMLElement::SetFormInternal(HTMLFormElement
* aForm
, bool aBindToTree
) {
359 ElementInternals
* internals
= GetElementInternals();
360 MOZ_ASSERT(internals
);
361 internals
->SetForm(aForm
);
364 HTMLFormElement
* HTMLElement::GetFormInternal() const {
365 ElementInternals
* internals
= GetElementInternals();
366 MOZ_ASSERT(internals
);
367 return internals
->GetForm();
370 void HTMLElement::SetFieldSetInternal(HTMLFieldSetElement
* aFieldset
) {
371 ElementInternals
* internals
= GetElementInternals();
372 MOZ_ASSERT(internals
);
373 internals
->SetFieldSet(aFieldset
);
376 HTMLFieldSetElement
* HTMLElement::GetFieldSetInternal() const {
377 ElementInternals
* internals
= GetElementInternals();
378 MOZ_ASSERT(internals
);
379 return internals
->GetFieldSet();
382 bool HTMLElement::CanBeDisabled() const { return IsFormAssociatedElement(); }
384 bool HTMLElement::DoesReadOnlyApply() const {
385 return IsFormAssociatedElement();
388 void HTMLElement::UpdateDisabledState(bool aNotify
) {
389 bool oldState
= IsDisabled();
390 nsGenericHTMLFormElement::UpdateDisabledState(aNotify
);
391 if (oldState
!= IsDisabled()) {
392 MOZ_ASSERT(IsFormAssociatedElement());
393 LifecycleCallbackArgs args
;
394 args
.mDisabled
= !oldState
;
395 nsContentUtils::EnqueueLifecycleCallback(ElementCallbackType::eFormDisabled
,
400 void HTMLElement::UpdateFormOwner(bool aBindToTree
, Element
* aFormIdElement
) {
401 HTMLFormElement
* oldForm
= GetFormInternal();
402 nsGenericHTMLFormElement::UpdateFormOwner(aBindToTree
, aFormIdElement
);
403 HTMLFormElement
* newForm
= GetFormInternal();
404 if (newForm
!= oldForm
) {
405 LifecycleCallbackArgs args
;
406 args
.mForm
= newForm
;
407 nsContentUtils::EnqueueLifecycleCallback(
408 ElementCallbackType::eFormAssociated
, this, args
);
412 bool HTMLElement::IsFormAssociatedElement() const {
413 CustomElementData
* data
= GetCustomElementData();
414 return data
&& data
->IsFormAssociated();
417 void HTMLElement::FieldSetDisabledChanged(bool aNotify
) {
418 // This *has* to be called *before* UpdateBarredFromConstraintValidation
419 // because this function depend on our disabled state.
420 nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify
);
422 UpdateBarredFromConstraintValidation();
423 UpdateValidityElementStates(aNotify
);
426 ElementInternals
* HTMLElement::GetElementInternals() const {
427 CustomElementData
* data
= GetCustomElementData();
428 if (!data
|| !data
->IsFormAssociated()) {
429 // If the element is not a form associated custom element, it should not be
430 // able to be QueryInterfaced to nsIFormControl and could not perform
431 // the form operation, either, so we return nullptr here.
435 return data
->GetElementInternals();
438 void HTMLElement::UpdateBarredFromConstraintValidation() {
439 CustomElementData
* data
= GetCustomElementData();
440 if (data
&& data
->IsFormAssociated()) {
441 ElementInternals
* internals
= data
->GetElementInternals();
442 MOZ_ASSERT(internals
);
443 internals
->UpdateBarredFromConstraintValidation();
447 } // namespace mozilla::dom
449 // Here, we expand 'NS_IMPL_NS_NEW_HTML_ELEMENT()' by hand.
450 // (Calling the macro directly (with no args) produces compiler warnings.)
451 nsGenericHTMLElement
* NS_NewHTMLElement(
452 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
453 mozilla::dom::FromParser aFromParser
) {
454 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo(aNodeInfo
);
455 auto* nim
= nodeInfo
->NodeInfoManager();
456 return new (nim
) mozilla::dom::HTMLElement(nodeInfo
.forget(), aFromParser
);
459 // Distinct from the above in order to have function pointer that compared
460 // unequal to a function pointer to the above.
461 nsGenericHTMLElement
* NS_NewCustomElement(
462 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
463 mozilla::dom::FromParser aFromParser
) {
464 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo(aNodeInfo
);
465 auto* nim
= nodeInfo
->NodeInfoManager();
466 return new (nim
) mozilla::dom::HTMLElement(nodeInfo
.forget(), aFromParser
);