Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / html / nsGenericHTMLElement.cpp
blob1deaf719d30187e2aeda62143e8dd52f625f1bfb
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/EditorBase.h"
8 #include "mozilla/EventDispatcher.h"
9 #include "mozilla/EventListenerManager.h"
10 #include "mozilla/EventStateManager.h"
11 #include "mozilla/HTMLEditor.h"
12 #include "mozilla/IMEContentObserver.h"
13 #include "mozilla/IMEStateManager.h"
14 #include "mozilla/MappedDeclarationsBuilder.h"
15 #include "mozilla/Maybe.h"
16 #include "mozilla/MouseEvents.h"
17 #include "mozilla/PresShell.h"
18 #include "mozilla/StaticPrefs_dom.h"
19 #include "mozilla/TextEditor.h"
20 #include "mozilla/TextEvents.h"
21 #include "mozilla/StaticPrefs_accessibility.h"
22 #include "mozilla/dom/FetchPriority.h"
23 #include "mozilla/dom/FormData.h"
24 #include "nscore.h"
25 #include "nsGenericHTMLElement.h"
26 #include "nsCOMPtr.h"
27 #include "nsAtom.h"
28 #include "nsQueryObject.h"
29 #include "mozilla/dom/BindContext.h"
30 #include "mozilla/dom/Document.h"
31 #include "mozilla/dom/UnbindContext.h"
32 #include "nsPIDOMWindow.h"
33 #include "nsIFrameInlines.h"
34 #include "nsIScrollableFrame.h"
35 #include "nsView.h"
36 #include "nsViewManager.h"
37 #include "nsIWidget.h"
38 #include "nsRange.h"
39 #include "nsPresContext.h"
40 #include "nsError.h"
41 #include "nsIPrincipal.h"
42 #include "nsContainerFrame.h"
43 #include "nsStyleUtil.h"
44 #include "ReferrerInfo.h"
46 #include "mozilla/PresState.h"
47 #include "nsILayoutHistoryState.h"
49 #include "nsHTMLParts.h"
50 #include "nsContentUtils.h"
51 #include "mozilla/dom/DirectionalityUtils.h"
52 #include "mozilla/dom/DocumentOrShadowRoot.h"
53 #include "nsString.h"
54 #include "nsGkAtoms.h"
55 #include "nsDOMCSSDeclaration.h"
56 #include "nsITextControlFrame.h"
57 #include "nsIFormControl.h"
58 #include "mozilla/dom/HTMLFormElement.h"
59 #include "nsFocusManager.h"
61 #include "nsDOMStringMap.h"
62 #include "nsDOMString.h"
64 #include "nsLayoutUtils.h"
65 #include "mozilla/dom/DocumentInlines.h"
66 #include "HTMLFieldSetElement.h"
67 #include "nsTextNode.h"
68 #include "HTMLBRElement.h"
69 #include "nsDOMMutationObserver.h"
70 #include "mozilla/Preferences.h"
71 #include "mozilla/dom/FromParser.h"
72 #include "mozilla/dom/Link.h"
73 #include "mozilla/dom/ScriptLoader.h"
75 #include "nsDOMTokenList.h"
76 #include "nsThreadUtils.h"
77 #include "mozilla/dom/BindingUtils.h"
78 #include "mozilla/dom/MouseEventBinding.h"
79 #include "mozilla/dom/ToggleEvent.h"
80 #include "mozilla/dom/TouchEvent.h"
81 #include "mozilla/dom/InputEvent.h"
82 #include "mozilla/dom/InvokeEvent.h"
83 #include "mozilla/ErrorResult.h"
84 #include "nsHTMLDocument.h"
85 #include "nsGlobalWindowInner.h"
86 #include "mozilla/dom/HTMLBodyElement.h"
87 #include "imgIContainer.h"
88 #include "nsComputedDOMStyle.h"
89 #include "mozilla/dom/HTMLDialogElement.h"
90 #include "mozilla/dom/HTMLLabelElement.h"
91 #include "mozilla/dom/HTMLInputElement.h"
92 #include "mozilla/dom/CustomElementRegistry.h"
93 #include "mozilla/dom/ElementBinding.h"
94 #include "mozilla/dom/ElementInternals.h"
96 using namespace mozilla;
97 using namespace mozilla::dom;
99 static const uint8_t NS_INPUTMODE_NONE = 1;
100 static const uint8_t NS_INPUTMODE_TEXT = 2;
101 static const uint8_t NS_INPUTMODE_TEL = 3;
102 static const uint8_t NS_INPUTMODE_URL = 4;
103 static const uint8_t NS_INPUTMODE_EMAIL = 5;
104 static const uint8_t NS_INPUTMODE_NUMERIC = 6;
105 static const uint8_t NS_INPUTMODE_DECIMAL = 7;
106 static const uint8_t NS_INPUTMODE_SEARCH = 8;
108 static const nsAttrValue::EnumTable kInputmodeTable[] = {
109 {"none", NS_INPUTMODE_NONE},
110 {"text", NS_INPUTMODE_TEXT},
111 {"tel", NS_INPUTMODE_TEL},
112 {"url", NS_INPUTMODE_URL},
113 {"email", NS_INPUTMODE_EMAIL},
114 {"numeric", NS_INPUTMODE_NUMERIC},
115 {"decimal", NS_INPUTMODE_DECIMAL},
116 {"search", NS_INPUTMODE_SEARCH},
117 {nullptr, 0}};
119 static const uint8_t NS_ENTERKEYHINT_ENTER = 1;
120 static const uint8_t NS_ENTERKEYHINT_DONE = 2;
121 static const uint8_t NS_ENTERKEYHINT_GO = 3;
122 static const uint8_t NS_ENTERKEYHINT_NEXT = 4;
123 static const uint8_t NS_ENTERKEYHINT_PREVIOUS = 5;
124 static const uint8_t NS_ENTERKEYHINT_SEARCH = 6;
125 static const uint8_t NS_ENTERKEYHINT_SEND = 7;
127 static const nsAttrValue::EnumTable kEnterKeyHintTable[] = {
128 {"enter", NS_ENTERKEYHINT_ENTER},
129 {"done", NS_ENTERKEYHINT_DONE},
130 {"go", NS_ENTERKEYHINT_GO},
131 {"next", NS_ENTERKEYHINT_NEXT},
132 {"previous", NS_ENTERKEYHINT_PREVIOUS},
133 {"search", NS_ENTERKEYHINT_SEARCH},
134 {"send", NS_ENTERKEYHINT_SEND},
135 {nullptr, 0}};
137 static const uint8_t NS_AUTOCAPITALIZE_NONE = 1;
138 static const uint8_t NS_AUTOCAPITALIZE_SENTENCES = 2;
139 static const uint8_t NS_AUTOCAPITALIZE_WORDS = 3;
140 static const uint8_t NS_AUTOCAPITALIZE_CHARACTERS = 4;
142 static const nsAttrValue::EnumTable kAutocapitalizeTable[] = {
143 {"none", NS_AUTOCAPITALIZE_NONE},
144 {"sentences", NS_AUTOCAPITALIZE_SENTENCES},
145 {"words", NS_AUTOCAPITALIZE_WORDS},
146 {"characters", NS_AUTOCAPITALIZE_CHARACTERS},
147 {"off", NS_AUTOCAPITALIZE_NONE},
148 {"on", NS_AUTOCAPITALIZE_SENTENCES},
149 {"", 0},
150 {nullptr, 0}};
152 static const nsAttrValue::EnumTable* kDefaultAutocapitalize =
153 &kAutocapitalizeTable[1];
155 nsresult nsGenericHTMLElement::CopyInnerTo(Element* aDst) {
156 MOZ_ASSERT(!aDst->GetUncomposedDoc(),
157 "Should not CopyInnerTo an Element in a document");
159 auto reparse = aDst->OwnerDoc() == OwnerDoc() ? ReparseAttributes::No
160 : ReparseAttributes::Yes;
161 nsresult rv = Element::CopyInnerTo(aDst, reparse);
162 NS_ENSURE_SUCCESS(rv, rv);
164 // cloning a node must retain its internal nonce slot
165 nsString* nonce = static_cast<nsString*>(GetProperty(nsGkAtoms::nonce));
166 if (nonce) {
167 static_cast<nsGenericHTMLElement*>(aDst)->SetNonce(*nonce);
169 return NS_OK;
172 static const nsAttrValue::EnumTable kDirTable[] = {
173 {"ltr", Directionality::Ltr},
174 {"rtl", Directionality::Rtl},
175 {"auto", Directionality::Auto},
176 {nullptr, 0},
179 namespace {
180 // See <https://html.spec.whatwg.org/#the-popover-attribute>.
181 enum class PopoverAttributeKeyword : uint8_t { Auto, EmptyString, Manual };
183 static const char* kPopoverAttributeValueAuto = "auto";
184 static const char* kPopoverAttributeValueEmptyString = "";
185 static const char* kPopoverAttributeValueManual = "manual";
187 static const nsAttrValue::EnumTable kPopoverTable[] = {
188 {kPopoverAttributeValueAuto, PopoverAttributeKeyword::Auto},
189 {kPopoverAttributeValueEmptyString, PopoverAttributeKeyword::EmptyString},
190 {kPopoverAttributeValueManual, PopoverAttributeKeyword::Manual},
191 {nullptr, 0}};
193 // See <https://html.spec.whatwg.org/#the-popover-attribute>.
194 static const nsAttrValue::EnumTable* kPopoverTableInvalidValueDefault =
195 &kPopoverTable[2];
196 } // namespace
198 void nsGenericHTMLElement::GetFetchPriority(nsAString& aFetchPriority) const {
199 // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
200 GetEnumAttr(nsGkAtoms::fetchpriority, kFetchPriorityAttributeValueAuto,
201 aFetchPriority);
204 /* static */
205 FetchPriority nsGenericHTMLElement::ToFetchPriority(const nsAString& aValue) {
206 nsAttrValue attrValue;
207 ParseFetchPriority(aValue, attrValue);
208 MOZ_ASSERT(attrValue.Type() == nsAttrValue::eEnum);
209 return FetchPriority(attrValue.GetEnumValue());
212 namespace {
213 // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
214 static const nsAttrValue::EnumTable kFetchPriorityEnumTable[] = {
215 {kFetchPriorityAttributeValueHigh, FetchPriority::High},
216 {kFetchPriorityAttributeValueLow, FetchPriority::Low},
217 {kFetchPriorityAttributeValueAuto, FetchPriority::Auto},
218 {nullptr, 0}};
220 // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
221 static const nsAttrValue::EnumTable*
222 kFetchPriorityEnumTableInvalidValueDefault = &kFetchPriorityEnumTable[2];
223 } // namespace
225 FetchPriority nsGenericHTMLElement::GetFetchPriority() const {
226 const nsAttrValue* fetchpriorityAttribute =
227 GetParsedAttr(nsGkAtoms::fetchpriority);
228 if (fetchpriorityAttribute) {
229 MOZ_ASSERT(fetchpriorityAttribute->Type() == nsAttrValue::eEnum);
230 return FetchPriority(fetchpriorityAttribute->GetEnumValue());
233 return FetchPriority::Auto;
236 /* static */
237 void nsGenericHTMLElement::ParseFetchPriority(const nsAString& aValue,
238 nsAttrValue& aResult) {
239 aResult.ParseEnumValue(aValue, kFetchPriorityEnumTable,
240 false /* aCaseSensitive */,
241 kFetchPriorityEnumTableInvalidValueDefault);
244 void nsGenericHTMLElement::AddToNameTable(nsAtom* aName) {
245 MOZ_ASSERT(HasName(), "Node doesn't have name?");
246 Document* doc = GetUncomposedDoc();
247 if (doc && !IsInNativeAnonymousSubtree()) {
248 doc->AddToNameTable(this, aName);
252 void nsGenericHTMLElement::RemoveFromNameTable() {
253 if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
254 if (Document* doc = GetUncomposedDoc()) {
255 doc->RemoveFromNameTable(this,
256 GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
261 void nsGenericHTMLElement::GetAccessKeyLabel(nsString& aLabel) {
262 nsAutoString suffix;
263 GetAccessKey(suffix);
264 if (!suffix.IsEmpty()) {
265 EventStateManager::GetAccessKeyLabelPrefix(this, aLabel);
266 aLabel.Append(suffix);
270 static bool IsOffsetParent(nsIFrame* aFrame) {
271 LayoutFrameType frameType = aFrame->Type();
273 if (frameType == LayoutFrameType::TableCell ||
274 frameType == LayoutFrameType::TableWrapper) {
275 // Per the IDL for Element, only td, th, and table are acceptable
276 // offsetParents apart from body or positioned elements; we need to check
277 // the content type as well as the frame type so we ignore anonymous tables
278 // created by an element with display: table-cell with no actual table
279 nsIContent* content = aFrame->GetContent();
281 return content->IsAnyOfHTMLElements(nsGkAtoms::table, nsGkAtoms::td,
282 nsGkAtoms::th);
284 return false;
287 struct OffsetResult {
288 Element* mParent = nullptr;
289 CSSIntRect mRect;
292 static OffsetResult GetUnretargetedOffsetsFor(const Element& aElement) {
293 nsIFrame* frame = aElement.GetPrimaryFrame();
294 if (!frame) {
295 return {};
298 nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(frame);
300 nsIFrame* parent = frame->GetParent();
301 nsPoint origin(0, 0);
303 nsIContent* offsetParent = nullptr;
304 Element* docElement = aElement.GetComposedDoc()->GetRootElement();
305 nsIContent* content = frame->GetContent();
307 if (content &&
308 (content->IsHTMLElement(nsGkAtoms::body) || content == docElement)) {
309 parent = frame;
310 } else {
311 const bool isPositioned = styleFrame->IsAbsPosContainingBlock();
312 const bool isAbsolutelyPositioned = frame->IsAbsolutelyPositioned();
313 origin += frame->GetPositionIgnoringScrolling();
315 for (; parent; parent = parent->GetParent()) {
316 content = parent->GetContent();
318 // Stop at the first ancestor that is positioned.
319 if (parent->IsAbsPosContainingBlock()) {
320 offsetParent = content;
321 break;
324 // Add the parent's origin to our own to get to the
325 // right coordinate system.
326 const bool isOffsetParent = !isPositioned && IsOffsetParent(parent);
327 if (!isOffsetParent) {
328 origin += parent->GetPositionIgnoringScrolling();
331 if (content) {
332 // If we've hit the document element, break here.
333 if (content == docElement) {
334 break;
337 // Break if the ancestor frame type makes it suitable as offset parent
338 // and this element is *not* positioned or if we found the body element.
339 if (isOffsetParent || content->IsHTMLElement(nsGkAtoms::body)) {
340 offsetParent = content;
341 break;
346 if (isAbsolutelyPositioned && !offsetParent) {
347 // If this element is absolutely positioned, but we don't have
348 // an offset parent it means this element is an absolutely
349 // positioned child that's not nested inside another positioned
350 // element, in this case the element's frame's parent is the
351 // frame for the HTML element so we fail to find the body in the
352 // parent chain. We want the offset parent in this case to be
353 // the body, so we just get the body element from the document.
355 // We use GetBodyElement() here, not GetBody(), because we don't want to
356 // end up with framesets here.
357 offsetParent = aElement.GetComposedDoc()->GetBodyElement();
361 // Make the position relative to the padding edge.
362 if (parent) {
363 const nsStyleBorder* border = parent->StyleBorder();
364 origin.x -= border->GetComputedBorderWidth(eSideLeft);
365 origin.y -= border->GetComputedBorderWidth(eSideTop);
368 // Get the union of all rectangles in this and continuation frames.
369 // It doesn't really matter what we use as aRelativeTo here, since
370 // we only care about the size. We just have to use something non-null.
371 nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
372 rcFrame.MoveTo(origin);
373 return {Element::FromNodeOrNull(offsetParent),
374 CSSIntRect::FromAppUnitsRounded(rcFrame)};
377 static bool ShouldBeRetargeted(const Element& aReferenceElement,
378 const Element& aElementToMaybeRetarget) {
379 ShadowRoot* shadow = aElementToMaybeRetarget.GetContainingShadow();
380 if (!shadow) {
381 return false;
383 for (ShadowRoot* scope = aReferenceElement.GetContainingShadow(); scope;
384 scope = scope->Host()->GetContainingShadow()) {
385 if (scope == shadow) {
386 return false;
390 return true;
393 Element* nsGenericHTMLElement::GetOffsetRect(CSSIntRect& aRect) {
394 aRect = CSSIntRect();
396 if (!GetPrimaryFrame(FlushType::Layout)) {
397 return nullptr;
400 OffsetResult thisResult = GetUnretargetedOffsetsFor(*this);
401 aRect = thisResult.mRect;
403 Element* parent = thisResult.mParent;
404 while (parent && ShouldBeRetargeted(*this, *parent)) {
405 OffsetResult result = GetUnretargetedOffsetsFor(*parent);
406 aRect += result.mRect.TopLeft();
407 parent = result.mParent;
410 return parent;
413 bool nsGenericHTMLElement::Spellcheck() {
414 // Has the state has been explicitly set?
415 nsIContent* node;
416 for (node = this; node; node = node->GetParent()) {
417 if (node->IsHTMLElement()) {
418 static Element::AttrValuesArray strings[] = {nsGkAtoms::_true,
419 nsGkAtoms::_false, nullptr};
420 switch (node->AsElement()->FindAttrValueIn(
421 kNameSpaceID_None, nsGkAtoms::spellcheck, strings, eCaseMatters)) {
422 case 0: // spellcheck = "true"
423 return true;
424 case 1: // spellcheck = "false"
425 return false;
430 // contenteditable/designMode are spellchecked by default
431 if (IsEditable()) {
432 return true;
435 // Is this a chrome element?
436 if (nsContentUtils::IsChromeDoc(OwnerDoc())) {
437 return false; // Not spellchecked by default
440 // Anything else that's not a form control is not spellchecked by default
441 nsCOMPtr<nsIFormControl> formControl = do_QueryObject(this);
442 if (!formControl) {
443 return false; // Not spellchecked by default
446 // Is this a multiline plaintext input?
447 auto controlType = formControl->ControlType();
448 if (controlType == FormControlType::Textarea) {
449 return true; // Spellchecked by default
452 // Is this anything other than an input text?
453 // Other inputs are not spellchecked.
454 if (controlType != FormControlType::InputText) {
455 return false; // Not spellchecked by default
458 // Does the user want input text spellchecked by default?
459 // NOTE: Do not reflect a pref value of 0 back to the DOM getter.
460 // The web page should not know if the user has disabled spellchecking.
461 // We'll catch this in the editor itself.
462 int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1);
463 return spellcheckLevel == 2; // "Spellcheck multi- and single-line"
466 bool nsGenericHTMLElement::InNavQuirksMode(Document* aDoc) {
467 return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks;
470 void nsGenericHTMLElement::UpdateEditableState(bool aNotify) {
471 // XXX Should we do this only when in a document?
472 ContentEditableTristate value = GetContentEditableValue();
473 if (value != eInherit) {
474 SetEditableFlag(!!value);
475 UpdateReadOnlyState(aNotify);
476 return;
478 nsStyledElement::UpdateEditableState(aNotify);
481 nsresult nsGenericHTMLElement::BindToTree(BindContext& aContext,
482 nsINode& aParent) {
483 nsresult rv = nsGenericHTMLElementBase::BindToTree(aContext, aParent);
484 NS_ENSURE_SUCCESS(rv, rv);
486 if (IsInComposedDoc()) {
487 RegUnRegAccessKey(true);
490 if (IsInUncomposedDoc()) {
491 if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
492 aContext.OwnerDoc().AddToNameTable(
493 this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
497 if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue &&
498 IsInComposedDoc()) {
499 aContext.OwnerDoc().ChangeContentEditableCount(this, +1);
502 // Hide any nonce from the DOM, but keep the internal value of the
503 // nonce by copying and resetting the internal nonce value.
504 if (HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && IsInComposedDoc() &&
505 OwnerDoc()->GetBrowsingContext()) {
506 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
507 "nsGenericHTMLElement::ResetNonce::Runnable",
508 [self = RefPtr<nsGenericHTMLElement>(this)]() {
509 nsAutoString nonce;
510 self->GetNonce(nonce);
511 self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, u""_ns, true);
512 self->SetNonce(nonce);
513 }));
516 // We need to consider a labels element is moved to another subtree
517 // with different root, it needs to update labels list and its root
518 // as well.
519 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
520 if (slots && slots->mLabelsList) {
521 slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
524 return rv;
527 void nsGenericHTMLElement::UnbindFromTree(UnbindContext& aContext) {
528 if (IsInComposedDoc()) {
529 // https://html.spec.whatwg.org/#dom-trees:hide-popover-algorithm
530 // If removedNode's popover attribute is not in the no popover state, then
531 // run the hide popover algorithm given removedNode, false, false, and
532 // false.
533 if (GetPopoverData()) {
534 HidePopoverWithoutRunningScript();
536 RegUnRegAccessKey(false);
539 RemoveFromNameTable();
541 if (GetContentEditableValue() == eTrue) {
542 if (Document* doc = GetComposedDoc()) {
543 doc->ChangeContentEditableCount(this, -1);
547 nsStyledElement::UnbindFromTree(aContext);
549 // Invalidate .labels list. It will be repopulated when used the next time.
550 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
551 if (slots && slots->mLabelsList) {
552 slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
556 HTMLFormElement* nsGenericHTMLElement::FindAncestorForm(
557 HTMLFormElement* aCurrentForm) {
558 NS_ASSERTION(!HasAttr(nsGkAtoms::form) || IsHTMLElement(nsGkAtoms::img),
559 "FindAncestorForm should not be called if @form is set!");
560 if (IsInNativeAnonymousSubtree()) {
561 return nullptr;
564 nsIContent* content = this;
565 while (content) {
566 // If the current ancestor is a form, return it as our form
567 if (content->IsHTMLElement(nsGkAtoms::form)) {
568 #ifdef DEBUG
569 if (!nsContentUtils::IsInSameAnonymousTree(this, content)) {
570 // It's possible that we started unbinding at |content| or
571 // some ancestor of it, and |content| and |this| used to all be
572 // anonymous. Check for this the hard way.
573 for (nsIContent* child = this; child != content;
574 child = child->GetParent()) {
575 NS_ASSERTION(child->ComputeIndexInParentContent().isSome(),
576 "Walked too far?");
579 #endif
580 return static_cast<HTMLFormElement*>(content);
583 nsIContent* prevContent = content;
584 content = prevContent->GetParent();
586 if (!content && aCurrentForm) {
587 // We got to the root of the subtree we're in, and we're being removed
588 // from the DOM (the only time we get into this method with a non-null
589 // aCurrentForm). Check whether aCurrentForm is in the same subtree. If
590 // it is, we want to return aCurrentForm, since this case means that
591 // we're one of those inputs-in-a-table that have a hacked mForm pointer
592 // and a subtree containing both us and the form got removed from the
593 // DOM.
594 if (aCurrentForm->IsInclusiveDescendantOf(prevContent)) {
595 return aCurrentForm;
600 return nullptr;
603 bool nsGenericHTMLElement::CheckHandleEventForAnchorsPreconditions(
604 EventChainVisitor& aVisitor) {
605 MOZ_ASSERT(nsCOMPtr<Link>(do_QueryObject(this)),
606 "should be called only when |this| implements |Link|");
607 // When disconnected, only <a> should navigate away per
608 // https://html.spec.whatwg.org/#cannot-navigate
609 return IsInComposedDoc() || IsHTMLElement(nsGkAtoms::a);
612 void nsGenericHTMLElement::GetEventTargetParentForAnchors(
613 EventChainPreVisitor& aVisitor) {
614 nsGenericHTMLElementBase::GetEventTargetParent(aVisitor);
616 if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
617 return;
620 GetEventTargetParentForLinks(aVisitor);
623 nsresult nsGenericHTMLElement::PostHandleEventForAnchors(
624 EventChainPostVisitor& aVisitor) {
625 if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
626 return NS_OK;
629 return PostHandleEventForLinks(aVisitor);
632 bool nsGenericHTMLElement::IsHTMLLink(nsIURI** aURI) const {
633 MOZ_ASSERT(aURI, "Must provide aURI out param");
635 *aURI = GetHrefURIForAnchors().take();
636 // We promise out param is non-null if we return true, so base rv on it
637 return *aURI != nullptr;
640 already_AddRefed<nsIURI> nsGenericHTMLElement::GetHrefURIForAnchors() const {
641 // This is used by the three Link implementations and
642 // nsHTMLStyleElement.
644 // Get href= attribute (relative URI).
646 // We use the nsAttrValue's copy of the URI string to avoid copying.
647 nsCOMPtr<nsIURI> uri;
648 GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri));
649 return uri.forget();
652 void nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
653 const nsAttrValue* aValue,
654 bool aNotify) {
655 if (aNamespaceID == kNameSpaceID_None) {
656 if (aName == nsGkAtoms::accesskey) {
657 // Have to unregister before clearing flag. See UnregAccessKey
658 RegUnRegAccessKey(false);
659 if (!aValue) {
660 UnsetFlags(NODE_HAS_ACCESSKEY);
662 } else if (aName == nsGkAtoms::name) {
663 // Have to do this before clearing flag. See RemoveFromNameTable
664 RemoveFromNameTable();
665 if (!aValue || aValue->IsEmptyString()) {
666 ClearHasName();
668 } else if (aName == nsGkAtoms::contenteditable) {
669 if (aValue) {
670 // Set this before the attribute is set so that any subclass code that
671 // runs before the attribute is set won't think we're missing a
672 // contenteditable attr when we actually have one.
673 SetMayHaveContentEditableAttr();
676 if (!aValue && IsEventAttributeName(aName)) {
677 if (EventListenerManager* manager = GetExistingListenerManager()) {
678 manager->RemoveEventHandler(GetEventNameForAttr(aName));
683 return nsGenericHTMLElementBase::BeforeSetAttr(aNamespaceID, aName, aValue,
684 aNotify);
687 namespace {
688 constexpr PopoverAttributeState ToPopoverAttributeState(
689 PopoverAttributeKeyword aPopoverAttributeKeyword) {
690 // See <https://html.spec.whatwg.org/#the-popover-attribute>.
691 switch (aPopoverAttributeKeyword) {
692 case PopoverAttributeKeyword::Auto:
693 return PopoverAttributeState::Auto;
694 case PopoverAttributeKeyword::EmptyString:
695 return PopoverAttributeState::Auto;
696 case PopoverAttributeKeyword::Manual:
697 return PopoverAttributeState::Manual;
698 default: {
699 MOZ_ASSERT_UNREACHABLE();
700 return PopoverAttributeState::None;
704 } // namespace
706 void nsGenericHTMLElement::AfterSetPopoverAttr() {
707 auto mapPopoverState = [](const nsAttrValue* value) -> PopoverAttributeState {
708 if (value) {
709 MOZ_ASSERT(value->Type() == nsAttrValue::eEnum);
710 const auto popoverAttributeKeyword =
711 static_cast<PopoverAttributeKeyword>(value->GetEnumValue());
712 return ToPopoverAttributeState(popoverAttributeKeyword);
715 // The missing value default is the no popover state, see
716 // <https://html.spec.whatwg.org/multipage/popover.html#attr-popover>.
717 return PopoverAttributeState::None;
720 PopoverAttributeState newState =
721 mapPopoverState(GetParsedAttr(nsGkAtoms::popover));
723 const PopoverAttributeState oldState = GetPopoverAttributeState();
725 if (newState != oldState) {
726 PopoverPseudoStateUpdate(false, true);
728 if (IsPopoverOpen()) {
729 HidePopoverInternal(/* aFocusPreviousElement = */ true,
730 /* aFireEvents = */ true, IgnoreErrors());
731 // Event handlers could have removed the popover attribute, or changed
732 // its value.
733 // https://github.com/whatwg/html/issues/9034
734 newState = mapPopoverState(GetParsedAttr(nsGkAtoms::popover));
737 if (newState == PopoverAttributeState::None) {
738 ClearPopoverData();
739 RemoveStates(ElementState::POPOVER_OPEN);
740 } else {
741 // TODO: what if `HidePopoverInternal` called `ShowPopup()`?
742 EnsurePopoverData().SetPopoverAttributeState(newState);
747 void nsGenericHTMLElement::OnAttrSetButNotChanged(
748 int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue,
749 bool aNotify) {
750 if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::popovertarget) {
751 ClearExplicitlySetAttrElement(aName);
753 return nsGenericHTMLElementBase::OnAttrSetButNotChanged(aNamespaceID, aName,
754 aValue, aNotify);
757 void nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
758 const nsAttrValue* aValue,
759 const nsAttrValue* aOldValue,
760 nsIPrincipal* aMaybeScriptedPrincipal,
761 bool aNotify) {
762 if (aNamespaceID == kNameSpaceID_None) {
763 if (IsEventAttributeName(aName) && aValue) {
764 MOZ_ASSERT(aValue->Type() == nsAttrValue::eString,
765 "Expected string value for script body");
766 SetEventHandler(GetEventNameForAttr(aName), aValue->GetStringValue());
767 } else if (aNotify && aName == nsGkAtoms::spellcheck) {
768 SyncEditorsOnSubtree(this);
769 } else if (aName == nsGkAtoms::popover &&
770 StaticPrefs::dom_element_popover_enabled()) {
771 nsContentUtils::AddScriptRunner(
772 NewRunnableMethod("nsGenericHTMLElement::AfterSetPopoverAttr", this,
773 &nsGenericHTMLElement::AfterSetPopoverAttr));
774 } else if (aName == nsGkAtoms::popovertarget) {
775 ClearExplicitlySetAttrElement(aName);
776 } else if (aName == nsGkAtoms::dir) {
777 auto dir = Directionality::Ltr;
778 // A boolean tracking whether we need to recompute our directionality.
779 // This needs to happen after we update our internal "dir" attribute
780 // state but before we call SetDirectionalityOnDescendants.
781 bool recomputeDirectionality = false;
782 ElementState dirStates;
783 if (aValue && aValue->Type() == nsAttrValue::eEnum) {
784 SetHasValidDir();
785 dirStates |= ElementState::HAS_DIR_ATTR;
786 auto dirValue = Directionality(aValue->GetEnumValue());
787 if (dirValue == Directionality::Auto) {
788 dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO;
789 } else {
790 dir = dirValue;
791 SetDirectionality(dir, aNotify);
792 if (dirValue == Directionality::Ltr) {
793 dirStates |= ElementState::HAS_DIR_ATTR_LTR;
794 } else {
795 MOZ_ASSERT(dirValue == Directionality::Rtl);
796 dirStates |= ElementState::HAS_DIR_ATTR_RTL;
799 } else {
800 if (aValue) {
801 // We have a value, just not a valid one.
802 dirStates |= ElementState::HAS_DIR_ATTR;
804 ClearHasValidDir();
805 if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
806 dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO;
807 } else {
808 recomputeDirectionality = true;
811 // Now figure out what's changed about our dir states.
812 ElementState oldDirStates = State() & ElementState::DIR_ATTR_STATES;
813 ElementState changedStates = dirStates ^ oldDirStates;
814 if (!changedStates.IsEmpty()) {
815 ToggleStates(changedStates, aNotify);
817 if (recomputeDirectionality) {
818 dir = RecomputeDirectionality(this, aNotify);
820 SetDirectionalityOnDescendants(this, dir, aNotify);
821 } else if (aName == nsGkAtoms::contenteditable) {
822 int32_t editableCountDelta = 0;
823 if (aOldValue && (aOldValue->Equals(u"true"_ns, eIgnoreCase) ||
824 aOldValue->Equals(u""_ns, eIgnoreCase))) {
825 editableCountDelta = -1;
827 if (aValue && (aValue->Equals(u"true"_ns, eIgnoreCase) ||
828 aValue->Equals(u""_ns, eIgnoreCase))) {
829 ++editableCountDelta;
831 ChangeEditableState(editableCountDelta);
832 } else if (aName == nsGkAtoms::accesskey) {
833 if (aValue && !aValue->Equals(u""_ns, eIgnoreCase)) {
834 SetFlags(NODE_HAS_ACCESSKEY);
835 RegUnRegAccessKey(true);
837 } else if (aName == nsGkAtoms::inert) {
838 if (aValue) {
839 AddStates(ElementState::INERT);
840 } else {
841 RemoveStates(ElementState::INERT);
843 } else if (aName == nsGkAtoms::name) {
844 if (aValue && !aValue->Equals(u""_ns, eIgnoreCase)) {
845 // This may not be quite right because we can have subclass code run
846 // before here. But in practice subclasses don't care about this flag,
847 // and in particular selector matching does not care. Otherwise we'd
848 // want to handle it like we handle id attributes (in PreIdMaybeChange
849 // and PostIdMaybeChange).
850 SetHasName();
851 if (CanHaveName(NodeInfo()->NameAtom())) {
852 AddToNameTable(aValue->GetAtomValue());
855 } else if (aName == nsGkAtoms::inputmode ||
856 aName == nsGkAtoms::enterkeyhint) {
857 if (nsFocusManager::GetFocusedElementStatic() == this) {
858 if (const nsPresContext* presContext =
859 GetPresContext(eForComposedDoc)) {
860 IMEContentObserver* observer =
861 IMEStateManager::GetActiveContentObserver();
862 if (observer && observer->IsObserving(*presContext, this)) {
863 if (RefPtr<EditorBase> editorBase = GetEditorWithoutCreation()) {
864 IMEState newState;
865 editorBase->GetPreferredIMEState(&newState);
866 OwningNonNull<nsGenericHTMLElement> kungFuDeathGrip(*this);
867 IMEStateManager::UpdateIMEState(
868 newState, kungFuDeathGrip, *editorBase,
869 {IMEStateManager::UpdateIMEStateOption::ForceUpdate,
870 IMEStateManager::UpdateIMEStateOption::
871 DontCommitComposition});
878 // The nonce will be copied over to an internal slot and cleared from the
879 // Element within BindToTree to avoid CSS Selector nonce exfiltration if
880 // the CSP list contains a header-delivered CSP.
881 if (nsGkAtoms::nonce == aName) {
882 if (aValue) {
883 SetNonce(aValue->GetStringValue());
884 if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) {
885 SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP);
887 } else {
888 RemoveNonce();
893 return nsGenericHTMLElementBase::AfterSetAttr(
894 aNamespaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
897 EventListenerManager* nsGenericHTMLElement::GetEventListenerManagerForAttr(
898 nsAtom* aAttrName, bool* aDefer) {
899 // Attributes on the body and frameset tags get set on the global object
900 if ((mNodeInfo->Equals(nsGkAtoms::body) ||
901 mNodeInfo->Equals(nsGkAtoms::frameset)) &&
902 // We only forward some event attributes from body/frameset to window
904 #define EVENT(name_, id_, type_, struct_) /* nothing */
905 #define FORWARDED_EVENT(name_, id_, type_, struct_) \
906 || nsGkAtoms::on##name_ == aAttrName
907 #define WINDOW_EVENT FORWARDED_EVENT
908 #include "mozilla/EventNameList.h" // IWYU pragma: keep
909 #undef WINDOW_EVENT
910 #undef FORWARDED_EVENT
911 #undef EVENT
912 )) {
913 nsPIDOMWindowInner* win;
915 // If we have a document, and it has a window, add the event
916 // listener on the window (the inner window). If not, proceed as
917 // normal.
918 // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() here,
919 // override BindToTree for those classes and munge event listeners there?
920 Document* document = OwnerDoc();
922 *aDefer = false;
923 if ((win = document->GetInnerWindow())) {
924 nsCOMPtr<EventTarget> piTarget(do_QueryInterface(win));
926 return piTarget->GetOrCreateListenerManager();
929 return nullptr;
932 return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName,
933 aDefer);
936 #define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
937 #define FORWARDED_EVENT(name_, id_, type_, struct_) \
938 EventHandlerNonNull* nsGenericHTMLElement::GetOn##name_() { \
939 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
940 /* XXXbz note to self: add tests for this! */ \
941 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
942 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
943 return globalWin->GetOn##name_(); \
945 return nullptr; \
948 return nsINode::GetOn##name_(); \
950 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \
951 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
952 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
953 if (!win) { \
954 return; \
957 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
958 return globalWin->SetOn##name_(handler); \
961 return nsINode::SetOn##name_(handler); \
963 #define ERROR_EVENT(name_, id_, type_, struct_) \
964 already_AddRefed<EventHandlerNonNull> nsGenericHTMLElement::GetOn##name_() { \
965 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
966 /* XXXbz note to self: add tests for this! */ \
967 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
968 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
969 OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_(); \
970 if (errorHandler) { \
971 RefPtr<EventHandlerNonNull> handler = \
972 new EventHandlerNonNull(errorHandler); \
973 return handler.forget(); \
976 return nullptr; \
979 RefPtr<EventHandlerNonNull> handler = nsINode::GetOn##name_(); \
980 return handler.forget(); \
982 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \
983 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
984 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
985 if (!win) { \
986 return; \
989 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
990 RefPtr<OnErrorEventHandlerNonNull> errorHandler; \
991 if (handler) { \
992 errorHandler = new OnErrorEventHandlerNonNull(handler); \
994 return globalWin->SetOn##name_(errorHandler); \
997 return nsINode::SetOn##name_(handler); \
999 #include "mozilla/EventNameList.h" // IWYU pragma: keep
1000 #undef ERROR_EVENT
1001 #undef FORWARDED_EVENT
1002 #undef EVENT
1004 void nsGenericHTMLElement::GetBaseTarget(nsAString& aBaseTarget) const {
1005 OwnerDoc()->GetBaseTarget(aBaseTarget);
1008 //----------------------------------------------------------------------
1010 bool nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID,
1011 nsAtom* aAttribute,
1012 const nsAString& aValue,
1013 nsIPrincipal* aMaybeScriptedPrincipal,
1014 nsAttrValue& aResult) {
1015 if (aNamespaceID == kNameSpaceID_None) {
1016 if (aAttribute == nsGkAtoms::dir) {
1017 return aResult.ParseEnumValue(aValue, kDirTable, false);
1020 if (aAttribute == nsGkAtoms::popover &&
1021 StaticPrefs::dom_element_popover_enabled()) {
1022 return aResult.ParseEnumValue(aValue, kPopoverTable, false,
1023 kPopoverTableInvalidValueDefault);
1026 if (aAttribute == nsGkAtoms::tabindex) {
1027 return aResult.ParseIntValue(aValue);
1030 if (aAttribute == nsGkAtoms::referrerpolicy) {
1031 return ParseReferrerAttribute(aValue, aResult);
1034 if (aAttribute == nsGkAtoms::name) {
1035 // Store name as an atom. name="" means that the element has no name,
1036 // not that it has an empty string as the name.
1037 if (aValue.IsEmpty()) {
1038 return false;
1040 aResult.ParseAtom(aValue);
1041 return true;
1044 if (aAttribute == nsGkAtoms::contenteditable ||
1045 aAttribute == nsGkAtoms::translate) {
1046 aResult.ParseAtom(aValue);
1047 return true;
1050 if (aAttribute == nsGkAtoms::rel) {
1051 aResult.ParseAtomArray(aValue);
1052 return true;
1055 if (aAttribute == nsGkAtoms::inputmode) {
1056 return aResult.ParseEnumValue(aValue, kInputmodeTable, false);
1059 if (aAttribute == nsGkAtoms::enterkeyhint) {
1060 return aResult.ParseEnumValue(aValue, kEnterKeyHintTable, false);
1063 if (aAttribute == nsGkAtoms::autocapitalize) {
1064 return aResult.ParseEnumValue(aValue, kAutocapitalizeTable, false);
1068 return nsGenericHTMLElementBase::ParseAttribute(
1069 aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
1072 bool nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID,
1073 nsAtom* aAttribute,
1074 const nsAString& aValue,
1075 nsAttrValue& aResult) {
1076 if (aNamespaceID == kNameSpaceID_None &&
1077 aAttribute == nsGkAtoms::background && !aValue.IsEmpty()) {
1078 // Resolve url to an absolute url
1079 Document* doc = OwnerDoc();
1080 nsCOMPtr<nsIURI> uri;
1081 nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
1082 getter_AddRefs(uri), aValue, doc, GetBaseURI());
1083 if (NS_FAILED(rv)) {
1084 return false;
1086 aResult.SetTo(uri, &aValue);
1087 return true;
1090 return false;
1093 bool nsGenericHTMLElement::IsAttributeMapped(const nsAtom* aAttribute) const {
1094 static const MappedAttributeEntry* const map[] = {sCommonAttributeMap};
1096 return FindAttributeDependence(aAttribute, map);
1099 nsMapRuleToAttributesFunc nsGenericHTMLElement::GetAttributeMappingFunction()
1100 const {
1101 return &MapCommonAttributesInto;
1104 nsIFormControlFrame* nsGenericHTMLElement::GetFormControlFrame(
1105 bool aFlushFrames) {
1106 auto flushType = aFlushFrames ? FlushType::Frames : FlushType::None;
1107 nsIFrame* frame = GetPrimaryFrame(flushType);
1108 if (!frame) {
1109 return nullptr;
1112 if (nsIFormControlFrame* f = do_QueryFrame(frame)) {
1113 return f;
1116 // If we have generated content, the primary frame will be a wrapper frame...
1117 // Our real frame will be in its child list.
1119 // FIXME(emilio): I don't think that's true... See bug 155957 for test-cases
1120 // though, we should figure out whether this is still needed.
1121 for (nsIFrame* kid : frame->PrincipalChildList()) {
1122 if (nsIFormControlFrame* f = do_QueryFrame(kid)) {
1123 return f;
1127 return nullptr;
1130 static const nsAttrValue::EnumTable kDivAlignTable[] = {
1131 {"left", StyleTextAlign::MozLeft},
1132 {"right", StyleTextAlign::MozRight},
1133 {"center", StyleTextAlign::MozCenter},
1134 {"middle", StyleTextAlign::MozCenter},
1135 {"justify", StyleTextAlign::Justify},
1136 {nullptr, 0}};
1138 static const nsAttrValue::EnumTable kFrameborderTable[] = {
1139 {"yes", FrameBorderProperty::Yes},
1140 {"no", FrameBorderProperty::No},
1141 {"1", FrameBorderProperty::One},
1142 {"0", FrameBorderProperty::Zero},
1143 {nullptr, 0}};
1145 // TODO(emilio): Nobody uses the parsed attribute here.
1146 static const nsAttrValue::EnumTable kScrollingTable[] = {
1147 {"yes", ScrollingAttribute::Yes},
1148 {"no", ScrollingAttribute::No},
1149 {"on", ScrollingAttribute::On},
1150 {"off", ScrollingAttribute::Off},
1151 {"scroll", ScrollingAttribute::Scroll},
1152 {"noscroll", ScrollingAttribute::Noscroll},
1153 {"auto", ScrollingAttribute::Auto},
1154 {nullptr, 0}};
1156 static const nsAttrValue::EnumTable kTableVAlignTable[] = {
1157 {"top", StyleVerticalAlignKeyword::Top},
1158 {"middle", StyleVerticalAlignKeyword::Middle},
1159 {"bottom", StyleVerticalAlignKeyword::Bottom},
1160 {"baseline", StyleVerticalAlignKeyword::Baseline},
1161 {nullptr, 0}};
1163 bool nsGenericHTMLElement::ParseAlignValue(const nsAString& aString,
1164 nsAttrValue& aResult) {
1165 static const nsAttrValue::EnumTable kAlignTable[] = {
1166 {"left", StyleTextAlign::Left},
1167 {"right", StyleTextAlign::Right},
1169 {"top", StyleVerticalAlignKeyword::Top},
1170 {"middle", StyleVerticalAlignKeyword::MozMiddleWithBaseline},
1172 // Intentionally not bottom.
1173 {"bottom", StyleVerticalAlignKeyword::Baseline},
1175 {"center", StyleVerticalAlignKeyword::MozMiddleWithBaseline},
1176 {"baseline", StyleVerticalAlignKeyword::Baseline},
1178 {"texttop", StyleVerticalAlignKeyword::TextTop},
1179 {"absmiddle", StyleVerticalAlignKeyword::Middle},
1180 {"abscenter", StyleVerticalAlignKeyword::Middle},
1181 {"absbottom", StyleVerticalAlignKeyword::Bottom},
1182 {nullptr, 0}};
1184 static_assert(uint8_t(StyleTextAlign::Left) !=
1185 uint8_t(StyleVerticalAlignKeyword::Top) &&
1186 uint8_t(StyleTextAlign::Left) !=
1187 uint8_t(StyleVerticalAlignKeyword::MozMiddleWithBaseline) &&
1188 uint8_t(StyleTextAlign::Left) !=
1189 uint8_t(StyleVerticalAlignKeyword::Baseline) &&
1190 uint8_t(StyleTextAlign::Left) !=
1191 uint8_t(StyleVerticalAlignKeyword::TextTop) &&
1192 uint8_t(StyleTextAlign::Left) !=
1193 uint8_t(StyleVerticalAlignKeyword::Middle) &&
1194 uint8_t(StyleTextAlign::Left) !=
1195 uint8_t(StyleVerticalAlignKeyword::Bottom));
1197 static_assert(uint8_t(StyleTextAlign::Right) !=
1198 uint8_t(StyleVerticalAlignKeyword::Top) &&
1199 uint8_t(StyleTextAlign::Right) !=
1200 uint8_t(StyleVerticalAlignKeyword::MozMiddleWithBaseline) &&
1201 uint8_t(StyleTextAlign::Right) !=
1202 uint8_t(StyleVerticalAlignKeyword::Baseline) &&
1203 uint8_t(StyleTextAlign::Right) !=
1204 uint8_t(StyleVerticalAlignKeyword::TextTop) &&
1205 uint8_t(StyleTextAlign::Right) !=
1206 uint8_t(StyleVerticalAlignKeyword::Middle) &&
1207 uint8_t(StyleTextAlign::Right) !=
1208 uint8_t(StyleVerticalAlignKeyword::Bottom));
1210 return aResult.ParseEnumValue(aString, kAlignTable, false);
1213 //----------------------------------------
1215 static const nsAttrValue::EnumTable kTableHAlignTable[] = {
1216 {"left", StyleTextAlign::Left}, {"right", StyleTextAlign::Right},
1217 {"center", StyleTextAlign::Center}, {"char", StyleTextAlign::Char},
1218 {"justify", StyleTextAlign::Justify}, {nullptr, 0}};
1220 bool nsGenericHTMLElement::ParseTableHAlignValue(const nsAString& aString,
1221 nsAttrValue& aResult) {
1222 return aResult.ParseEnumValue(aString, kTableHAlignTable, false);
1225 //----------------------------------------
1227 // This table is used for td, th, tr, col, thead, tbody and tfoot.
1228 static const nsAttrValue::EnumTable kTableCellHAlignTable[] = {
1229 {"left", StyleTextAlign::MozLeft},
1230 {"right", StyleTextAlign::MozRight},
1231 {"center", StyleTextAlign::MozCenter},
1232 {"char", StyleTextAlign::Char},
1233 {"justify", StyleTextAlign::Justify},
1234 {"middle", StyleTextAlign::MozCenter},
1235 {"absmiddle", StyleTextAlign::Center},
1236 {nullptr, 0}};
1238 bool nsGenericHTMLElement::ParseTableCellHAlignValue(const nsAString& aString,
1239 nsAttrValue& aResult) {
1240 return aResult.ParseEnumValue(aString, kTableCellHAlignTable, false);
1243 //----------------------------------------
1245 bool nsGenericHTMLElement::ParseTableVAlignValue(const nsAString& aString,
1246 nsAttrValue& aResult) {
1247 return aResult.ParseEnumValue(aString, kTableVAlignTable, false);
1250 bool nsGenericHTMLElement::ParseDivAlignValue(const nsAString& aString,
1251 nsAttrValue& aResult) {
1252 return aResult.ParseEnumValue(aString, kDivAlignTable, false);
1255 bool nsGenericHTMLElement::ParseImageAttribute(nsAtom* aAttribute,
1256 const nsAString& aString,
1257 nsAttrValue& aResult) {
1258 if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
1259 aAttribute == nsGkAtoms::hspace || aAttribute == nsGkAtoms::vspace) {
1260 return aResult.ParseHTMLDimension(aString);
1262 if (aAttribute == nsGkAtoms::border) {
1263 return aResult.ParseNonNegativeIntValue(aString);
1265 return false;
1268 bool nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString,
1269 nsAttrValue& aResult) {
1270 using mozilla::dom::ReferrerInfo;
1271 // This is a bit sketchy, we assume GetEnumString(…).get() points to a static
1272 // buffer, relying on the fact that GetEnumString(…) returns a literal string.
1273 static const nsAttrValue::EnumTable kReferrerPolicyTable[] = {
1274 {GetEnumString(ReferrerPolicy::No_referrer).get(),
1275 static_cast<int16_t>(ReferrerPolicy::No_referrer)},
1276 {GetEnumString(ReferrerPolicy::Origin).get(),
1277 static_cast<int16_t>(ReferrerPolicy::Origin)},
1278 {GetEnumString(ReferrerPolicy::Origin_when_cross_origin).get(),
1279 static_cast<int16_t>(ReferrerPolicy::Origin_when_cross_origin)},
1280 {GetEnumString(ReferrerPolicy::No_referrer_when_downgrade).get(),
1281 static_cast<int16_t>(ReferrerPolicy::No_referrer_when_downgrade)},
1282 {GetEnumString(ReferrerPolicy::Unsafe_url).get(),
1283 static_cast<int16_t>(ReferrerPolicy::Unsafe_url)},
1284 {GetEnumString(ReferrerPolicy::Strict_origin).get(),
1285 static_cast<int16_t>(ReferrerPolicy::Strict_origin)},
1286 {GetEnumString(ReferrerPolicy::Same_origin).get(),
1287 static_cast<int16_t>(ReferrerPolicy::Same_origin)},
1288 {GetEnumString(ReferrerPolicy::Strict_origin_when_cross_origin).get(),
1289 static_cast<int16_t>(ReferrerPolicy::Strict_origin_when_cross_origin)},
1290 {nullptr, ReferrerPolicy::_empty}};
1291 return aResult.ParseEnumValue(aString, kReferrerPolicyTable, false);
1294 bool nsGenericHTMLElement::ParseFrameborderValue(const nsAString& aString,
1295 nsAttrValue& aResult) {
1296 return aResult.ParseEnumValue(aString, kFrameborderTable, false);
1299 bool nsGenericHTMLElement::ParseScrollingValue(const nsAString& aString,
1300 nsAttrValue& aResult) {
1301 return aResult.ParseEnumValue(aString, kScrollingTable, false);
1304 static inline void MapLangAttributeInto(MappedDeclarationsBuilder& aBuilder) {
1305 const nsAttrValue* langValue = aBuilder.GetAttr(nsGkAtoms::lang);
1306 if (!langValue) {
1307 return;
1309 MOZ_ASSERT(langValue->Type() == nsAttrValue::eAtom);
1310 aBuilder.SetIdentAtomValueIfUnset(eCSSProperty__x_lang,
1311 langValue->GetAtomValue());
1312 if (!aBuilder.PropertyIsSet(eCSSProperty_text_emphasis_position)) {
1313 const nsAtom* lang = langValue->GetAtomValue();
1314 if (nsStyleUtil::MatchesLanguagePrefix(lang, u"zh")) {
1315 aBuilder.SetKeywordValue(eCSSProperty_text_emphasis_position,
1316 StyleTextEmphasisPosition::UNDER._0);
1317 } else if (nsStyleUtil::MatchesLanguagePrefix(lang, u"ja") ||
1318 nsStyleUtil::MatchesLanguagePrefix(lang, u"mn")) {
1319 // This branch is currently no part of the spec.
1320 // See bug 1040668 comment 69 and comment 75.
1321 aBuilder.SetKeywordValue(eCSSProperty_text_emphasis_position,
1322 StyleTextEmphasisPosition::OVER._0);
1328 * Handle attributes common to all html elements
1330 void nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(
1331 MappedDeclarationsBuilder& aBuilder) {
1332 if (!aBuilder.PropertyIsSet(eCSSProperty__moz_user_modify)) {
1333 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::contenteditable);
1334 if (value) {
1335 if (value->Equals(nsGkAtoms::_empty, eCaseMatters) ||
1336 value->Equals(nsGkAtoms::_true, eIgnoreCase)) {
1337 aBuilder.SetKeywordValue(eCSSProperty__moz_user_modify,
1338 StyleUserModify::ReadWrite);
1339 } else if (value->Equals(nsGkAtoms::_false, eIgnoreCase)) {
1340 aBuilder.SetKeywordValue(eCSSProperty__moz_user_modify,
1341 StyleUserModify::ReadOnly);
1346 MapLangAttributeInto(aBuilder);
1349 void nsGenericHTMLElement::MapCommonAttributesInto(
1350 MappedDeclarationsBuilder& aBuilder) {
1351 MapCommonAttributesIntoExceptHidden(aBuilder);
1352 if (!aBuilder.PropertyIsSet(eCSSProperty_display)) {
1353 if (aBuilder.GetAttr(nsGkAtoms::hidden)) {
1354 aBuilder.SetKeywordValue(eCSSProperty_display, StyleDisplay::None._0);
1359 /* static */
1360 const nsGenericHTMLElement::MappedAttributeEntry
1361 nsGenericHTMLElement::sCommonAttributeMap[] = {{nsGkAtoms::contenteditable},
1362 {nsGkAtoms::lang},
1363 {nsGkAtoms::hidden},
1364 {nullptr}};
1366 /* static */
1367 const Element::MappedAttributeEntry
1368 nsGenericHTMLElement::sImageMarginSizeAttributeMap[] = {{nsGkAtoms::width},
1369 {nsGkAtoms::height},
1370 {nsGkAtoms::hspace},
1371 {nsGkAtoms::vspace},
1372 {nullptr}};
1374 /* static */
1375 const Element::MappedAttributeEntry
1376 nsGenericHTMLElement::sImageAlignAttributeMap[] = {{nsGkAtoms::align},
1377 {nullptr}};
1379 /* static */
1380 const Element::MappedAttributeEntry
1381 nsGenericHTMLElement::sDivAlignAttributeMap[] = {{nsGkAtoms::align},
1382 {nullptr}};
1384 /* static */
1385 const Element::MappedAttributeEntry
1386 nsGenericHTMLElement::sImageBorderAttributeMap[] = {{nsGkAtoms::border},
1387 {nullptr}};
1389 /* static */
1390 const Element::MappedAttributeEntry
1391 nsGenericHTMLElement::sBackgroundAttributeMap[] = {
1392 {nsGkAtoms::background}, {nsGkAtoms::bgcolor}, {nullptr}};
1394 /* static */
1395 const Element::MappedAttributeEntry
1396 nsGenericHTMLElement::sBackgroundColorAttributeMap[] = {
1397 {nsGkAtoms::bgcolor}, {nullptr}};
1399 void nsGenericHTMLElement::MapImageAlignAttributeInto(
1400 MappedDeclarationsBuilder& aBuilder) {
1401 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align);
1402 if (value && value->Type() == nsAttrValue::eEnum) {
1403 int32_t align = value->GetEnumValue();
1404 if (!aBuilder.PropertyIsSet(eCSSProperty_float)) {
1405 if (align == uint8_t(StyleTextAlign::Left)) {
1406 aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Left);
1407 } else if (align == uint8_t(StyleTextAlign::Right)) {
1408 aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Right);
1411 if (!aBuilder.PropertyIsSet(eCSSProperty_vertical_align)) {
1412 switch (align) {
1413 case uint8_t(StyleTextAlign::Left):
1414 case uint8_t(StyleTextAlign::Right):
1415 break;
1416 default:
1417 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, align);
1418 break;
1424 void nsGenericHTMLElement::MapDivAlignAttributeInto(
1425 MappedDeclarationsBuilder& aBuilder) {
1426 if (!aBuilder.PropertyIsSet(eCSSProperty_text_align)) {
1427 // align: enum
1428 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align);
1429 if (value && value->Type() == nsAttrValue::eEnum)
1430 aBuilder.SetKeywordValue(eCSSProperty_text_align, value->GetEnumValue());
1434 void nsGenericHTMLElement::MapVAlignAttributeInto(
1435 MappedDeclarationsBuilder& aBuilder) {
1436 if (!aBuilder.PropertyIsSet(eCSSProperty_vertical_align)) {
1437 // align: enum
1438 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::valign);
1439 if (value && value->Type() == nsAttrValue::eEnum)
1440 aBuilder.SetKeywordValue(eCSSProperty_vertical_align,
1441 value->GetEnumValue());
1445 void nsGenericHTMLElement::MapDimensionAttributeInto(
1446 MappedDeclarationsBuilder& aBuilder, nsCSSPropertyID aProp,
1447 const nsAttrValue& aValue) {
1448 MOZ_ASSERT(!aBuilder.PropertyIsSet(aProp),
1449 "Why mapping the same property twice?");
1450 if (aValue.Type() == nsAttrValue::eInteger) {
1451 return aBuilder.SetPixelValue(aProp, aValue.GetIntegerValue());
1453 if (aValue.Type() == nsAttrValue::ePercent) {
1454 return aBuilder.SetPercentValue(aProp, aValue.GetPercentValue());
1456 if (aValue.Type() == nsAttrValue::eDoubleValue) {
1457 return aBuilder.SetPixelValue(aProp, aValue.GetDoubleValue());
1461 void nsGenericHTMLElement::MapImageMarginAttributeInto(
1462 MappedDeclarationsBuilder& aBuilder) {
1463 // hspace: value
1464 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::hspace)) {
1465 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_left, *value);
1466 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_right, *value);
1469 // vspace: value
1470 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::vspace)) {
1471 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_top, *value);
1472 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_bottom, *value);
1476 void nsGenericHTMLElement::MapWidthAttributeInto(
1477 MappedDeclarationsBuilder& aBuilder) {
1478 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::width)) {
1479 MapDimensionAttributeInto(aBuilder, eCSSProperty_width, *value);
1483 void nsGenericHTMLElement::MapHeightAttributeInto(
1484 MappedDeclarationsBuilder& aBuilder) {
1485 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::height)) {
1486 MapDimensionAttributeInto(aBuilder, eCSSProperty_height, *value);
1490 void nsGenericHTMLElement::DoMapAspectRatio(
1491 const nsAttrValue& aWidth, const nsAttrValue& aHeight,
1492 MappedDeclarationsBuilder& aBuilder) {
1493 Maybe<double> w;
1494 if (aWidth.Type() == nsAttrValue::eInteger) {
1495 w.emplace(aWidth.GetIntegerValue());
1496 } else if (aWidth.Type() == nsAttrValue::eDoubleValue) {
1497 w.emplace(aWidth.GetDoubleValue());
1500 Maybe<double> h;
1501 if (aHeight.Type() == nsAttrValue::eInteger) {
1502 h.emplace(aHeight.GetIntegerValue());
1503 } else if (aHeight.Type() == nsAttrValue::eDoubleValue) {
1504 h.emplace(aHeight.GetDoubleValue());
1507 if (w && h) {
1508 aBuilder.SetAspectRatio(*w, *h);
1512 void nsGenericHTMLElement::MapImageSizeAttributesInto(
1513 MappedDeclarationsBuilder& aBuilder, MapAspectRatio aMapAspectRatio) {
1514 auto* width = aBuilder.GetAttr(nsGkAtoms::width);
1515 auto* height = aBuilder.GetAttr(nsGkAtoms::height);
1516 if (width) {
1517 MapDimensionAttributeInto(aBuilder, eCSSProperty_width, *width);
1519 if (height) {
1520 MapDimensionAttributeInto(aBuilder, eCSSProperty_height, *height);
1522 if (aMapAspectRatio == MapAspectRatio::Yes && width && height) {
1523 DoMapAspectRatio(*width, *height, aBuilder);
1527 void nsGenericHTMLElement::MapAspectRatioInto(
1528 MappedDeclarationsBuilder& aBuilder) {
1529 auto* width = aBuilder.GetAttr(nsGkAtoms::width);
1530 auto* height = aBuilder.GetAttr(nsGkAtoms::height);
1531 if (width && height) {
1532 DoMapAspectRatio(*width, *height, aBuilder);
1536 void nsGenericHTMLElement::MapImageBorderAttributeInto(
1537 MappedDeclarationsBuilder& aBuilder) {
1538 // border: pixels
1539 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::border);
1540 if (!value) return;
1542 nscoord val = 0;
1543 if (value->Type() == nsAttrValue::eInteger) val = value->GetIntegerValue();
1545 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_top_width, (float)val);
1546 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_right_width, (float)val);
1547 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_bottom_width, (float)val);
1548 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_left_width, (float)val);
1550 aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_top_style,
1551 StyleBorderStyle::Solid);
1552 aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_right_style,
1553 StyleBorderStyle::Solid);
1554 aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_bottom_style,
1555 StyleBorderStyle::Solid);
1556 aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_left_style,
1557 StyleBorderStyle::Solid);
1559 aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_top_color);
1560 aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_right_color);
1561 aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_bottom_color);
1562 aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_left_color);
1565 void nsGenericHTMLElement::MapBackgroundInto(
1566 MappedDeclarationsBuilder& aBuilder) {
1567 if (!aBuilder.PropertyIsSet(eCSSProperty_background_image)) {
1568 // background
1569 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::background)) {
1570 aBuilder.SetBackgroundImage(*value);
1575 void nsGenericHTMLElement::MapBGColorInto(MappedDeclarationsBuilder& aBuilder) {
1576 if (aBuilder.PropertyIsSet(eCSSProperty_background_color)) {
1577 return;
1579 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::bgcolor);
1580 nscolor color;
1581 if (value && value->GetColorValue(color)) {
1582 aBuilder.SetColorValue(eCSSProperty_background_color, color);
1586 void nsGenericHTMLElement::MapBackgroundAttributesInto(
1587 MappedDeclarationsBuilder& aBuilder) {
1588 MapBackgroundInto(aBuilder);
1589 MapBGColorInto(aBuilder);
1592 //----------------------------------------------------------------------
1594 int32_t nsGenericHTMLElement::GetIntAttr(nsAtom* aAttr,
1595 int32_t aDefault) const {
1596 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
1597 if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
1598 return attrVal->GetIntegerValue();
1600 return aDefault;
1603 nsresult nsGenericHTMLElement::SetIntAttr(nsAtom* aAttr, int32_t aValue) {
1604 nsAutoString value;
1605 value.AppendInt(aValue);
1607 return SetAttr(kNameSpaceID_None, aAttr, value, true);
1610 uint32_t nsGenericHTMLElement::GetUnsignedIntAttr(nsAtom* aAttr,
1611 uint32_t aDefault) const {
1612 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
1613 if (!attrVal || attrVal->Type() != nsAttrValue::eInteger) {
1614 return aDefault;
1617 return attrVal->GetIntegerValue();
1620 uint32_t nsGenericHTMLElement::GetDimensionAttrAsUnsignedInt(
1621 nsAtom* aAttr, uint32_t aDefault) const {
1622 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
1623 if (!attrVal) {
1624 return aDefault;
1627 if (attrVal->Type() == nsAttrValue::eInteger) {
1628 return attrVal->GetIntegerValue();
1631 if (attrVal->Type() == nsAttrValue::ePercent) {
1632 // This is a nasty hack. When we parsed the value, we stored it as an
1633 // ePercent, not eInteger, because there was a '%' after it in the string.
1634 // But the spec says to basically re-parse the string as an integer.
1635 // Luckily, we can just return the value we have stored. But
1636 // GetPercentValue() divides it by 100, so we need to multiply it back.
1637 return uint32_t(attrVal->GetPercentValue() * 100.0f);
1640 if (attrVal->Type() == nsAttrValue::eDoubleValue) {
1641 return uint32_t(attrVal->GetDoubleValue());
1644 // Unfortunately, the set of values that are valid dimensions is not a
1645 // superset of values that are valid unsigned ints. In particular "+100" is
1646 // not a valid dimension, but should parse as the unsigned int "100". So if
1647 // we got here and we don't have a valid dimension value, just try re-parsing
1648 // the string we have as an integer.
1649 nsAutoString val;
1650 attrVal->ToString(val);
1651 nsContentUtils::ParseHTMLIntegerResultFlags result;
1652 int32_t parsedInt = nsContentUtils::ParseHTMLInteger(val, &result);
1653 if ((result & nsContentUtils::eParseHTMLInteger_Error) || parsedInt < 0) {
1654 return aDefault;
1657 return parsedInt;
1660 void nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
1661 nsAString& aResult) const {
1662 nsCOMPtr<nsIURI> uri;
1663 const nsAttrValue* attr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri));
1664 if (!attr) {
1665 aResult.Truncate();
1666 return;
1668 if (!uri) {
1669 // Just return the attr value
1670 attr->ToString(aResult);
1671 return;
1673 nsAutoCString spec;
1674 uri->GetSpec(spec);
1675 CopyUTF8toUTF16(spec, aResult);
1678 void nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
1679 nsACString& aResult) const {
1680 nsCOMPtr<nsIURI> uri;
1681 const nsAttrValue* attr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri));
1682 if (!attr) {
1683 aResult.Truncate();
1684 return;
1686 if (!uri) {
1687 // Just return the attr value
1688 nsAutoString value;
1689 attr->ToString(value);
1690 CopyUTF16toUTF8(value, aResult);
1691 return;
1693 uri->GetSpec(aResult);
1696 const nsAttrValue* nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr,
1697 nsAtom* aBaseAttr,
1698 nsIURI** aURI) const {
1699 *aURI = nullptr;
1701 const nsAttrValue* attr = mAttrs.GetAttr(aAttr);
1702 if (!attr) {
1703 return nullptr;
1706 nsCOMPtr<nsIURI> baseURI = GetBaseURI();
1707 if (aBaseAttr) {
1708 nsAutoString baseAttrValue;
1709 if (GetAttr(aBaseAttr, baseAttrValue)) {
1710 nsCOMPtr<nsIURI> baseAttrURI;
1711 nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
1712 getter_AddRefs(baseAttrURI), baseAttrValue, OwnerDoc(), baseURI);
1713 if (NS_FAILED(rv)) {
1714 return attr;
1716 baseURI.swap(baseAttrURI);
1720 // Don't care about return value. If it fails, we still want to
1721 // return true, and *aURI will be null.
1722 nsContentUtils::NewURIWithDocumentCharset(aURI, attr->GetStringValue(),
1723 OwnerDoc(), baseURI);
1724 return attr;
1727 bool nsGenericHTMLElement::IsLabelable() const {
1728 return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter);
1731 /* static */
1732 bool nsGenericHTMLElement::MatchLabelsElement(Element* aElement,
1733 int32_t aNamespaceID,
1734 nsAtom* aAtom, void* aData) {
1735 HTMLLabelElement* element = HTMLLabelElement::FromNode(aElement);
1736 return element && element->GetControl() == aData;
1739 already_AddRefed<nsINodeList> nsGenericHTMLElement::Labels() {
1740 MOZ_ASSERT(IsLabelable(),
1741 "Labels() only allow labelable elements to use it.");
1742 nsExtendedDOMSlots* slots = ExtendedDOMSlots();
1744 if (!slots->mLabelsList) {
1745 slots->mLabelsList =
1746 new nsLabelsNodeList(SubtreeRoot(), MatchLabelsElement, nullptr, this);
1749 RefPtr<nsLabelsNodeList> labels = slots->mLabelsList;
1750 return labels.forget();
1753 // static
1754 bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext* aCx,
1755 JSObject* aGlobal) {
1756 return TouchEvent::LegacyAPIEnabled(aCx, aGlobal);
1759 bool nsGenericHTMLElement::IsFormControlDefaultFocusable(
1760 bool aWithMouse) const {
1761 if (!aWithMouse) {
1762 return true;
1764 switch (StaticPrefs::accessibility_mouse_focuses_formcontrol()) {
1765 case 0:
1766 return false;
1767 case 1:
1768 return true;
1769 default:
1770 return !IsInChromeDocument();
1774 //----------------------------------------------------------------------
1776 nsGenericHTMLFormElement::nsGenericHTMLFormElement(
1777 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
1778 : nsGenericHTMLElement(std::move(aNodeInfo)) {
1779 // We should add the ElementState::ENABLED bit here as needed, but that
1780 // depends on our type, which is not initialized yet. So we have to do this
1781 // in subclasses. Same for a couple other bits.
1784 void nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm,
1785 bool aUnbindOrDelete) {
1786 MOZ_ASSERT(IsFormAssociatedElement());
1788 HTMLFormElement* form = GetFormInternal();
1789 NS_ASSERTION((form != nullptr) == HasFlag(ADDED_TO_FORM),
1790 "Form control should have had flag set correctly");
1792 if (!form) {
1793 return;
1796 if (aRemoveFromForm) {
1797 nsAutoString nameVal, idVal;
1798 GetAttr(nsGkAtoms::name, nameVal);
1799 GetAttr(nsGkAtoms::id, idVal);
1801 form->RemoveElement(this, true);
1803 if (!nameVal.IsEmpty()) {
1804 form->RemoveElementFromTable(this, nameVal);
1807 if (!idVal.IsEmpty()) {
1808 form->RemoveElementFromTable(this, idVal);
1812 UnsetFlags(ADDED_TO_FORM);
1813 SetFormInternal(nullptr, false);
1814 AfterClearForm(aUnbindOrDelete);
1817 nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext,
1818 nsINode& aParent) {
1819 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
1820 NS_ENSURE_SUCCESS(rv, rv);
1822 if (IsFormAssociatedElement()) {
1823 // If @form is set, the element *has* to be in a composed document,
1824 // otherwise it wouldn't be possible to find an element with the
1825 // corresponding id. If @form isn't set, the element *has* to have a parent,
1826 // otherwise it wouldn't be possible to find a form ancestor. We should not
1827 // call UpdateFormOwner if none of these conditions are fulfilled.
1828 if (HasAttr(nsGkAtoms::form) ? IsInComposedDoc() : aParent.IsContent()) {
1829 UpdateFormOwner(true, nullptr);
1833 // Set parent fieldset which should be used for the disabled state.
1834 UpdateFieldSet(false);
1835 return NS_OK;
1838 void nsGenericHTMLFormElement::UnbindFromTree(UnbindContext& aContext) {
1839 // Save state before doing anything else.
1840 SaveState();
1842 if (IsFormAssociatedElement()) {
1843 if (HTMLFormElement* form = GetFormInternal()) {
1844 // Might need to unset form
1845 if (aContext.IsUnbindRoot(this)) {
1846 // No more parent means no more form
1847 ClearForm(true, true);
1848 } else {
1849 // Recheck whether we should still have an form.
1850 if (HasAttr(nsGkAtoms::form) || !FindAncestorForm(form)) {
1851 ClearForm(true, true);
1852 } else {
1853 UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
1858 // We have to remove the form id observer if there was one.
1859 // We will re-add one later if needed (during bind to tree).
1860 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
1861 nsGkAtoms::form)) {
1862 RemoveFormIdObserver();
1866 nsGenericHTMLElement::UnbindFromTree(aContext);
1868 // The element might not have a fieldset anymore.
1869 UpdateFieldSet(false);
1872 void nsGenericHTMLFormElement::BeforeSetAttr(int32_t aNameSpaceID,
1873 nsAtom* aName,
1874 const nsAttrValue* aValue,
1875 bool aNotify) {
1876 if (aNameSpaceID == kNameSpaceID_None && IsFormAssociatedElement()) {
1877 nsAutoString tmp;
1878 HTMLFormElement* form = GetFormInternal();
1880 // remove the control from the hashtable as needed
1882 if (form && (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
1883 GetAttr(aName, tmp);
1885 if (!tmp.IsEmpty()) {
1886 form->RemoveElementFromTable(this, tmp);
1890 if (form && aName == nsGkAtoms::type) {
1891 GetAttr(nsGkAtoms::name, tmp);
1893 if (!tmp.IsEmpty()) {
1894 form->RemoveElementFromTable(this, tmp);
1897 GetAttr(nsGkAtoms::id, tmp);
1899 if (!tmp.IsEmpty()) {
1900 form->RemoveElementFromTable(this, tmp);
1903 form->RemoveElement(this, false);
1906 if (aName == nsGkAtoms::form) {
1907 // If @form isn't set or set to the empty string, there were no observer
1908 // so we don't have to remove it.
1909 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
1910 nsGkAtoms::form)) {
1911 // The current form id observer is no longer needed.
1912 // A new one may be added in AfterSetAttr.
1913 RemoveFormIdObserver();
1918 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
1919 aNotify);
1922 void nsGenericHTMLFormElement::AfterSetAttr(
1923 int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue,
1924 const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal,
1925 bool aNotify) {
1926 if (aNameSpaceID == kNameSpaceID_None && IsFormAssociatedElement()) {
1927 HTMLFormElement* form = GetFormInternal();
1929 // add the control to the hashtable as needed
1930 if (form && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
1931 aValue && !aValue->IsEmptyString()) {
1932 MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
1933 "Expected atom value for name/id");
1934 form->AddElementToTable(this,
1935 nsDependentAtomString(aValue->GetAtomValue()));
1938 if (form && aName == nsGkAtoms::type) {
1939 nsAutoString tmp;
1941 GetAttr(nsGkAtoms::name, tmp);
1943 if (!tmp.IsEmpty()) {
1944 form->AddElementToTable(this, tmp);
1947 GetAttr(nsGkAtoms::id, tmp);
1949 if (!tmp.IsEmpty()) {
1950 form->AddElementToTable(this, tmp);
1953 form->AddElement(this, false, aNotify);
1956 if (aName == nsGkAtoms::form) {
1957 // We need a new form id observer.
1958 DocumentOrShadowRoot* docOrShadow =
1959 GetUncomposedDocOrConnectedShadowRoot();
1960 if (docOrShadow) {
1961 Element* formIdElement = nullptr;
1962 if (aValue && !aValue->IsEmptyString()) {
1963 formIdElement = AddFormIdObserver();
1966 // Because we have a new @form value (or no more @form), we have to
1967 // update our form owner.
1968 UpdateFormOwner(false, formIdElement);
1973 return nsGenericHTMLElement::AfterSetAttr(
1974 aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
1977 void nsGenericHTMLFormElement::ForgetFieldSet(nsIContent* aFieldset) {
1978 MOZ_DIAGNOSTIC_ASSERT(IsFormAssociatedElement());
1979 if (GetFieldSetInternal() == aFieldset) {
1980 SetFieldSetInternal(nullptr);
1984 Element* nsGenericHTMLFormElement::AddFormIdObserver() {
1985 MOZ_ASSERT(IsFormAssociatedElement());
1987 nsAutoString formId;
1988 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
1989 GetAttr(nsGkAtoms::form, formId);
1990 NS_ASSERTION(!formId.IsEmpty(),
1991 "@form value should not be the empty string!");
1992 RefPtr<nsAtom> atom = NS_Atomize(formId);
1994 return docOrShadow->AddIDTargetObserver(atom, FormIdUpdated, this, false);
1997 void nsGenericHTMLFormElement::RemoveFormIdObserver() {
1998 MOZ_ASSERT(IsFormAssociatedElement());
2000 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
2001 if (!docOrShadow) {
2002 return;
2005 nsAutoString formId;
2006 GetAttr(nsGkAtoms::form, formId);
2007 NS_ASSERTION(!formId.IsEmpty(),
2008 "@form value should not be the empty string!");
2009 RefPtr<nsAtom> atom = NS_Atomize(formId);
2011 docOrShadow->RemoveIDTargetObserver(atom, FormIdUpdated, this, false);
2014 /* static */
2015 bool nsGenericHTMLFormElement::FormIdUpdated(Element* aOldElement,
2016 Element* aNewElement,
2017 void* aData) {
2018 nsGenericHTMLFormElement* element =
2019 static_cast<nsGenericHTMLFormElement*>(aData);
2021 NS_ASSERTION(element->IsHTMLElement(), "aData should be an HTML element");
2023 element->UpdateFormOwner(false, aNewElement);
2025 return true;
2028 bool nsGenericHTMLFormElement::IsElementDisabledForEvents(WidgetEvent* aEvent,
2029 nsIFrame* aFrame) {
2030 MOZ_ASSERT(aEvent);
2032 // Allow dispatch of CustomEvent and untrusted Events.
2033 if (!aEvent->IsTrusted()) {
2034 return false;
2037 switch (aEvent->mMessage) {
2038 case eAnimationStart:
2039 case eAnimationEnd:
2040 case eAnimationIteration:
2041 case eAnimationCancel:
2042 case eFormChange:
2043 case eMouseMove:
2044 case eMouseOver:
2045 case eMouseOut:
2046 case eMouseEnter:
2047 case eMouseLeave:
2048 case ePointerMove:
2049 case ePointerOver:
2050 case ePointerOut:
2051 case ePointerEnter:
2052 case ePointerLeave:
2053 case eTransitionCancel:
2054 case eTransitionEnd:
2055 case eTransitionRun:
2056 case eTransitionStart:
2057 case eWheel:
2058 case eLegacyMouseLineOrPageScroll:
2059 case eLegacyMousePixelScroll:
2060 return false;
2061 case eFocus:
2062 case eBlur:
2063 case eFocusIn:
2064 case eFocusOut:
2065 case eKeyPress:
2066 case eKeyUp:
2067 case eKeyDown:
2068 if (StaticPrefs::dom_forms_always_allow_key_and_focus_events_enabled()) {
2069 return false;
2071 [[fallthrough]];
2072 case ePointerDown:
2073 case ePointerUp:
2074 case ePointerCancel:
2075 case ePointerGotCapture:
2076 case ePointerLostCapture:
2077 if (StaticPrefs::dom_forms_always_allow_pointer_events_enabled()) {
2078 return false;
2080 [[fallthrough]];
2081 default:
2082 break;
2085 if (aEvent->mSpecifiedEventType == nsGkAtoms::oninput) {
2086 return false;
2089 // FIXME(emilio): This poking at the style of the frame is slightly bogus
2090 // unless we flush before every event, which we don't really want to do.
2091 if (aFrame && aFrame->StyleUI()->UserInput() == StyleUserInput::None) {
2092 return true;
2095 return IsDisabled();
2098 void nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree,
2099 Element* aFormIdElement) {
2100 MOZ_ASSERT(IsFormAssociatedElement());
2101 MOZ_ASSERT(!aBindToTree || !aFormIdElement,
2102 "aFormIdElement shouldn't be set if aBindToTree is true!");
2104 HTMLFormElement* form = GetFormInternal();
2105 if (!aBindToTree) {
2106 ClearForm(true, false);
2107 form = nullptr;
2110 HTMLFormElement* oldForm = form;
2111 if (!form) {
2112 // If @form is set, we have to use that to find the form.
2113 nsAutoString formId;
2114 if (GetAttr(nsGkAtoms::form, formId)) {
2115 if (!formId.IsEmpty()) {
2116 Element* element = nullptr;
2118 if (aBindToTree) {
2119 element = AddFormIdObserver();
2120 } else {
2121 element = aFormIdElement;
2124 NS_ASSERTION(!IsInComposedDoc() ||
2125 element == GetUncomposedDocOrConnectedShadowRoot()
2126 ->GetElementById(formId),
2127 "element should be equals to the current element "
2128 "associated with the id in @form!");
2130 if (element && element->IsHTMLElement(nsGkAtoms::form) &&
2131 nsContentUtils::IsInSameAnonymousTree(this, element)) {
2132 form = static_cast<HTMLFormElement*>(element);
2133 SetFormInternal(form, aBindToTree);
2136 } else {
2137 // We now have a parent, so we may have picked up an ancestor form. Search
2138 // for it. Note that if form is already set we don't want to do this,
2139 // because that means someone (probably the content sink) has already set
2140 // it to the right value. Also note that even if being bound here didn't
2141 // change our parent, we still need to search, since our parent chain
2142 // probably changed _somewhere_.
2143 form = FindAncestorForm();
2144 SetFormInternal(form, aBindToTree);
2148 if (form && !HasFlag(ADDED_TO_FORM)) {
2149 // Now we need to add ourselves to the form
2150 nsAutoString nameVal, idVal;
2151 GetAttr(nsGkAtoms::name, nameVal);
2152 GetAttr(nsGkAtoms::id, idVal);
2154 SetFlags(ADDED_TO_FORM);
2156 // Notify only if we just found this form.
2157 form->AddElement(this, true, oldForm == nullptr);
2159 if (!nameVal.IsEmpty()) {
2160 form->AddElementToTable(this, nameVal);
2163 if (!idVal.IsEmpty()) {
2164 form->AddElementToTable(this, idVal);
2169 void nsGenericHTMLFormElement::UpdateFieldSet(bool aNotify) {
2170 if (IsInNativeAnonymousSubtree() || !IsFormAssociatedElement()) {
2171 MOZ_ASSERT_IF(IsFormAssociatedElement(), !GetFieldSetInternal());
2172 return;
2175 nsIContent* parent = nullptr;
2176 nsIContent* prev = nullptr;
2177 HTMLFieldSetElement* fieldset = GetFieldSetInternal();
2179 for (parent = GetParent(); parent;
2180 prev = parent, parent = parent->GetParent()) {
2181 HTMLFieldSetElement* parentFieldset = HTMLFieldSetElement::FromNode(parent);
2182 if (parentFieldset && (!prev || parentFieldset->GetFirstLegend() != prev)) {
2183 if (fieldset == parentFieldset) {
2184 // We already have the right fieldset;
2185 return;
2188 if (fieldset) {
2189 fieldset->RemoveElement(this);
2191 SetFieldSetInternal(parentFieldset);
2192 parentFieldset->AddElement(this);
2194 // The disabled state may have changed
2195 FieldSetDisabledChanged(aNotify);
2196 return;
2200 // No fieldset found.
2201 if (fieldset) {
2202 fieldset->RemoveElement(this);
2203 SetFieldSetInternal(nullptr);
2204 // The disabled state may have changed
2205 FieldSetDisabledChanged(aNotify);
2209 void nsGenericHTMLFormElement::UpdateDisabledState(bool aNotify) {
2210 if (!CanBeDisabled()) {
2211 return;
2214 HTMLFieldSetElement* fieldset = GetFieldSetInternal();
2215 const bool isDisabled =
2216 HasAttr(nsGkAtoms::disabled) || (fieldset && fieldset->IsDisabled());
2218 const ElementState disabledStates =
2219 isDisabled ? ElementState::DISABLED : ElementState::ENABLED;
2221 ElementState oldDisabledStates = State() & ElementState::DISABLED_STATES;
2222 ElementState changedStates = disabledStates ^ oldDisabledStates;
2224 if (!changedStates.IsEmpty()) {
2225 ToggleStates(changedStates, aNotify);
2226 if (DoesReadOnlyApply()) {
2227 // :disabled influences :read-only / :read-write.
2228 UpdateReadOnlyState(aNotify);
2233 bool nsGenericHTMLFormElement::IsReadOnlyInternal() const {
2234 if (DoesReadOnlyApply()) {
2235 return IsDisabled() || GetBoolAttr(nsGkAtoms::readonly);
2237 return nsGenericHTMLElement::IsReadOnlyInternal();
2240 void nsGenericHTMLFormElement::FieldSetDisabledChanged(bool aNotify) {
2241 UpdateDisabledState(aNotify);
2244 void nsGenericHTMLFormElement::SaveSubtreeState() {
2245 SaveState();
2247 nsGenericHTMLElement::SaveSubtreeState();
2250 //----------------------------------------------------------------------
2252 void nsGenericHTMLElement::Click(CallerType aCallerType) {
2253 if (HandlingClick()) {
2254 return;
2257 // There are two notions of disabled.
2258 // "disabled":
2259 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled
2260 // "actually disabled":
2261 // https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled
2262 // click() reads the former but IsDisabled() is for the latter. <fieldset> is
2263 // included only in the latter, so we exclude it here.
2264 // XXX(krosylight): What about <optgroup>? And should we add a separate method
2265 // for this?
2266 if (IsDisabled() &&
2267 !(mNodeInfo->Equals(nsGkAtoms::fieldset) &&
2268 StaticPrefs::dom_forms_fieldset_disable_only_descendants_enabled())) {
2269 return;
2272 // Strong in case the event kills it
2273 nsCOMPtr<Document> doc = GetComposedDoc();
2275 RefPtr<nsPresContext> context;
2276 if (doc) {
2277 PresShell* presShell = doc->GetPresShell();
2278 if (!presShell) {
2279 // We need the nsPresContext for dispatching the click event. In some
2280 // rare cases we need to flush notifications to force creation of the
2281 // nsPresContext here (for example when a script calls button.click()
2282 // from script early during page load). We only flush the notifications
2283 // if the PresShell hasn't been created yet, to limit the performance
2284 // impact.
2285 doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
2286 presShell = doc->GetPresShell();
2288 if (presShell) {
2289 context = presShell->GetPresContext();
2293 SetHandlingClick();
2295 // Mark this event trusted if Click() is called from system code.
2296 WidgetMouseEvent event(aCallerType == CallerType::System, eMouseClick,
2297 nullptr, WidgetMouseEvent::eReal);
2298 event.mFlags.mIsPositionless = true;
2299 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
2301 EventDispatcher::Dispatch(this, context, &event);
2303 ClearHandlingClick();
2306 bool nsGenericHTMLElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
2307 int32_t* aTabIndex) {
2308 MOZ_ASSERT(aIsFocusable);
2309 MOZ_ASSERT(aTabIndex);
2310 if (ShadowRoot* root = GetShadowRoot()) {
2311 if (root->DelegatesFocus()) {
2312 *aIsFocusable = false;
2313 return true;
2317 if (!IsInComposedDoc() || IsInDesignMode()) {
2318 // In designMode documents we only allow focusing the document.
2319 *aTabIndex = -1;
2320 *aIsFocusable = false;
2321 return true;
2324 *aTabIndex = TabIndex();
2325 bool disabled = false;
2326 bool disallowOverridingFocusability = true;
2327 Maybe<int32_t> attrVal = GetTabIndexAttrValue();
2328 if (IsEditingHost()) {
2329 // Editable roots should always be focusable.
2330 disallowOverridingFocusability = true;
2332 // Ignore the disabled attribute in editable contentEditable/designMode
2333 // roots.
2334 if (attrVal.isNothing()) {
2335 // The default value for tabindex should be 0 for editable
2336 // contentEditable roots.
2337 *aTabIndex = 0;
2339 } else {
2340 disallowOverridingFocusability = false;
2342 // Just check for disabled attribute on form controls
2343 disabled = IsDisabled();
2344 if (disabled) {
2345 *aTabIndex = -1;
2349 // If a tabindex is specified at all, or the default tabindex is 0, we're
2350 // focusable.
2351 *aIsFocusable = (*aTabIndex >= 0 || (!disabled && attrVal.isSome()));
2352 return disallowOverridingFocusability;
2355 Result<bool, nsresult> nsGenericHTMLElement::PerformAccesskey(
2356 bool aKeyCausesActivation, bool aIsTrustedEvent) {
2357 RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc);
2358 if (!presContext) {
2359 return Err(NS_ERROR_UNEXPECTED);
2362 // It's hard to say what HTML4 wants us to do in all cases.
2363 bool focused = true;
2364 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
2365 fm->SetFocus(this, nsIFocusManager::FLAG_BYKEY);
2367 // Return true if the element became the current focus within its window.
2368 nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
2369 focused = window && window->GetFocusedElement() == this;
2372 if (aKeyCausesActivation) {
2373 // Click on it if the users prefs indicate to do so.
2374 AutoHandlingUserInputStatePusher userInputStatePusher(aIsTrustedEvent);
2375 AutoPopupStatePusher popupStatePusher(
2376 aIsTrustedEvent ? PopupBlocker::openAllowed : PopupBlocker::openAbused);
2377 DispatchSimulatedClick(this, aIsTrustedEvent, presContext);
2378 return focused;
2381 // If the accesskey won't cause the activation and the focus isn't changed,
2382 // either. Return error so EventStateManager would try to find next element
2383 // to handle the accesskey.
2384 return focused ? Result<bool, nsresult>{focused} : Err(NS_ERROR_ABORT);
2387 void nsGenericHTMLElement::HandleKeyboardActivation(
2388 EventChainPostVisitor& aVisitor) {
2389 MOZ_ASSERT(aVisitor.mEvent->HasKeyEventMessage());
2390 MOZ_ASSERT(aVisitor.mEvent->IsTrusted());
2392 // If focused element is different from this element, it may be editable.
2393 // In that case, associated editor for the element should handle the keyboard
2394 // instead. Therefore, if this is not the focused element, we should not
2395 // handle the event here. Note that this element may be an editing host,
2396 // i.e., focused and editable. In the case, keyboard events should be
2397 // handled by the focused element instead of associated editor because
2398 // Chrome handles the case so. For compatibility with Chrome, we follow them.
2399 if (nsFocusManager::GetFocusedElementStatic() != this) {
2400 return;
2403 const auto message = aVisitor.mEvent->mMessage;
2404 const WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
2405 if (nsEventStatus_eIgnore != aVisitor.mEventStatus) {
2406 if (message == eKeyUp && keyEvent->mKeyCode == NS_VK_SPACE) {
2407 // Unset the flag even if the event is default-prevented or something.
2408 UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
2410 return;
2413 bool shouldActivate = false;
2414 switch (message) {
2415 case eKeyDown:
2416 if (keyEvent->ShouldWorkAsSpaceKey()) {
2417 SetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
2419 return;
2420 case eKeyPress:
2421 shouldActivate = keyEvent->mKeyCode == NS_VK_RETURN;
2422 if (keyEvent->ShouldWorkAsSpaceKey()) {
2423 // Consume 'space' key to prevent scrolling the page down.
2424 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
2426 break;
2427 case eKeyUp:
2428 shouldActivate = keyEvent->ShouldWorkAsSpaceKey() &&
2429 HasFlag(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
2430 if (shouldActivate) {
2431 UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
2433 break;
2434 default:
2435 MOZ_ASSERT_UNREACHABLE("why didn't we bail out earlier?");
2436 break;
2439 if (!shouldActivate) {
2440 return;
2443 RefPtr<nsPresContext> presContext = aVisitor.mPresContext;
2444 DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(), presContext);
2445 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
2448 nsresult nsGenericHTMLElement::DispatchSimulatedClick(
2449 nsGenericHTMLElement* aElement, bool aIsTrusted,
2450 nsPresContext* aPresContext) {
2451 WidgetMouseEvent event(aIsTrusted, eMouseClick, nullptr,
2452 WidgetMouseEvent::eReal);
2453 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_KEYBOARD;
2454 event.mFlags.mIsPositionless = true;
2455 return EventDispatcher::Dispatch(aElement, aPresContext, &event);
2458 already_AddRefed<EditorBase> nsGenericHTMLElement::GetAssociatedEditor() {
2459 // If contenteditable is ever implemented, it might need to do something
2460 // different here?
2462 RefPtr<TextEditor> textEditor = GetTextEditorInternal();
2463 return textEditor.forget();
2466 // static
2467 void nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent* content) {
2468 /* Sync this node */
2469 nsGenericHTMLElement* element = FromNode(content);
2470 if (element) {
2471 if (RefPtr<EditorBase> editorBase = element->GetAssociatedEditor()) {
2472 editorBase->SyncRealTimeSpell();
2476 /* Sync all children */
2477 for (nsIContent* child = content->GetFirstChild(); child;
2478 child = child->GetNextSibling()) {
2479 SyncEditorsOnSubtree(child);
2483 static void MakeContentDescendantsEditable(nsIContent* aContent) {
2484 // If aContent is not an element, we just need to update its
2485 // internal editable state and don't need to notify anyone about
2486 // that. For elements, we need to send a ElementStateChanged
2487 // notification.
2488 if (!aContent->IsElement()) {
2489 aContent->UpdateEditableState(false);
2490 return;
2493 Element* element = aContent->AsElement();
2495 element->UpdateEditableState(true);
2497 for (nsIContent* child = aContent->GetFirstChild(); child;
2498 child = child->GetNextSibling()) {
2499 if (!child->IsElement() ||
2500 !child->AsElement()->HasAttr(nsGkAtoms::contenteditable)) {
2501 MakeContentDescendantsEditable(child);
2506 void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) {
2507 Document* document = GetComposedDoc();
2508 if (!document) {
2509 return;
2512 Document::EditingState previousEditingState = Document::EditingState::eOff;
2513 if (aChange != 0) {
2514 document->ChangeContentEditableCount(this, aChange);
2515 previousEditingState = document->GetEditingState();
2518 // MakeContentDescendantsEditable is going to call ElementStateChanged for
2519 // this element and all descendants if editable state has changed.
2520 // We might as well wrap it all in one script blocker.
2521 nsAutoScriptBlocker scriptBlocker;
2522 MakeContentDescendantsEditable(this);
2524 // If the document already had contenteditable and JS adds new
2525 // contenteditable, that might cause changing editing host to current editing
2526 // host's ancestor. In such case, HTMLEditor needs to know that
2527 // synchronously to update selection limitter.
2528 // Additionally, elements in shadow DOM is not editable in the normal cases,
2529 // but if its content has `contenteditable`, only in it can be ediable.
2530 // So we don't need to notify HTMLEditor of this change only when we're not
2531 // in shadow DOM and the composed document is in design mode.
2532 if (IsInDesignMode() && !IsInShadowTree() && aChange > 0 &&
2533 previousEditingState == Document::EditingState::eContentEditable) {
2534 if (HTMLEditor* htmlEditor =
2535 nsContentUtils::GetHTMLEditor(document->GetPresContext())) {
2536 htmlEditor->NotifyEditingHostMaybeChanged();
2541 //----------------------------------------------------------------------
2543 nsGenericHTMLFormControlElement::nsGenericHTMLFormControlElement(
2544 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, FormControlType aType)
2545 : nsGenericHTMLFormElement(std::move(aNodeInfo)),
2546 nsIFormControl(aType),
2547 mForm(nullptr),
2548 mFieldSet(nullptr) {}
2550 nsGenericHTMLFormControlElement::~nsGenericHTMLFormControlElement() {
2551 if (mFieldSet) {
2552 mFieldSet->RemoveElement(this);
2555 // Check that this element doesn't know anything about its form at this point.
2556 NS_ASSERTION(!mForm, "mForm should be null at this point!");
2559 NS_IMPL_ISUPPORTS_INHERITED(nsGenericHTMLFormControlElement,
2560 nsGenericHTMLFormElement, nsIFormControl)
2562 nsINode* nsGenericHTMLFormControlElement::GetScopeChainParent() const {
2563 return mForm ? mForm : nsGenericHTMLElement::GetScopeChainParent();
2566 nsIContent::IMEState nsGenericHTMLFormControlElement::GetDesiredIMEState() {
2567 TextEditor* textEditor = GetTextEditorInternal();
2568 if (!textEditor) {
2569 return nsGenericHTMLFormElement::GetDesiredIMEState();
2571 IMEState state;
2572 nsresult rv = textEditor->GetPreferredIMEState(&state);
2573 if (NS_FAILED(rv)) {
2574 return nsGenericHTMLFormElement::GetDesiredIMEState();
2576 return state;
2579 void nsGenericHTMLFormControlElement::GetAutocapitalize(
2580 nsAString& aValue) const {
2581 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
2582 nsGkAtoms::autocapitalize)) {
2583 nsGenericHTMLFormElement::GetAutocapitalize(aValue);
2584 return;
2587 if (mForm && IsAutocapitalizeInheriting()) {
2588 mForm->GetAutocapitalize(aValue);
2592 bool nsGenericHTMLFormControlElement::IsHTMLFocusable(bool aWithMouse,
2593 bool* aIsFocusable,
2594 int32_t* aTabIndex) {
2595 if (nsGenericHTMLFormElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
2596 aTabIndex)) {
2597 return true;
2600 *aIsFocusable = *aIsFocusable && IsFormControlDefaultFocusable(aWithMouse);
2601 return false;
2604 void nsGenericHTMLFormControlElement::GetEventTargetParent(
2605 EventChainPreVisitor& aVisitor) {
2606 if (aVisitor.mEvent->IsTrusted() && (aVisitor.mEvent->mMessage == eFocus ||
2607 aVisitor.mEvent->mMessage == eBlur)) {
2608 // We have to handle focus/blur event to change focus states in
2609 // PreHandleEvent to prevent it breaks event target chain creation.
2610 aVisitor.mWantsPreHandleEvent = true;
2612 nsGenericHTMLFormElement::GetEventTargetParent(aVisitor);
2615 nsresult nsGenericHTMLFormControlElement::PreHandleEvent(
2616 EventChainVisitor& aVisitor) {
2617 if (aVisitor.mEvent->IsTrusted()) {
2618 switch (aVisitor.mEvent->mMessage) {
2619 case eFocus: {
2620 // Check to see if focus has bubbled up from a form control's
2621 // child textfield or button. If that's the case, don't focus
2622 // this parent file control -- leave focus on the child.
2623 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
2624 if (formControlFrame &&
2625 aVisitor.mEvent->mOriginalTarget == static_cast<nsINode*>(this)) {
2626 formControlFrame->SetFocus(true, true);
2628 break;
2630 case eBlur: {
2631 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
2632 if (formControlFrame) {
2633 formControlFrame->SetFocus(false, false);
2635 break;
2637 default:
2638 break;
2641 return nsGenericHTMLFormElement::PreHandleEvent(aVisitor);
2644 HTMLFieldSetElement* nsGenericHTMLFormControlElement::GetFieldSet() {
2645 return GetFieldSetInternal();
2648 void nsGenericHTMLFormControlElement::SetForm(HTMLFormElement* aForm) {
2649 MOZ_ASSERT(aForm, "Don't pass null here");
2650 NS_ASSERTION(!mForm,
2651 "We don't support switching from one non-null form to another.");
2653 SetFormInternal(aForm, false);
2656 void nsGenericHTMLFormControlElement::ClearForm(bool aRemoveFromForm,
2657 bool aUnbindOrDelete) {
2658 nsGenericHTMLFormElement::ClearForm(aRemoveFromForm, aUnbindOrDelete);
2661 bool nsGenericHTMLFormControlElement::IsLabelable() const {
2662 auto type = ControlType();
2663 return (IsInputElement(type) && type != FormControlType::InputHidden) ||
2664 IsButtonElement(type) || type == FormControlType::Output ||
2665 type == FormControlType::Select || type == FormControlType::Textarea;
2668 bool nsGenericHTMLFormControlElement::CanBeDisabled() const {
2669 auto type = ControlType();
2670 // It's easier to test the types that _cannot_ be disabled
2671 return type != FormControlType::Object && type != FormControlType::Output;
2674 bool nsGenericHTMLFormControlElement::DoesReadOnlyApply() const {
2675 auto type = ControlType();
2676 if (!IsInputElement(type) && type != FormControlType::Textarea) {
2677 return false;
2680 switch (type) {
2681 case FormControlType::InputHidden:
2682 case FormControlType::InputButton:
2683 case FormControlType::InputImage:
2684 case FormControlType::InputReset:
2685 case FormControlType::InputSubmit:
2686 case FormControlType::InputRadio:
2687 case FormControlType::InputFile:
2688 case FormControlType::InputCheckbox:
2689 case FormControlType::InputRange:
2690 case FormControlType::InputColor:
2691 return false;
2692 #ifdef DEBUG
2693 case FormControlType::Textarea:
2694 case FormControlType::InputText:
2695 case FormControlType::InputPassword:
2696 case FormControlType::InputSearch:
2697 case FormControlType::InputTel:
2698 case FormControlType::InputEmail:
2699 case FormControlType::InputUrl:
2700 case FormControlType::InputNumber:
2701 case FormControlType::InputDate:
2702 case FormControlType::InputTime:
2703 case FormControlType::InputMonth:
2704 case FormControlType::InputWeek:
2705 case FormControlType::InputDatetimeLocal:
2706 return true;
2707 default:
2708 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesReadOnlyApply()");
2709 return true;
2710 #else // DEBUG
2711 default:
2712 return true;
2713 #endif // DEBUG
2717 void nsGenericHTMLFormControlElement::SetFormInternal(HTMLFormElement* aForm,
2718 bool aBindToTree) {
2719 if (aForm) {
2720 BeforeSetForm(aForm, aBindToTree);
2723 // keep a *weak* ref to the form here
2724 mForm = aForm;
2727 HTMLFormElement* nsGenericHTMLFormControlElement::GetFormInternal() const {
2728 return mForm;
2731 HTMLFieldSetElement* nsGenericHTMLFormControlElement::GetFieldSetInternal()
2732 const {
2733 return mFieldSet;
2736 void nsGenericHTMLFormControlElement::SetFieldSetInternal(
2737 HTMLFieldSetElement* aFieldset) {
2738 mFieldSet = aFieldset;
2741 void nsGenericHTMLFormControlElement::UpdateRequiredState(bool aIsRequired,
2742 bool aNotify) {
2743 #ifdef DEBUG
2744 auto type = ControlType();
2745 #endif
2746 MOZ_ASSERT(IsInputElement(type) || type == FormControlType::Select ||
2747 type == FormControlType::Textarea,
2748 "This should be called only on types that @required applies");
2750 #ifdef DEBUG
2751 if (HTMLInputElement* input = HTMLInputElement::FromNode(this)) {
2752 MOZ_ASSERT(
2753 input->DoesRequiredApply(),
2754 "This should be called only on input types that @required applies");
2756 #endif
2758 ElementState requiredStates;
2759 if (aIsRequired) {
2760 requiredStates |= ElementState::REQUIRED;
2761 } else {
2762 requiredStates |= ElementState::OPTIONAL_;
2765 ElementState oldRequiredStates = State() & ElementState::REQUIRED_STATES;
2766 ElementState changedStates = requiredStates ^ oldRequiredStates;
2768 if (!changedStates.IsEmpty()) {
2769 ToggleStates(changedStates, aNotify);
2773 bool nsGenericHTMLFormControlElement::IsAutocapitalizeInheriting() const {
2774 auto type = ControlType();
2775 return IsInputElement(type) || IsButtonElement(type) ||
2776 type == FormControlType::Fieldset || type == FormControlType::Output ||
2777 type == FormControlType::Select || type == FormControlType::Textarea;
2780 nsresult nsGenericHTMLFormControlElement::SubmitDirnameDir(
2781 FormData* aFormData) {
2782 // Submit dirname=dir if element has non-empty dirname attribute
2783 if (HasAttr(nsGkAtoms::dirname)) {
2784 nsAutoString dirname;
2785 GetAttr(nsGkAtoms::dirname, dirname);
2786 if (!dirname.IsEmpty()) {
2787 const Directionality dir = GetDirectionality();
2788 MOZ_ASSERT(dir == Directionality::Ltr || dir == Directionality::Rtl,
2789 "The directionality of an element is either ltr or rtl");
2790 return aFormData->AddNameValuePair(
2791 dirname, dir == Directionality::Ltr ? u"ltr"_ns : u"rtl"_ns);
2794 return NS_OK;
2797 //----------------------------------------------------------------------
2799 static const nsAttrValue::EnumTable kPopoverTargetActionTable[] = {
2800 {"toggle", PopoverTargetAction::Toggle},
2801 {"show", PopoverTargetAction::Show},
2802 {"hide", PopoverTargetAction::Hide},
2803 {nullptr, 0}};
2805 static const nsAttrValue::EnumTable* kPopoverTargetActionDefault =
2806 &kPopoverTargetActionTable[0];
2808 nsGenericHTMLFormControlElementWithState::
2809 nsGenericHTMLFormControlElementWithState(
2810 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
2811 FromParser aFromParser, FormControlType aType)
2812 : nsGenericHTMLFormControlElement(std::move(aNodeInfo), aType),
2813 mControlNumber(!!(aFromParser & FROM_PARSER_NETWORK)
2814 ? OwnerDoc()->GetNextControlNumber()
2815 : -1) {
2816 mStateKey.SetIsVoid(true);
2819 bool nsGenericHTMLFormControlElementWithState::ParseAttribute(
2820 int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue,
2821 nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) {
2822 if (aNamespaceID == kNameSpaceID_None) {
2823 if (StaticPrefs::dom_element_popover_enabled()) {
2824 if (aAttribute == nsGkAtoms::popovertargetaction) {
2825 return aResult.ParseEnumValue(aValue, kPopoverTargetActionTable, false,
2826 kPopoverTargetActionDefault);
2828 if (aAttribute == nsGkAtoms::popovertarget) {
2829 aResult.ParseAtom(aValue);
2830 return true;
2834 if (StaticPrefs::dom_element_invokers_enabled()) {
2835 if (aAttribute == nsGkAtoms::invokeaction) {
2836 aResult.ParseAtom(aValue);
2837 return true;
2839 if (aAttribute == nsGkAtoms::invoketarget) {
2840 aResult.ParseAtom(aValue);
2841 return true;
2846 return nsGenericHTMLFormControlElement::ParseAttribute(
2847 aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
2850 mozilla::dom::Element*
2851 nsGenericHTMLFormControlElementWithState::GetPopoverTargetElement() const {
2852 return GetAttrAssociatedElement(nsGkAtoms::popovertarget);
2855 void nsGenericHTMLFormControlElementWithState::SetPopoverTargetElement(
2856 mozilla::dom::Element* aElement) {
2857 ExplicitlySetAttrElement(nsGkAtoms::popovertarget, aElement);
2860 void nsGenericHTMLFormControlElementWithState::HandlePopoverTargetAction() {
2861 RefPtr<nsGenericHTMLElement> target = GetEffectivePopoverTargetElement();
2862 if (!target) {
2863 return;
2866 auto action = PopoverTargetAction::Toggle;
2867 if (const nsAttrValue* value =
2868 GetParsedAttr(nsGkAtoms::popovertargetaction)) {
2869 MOZ_ASSERT(value->Type() == nsAttrValue::eEnum);
2870 action = static_cast<PopoverTargetAction>(value->GetEnumValue());
2873 bool canHide = action == PopoverTargetAction::Hide ||
2874 action == PopoverTargetAction::Toggle;
2875 bool shouldHide = canHide && target->IsPopoverOpen();
2876 bool canShow = action == PopoverTargetAction::Show ||
2877 action == PopoverTargetAction::Toggle;
2878 bool shouldShow = canShow && !target->IsPopoverOpen();
2880 if (shouldHide) {
2881 target->HidePopover(IgnoreErrors());
2882 } else if (shouldShow) {
2883 target->ShowPopoverInternal(this, IgnoreErrors());
2887 void nsGenericHTMLFormControlElementWithState::GetInvokeAction(
2888 nsAString& aValue) const {
2889 GetInvokeAction()->ToString(aValue);
2892 nsAtom* nsGenericHTMLFormControlElementWithState::GetInvokeAction() const {
2893 const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::invokeaction);
2894 if (attr && attr->GetAtomValue() != nsGkAtoms::_empty) {
2895 return attr->GetAtomValue();
2897 return nsGkAtoms::_auto;
2900 mozilla::dom::Element*
2901 nsGenericHTMLFormControlElementWithState::GetInvokeTargetElement() const {
2902 if (StaticPrefs::dom_element_invokers_enabled()) {
2903 return GetAttrAssociatedElement(nsGkAtoms::invoketarget);
2905 return nullptr;
2908 void nsGenericHTMLFormControlElementWithState::SetInvokeTargetElement(
2909 mozilla::dom::Element* aElement) {
2910 ExplicitlySetAttrElement(nsGkAtoms::invoketarget, aElement);
2913 void nsGenericHTMLFormControlElementWithState::HandleInvokeTargetAction() {
2914 // 1. Let invokee be node's invoke target element.
2915 RefPtr<Element> invokee = GetInvokeTargetElement();
2917 // 2. If invokee is null, then return.
2918 if (!invokee) {
2919 return;
2922 // 3. Let action be node's invokeaction attribute
2923 // 4. If action is null or empty, then let action be the string "auto".
2924 RefPtr<nsAtom> aAction = GetInvokeAction();
2925 MOZ_ASSERT(!aAction->IsEmpty(), "Action should not be empty");
2927 // 5. Let notCancelled be the result of firing an event named invoke at
2928 // invokee with its action set to action, its invoker set to node,
2929 // and its cancelable attribute initialized to true.
2930 InvokeEventInit init;
2931 aAction->ToString(init.mAction);
2932 init.mInvoker = this;
2933 init.mCancelable = true;
2934 init.mComposed = true;
2935 RefPtr<Event> event = InvokeEvent::Constructor(this, u"invoke"_ns, init);
2936 event->SetTrusted(true);
2937 event->SetTarget(invokee);
2939 EventDispatcher::DispatchDOMEvent(invokee, nullptr, event, nullptr, nullptr);
2941 // 6. If notCancelled is true and invokee has an associated invocation action
2942 // algorithm then run the invokee's invocation action algorithm given action.
2943 if (event->DefaultPrevented()) {
2944 return;
2947 invokee->HandleInvokeInternal(aAction, IgnoreErrors());
2950 void nsGenericHTMLFormControlElementWithState::GenerateStateKey() {
2951 // Keep the key if already computed
2952 if (!mStateKey.IsVoid()) {
2953 return;
2956 Document* doc = GetUncomposedDoc();
2957 if (!doc) {
2958 mStateKey.Truncate();
2959 return;
2962 // Generate the state key
2963 nsContentUtils::GenerateStateKey(this, doc, mStateKey);
2965 // If the state key is blank, this is anonymous content or for whatever
2966 // reason we are not supposed to save/restore state: keep it as such.
2967 if (!mStateKey.IsEmpty()) {
2968 // Add something unique to content so layout doesn't muck us up.
2969 mStateKey += "-C";
2973 PresState* nsGenericHTMLFormControlElementWithState::GetPrimaryPresState() {
2974 if (mStateKey.IsEmpty()) {
2975 return nullptr;
2978 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false);
2980 if (!history) {
2981 return nullptr;
2984 // Get the pres state for this key, if it doesn't exist, create one.
2985 PresState* result = history->GetState(mStateKey);
2986 if (!result) {
2987 UniquePtr<PresState> newState = NewPresState();
2988 result = newState.get();
2989 history->AddState(mStateKey, std::move(newState));
2992 return result;
2995 already_AddRefed<nsILayoutHistoryState>
2996 nsGenericHTMLFormElement::GetLayoutHistory(bool aRead) {
2997 nsCOMPtr<Document> doc = GetUncomposedDoc();
2998 if (!doc) {
2999 return nullptr;
3003 // Get the history
3005 nsCOMPtr<nsILayoutHistoryState> history = doc->GetLayoutHistoryState();
3006 if (!history) {
3007 return nullptr;
3010 if (aRead && !history->HasStates()) {
3011 return nullptr;
3014 return history.forget();
3017 bool nsGenericHTMLFormControlElementWithState::RestoreFormControlState() {
3018 MOZ_ASSERT(!mStateKey.IsVoid(),
3019 "GenerateStateKey must already have been called");
3021 if (mStateKey.IsEmpty()) {
3022 return false;
3025 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true);
3026 if (!history) {
3027 return false;
3030 // Get the pres state for this key
3031 PresState* state = history->GetState(mStateKey);
3032 if (state) {
3033 bool result = RestoreState(state);
3034 history->RemoveState(mStateKey);
3035 return result;
3038 return false;
3041 void nsGenericHTMLFormControlElementWithState::NodeInfoChanged(
3042 Document* aOldDoc) {
3043 nsGenericHTMLFormControlElement::NodeInfoChanged(aOldDoc);
3045 // We need to regenerate the state key now we're in a new document. Clearing
3046 // mControlNumber means we stop considering this control to be parser
3047 // inserted, and we'll generate a state key based on its position in the
3048 // document rather than the order it was inserted into the document.
3049 mControlNumber = -1;
3050 mStateKey.SetIsVoid(true);
3053 void nsGenericHTMLFormControlElementWithState::GetFormAction(nsString& aValue) {
3054 auto type = ControlType();
3055 if (!IsInputElement(type) && !IsButtonElement(type)) {
3056 return;
3059 if (!GetAttr(nsGkAtoms::formaction, aValue) || aValue.IsEmpty()) {
3060 Document* document = OwnerDoc();
3061 nsIURI* docURI = document->GetDocumentURI();
3062 if (docURI) {
3063 nsAutoCString spec;
3064 nsresult rv = docURI->GetSpec(spec);
3065 if (NS_FAILED(rv)) {
3066 return;
3069 CopyUTF8toUTF16(spec, aValue);
3071 } else {
3072 GetURIAttr(nsGkAtoms::formaction, nullptr, aValue);
3076 bool nsGenericHTMLElement::IsEventAttributeNameInternal(nsAtom* aName) {
3077 return nsContentUtils::IsEventAttributeName(aName, EventNameType_HTML);
3081 * Construct a URI from a string, as an element.src attribute
3082 * would be set to. Helper for the media elements.
3084 nsresult nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec,
3085 nsIURI** aURI) {
3086 NS_ENSURE_ARG_POINTER(aURI);
3088 *aURI = nullptr;
3090 nsCOMPtr<Document> doc = OwnerDoc();
3092 nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI, aURISpec, doc,
3093 GetBaseURI());
3094 NS_ENSURE_SUCCESS(rv, rv);
3096 bool equal;
3097 if (aURISpec.IsEmpty() && doc->GetDocumentURI() &&
3098 NS_SUCCEEDED(doc->GetDocumentURI()->Equals(*aURI, &equal)) && equal) {
3099 // Assume an element can't point to a fragment of its embedding
3100 // document. Fail here instead of returning the recursive URI
3101 // and waiting for the subsequent load to fail.
3102 NS_RELEASE(*aURI);
3103 return NS_ERROR_DOM_INVALID_STATE_ERR;
3106 return NS_OK;
3109 void nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue,
3110 mozilla::ErrorResult& aError) {
3111 // innerText depends on layout. For example, white space processing is
3112 // something that happens during reflow and which must be reflected by
3113 // innerText. So for:
3115 // <div style="white-space:normal"> A B C </div>
3117 // innerText should give "A B C".
3119 // The approach taken here to avoid the expense of reflow is to flush style
3120 // and then see whether it's necessary to flush layout afterwards. Flushing
3121 // layout can be skipped if we can detect that the element or its descendants
3122 // are not dirty.
3124 // Obtain the composed doc to handle elements in Shadow DOM.
3125 Document* doc = GetComposedDoc();
3126 if (doc) {
3127 doc->FlushPendingNotifications(FlushType::Style);
3130 // Elements with `display: content` will not have a frame. To handle Shadow
3131 // DOM, walk the flattened tree looking for parent frame.
3132 nsIFrame* frame = GetPrimaryFrame();
3133 if (IsDisplayContents()) {
3134 for (Element* parent = GetFlattenedTreeParentElement(); parent;
3135 parent = parent->GetFlattenedTreeParentElement()) {
3136 frame = parent->GetPrimaryFrame();
3137 if (frame) {
3138 break;
3143 // Check for dirty reflow roots in the subtree from targetFrame; this requires
3144 // a reflow flush.
3145 bool dirty = frame && frame->PresShell()->FrameIsAncestorOfDirtyRoot(frame);
3147 // The way we do that is by checking whether the element has either of the two
3148 // dirty bits (NS_FRAME_IS_DIRTY or NS_FRAME_HAS_DIRTY_DESCENDANTS) or if any
3149 // ancestor has NS_FRAME_IS_DIRTY. We need to check for NS_FRAME_IS_DIRTY on
3150 // ancestors since that is something that implies NS_FRAME_IS_DIRTY on all
3151 // descendants.
3152 dirty |= frame && frame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
3153 while (!dirty && frame) {
3154 dirty |= frame->HasAnyStateBits(NS_FRAME_IS_DIRTY);
3155 frame = frame->GetInFlowParent();
3158 // Flush layout if we determined a reflow is required.
3159 if (dirty && doc) {
3160 doc->FlushPendingNotifications(FlushType::Layout);
3163 if (!IsRendered()) {
3164 GetTextContentInternal(aValue, aError);
3165 } else {
3166 nsRange::GetInnerTextNoFlush(aValue, aError, this);
3170 static already_AddRefed<nsINode> TextToNode(const nsAString& aString,
3171 nsNodeInfoManager* aNim) {
3172 nsString str;
3173 const char16_t* s = aString.BeginReading();
3174 const char16_t* end = aString.EndReading();
3175 RefPtr<DocumentFragment> fragment;
3176 while (true) {
3177 if (s != end && *s == '\r' && s + 1 != end && s[1] == '\n') {
3178 // a \r\n pair should only generate one <br>, so just skip the \r
3179 ++s;
3181 if (s == end || *s == '\r' || *s == '\n') {
3182 if (!str.IsEmpty()) {
3183 RefPtr<nsTextNode> textContent = new (aNim) nsTextNode(aNim);
3184 textContent->SetText(str, true);
3185 if (!fragment) {
3186 if (s == end) {
3187 return textContent.forget();
3189 fragment = new (aNim) DocumentFragment(aNim);
3191 fragment->AppendChildTo(textContent, true, IgnoreErrors());
3193 if (s == end) {
3194 break;
3196 str.Truncate();
3197 RefPtr<NodeInfo> ni = aNim->GetNodeInfo(
3198 nsGkAtoms::br, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
3199 auto* nim = ni->NodeInfoManager();
3200 RefPtr<HTMLBRElement> br = new (nim) HTMLBRElement(ni.forget());
3201 if (!fragment) {
3202 if (s + 1 == end) {
3203 return br.forget();
3205 fragment = new (aNim) DocumentFragment(aNim);
3207 fragment->AppendChildTo(br, true, IgnoreErrors());
3208 } else {
3209 str.Append(*s);
3211 ++s;
3213 return fragment.forget();
3216 void nsGenericHTMLElement::SetInnerText(const nsAString& aValue) {
3217 RefPtr<nsINode> node = TextToNode(aValue, NodeInfo()->NodeInfoManager());
3218 ReplaceChildren(node, IgnoreErrors());
3221 // https://html.spec.whatwg.org/#merge-with-the-next-text-node
3222 static void MergeWithNextTextNode(Text& aText, ErrorResult& aRv) {
3223 RefPtr<Text> nextSibling = Text::FromNodeOrNull(aText.GetNextSibling());
3224 if (!nextSibling) {
3225 return;
3227 nsAutoString data;
3228 nextSibling->GetData(data);
3229 aText.AppendData(data, aRv);
3230 nextSibling->Remove();
3233 // https://html.spec.whatwg.org/#dom-outertext
3234 void nsGenericHTMLElement::SetOuterText(const nsAString& aValue,
3235 ErrorResult& aRv) {
3236 nsCOMPtr<nsINode> parent = GetParentNode();
3237 if (!parent) {
3238 return aRv.ThrowNoModificationAllowedError("Element has no parent");
3241 RefPtr<nsINode> next = GetNextSibling();
3242 RefPtr<nsINode> previous = GetPreviousSibling();
3244 // Batch possible DOMSubtreeModified events.
3245 mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
3247 nsNodeInfoManager* nim = NodeInfo()->NodeInfoManager();
3248 RefPtr<nsINode> node = TextToNode(aValue, nim);
3249 if (!node) {
3250 // This doesn't match the spec, see
3251 // https://github.com/whatwg/html/issues/7508
3252 node = new (nim) nsTextNode(nim);
3254 parent->ReplaceChild(*node, *this, aRv);
3255 if (aRv.Failed()) {
3256 return;
3259 if (next) {
3260 if (RefPtr<Text> text = Text::FromNodeOrNull(next->GetPreviousSibling())) {
3261 MergeWithNextTextNode(*text, aRv);
3262 if (aRv.Failed()) {
3263 return;
3267 if (auto* text = Text::FromNodeOrNull(previous)) {
3268 MergeWithNextTextNode(*text, aRv);
3272 // This should be true when `:open` should match.
3273 bool nsGenericHTMLElement::PopoverOpen() const {
3274 if (PopoverData* popoverData = GetPopoverData()) {
3275 return popoverData->GetPopoverVisibilityState() ==
3276 PopoverVisibilityState::Showing;
3278 return false;
3281 // https://html.spec.whatwg.org/#check-popover-validity
3282 bool nsGenericHTMLElement::CheckPopoverValidity(
3283 PopoverVisibilityState aExpectedState, Document* aExpectedDocument,
3284 ErrorResult& aRv) {
3285 if (GetPopoverAttributeState() == PopoverAttributeState::None) {
3286 aRv.ThrowNotSupportedError("Element is in the no popover state");
3287 return false;
3290 if (GetPopoverData()->GetPopoverVisibilityState() != aExpectedState) {
3291 return false;
3294 if (!IsInComposedDoc()) {
3295 aRv.ThrowInvalidStateError("Element is not connected");
3296 return false;
3299 if (aExpectedDocument && aExpectedDocument != OwnerDoc()) {
3300 aRv.ThrowInvalidStateError("Element is moved to other document");
3301 return false;
3304 if (auto* dialog = HTMLDialogElement::FromNode(this)) {
3305 if (dialog->IsInTopLayer()) {
3306 aRv.ThrowInvalidStateError("Element is a modal <dialog> element");
3307 return false;
3311 if (State().HasState(ElementState::FULLSCREEN)) {
3312 aRv.ThrowInvalidStateError("Element is fullscreen");
3313 return false;
3316 return true;
3319 PopoverAttributeState nsGenericHTMLElement::GetPopoverAttributeState() const {
3320 return GetPopoverData() ? GetPopoverData()->GetPopoverAttributeState()
3321 : PopoverAttributeState::None;
3324 void nsGenericHTMLElement::PopoverPseudoStateUpdate(bool aOpen, bool aNotify) {
3325 SetStates(ElementState::POPOVER_OPEN, aOpen, aNotify);
3328 already_AddRefed<ToggleEvent> nsGenericHTMLElement::CreateToggleEvent(
3329 const nsAString& aEventType, const nsAString& aOldState,
3330 const nsAString& aNewState, Cancelable aCancelable) {
3331 ToggleEventInit init;
3332 init.mBubbles = false;
3333 init.mOldState = aOldState;
3334 init.mNewState = aNewState;
3335 init.mCancelable = aCancelable == Cancelable::eYes;
3336 RefPtr<ToggleEvent> event = ToggleEvent::Constructor(this, aEventType, init);
3337 event->SetTrusted(true);
3338 event->SetTarget(this);
3339 return event.forget();
3342 bool nsGenericHTMLElement::FireToggleEvent(PopoverVisibilityState aOldState,
3343 PopoverVisibilityState aNewState,
3344 const nsAString& aType) {
3345 auto stringForState = [](PopoverVisibilityState state) {
3346 return state == PopoverVisibilityState::Hidden ? u"closed"_ns : u"open"_ns;
3348 const auto cancelable = aType == u"beforetoggle"_ns &&
3349 aNewState == PopoverVisibilityState::Showing
3350 ? Cancelable::eYes
3351 : Cancelable::eNo;
3352 RefPtr event = CreateToggleEvent(aType, stringForState(aOldState),
3353 stringForState(aNewState), cancelable);
3354 EventDispatcher::DispatchDOMEvent(this, nullptr, event, nullptr, nullptr);
3355 return event->DefaultPrevented();
3358 // https://html.spec.whatwg.org/#queue-a-popover-toggle-event-task
3359 void nsGenericHTMLElement::QueuePopoverEventTask(
3360 PopoverVisibilityState aOldState) {
3361 auto* data = GetPopoverData();
3362 MOZ_ASSERT(data, "Should have popover data");
3364 if (auto* queuedToggleEventTask = data->GetToggleEventTask()) {
3365 aOldState = queuedToggleEventTask->GetOldState();
3368 auto task =
3369 MakeRefPtr<PopoverToggleEventTask>(do_GetWeakReference(this), aOldState);
3370 data->SetToggleEventTask(task);
3371 OwnerDoc()->Dispatch(task.forget());
3374 void nsGenericHTMLElement::RunPopoverToggleEventTask(
3375 PopoverToggleEventTask* aTask, PopoverVisibilityState aOldState) {
3376 auto* data = GetPopoverData();
3377 if (!data) {
3378 return;
3381 auto* popoverToggleEventTask = data->GetToggleEventTask();
3382 if (!popoverToggleEventTask || aTask != popoverToggleEventTask) {
3383 return;
3385 data->ClearToggleEventTask();
3386 // Intentionally ignore the return value here as only on open event the
3387 // cancelable attribute is initialized to true for beforetoggle event.
3388 FireToggleEvent(aOldState, data->GetPopoverVisibilityState(), u"toggle"_ns);
3391 // https://html.spec.whatwg.org/#dom-showpopover
3392 void nsGenericHTMLElement::ShowPopover(ErrorResult& aRv) {
3393 return ShowPopoverInternal(nullptr, aRv);
3395 void nsGenericHTMLElement::ShowPopoverInternal(Element* aInvoker,
3396 ErrorResult& aRv) {
3397 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, nullptr, aRv)) {
3398 return;
3400 RefPtr<Document> document = OwnerDoc();
3402 MOZ_ASSERT(!GetPopoverData() || !GetPopoverData()->GetInvoker());
3403 MOZ_ASSERT(!OwnerDoc()->TopLayerContains(*this));
3405 bool wasShowingOrHiding = GetPopoverData()->IsShowingOrHiding();
3406 GetPopoverData()->SetIsShowingOrHiding(true);
3407 auto cleanupShowingFlag = MakeScopeExit([&]() {
3408 if (auto* popoverData = GetPopoverData()) {
3409 popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
3413 // Fire beforetoggle event and re-check popover validity.
3414 if (FireToggleEvent(PopoverVisibilityState::Hidden,
3415 PopoverVisibilityState::Showing, u"beforetoggle"_ns)) {
3416 return;
3418 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) {
3419 return;
3422 bool shouldRestoreFocus = false;
3423 nsWeakPtr originallyFocusedElement;
3424 if (IsAutoPopover()) {
3425 auto originalState = GetPopoverAttributeState();
3426 RefPtr<nsINode> ancestor = GetTopmostPopoverAncestor(aInvoker, true);
3427 if (!ancestor) {
3428 ancestor = document;
3430 document->HideAllPopoversUntil(*ancestor, false,
3431 /* aFireEvents = */ !wasShowingOrHiding);
3432 if (GetPopoverAttributeState() != originalState) {
3433 aRv.ThrowInvalidStateError(
3434 "The value of the popover attribute was changed while hiding the "
3435 "popover.");
3436 return;
3439 // TODO: Handle if document changes, see
3440 // https://github.com/whatwg/html/issues/9177
3441 if (!IsAutoPopover() ||
3442 !CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) {
3443 return;
3446 shouldRestoreFocus = !document->GetTopmostAutoPopover();
3447 // Let originallyFocusedElement be document's focused area of the document's
3448 // DOM anchor.
3449 if (nsIContent* unretargetedFocus =
3450 document->GetUnretargetedFocusedContent()) {
3451 originallyFocusedElement =
3452 do_GetWeakReference(unretargetedFocus->AsElement());
3456 document->AddPopoverToTopLayer(*this);
3458 PopoverPseudoStateUpdate(true, true);
3461 auto* popoverData = GetPopoverData();
3462 popoverData->SetPopoverVisibilityState(PopoverVisibilityState::Showing);
3463 popoverData->SetInvoker(aInvoker);
3466 // Run the popover focusing steps given element.
3467 FocusPopover();
3468 if (shouldRestoreFocus &&
3469 GetPopoverAttributeState() != PopoverAttributeState::None) {
3470 GetPopoverData()->SetPreviouslyFocusedElement(originallyFocusedElement);
3473 // Queue popover toggle event task.
3474 QueuePopoverEventTask(PopoverVisibilityState::Hidden);
3477 void nsGenericHTMLElement::HidePopoverWithoutRunningScript() {
3478 HidePopoverInternal(/* aFocusPreviousElement = */ false,
3479 /* aFireEvents = */ false, IgnoreErrors());
3482 // https://html.spec.whatwg.org/#dom-hidepopover
3483 void nsGenericHTMLElement::HidePopover(ErrorResult& aRv) {
3484 HidePopoverInternal(/* aFocusPreviousElement = */ true,
3485 /* aFireEvents = */ true, aRv);
3488 void nsGenericHTMLElement::HidePopoverInternal(bool aFocusPreviousElement,
3489 bool aFireEvents,
3490 ErrorResult& aRv) {
3491 OwnerDoc()->HidePopover(*this, aFocusPreviousElement, aFireEvents, aRv);
3494 void nsGenericHTMLElement::ForgetPreviouslyFocusedElementAfterHidingPopover() {
3495 auto* data = GetPopoverData();
3496 MOZ_ASSERT(data, "Should have popover data");
3497 data->SetPreviouslyFocusedElement(nullptr);
3500 void nsGenericHTMLElement::FocusPreviousElementAfterHidingPopover() {
3501 auto* data = GetPopoverData();
3502 MOZ_ASSERT(data, "Should have popover data");
3504 RefPtr<Element> control =
3505 do_QueryReferent(data->GetPreviouslyFocusedElement().get());
3506 data->SetPreviouslyFocusedElement(nullptr);
3508 if (!control) {
3509 return;
3512 // Step 14.2 at
3513 // https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
3514 // If focusPreviousElement is true and document's focused area of the
3515 // document's DOM anchor is a shadow-including inclusive descendant of
3516 // element, then run the focusing steps for previouslyFocusedElement;
3517 nsIContent* currentFocus = OwnerDoc()->GetUnretargetedFocusedContent();
3518 if (currentFocus &&
3519 currentFocus->IsShadowIncludingInclusiveDescendantOf(this)) {
3520 FocusOptions options;
3521 options.mPreventScroll = true;
3522 control->Focus(options, CallerType::NonSystem, IgnoreErrors());
3526 // https://html.spec.whatwg.org/multipage/popover.html#dom-togglepopover
3527 bool nsGenericHTMLElement::TogglePopover(const Optional<bool>& aForce,
3528 ErrorResult& aRv) {
3529 if (PopoverOpen() && (!aForce.WasPassed() || !aForce.Value())) {
3530 HidePopover(aRv);
3531 } else if (!aForce.WasPassed() || aForce.Value()) {
3532 ShowPopover(aRv);
3533 } else {
3534 CheckPopoverValidity(GetPopoverData()
3535 ? GetPopoverData()->GetPopoverVisibilityState()
3536 : PopoverVisibilityState::Showing,
3537 nullptr, aRv);
3540 return PopoverOpen();
3543 // https://html.spec.whatwg.org/multipage/popover.html#popover-focusing-steps
3544 void nsGenericHTMLElement::FocusPopover() {
3545 if (auto* dialog = HTMLDialogElement::FromNode(this)) {
3546 return MOZ_KnownLive(dialog)->FocusDialog();
3549 if (RefPtr<Document> doc = GetComposedDoc()) {
3550 doc->FlushPendingNotifications(FlushType::Frames);
3553 RefPtr<Element> control = GetBoolAttr(nsGkAtoms::autofocus)
3554 ? this
3555 : GetAutofocusDelegate(false /* aWithMouse */);
3557 if (!control) {
3558 return;
3560 FocusCandidate(control, false /* aClearUpFocus */);
3563 void nsGenericHTMLElement::FocusCandidate(Element* aControl,
3564 bool aClearUpFocus) {
3565 // 1) Run the focusing steps given control.
3566 IgnoredErrorResult rv;
3567 if (RefPtr<Element> elementToFocus = nsFocusManager::GetTheFocusableArea(
3568 aControl, nsFocusManager::ProgrammaticFocusFlags(FocusOptions()))) {
3569 elementToFocus->Focus(FocusOptions(), CallerType::NonSystem, rv);
3570 if (rv.Failed()) {
3571 return;
3573 } else if (aClearUpFocus) {
3574 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
3575 // Clear the focus which ends up making the body gets focused
3576 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = OwnerDoc()->GetWindow();
3577 fm->ClearFocus(outerWindow);
3581 // 2) Let topDocument be the active document of control's node document's
3582 // browsing context's top-level browsing context.
3583 // 3) If control's node document's origin is not the same as the origin of
3584 // topDocument, then return.
3585 BrowsingContext* bc = aControl->OwnerDoc()->GetBrowsingContext();
3586 if (bc && bc->IsInProcess() && bc->SameOriginWithTop()) {
3587 if (nsCOMPtr<nsIDocShell> docShell = bc->Top()->GetDocShell()) {
3588 if (Document* topDocument = docShell->GetExtantDocument()) {
3589 // 4) Empty topDocument's autofocus candidates.
3590 // 5) Set topDocument's autofocus processed flag to true.
3591 topDocument->SetAutoFocusFired();
3597 already_AddRefed<ElementInternals> nsGenericHTMLElement::AttachInternals(
3598 ErrorResult& aRv) {
3599 // ElementInternals is only available on autonomous custom element, so throws
3600 // an error by default. The spec steps are implemented in HTMLElement because
3601 // ElementInternals needs to hold a pointer to HTMLElement in order to forward
3602 // form operation to it.
3603 aRv.ThrowNotSupportedError(nsPrintfCString(
3604 "Cannot attach ElementInternals to a customized built-in or non-custom "
3605 "element "
3606 "'%s'",
3607 NS_ConvertUTF16toUTF8(NodeInfo()->NameAtom()->GetUTF16String()).get()));
3608 return nullptr;
3611 ElementInternals* nsGenericHTMLElement::GetInternals() const {
3612 if (CustomElementData* data = GetCustomElementData()) {
3613 return data->GetElementInternals();
3615 return nullptr;
3618 bool nsGenericHTMLElement::IsFormAssociatedCustomElements() const {
3619 if (CustomElementData* data = GetCustomElementData()) {
3620 return data->IsFormAssociated();
3622 return false;
3625 void nsGenericHTMLElement::GetAutocapitalize(nsAString& aValue) const {
3626 GetEnumAttr(nsGkAtoms::autocapitalize, nullptr, kDefaultAutocapitalize->tag,
3627 aValue);
3630 bool nsGenericHTMLElement::Translate() const {
3631 if (const nsAttrValue* attr = mAttrs.GetAttr(nsGkAtoms::translate)) {
3632 if (attr->IsEmptyString() || attr->Equals(nsGkAtoms::yes, eIgnoreCase)) {
3633 return true;
3635 if (attr->Equals(nsGkAtoms::no, eIgnoreCase)) {
3636 return false;
3639 return nsGenericHTMLElementBase::Translate();
3642 void nsGenericHTMLElement::GetPopover(nsString& aPopover) const {
3643 GetHTMLEnumAttr(nsGkAtoms::popover, aPopover);
3644 if (aPopover.IsEmpty() && !DOMStringIsNull(aPopover)) {
3645 aPopover.Assign(NS_ConvertUTF8toUTF16(kPopoverAttributeValueAuto));