Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / html / HTMLElement.cpp
blobbccb117034ec6320b83b71312437a3ea4b4c0c47
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;
55 return;
58 nsGenericHTMLFormElement::GetEventTargetParent(aVisitor);
61 nsINode* HTMLElement::GetScopeChainParent() const {
62 if (IsFormAssociatedCustomElements()) {
63 auto* form = GetFormInternal();
64 if (form) {
65 return form;
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);
77 return rv;
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())) {
95 return;
98 auto* internals = GetElementInternals();
100 nsCString stateKey = internals->GetStateKey();
101 if (stateKey.IsEmpty()) {
102 return;
105 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false);
106 if (!history) {
107 return;
110 // Get the pres state for this key, if it doesn't exist, create one.
111 PresState* result = history->GetState(stateKey);
112 if (!result) {
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)) {
129 return;
132 auto* internals = GetElementInternals();
133 if (internals->GetStateKey().IsEmpty()) {
134 Document* doc = GetUncomposedDoc();
135 nsCString stateKey;
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()) {
150 return;
152 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true);
153 if (!history) {
154 return;
156 PresState* result = history->GetState(stateKey);
157 if (!result) {
158 return;
160 auto& content = result->contentData();
161 if (content.type() != PresContentData::TCustomElementTuple) {
162 return;
165 auto& ce = content.get_CustomElementTuple();
166 nsCOMPtr<nsIGlobalObject> global = GetOwnerDocument()->GetOwnerGlobal();
167 internals->RestoreFormValue(
168 nsContentUtils::ExtractFormAssociatedCustomElementValue(global,
169 ce.value()),
170 nsContentUtils::ExtractFormAssociatedCustomElementValue(global,
171 ce.state()));
174 void HTMLElement::InhibitRestoration(bool aShouldInhibit) {
175 if (aShouldInhibit) {
176 SetFlags(HTML_ELEMENT_INHIBIT_RESTORATION);
177 } else {
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();
192 MOZ_ASSERT(data);
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(
208 ErrorResult& aRv) {
209 CustomElementData* ceData = GetCustomElementData();
211 // 1. If element's is value is not null, then throw a "NotSupportedError"
212 // DOMException.
213 if (nsAtom* isAtom = ceData ? ceData->GetIs(this) : nullptr) {
214 aRv.ThrowNotSupportedError(nsPrintfCString(
215 "Cannot attach ElementInternals to a customized built-in element "
216 "'%s'",
217 NS_ConvertUTF16toUTF8(isAtom->GetUTF16String()).get()));
218 return nullptr;
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
223 // as is value.
224 nsAtom* nameAtom = NodeInfo()->NameAtom();
225 CustomElementDefinition* definition = nullptr;
226 if (ceData) {
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.
231 if (!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.
239 if (!definition) {
240 aRv.ThrowNotSupportedError(nsPrintfCString(
241 "Cannot attach ElementInternals to a non-custom element '%s'",
242 NS_ConvertUTF16toUTF8(nameAtom->GetUTF16String()).get()));
243 return nullptr;
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()));
252 return nullptr;
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.
257 MOZ_ASSERT(ceData);
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()));
265 return nullptr;
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".)");
274 return nullptr;
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());
318 return false;
321 void HTMLElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
322 const nsAttrValue* aValue,
323 const nsAttrValue* aOldValue,
324 nsIPrincipal* aMaybeScriptedPrincipal,
325 bool aNotify) {
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()) {
349 return;
351 if (internals->IsValid()) {
352 AddStatesSilently(ElementState::VALID | ElementState::USER_VALID);
353 } else {
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,
396 this, args);
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.
432 return nullptr;
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);