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"
25 #include "nsGenericHTMLElement.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"
36 #include "nsViewManager.h"
37 #include "nsIWidget.h"
39 #include "nsPresContext.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"
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
},
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
},
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
},
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
));
167 static_cast<nsGenericHTMLElement
*>(aDst
)->SetNonce(*nonce
);
172 static const nsAttrValue::EnumTable kDirTable
[] = {
173 {"ltr", Directionality::Ltr
},
174 {"rtl", Directionality::Rtl
},
175 {"auto", Directionality::Auto
},
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
},
193 // See <https://html.spec.whatwg.org/#the-popover-attribute>.
194 static const nsAttrValue::EnumTable
* kPopoverTableInvalidValueDefault
=
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
,
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());
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
},
220 // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
221 static const nsAttrValue::EnumTable
*
222 kFetchPriorityEnumTableInvalidValueDefault
= &kFetchPriorityEnumTable
[2];
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
;
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
) {
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
,
287 struct OffsetResult
{
288 Element
* mParent
= nullptr;
292 static OffsetResult
GetUnretargetedOffsetsFor(const Element
& aElement
) {
293 nsIFrame
* frame
= aElement
.GetPrimaryFrame();
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();
308 (content
->IsHTMLElement(nsGkAtoms::body
) || content
== docElement
)) {
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
;
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();
332 // If we've hit the document element, break here.
333 if (content
== docElement
) {
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
;
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.
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();
383 for (ShadowRoot
* scope
= aReferenceElement
.GetContainingShadow(); scope
;
384 scope
= scope
->Host()->GetContainingShadow()) {
385 if (scope
== shadow
) {
393 Element
* nsGenericHTMLElement::GetOffsetRect(CSSIntRect
& aRect
) {
394 aRect
= CSSIntRect();
396 if (!GetPrimaryFrame(FlushType::Layout
)) {
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
;
413 bool nsGenericHTMLElement::Spellcheck() {
414 // Has the state has been explicitly set?
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"
424 case 1: // spellcheck = "false"
430 // contenteditable/designMode are spellchecked by default
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);
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
);
478 nsStyledElement::UpdateEditableState(aNotify
);
481 nsresult
nsGenericHTMLElement::BindToTree(BindContext
& aContext
,
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
&&
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)]() {
510 self
->GetNonce(nonce
);
511 self
->SetAttr(kNameSpaceID_None
, nsGkAtoms::nonce
, u
""_ns
, true);
512 self
->SetNonce(nonce
);
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
519 nsExtendedDOMSlots
* slots
= GetExistingExtendedDOMSlots();
520 if (slots
&& slots
->mLabelsList
) {
521 slots
->mLabelsList
->MaybeResetRoot(SubtreeRoot());
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
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()) {
564 nsIContent
* content
= this;
566 // If the current ancestor is a form, return it as our form
567 if (content
->IsHTMLElement(nsGkAtoms::form
)) {
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(),
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
594 if (aCurrentForm
->IsInclusiveDescendantOf(prevContent
)) {
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
)) {
620 GetEventTargetParentForLinks(aVisitor
);
623 nsresult
nsGenericHTMLElement::PostHandleEventForAnchors(
624 EventChainPostVisitor
& aVisitor
) {
625 if (!CheckHandleEventForAnchorsPreconditions(aVisitor
)) {
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
));
652 void nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID
, nsAtom
* aName
,
653 const nsAttrValue
* aValue
,
655 if (aNamespaceID
== kNameSpaceID_None
) {
656 if (aName
== nsGkAtoms::accesskey
) {
657 // Have to unregister before clearing flag. See UnregAccessKey
658 RegUnRegAccessKey(false);
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()) {
668 } else if (aName
== nsGkAtoms::contenteditable
) {
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
,
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
;
699 MOZ_ASSERT_UNREACHABLE();
700 return PopoverAttributeState::None
;
706 void nsGenericHTMLElement::AfterSetPopoverAttr() {
707 auto mapPopoverState
= [](const nsAttrValue
* value
) -> PopoverAttributeState
{
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
733 // https://github.com/whatwg/html/issues/9034
734 newState
= mapPopoverState(GetParsedAttr(nsGkAtoms::popover
));
737 if (newState
== PopoverAttributeState::None
) {
739 RemoveStates(ElementState::POPOVER_OPEN
);
741 // TODO: what if `HidePopoverInternal` called `ShowPopup()`?
742 EnsurePopoverData().SetPopoverAttributeState(newState
);
747 void nsGenericHTMLElement::OnAttrSetButNotChanged(
748 int32_t aNamespaceID
, nsAtom
* aName
, const nsAttrValueOrString
& aValue
,
750 if (aNamespaceID
== kNameSpaceID_None
&& aName
== nsGkAtoms::popovertarget
) {
751 ClearExplicitlySetAttrElement(aName
);
753 return nsGenericHTMLElementBase::OnAttrSetButNotChanged(aNamespaceID
, aName
,
757 void nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID
, nsAtom
* aName
,
758 const nsAttrValue
* aValue
,
759 const nsAttrValue
* aOldValue
,
760 nsIPrincipal
* aMaybeScriptedPrincipal
,
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
) {
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
;
791 SetDirectionality(dir
, aNotify
);
792 if (dirValue
== Directionality::Ltr
) {
793 dirStates
|= ElementState::HAS_DIR_ATTR_LTR
;
795 MOZ_ASSERT(dirValue
== Directionality::Rtl
);
796 dirStates
|= ElementState::HAS_DIR_ATTR_RTL
;
801 // We have a value, just not a valid one.
802 dirStates
|= ElementState::HAS_DIR_ATTR
;
805 if (NodeInfo()->Equals(nsGkAtoms::bdi
)) {
806 dirStates
|= ElementState::HAS_DIR_ATTR_LIKE_AUTO
;
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
) {
839 AddStates(ElementState::INERT
);
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).
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()) {
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
) {
883 SetNonce(aValue
->GetStringValue());
884 if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) {
885 SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP
);
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
910 #undef FORWARDED_EVENT
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
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();
923 if ((win
= document
->GetInnerWindow())) {
924 nsCOMPtr
<EventTarget
> piTarget(do_QueryInterface(win
));
926 return piTarget
->GetOrCreateListenerManager();
932 return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName
,
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_(); \
948 return nsINode::GetOn##name_(); \
950 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \
951 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
952 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
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(); \
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(); \
989 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
990 RefPtr<OnErrorEventHandlerNonNull> errorHandler; \
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
1001 #undef FORWARDED_EVENT
1004 void nsGenericHTMLElement::GetBaseTarget(nsAString
& aBaseTarget
) const {
1005 OwnerDoc()->GetBaseTarget(aBaseTarget
);
1008 //----------------------------------------------------------------------
1010 bool nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID
,
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()) {
1040 aResult
.ParseAtom(aValue
);
1044 if (aAttribute
== nsGkAtoms::contenteditable
||
1045 aAttribute
== nsGkAtoms::translate
) {
1046 aResult
.ParseAtom(aValue
);
1050 if (aAttribute
== nsGkAtoms::rel
) {
1051 aResult
.ParseAtomArray(aValue
);
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
,
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
)) {
1086 aResult
.SetTo(uri
, &aValue
);
1093 bool nsGenericHTMLElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
1094 static const MappedAttributeEntry
* const map
[] = {sCommonAttributeMap
};
1096 return FindAttributeDependence(aAttribute
, map
);
1099 nsMapRuleToAttributesFunc
nsGenericHTMLElement::GetAttributeMappingFunction()
1101 return &MapCommonAttributesInto
;
1104 nsIFormControlFrame
* nsGenericHTMLElement::GetFormControlFrame(
1105 bool aFlushFrames
) {
1106 auto flushType
= aFlushFrames
? FlushType::Frames
: FlushType::None
;
1107 nsIFrame
* frame
= GetPrimaryFrame(flushType
);
1112 if (nsIFormControlFrame
* f
= do_QueryFrame(frame
)) {
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
)) {
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
},
1138 static const nsAttrValue::EnumTable kFrameborderTable
[] = {
1139 {"yes", FrameBorderProperty::Yes
},
1140 {"no", FrameBorderProperty::No
},
1141 {"1", FrameBorderProperty::One
},
1142 {"0", FrameBorderProperty::Zero
},
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
},
1156 static const nsAttrValue::EnumTable kTableVAlignTable
[] = {
1157 {"top", StyleVerticalAlignKeyword::Top
},
1158 {"middle", StyleVerticalAlignKeyword::Middle
},
1159 {"bottom", StyleVerticalAlignKeyword::Bottom
},
1160 {"baseline", StyleVerticalAlignKeyword::Baseline
},
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
},
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
},
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
);
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
);
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
);
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
);
1360 const nsGenericHTMLElement::MappedAttributeEntry
1361 nsGenericHTMLElement::sCommonAttributeMap
[] = {{nsGkAtoms::contenteditable
},
1363 {nsGkAtoms::hidden
},
1367 const Element::MappedAttributeEntry
1368 nsGenericHTMLElement::sImageMarginSizeAttributeMap
[] = {{nsGkAtoms::width
},
1369 {nsGkAtoms::height
},
1370 {nsGkAtoms::hspace
},
1371 {nsGkAtoms::vspace
},
1375 const Element::MappedAttributeEntry
1376 nsGenericHTMLElement::sImageAlignAttributeMap
[] = {{nsGkAtoms::align
},
1380 const Element::MappedAttributeEntry
1381 nsGenericHTMLElement::sDivAlignAttributeMap
[] = {{nsGkAtoms::align
},
1385 const Element::MappedAttributeEntry
1386 nsGenericHTMLElement::sImageBorderAttributeMap
[] = {{nsGkAtoms::border
},
1390 const Element::MappedAttributeEntry
1391 nsGenericHTMLElement::sBackgroundAttributeMap
[] = {
1392 {nsGkAtoms::background
}, {nsGkAtoms::bgcolor
}, {nullptr}};
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
)) {
1413 case uint8_t(StyleTextAlign::Left
):
1414 case uint8_t(StyleTextAlign::Right
):
1417 aBuilder
.SetKeywordValue(eCSSProperty_vertical_align
, align
);
1424 void nsGenericHTMLElement::MapDivAlignAttributeInto(
1425 MappedDeclarationsBuilder
& aBuilder
) {
1426 if (!aBuilder
.PropertyIsSet(eCSSProperty_text_align
)) {
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
)) {
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
) {
1464 if (const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::hspace
)) {
1465 MapDimensionAttributeInto(aBuilder
, eCSSProperty_margin_left
, *value
);
1466 MapDimensionAttributeInto(aBuilder
, eCSSProperty_margin_right
, *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
) {
1494 if (aWidth
.Type() == nsAttrValue::eInteger
) {
1495 w
.emplace(aWidth
.GetIntegerValue());
1496 } else if (aWidth
.Type() == nsAttrValue::eDoubleValue
) {
1497 w
.emplace(aWidth
.GetDoubleValue());
1501 if (aHeight
.Type() == nsAttrValue::eInteger
) {
1502 h
.emplace(aHeight
.GetIntegerValue());
1503 } else if (aHeight
.Type() == nsAttrValue::eDoubleValue
) {
1504 h
.emplace(aHeight
.GetDoubleValue());
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
);
1517 MapDimensionAttributeInto(aBuilder
, eCSSProperty_width
, *width
);
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
) {
1539 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::border
);
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
)) {
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
)) {
1579 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::bgcolor
);
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();
1603 nsresult
nsGenericHTMLElement::SetIntAttr(nsAtom
* aAttr
, int32_t aValue
) {
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
) {
1617 return attrVal
->GetIntegerValue();
1620 uint32_t nsGenericHTMLElement::GetDimensionAttrAsUnsignedInt(
1621 nsAtom
* aAttr
, uint32_t aDefault
) const {
1622 const nsAttrValue
* attrVal
= mAttrs
.GetAttr(aAttr
);
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.
1650 attrVal
->ToString(val
);
1651 nsContentUtils::ParseHTMLIntegerResultFlags result
;
1652 int32_t parsedInt
= nsContentUtils::ParseHTMLInteger(val
, &result
);
1653 if ((result
& nsContentUtils::eParseHTMLInteger_Error
) || parsedInt
< 0) {
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
));
1669 // Just return the attr value
1670 attr
->ToString(aResult
);
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
));
1687 // Just return the attr value
1689 attr
->ToString(value
);
1690 CopyUTF16toUTF8(value
, aResult
);
1693 uri
->GetSpec(aResult
);
1696 const nsAttrValue
* nsGenericHTMLElement::GetURIAttr(nsAtom
* aAttr
,
1698 nsIURI
** aURI
) const {
1701 const nsAttrValue
* attr
= mAttrs
.GetAttr(aAttr
);
1706 nsCOMPtr
<nsIURI
> baseURI
= GetBaseURI();
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
)) {
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
);
1727 bool nsGenericHTMLElement::IsLabelable() const {
1728 return IsAnyOfHTMLElements(nsGkAtoms::progress
, nsGkAtoms::meter
);
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();
1754 bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext
* aCx
,
1755 JSObject
* aGlobal
) {
1756 return TouchEvent::LegacyAPIEnabled(aCx
, aGlobal
);
1759 bool nsGenericHTMLElement::IsFormControlDefaultFocusable(
1760 bool aWithMouse
) const {
1764 switch (StaticPrefs::accessibility_mouse_focuses_formcontrol()) {
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");
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
,
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);
1838 void nsGenericHTMLFormElement::UnbindFromTree(UnbindContext
& aContext
) {
1839 // Save state before doing anything else.
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);
1849 // Recheck whether we should still have an form.
1850 if (HasAttr(nsGkAtoms::form
) || !FindAncestorForm(form
)) {
1851 ClearForm(true, true);
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
,
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
,
1874 const nsAttrValue
* aValue
,
1876 if (aNameSpaceID
== kNameSpaceID_None
&& IsFormAssociatedElement()) {
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
,
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
,
1922 void nsGenericHTMLFormElement::AfterSetAttr(
1923 int32_t aNameSpaceID
, nsAtom
* aName
, const nsAttrValue
* aValue
,
1924 const nsAttrValue
* aOldValue
, nsIPrincipal
* aMaybeScriptedPrincipal
,
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
) {
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();
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();
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);
2015 bool nsGenericHTMLFormElement::FormIdUpdated(Element
* aOldElement
,
2016 Element
* aNewElement
,
2018 nsGenericHTMLFormElement
* element
=
2019 static_cast<nsGenericHTMLFormElement
*>(aData
);
2021 NS_ASSERTION(element
->IsHTMLElement(), "aData should be an HTML element");
2023 element
->UpdateFormOwner(false, aNewElement
);
2028 bool nsGenericHTMLFormElement::IsElementDisabledForEvents(WidgetEvent
* aEvent
,
2032 // Allow dispatch of CustomEvent and untrusted Events.
2033 if (!aEvent
->IsTrusted()) {
2037 switch (aEvent
->mMessage
) {
2038 case eAnimationStart
:
2040 case eAnimationIteration
:
2041 case eAnimationCancel
:
2053 case eTransitionCancel
:
2054 case eTransitionEnd
:
2055 case eTransitionRun
:
2056 case eTransitionStart
:
2058 case eLegacyMouseLineOrPageScroll
:
2059 case eLegacyMousePixelScroll
:
2068 if (StaticPrefs::dom_forms_always_allow_key_and_focus_events_enabled()) {
2074 case ePointerCancel
:
2075 case ePointerGotCapture
:
2076 case ePointerLostCapture
:
2077 if (StaticPrefs::dom_forms_always_allow_pointer_events_enabled()) {
2085 if (aEvent
->mSpecifiedEventType
== nsGkAtoms::oninput
) {
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
) {
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();
2106 ClearForm(true, false);
2110 HTMLFormElement
* oldForm
= 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;
2119 element
= AddFormIdObserver();
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
);
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());
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;
2189 fieldset
->RemoveElement(this);
2191 SetFieldSetInternal(parentFieldset
);
2192 parentFieldset
->AddElement(this);
2194 // The disabled state may have changed
2195 FieldSetDisabledChanged(aNotify
);
2200 // No fieldset found.
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()) {
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() {
2247 nsGenericHTMLElement::SaveSubtreeState();
2250 //----------------------------------------------------------------------
2252 void nsGenericHTMLElement::Click(CallerType aCallerType
) {
2253 if (HandlingClick()) {
2257 // There are two notions of 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
2267 !(mNodeInfo
->Equals(nsGkAtoms::fieldset
) &&
2268 StaticPrefs::dom_forms_fieldset_disable_only_descendants_enabled())) {
2272 // Strong in case the event kills it
2273 nsCOMPtr
<Document
> doc
= GetComposedDoc();
2275 RefPtr
<nsPresContext
> context
;
2277 PresShell
* presShell
= doc
->GetPresShell();
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
2285 doc
->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames
);
2286 presShell
= doc
->GetPresShell();
2289 context
= presShell
->GetPresContext();
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;
2317 if (!IsInComposedDoc() || IsInDesignMode()) {
2318 // In designMode documents we only allow focusing the document.
2320 *aIsFocusable
= false;
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
2334 if (attrVal
.isNothing()) {
2335 // The default value for tabindex should be 0 for editable
2336 // contentEditable roots.
2340 disallowOverridingFocusability
= false;
2342 // Just check for disabled attribute on form controls
2343 disabled
= IsDisabled();
2349 // If a tabindex is specified at all, or the default tabindex is 0, we're
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
);
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
);
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) {
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
);
2413 bool shouldActivate
= false;
2416 if (keyEvent
->ShouldWorkAsSpaceKey()) {
2417 SetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD
);
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
;
2428 shouldActivate
= keyEvent
->ShouldWorkAsSpaceKey() &&
2429 HasFlag(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD
);
2430 if (shouldActivate
) {
2431 UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD
);
2435 MOZ_ASSERT_UNREACHABLE("why didn't we bail out earlier?");
2439 if (!shouldActivate
) {
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
2462 RefPtr
<TextEditor
> textEditor
= GetTextEditorInternal();
2463 return textEditor
.forget();
2467 void nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent
* content
) {
2468 /* Sync this node */
2469 nsGenericHTMLElement
* element
= FromNode(content
);
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
2488 if (!aContent
->IsElement()) {
2489 aContent
->UpdateEditableState(false);
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();
2512 Document::EditingState previousEditingState
= Document::EditingState::eOff
;
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
),
2548 mFieldSet(nullptr) {}
2550 nsGenericHTMLFormControlElement::~nsGenericHTMLFormControlElement() {
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();
2569 return nsGenericHTMLFormElement::GetDesiredIMEState();
2572 nsresult rv
= textEditor
->GetPreferredIMEState(&state
);
2573 if (NS_FAILED(rv
)) {
2574 return nsGenericHTMLFormElement::GetDesiredIMEState();
2579 void nsGenericHTMLFormControlElement::GetAutocapitalize(
2580 nsAString
& aValue
) const {
2581 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None
,
2582 nsGkAtoms::autocapitalize
)) {
2583 nsGenericHTMLFormElement::GetAutocapitalize(aValue
);
2587 if (mForm
&& IsAutocapitalizeInheriting()) {
2588 mForm
->GetAutocapitalize(aValue
);
2592 bool nsGenericHTMLFormControlElement::IsHTMLFocusable(bool aWithMouse
,
2594 int32_t* aTabIndex
) {
2595 if (nsGenericHTMLFormElement::IsHTMLFocusable(aWithMouse
, aIsFocusable
,
2600 *aIsFocusable
= *aIsFocusable
&& IsFormControlDefaultFocusable(aWithMouse
);
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
) {
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);
2631 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(true);
2632 if (formControlFrame
) {
2633 formControlFrame
->SetFocus(false, false);
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
) {
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
:
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
:
2708 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesReadOnlyApply()");
2717 void nsGenericHTMLFormControlElement::SetFormInternal(HTMLFormElement
* aForm
,
2720 BeforeSetForm(aForm
, aBindToTree
);
2723 // keep a *weak* ref to the form here
2727 HTMLFormElement
* nsGenericHTMLFormControlElement::GetFormInternal() const {
2731 HTMLFieldSetElement
* nsGenericHTMLFormControlElement::GetFieldSetInternal()
2736 void nsGenericHTMLFormControlElement::SetFieldSetInternal(
2737 HTMLFieldSetElement
* aFieldset
) {
2738 mFieldSet
= aFieldset
;
2741 void nsGenericHTMLFormControlElement::UpdateRequiredState(bool aIsRequired
,
2744 auto type
= ControlType();
2746 MOZ_ASSERT(IsInputElement(type
) || type
== FormControlType::Select
||
2747 type
== FormControlType::Textarea
,
2748 "This should be called only on types that @required applies");
2751 if (HTMLInputElement
* input
= HTMLInputElement::FromNode(this)) {
2753 input
->DoesRequiredApply(),
2754 "This should be called only on input types that @required applies");
2758 ElementState requiredStates
;
2760 requiredStates
|= ElementState::REQUIRED
;
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
);
2797 //----------------------------------------------------------------------
2799 static const nsAttrValue::EnumTable kPopoverTargetActionTable
[] = {
2800 {"toggle", PopoverTargetAction::Toggle
},
2801 {"show", PopoverTargetAction::Show
},
2802 {"hide", PopoverTargetAction::Hide
},
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()
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
);
2834 if (StaticPrefs::dom_element_invokers_enabled()) {
2835 if (aAttribute
== nsGkAtoms::invokeaction
) {
2836 aResult
.ParseAtom(aValue
);
2839 if (aAttribute
== nsGkAtoms::invoketarget
) {
2840 aResult
.ParseAtom(aValue
);
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();
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();
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
);
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.
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()) {
2947 invokee
->HandleInvokeInternal(aAction
, IgnoreErrors());
2950 void nsGenericHTMLFormControlElementWithState::GenerateStateKey() {
2951 // Keep the key if already computed
2952 if (!mStateKey
.IsVoid()) {
2956 Document
* doc
= GetUncomposedDoc();
2958 mStateKey
.Truncate();
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.
2973 PresState
* nsGenericHTMLFormControlElementWithState::GetPrimaryPresState() {
2974 if (mStateKey
.IsEmpty()) {
2978 nsCOMPtr
<nsILayoutHistoryState
> history
= GetLayoutHistory(false);
2984 // Get the pres state for this key, if it doesn't exist, create one.
2985 PresState
* result
= history
->GetState(mStateKey
);
2987 UniquePtr
<PresState
> newState
= NewPresState();
2988 result
= newState
.get();
2989 history
->AddState(mStateKey
, std::move(newState
));
2995 already_AddRefed
<nsILayoutHistoryState
>
2996 nsGenericHTMLFormElement::GetLayoutHistory(bool aRead
) {
2997 nsCOMPtr
<Document
> doc
= GetUncomposedDoc();
3005 nsCOMPtr
<nsILayoutHistoryState
> history
= doc
->GetLayoutHistoryState();
3010 if (aRead
&& !history
->HasStates()) {
3014 return history
.forget();
3017 bool nsGenericHTMLFormControlElementWithState::RestoreFormControlState() {
3018 MOZ_ASSERT(!mStateKey
.IsVoid(),
3019 "GenerateStateKey must already have been called");
3021 if (mStateKey
.IsEmpty()) {
3025 nsCOMPtr
<nsILayoutHistoryState
> history
= GetLayoutHistory(true);
3030 // Get the pres state for this key
3031 PresState
* state
= history
->GetState(mStateKey
);
3033 bool result
= RestoreState(state
);
3034 history
->RemoveState(mStateKey
);
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
)) {
3059 if (!GetAttr(nsGkAtoms::formaction
, aValue
) || aValue
.IsEmpty()) {
3060 Document
* document
= OwnerDoc();
3061 nsIURI
* docURI
= document
->GetDocumentURI();
3064 nsresult rv
= docURI
->GetSpec(spec
);
3065 if (NS_FAILED(rv
)) {
3069 CopyUTF8toUTF16(spec
, aValue
);
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
,
3086 NS_ENSURE_ARG_POINTER(aURI
);
3090 nsCOMPtr
<Document
> doc
= OwnerDoc();
3092 nsresult rv
= nsContentUtils::NewURIWithDocumentCharset(aURI
, aURISpec
, doc
,
3094 NS_ENSURE_SUCCESS(rv
, rv
);
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.
3103 return NS_ERROR_DOM_INVALID_STATE_ERR
;
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
3124 // Obtain the composed doc to handle elements in Shadow DOM.
3125 Document
* doc
= GetComposedDoc();
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();
3143 // Check for dirty reflow roots in the subtree from targetFrame; this requires
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
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.
3160 doc
->FlushPendingNotifications(FlushType::Layout
);
3163 if (!IsRendered()) {
3164 GetTextContentInternal(aValue
, aError
);
3166 nsRange::GetInnerTextNoFlush(aValue
, aError
, this);
3170 static already_AddRefed
<nsINode
> TextToNode(const nsAString
& aString
,
3171 nsNodeInfoManager
* aNim
) {
3173 const char16_t
* s
= aString
.BeginReading();
3174 const char16_t
* end
= aString
.EndReading();
3175 RefPtr
<DocumentFragment
> fragment
;
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
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);
3187 return textContent
.forget();
3189 fragment
= new (aNim
) DocumentFragment(aNim
);
3191 fragment
->AppendChildTo(textContent
, true, IgnoreErrors());
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());
3205 fragment
= new (aNim
) DocumentFragment(aNim
);
3207 fragment
->AppendChildTo(br
, true, IgnoreErrors());
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());
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
,
3236 nsCOMPtr
<nsINode
> parent
= GetParentNode();
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
);
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
);
3260 if (RefPtr
<Text
> text
= Text::FromNodeOrNull(next
->GetPreviousSibling())) {
3261 MergeWithNextTextNode(*text
, aRv
);
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
;
3281 // https://html.spec.whatwg.org/#check-popover-validity
3282 bool nsGenericHTMLElement::CheckPopoverValidity(
3283 PopoverVisibilityState aExpectedState
, Document
* aExpectedDocument
,
3285 if (GetPopoverAttributeState() == PopoverAttributeState::None
) {
3286 aRv
.ThrowNotSupportedError("Element is in the no popover state");
3290 if (GetPopoverData()->GetPopoverVisibilityState() != aExpectedState
) {
3294 if (!IsInComposedDoc()) {
3295 aRv
.ThrowInvalidStateError("Element is not connected");
3299 if (aExpectedDocument
&& aExpectedDocument
!= OwnerDoc()) {
3300 aRv
.ThrowInvalidStateError("Element is moved to other document");
3304 if (auto* dialog
= HTMLDialogElement::FromNode(this)) {
3305 if (dialog
->IsInTopLayer()) {
3306 aRv
.ThrowInvalidStateError("Element is a modal <dialog> element");
3311 if (State().HasState(ElementState::FULLSCREEN
)) {
3312 aRv
.ThrowInvalidStateError("Element is fullscreen");
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
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();
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();
3381 auto* popoverToggleEventTask
= data
->GetToggleEventTask();
3382 if (!popoverToggleEventTask
|| aTask
!= popoverToggleEventTask
) {
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
,
3397 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden
, nullptr, aRv
)) {
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
)) {
3418 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden
, document
, aRv
)) {
3422 bool shouldRestoreFocus
= false;
3423 nsWeakPtr originallyFocusedElement
;
3424 if (IsAutoPopover()) {
3425 auto originalState
= GetPopoverAttributeState();
3426 RefPtr
<nsINode
> ancestor
= GetTopmostPopoverAncestor(aInvoker
, true);
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 "
3439 // TODO: Handle if document changes, see
3440 // https://github.com/whatwg/html/issues/9177
3441 if (!IsAutoPopover() ||
3442 !CheckPopoverValidity(PopoverVisibilityState::Hidden
, document
, aRv
)) {
3446 shouldRestoreFocus
= !document
->GetTopmostAutoPopover();
3447 // Let originallyFocusedElement be document's focused area of the document's
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.
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
,
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);
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();
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
,
3529 if (PopoverOpen() && (!aForce
.WasPassed() || !aForce
.Value())) {
3531 } else if (!aForce
.WasPassed() || aForce
.Value()) {
3534 CheckPopoverValidity(GetPopoverData()
3535 ? GetPopoverData()->GetPopoverVisibilityState()
3536 : PopoverVisibilityState::Showing
,
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
)
3555 : GetAutofocusDelegate(false /* aWithMouse */);
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
);
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(
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 "
3607 NS_ConvertUTF16toUTF8(NodeInfo()->NameAtom()->GetUTF16String()).get()));
3611 ElementInternals
* nsGenericHTMLElement::GetInternals() const {
3612 if (CustomElementData
* data
= GetCustomElementData()) {
3613 return data
->GetElementInternals();
3618 bool nsGenericHTMLElement::IsFormAssociatedCustomElements() const {
3619 if (CustomElementData
* data
= GetCustomElementData()) {
3620 return data
->IsFormAssociated();
3625 void nsGenericHTMLElement::GetAutocapitalize(nsAString
& aValue
) const {
3626 GetEnumAttr(nsGkAtoms::autocapitalize
, nullptr, kDefaultAutocapitalize
->tag
,
3630 bool nsGenericHTMLElement::Translate() const {
3631 if (const nsAttrValue
* attr
= mAttrs
.GetAttr(nsGkAtoms::translate
)) {
3632 if (attr
->IsEmptyString() || attr
->Equals(nsGkAtoms::yes
, eIgnoreCase
)) {
3635 if (attr
->Equals(nsGkAtoms::no
, eIgnoreCase
)) {
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
));