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_html5.h"
22 #include "mozilla/StaticPrefs_accessibility.h"
23 #include "mozilla/dom/FetchPriority.h"
24 #include "mozilla/dom/FormData.h"
26 #include "nsGenericHTMLElement.h"
29 #include "nsQueryObject.h"
30 #include "mozilla/dom/BindContext.h"
31 #include "mozilla/dom/Document.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"
97 # include "nsAccessibilityService.h"
100 using namespace mozilla
;
101 using namespace mozilla::dom
;
103 static const uint8_t NS_INPUTMODE_NONE
= 1;
104 static const uint8_t NS_INPUTMODE_TEXT
= 2;
105 static const uint8_t NS_INPUTMODE_TEL
= 3;
106 static const uint8_t NS_INPUTMODE_URL
= 4;
107 static const uint8_t NS_INPUTMODE_EMAIL
= 5;
108 static const uint8_t NS_INPUTMODE_NUMERIC
= 6;
109 static const uint8_t NS_INPUTMODE_DECIMAL
= 7;
110 static const uint8_t NS_INPUTMODE_SEARCH
= 8;
112 static const nsAttrValue::EnumTable kInputmodeTable
[] = {
113 {"none", NS_INPUTMODE_NONE
},
114 {"text", NS_INPUTMODE_TEXT
},
115 {"tel", NS_INPUTMODE_TEL
},
116 {"url", NS_INPUTMODE_URL
},
117 {"email", NS_INPUTMODE_EMAIL
},
118 {"numeric", NS_INPUTMODE_NUMERIC
},
119 {"decimal", NS_INPUTMODE_DECIMAL
},
120 {"search", NS_INPUTMODE_SEARCH
},
123 static const uint8_t NS_ENTERKEYHINT_ENTER
= 1;
124 static const uint8_t NS_ENTERKEYHINT_DONE
= 2;
125 static const uint8_t NS_ENTERKEYHINT_GO
= 3;
126 static const uint8_t NS_ENTERKEYHINT_NEXT
= 4;
127 static const uint8_t NS_ENTERKEYHINT_PREVIOUS
= 5;
128 static const uint8_t NS_ENTERKEYHINT_SEARCH
= 6;
129 static const uint8_t NS_ENTERKEYHINT_SEND
= 7;
131 static const nsAttrValue::EnumTable kEnterKeyHintTable
[] = {
132 {"enter", NS_ENTERKEYHINT_ENTER
},
133 {"done", NS_ENTERKEYHINT_DONE
},
134 {"go", NS_ENTERKEYHINT_GO
},
135 {"next", NS_ENTERKEYHINT_NEXT
},
136 {"previous", NS_ENTERKEYHINT_PREVIOUS
},
137 {"search", NS_ENTERKEYHINT_SEARCH
},
138 {"send", NS_ENTERKEYHINT_SEND
},
141 static const uint8_t NS_AUTOCAPITALIZE_NONE
= 1;
142 static const uint8_t NS_AUTOCAPITALIZE_SENTENCES
= 2;
143 static const uint8_t NS_AUTOCAPITALIZE_WORDS
= 3;
144 static const uint8_t NS_AUTOCAPITALIZE_CHARACTERS
= 4;
146 static const nsAttrValue::EnumTable kAutocapitalizeTable
[] = {
147 {"none", NS_AUTOCAPITALIZE_NONE
},
148 {"sentences", NS_AUTOCAPITALIZE_SENTENCES
},
149 {"words", NS_AUTOCAPITALIZE_WORDS
},
150 {"characters", NS_AUTOCAPITALIZE_CHARACTERS
},
151 {"off", NS_AUTOCAPITALIZE_NONE
},
152 {"on", NS_AUTOCAPITALIZE_SENTENCES
},
156 static const nsAttrValue::EnumTable
* kDefaultAutocapitalize
=
157 &kAutocapitalizeTable
[1];
159 nsresult
nsGenericHTMLElement::CopyInnerTo(Element
* aDst
) {
160 MOZ_ASSERT(!aDst
->GetUncomposedDoc(),
161 "Should not CopyInnerTo an Element in a document");
163 auto reparse
= aDst
->OwnerDoc() == OwnerDoc() ? ReparseAttributes::No
164 : ReparseAttributes::Yes
;
165 nsresult rv
= Element::CopyInnerTo(aDst
, reparse
);
166 NS_ENSURE_SUCCESS(rv
, rv
);
168 // cloning a node must retain its internal nonce slot
169 nsString
* nonce
= static_cast<nsString
*>(GetProperty(nsGkAtoms::nonce
));
171 static_cast<nsGenericHTMLElement
*>(aDst
)->SetNonce(*nonce
);
176 static const nsAttrValue::EnumTable kDirTable
[] = {
177 {"ltr", eDir_LTR
}, {"rtl", eDir_RTL
}, {"auto", eDir_Auto
}, {nullptr, 0}};
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::Table
) {
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(bool aNullParent
) {
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(aNullParent
);
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
));
653 void nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID
, nsAtom
* aName
,
654 const nsAttrValue
* aValue
,
656 if (aNamespaceID
== kNameSpaceID_None
) {
657 if (aName
== nsGkAtoms::accesskey
) {
658 // Have to unregister before clearing flag. See UnregAccessKey
659 RegUnRegAccessKey(false);
661 UnsetFlags(NODE_HAS_ACCESSKEY
);
663 } else if (aName
== nsGkAtoms::name
) {
664 // Have to do this before clearing flag. See RemoveFromNameTable
665 RemoveFromNameTable();
666 if (!aValue
|| aValue
->IsEmptyString()) {
669 } else if (aName
== nsGkAtoms::contenteditable
) {
671 // Set this before the attribute is set so that any subclass code that
672 // runs before the attribute is set won't think we're missing a
673 // contenteditable attr when we actually have one.
674 SetMayHaveContentEditableAttr();
677 if (!aValue
&& IsEventAttributeName(aName
)) {
678 if (EventListenerManager
* manager
= GetExistingListenerManager()) {
679 manager
->RemoveEventHandler(GetEventNameForAttr(aName
));
684 return nsGenericHTMLElementBase::BeforeSetAttr(aNamespaceID
, aName
, aValue
,
689 constexpr PopoverAttributeState
ToPopoverAttributeState(
690 PopoverAttributeKeyword aPopoverAttributeKeyword
) {
691 // See <https://html.spec.whatwg.org/#the-popover-attribute>.
692 switch (aPopoverAttributeKeyword
) {
693 case PopoverAttributeKeyword::Auto
:
694 return PopoverAttributeState::Auto
;
695 case PopoverAttributeKeyword::EmptyString
:
696 return PopoverAttributeState::Auto
;
697 case PopoverAttributeKeyword::Manual
:
698 return PopoverAttributeState::Manual
;
700 MOZ_ASSERT_UNREACHABLE();
701 return PopoverAttributeState::None
;
707 void nsGenericHTMLElement::AfterSetPopoverAttr() {
708 auto mapPopoverState
= [](const nsAttrValue
* value
) -> PopoverAttributeState
{
710 MOZ_ASSERT(value
->Type() == nsAttrValue::eEnum
);
711 const auto popoverAttributeKeyword
=
712 static_cast<PopoverAttributeKeyword
>(value
->GetEnumValue());
713 return ToPopoverAttributeState(popoverAttributeKeyword
);
716 // The missing value default is the no popover state, see
717 // <https://html.spec.whatwg.org/multipage/popover.html#attr-popover>.
718 return PopoverAttributeState::None
;
721 PopoverAttributeState newState
=
722 mapPopoverState(GetParsedAttr(nsGkAtoms::popover
));
724 const PopoverAttributeState oldState
= GetPopoverAttributeState();
726 if (newState
!= oldState
) {
727 PopoverPseudoStateUpdate(false, true);
729 if (IsPopoverOpen()) {
730 HidePopoverInternal(/* aFocusPreviousElement = */ true,
731 /* aFireEvents = */ true, IgnoreErrors());
732 // Event handlers could have removed the popover attribute, or changed
734 // https://github.com/whatwg/html/issues/9034
735 newState
= mapPopoverState(GetParsedAttr(nsGkAtoms::popover
));
738 if (newState
== PopoverAttributeState::None
) {
739 // HidePopoverInternal above could have removed the popover from the top
741 if (GetPopoverData()) {
742 OwnerDoc()->RemovePopoverFromTopLayer(*this);
745 RemoveStates(ElementState::POPOVER_OPEN
);
747 // TODO: what if `HidePopoverInternal` called `ShowPopup()`?
748 EnsurePopoverData().SetPopoverAttributeState(newState
);
753 void nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID
, nsAtom
* aName
,
754 const nsAttrValue
* aValue
,
755 const nsAttrValue
* aOldValue
,
756 nsIPrincipal
* aMaybeScriptedPrincipal
,
758 if (aNamespaceID
== kNameSpaceID_None
) {
759 if (IsEventAttributeName(aName
) && aValue
) {
760 MOZ_ASSERT(aValue
->Type() == nsAttrValue::eString
,
761 "Expected string value for script body");
762 SetEventHandler(GetEventNameForAttr(aName
), aValue
->GetStringValue());
763 } else if (aNotify
&& aName
== nsGkAtoms::spellcheck
) {
764 SyncEditorsOnSubtree(this);
765 } else if (aName
== nsGkAtoms::popover
&&
766 StaticPrefs::dom_element_popover_enabled()) {
767 nsContentUtils::AddScriptRunner(
768 NewRunnableMethod("nsGenericHTMLElement::AfterSetPopoverAttr", this,
769 &nsGenericHTMLElement::AfterSetPopoverAttr
));
770 } else if (aName
== nsGkAtoms::dir
) {
771 Directionality dir
= eDir_LTR
;
772 // A boolean tracking whether we need to recompute our directionality.
773 // This needs to happen after we update our internal "dir" attribute
774 // state but before we call SetDirectionalityOnDescendants.
775 bool recomputeDirectionality
= false;
776 ElementState dirStates
;
777 if (aValue
&& aValue
->Type() == nsAttrValue::eEnum
) {
779 dirStates
|= ElementState::HAS_DIR_ATTR
;
780 Directionality dirValue
= (Directionality
)aValue
->GetEnumValue();
781 if (dirValue
== eDir_Auto
) {
782 dirStates
|= ElementState::HAS_DIR_ATTR_LIKE_AUTO
;
785 SetDirectionality(dir
, aNotify
);
786 if (dirValue
== eDir_LTR
) {
787 dirStates
|= ElementState::HAS_DIR_ATTR_LTR
;
789 MOZ_ASSERT(dirValue
== eDir_RTL
);
790 dirStates
|= ElementState::HAS_DIR_ATTR_RTL
;
795 // We have a value, just not a valid one.
796 dirStates
|= ElementState::HAS_DIR_ATTR
;
799 if (NodeInfo()->Equals(nsGkAtoms::bdi
)) {
800 dirStates
|= ElementState::HAS_DIR_ATTR_LIKE_AUTO
;
802 recomputeDirectionality
= true;
805 // Now figure out what's changed about our dir states.
806 ElementState oldDirStates
= State() & ElementState::DIR_ATTR_STATES
;
807 ElementState changedStates
= dirStates
^ oldDirStates
;
808 if (!changedStates
.IsEmpty()) {
809 ToggleStates(changedStates
, aNotify
);
811 if (recomputeDirectionality
) {
812 dir
= RecomputeDirectionality(this, aNotify
);
814 SetDirectionalityOnDescendants(this, dir
, aNotify
);
815 } else if (aName
== nsGkAtoms::contenteditable
) {
816 int32_t editableCountDelta
= 0;
817 if (aOldValue
&& (aOldValue
->Equals(u
"true"_ns
, eIgnoreCase
) ||
818 aOldValue
->Equals(u
""_ns
, eIgnoreCase
))) {
819 editableCountDelta
= -1;
821 if (aValue
&& (aValue
->Equals(u
"true"_ns
, eIgnoreCase
) ||
822 aValue
->Equals(u
""_ns
, eIgnoreCase
))) {
823 ++editableCountDelta
;
825 ChangeEditableState(editableCountDelta
);
826 } else if (aName
== nsGkAtoms::accesskey
) {
827 if (aValue
&& !aValue
->Equals(u
""_ns
, eIgnoreCase
)) {
828 SetFlags(NODE_HAS_ACCESSKEY
);
829 RegUnRegAccessKey(true);
831 } else if (aName
== nsGkAtoms::inert
) {
833 AddStates(ElementState::INERT
);
835 RemoveStates(ElementState::INERT
);
837 } else if (aName
== nsGkAtoms::name
) {
838 if (aValue
&& !aValue
->Equals(u
""_ns
, eIgnoreCase
)) {
839 // This may not be quite right because we can have subclass code run
840 // before here. But in practice subclasses don't care about this flag,
841 // and in particular selector matching does not care. Otherwise we'd
842 // want to handle it like we handle id attributes (in PreIdMaybeChange
843 // and PostIdMaybeChange).
845 if (CanHaveName(NodeInfo()->NameAtom())) {
846 AddToNameTable(aValue
->GetAtomValue());
849 } else if (aName
== nsGkAtoms::inputmode
||
850 aName
== nsGkAtoms::enterkeyhint
) {
851 if (nsFocusManager::GetFocusedElementStatic() == this) {
852 if (const nsPresContext
* presContext
=
853 GetPresContext(eForComposedDoc
)) {
854 IMEContentObserver
* observer
=
855 IMEStateManager::GetActiveContentObserver();
856 if (observer
&& observer
->IsObserving(*presContext
, this)) {
857 if (RefPtr
<EditorBase
> editorBase
= GetEditorWithoutCreation()) {
859 editorBase
->GetPreferredIMEState(&newState
);
860 OwningNonNull
<nsGenericHTMLElement
> kungFuDeathGrip(*this);
861 IMEStateManager::UpdateIMEState(
862 newState
, kungFuDeathGrip
, *editorBase
,
863 {IMEStateManager::UpdateIMEStateOption::ForceUpdate
,
864 IMEStateManager::UpdateIMEStateOption::
865 DontCommitComposition
});
872 // The nonce will be copied over to an internal slot and cleared from the
873 // Element within BindToTree to avoid CSS Selector nonce exfiltration if
874 // the CSP list contains a header-delivered CSP.
875 if (nsGkAtoms::nonce
== aName
) {
877 SetNonce(aValue
->GetStringValue());
878 if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) {
879 SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP
);
887 return nsGenericHTMLElementBase::AfterSetAttr(
888 aNamespaceID
, aName
, aValue
, aOldValue
, aMaybeScriptedPrincipal
, aNotify
);
891 EventListenerManager
* nsGenericHTMLElement::GetEventListenerManagerForAttr(
892 nsAtom
* aAttrName
, bool* aDefer
) {
893 // Attributes on the body and frameset tags get set on the global object
894 if ((mNodeInfo
->Equals(nsGkAtoms::body
) ||
895 mNodeInfo
->Equals(nsGkAtoms::frameset
)) &&
896 // We only forward some event attributes from body/frameset to window
898 #define EVENT(name_, id_, type_, struct_) /* nothing */
899 #define FORWARDED_EVENT(name_, id_, type_, struct_) \
900 || nsGkAtoms::on##name_ == aAttrName
901 #define WINDOW_EVENT FORWARDED_EVENT
902 #include "mozilla/EventNameList.h" // IWYU pragma: keep
904 #undef FORWARDED_EVENT
907 nsPIDOMWindowInner
* win
;
909 // If we have a document, and it has a window, add the event
910 // listener on the window (the inner window). If not, proceed as
912 // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() here,
913 // override BindToTree for those classes and munge event listeners there?
914 Document
* document
= OwnerDoc();
917 if ((win
= document
->GetInnerWindow())) {
918 nsCOMPtr
<EventTarget
> piTarget(do_QueryInterface(win
));
920 return piTarget
->GetOrCreateListenerManager();
926 return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName
,
930 #define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
931 #define FORWARDED_EVENT(name_, id_, type_, struct_) \
932 EventHandlerNonNull* nsGenericHTMLElement::GetOn##name_() { \
933 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
934 /* XXXbz note to self: add tests for this! */ \
935 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
936 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
937 return globalWin->GetOn##name_(); \
942 return nsINode::GetOn##name_(); \
944 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \
945 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
946 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
951 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
952 return globalWin->SetOn##name_(handler); \
955 return nsINode::SetOn##name_(handler); \
957 #define ERROR_EVENT(name_, id_, type_, struct_) \
958 already_AddRefed<EventHandlerNonNull> nsGenericHTMLElement::GetOn##name_() { \
959 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
960 /* XXXbz note to self: add tests for this! */ \
961 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
962 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
963 OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_(); \
964 if (errorHandler) { \
965 RefPtr<EventHandlerNonNull> handler = \
966 new EventHandlerNonNull(errorHandler); \
967 return handler.forget(); \
973 RefPtr<EventHandlerNonNull> handler = nsINode::GetOn##name_(); \
974 return handler.forget(); \
976 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \
977 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
978 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
983 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
984 RefPtr<OnErrorEventHandlerNonNull> errorHandler; \
986 errorHandler = new OnErrorEventHandlerNonNull(handler); \
988 return globalWin->SetOn##name_(errorHandler); \
991 return nsINode::SetOn##name_(handler); \
993 #include "mozilla/EventNameList.h" // IWYU pragma: keep
995 #undef FORWARDED_EVENT
998 void nsGenericHTMLElement::GetBaseTarget(nsAString
& aBaseTarget
) const {
999 OwnerDoc()->GetBaseTarget(aBaseTarget
);
1002 //----------------------------------------------------------------------
1004 bool nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID
,
1006 const nsAString
& aValue
,
1007 nsIPrincipal
* aMaybeScriptedPrincipal
,
1008 nsAttrValue
& aResult
) {
1009 if (aNamespaceID
== kNameSpaceID_None
) {
1010 if (aAttribute
== nsGkAtoms::dir
) {
1011 return aResult
.ParseEnumValue(aValue
, kDirTable
, false);
1014 if (aAttribute
== nsGkAtoms::popover
&&
1015 StaticPrefs::dom_element_popover_enabled()) {
1016 return aResult
.ParseEnumValue(aValue
, kPopoverTable
, false,
1017 kPopoverTableInvalidValueDefault
);
1020 if (aAttribute
== nsGkAtoms::tabindex
) {
1021 return aResult
.ParseIntValue(aValue
);
1024 if (aAttribute
== nsGkAtoms::referrerpolicy
) {
1025 return ParseReferrerAttribute(aValue
, aResult
);
1028 if (aAttribute
== nsGkAtoms::name
) {
1029 // Store name as an atom. name="" means that the element has no name,
1030 // not that it has an empty string as the name.
1031 if (aValue
.IsEmpty()) {
1034 aResult
.ParseAtom(aValue
);
1038 if (aAttribute
== nsGkAtoms::contenteditable
||
1039 aAttribute
== nsGkAtoms::translate
) {
1040 aResult
.ParseAtom(aValue
);
1044 if (aAttribute
== nsGkAtoms::rel
) {
1045 aResult
.ParseAtomArray(aValue
);
1049 if (aAttribute
== nsGkAtoms::inputmode
) {
1050 return aResult
.ParseEnumValue(aValue
, kInputmodeTable
, false);
1053 if (aAttribute
== nsGkAtoms::enterkeyhint
) {
1054 return aResult
.ParseEnumValue(aValue
, kEnterKeyHintTable
, false);
1057 if (aAttribute
== nsGkAtoms::autocapitalize
) {
1058 return aResult
.ParseEnumValue(aValue
, kAutocapitalizeTable
, false);
1062 return nsGenericHTMLElementBase::ParseAttribute(
1063 aNamespaceID
, aAttribute
, aValue
, aMaybeScriptedPrincipal
, aResult
);
1066 bool nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID
,
1068 const nsAString
& aValue
,
1069 nsAttrValue
& aResult
) {
1070 if (aNamespaceID
== kNameSpaceID_None
&&
1071 aAttribute
== nsGkAtoms::background
&& !aValue
.IsEmpty()) {
1072 // Resolve url to an absolute url
1073 Document
* doc
= OwnerDoc();
1074 nsCOMPtr
<nsIURI
> uri
;
1075 nsresult rv
= nsContentUtils::NewURIWithDocumentCharset(
1076 getter_AddRefs(uri
), aValue
, doc
, GetBaseURI());
1077 if (NS_FAILED(rv
)) {
1080 aResult
.SetTo(uri
, &aValue
);
1087 bool nsGenericHTMLElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
1088 static const MappedAttributeEntry
* const map
[] = {sCommonAttributeMap
};
1090 return FindAttributeDependence(aAttribute
, map
);
1093 nsMapRuleToAttributesFunc
nsGenericHTMLElement::GetAttributeMappingFunction()
1095 return &MapCommonAttributesInto
;
1098 nsIFormControlFrame
* nsGenericHTMLElement::GetFormControlFrame(
1099 bool aFlushFrames
) {
1100 auto flushType
= aFlushFrames
? FlushType::Frames
: FlushType::None
;
1101 nsIFrame
* frame
= GetPrimaryFrame(flushType
);
1106 if (nsIFormControlFrame
* f
= do_QueryFrame(frame
)) {
1110 // If we have generated content, the primary frame will be a wrapper frame...
1111 // Our real frame will be in its child list.
1113 // FIXME(emilio): I don't think that's true... See bug 155957 for test-cases
1114 // though, we should figure out whether this is still needed.
1115 for (nsIFrame
* kid
: frame
->PrincipalChildList()) {
1116 if (nsIFormControlFrame
* f
= do_QueryFrame(kid
)) {
1124 static const nsAttrValue::EnumTable kDivAlignTable
[] = {
1125 {"left", StyleTextAlign::MozLeft
},
1126 {"right", StyleTextAlign::MozRight
},
1127 {"center", StyleTextAlign::MozCenter
},
1128 {"middle", StyleTextAlign::MozCenter
},
1129 {"justify", StyleTextAlign::Justify
},
1132 static const nsAttrValue::EnumTable kFrameborderTable
[] = {
1133 {"yes", FrameBorderProperty::Yes
},
1134 {"no", FrameBorderProperty::No
},
1135 {"1", FrameBorderProperty::One
},
1136 {"0", FrameBorderProperty::Zero
},
1139 // TODO(emilio): Nobody uses the parsed attribute here.
1140 static const nsAttrValue::EnumTable kScrollingTable
[] = {
1141 {"yes", ScrollingAttribute::Yes
},
1142 {"no", ScrollingAttribute::No
},
1143 {"on", ScrollingAttribute::On
},
1144 {"off", ScrollingAttribute::Off
},
1145 {"scroll", ScrollingAttribute::Scroll
},
1146 {"noscroll", ScrollingAttribute::Noscroll
},
1147 {"auto", ScrollingAttribute::Auto
},
1150 static const nsAttrValue::EnumTable kTableVAlignTable
[] = {
1151 {"top", StyleVerticalAlignKeyword::Top
},
1152 {"middle", StyleVerticalAlignKeyword::Middle
},
1153 {"bottom", StyleVerticalAlignKeyword::Bottom
},
1154 {"baseline", StyleVerticalAlignKeyword::Baseline
},
1157 bool nsGenericHTMLElement::ParseAlignValue(const nsAString
& aString
,
1158 nsAttrValue
& aResult
) {
1159 static const nsAttrValue::EnumTable kAlignTable
[] = {
1160 {"left", StyleTextAlign::Left
},
1161 {"right", StyleTextAlign::Right
},
1163 {"top", StyleVerticalAlignKeyword::Top
},
1164 {"middle", StyleVerticalAlignKeyword::MozMiddleWithBaseline
},
1166 // Intentionally not bottom.
1167 {"bottom", StyleVerticalAlignKeyword::Baseline
},
1169 {"center", StyleVerticalAlignKeyword::MozMiddleWithBaseline
},
1170 {"baseline", StyleVerticalAlignKeyword::Baseline
},
1172 {"texttop", StyleVerticalAlignKeyword::TextTop
},
1173 {"absmiddle", StyleVerticalAlignKeyword::Middle
},
1174 {"abscenter", StyleVerticalAlignKeyword::Middle
},
1175 {"absbottom", StyleVerticalAlignKeyword::Bottom
},
1178 static_assert(uint8_t(StyleTextAlign::Left
) !=
1179 uint8_t(StyleVerticalAlignKeyword::Top
) &&
1180 uint8_t(StyleTextAlign::Left
) !=
1181 uint8_t(StyleVerticalAlignKeyword::MozMiddleWithBaseline
) &&
1182 uint8_t(StyleTextAlign::Left
) !=
1183 uint8_t(StyleVerticalAlignKeyword::Baseline
) &&
1184 uint8_t(StyleTextAlign::Left
) !=
1185 uint8_t(StyleVerticalAlignKeyword::TextTop
) &&
1186 uint8_t(StyleTextAlign::Left
) !=
1187 uint8_t(StyleVerticalAlignKeyword::Middle
) &&
1188 uint8_t(StyleTextAlign::Left
) !=
1189 uint8_t(StyleVerticalAlignKeyword::Bottom
));
1191 static_assert(uint8_t(StyleTextAlign::Right
) !=
1192 uint8_t(StyleVerticalAlignKeyword::Top
) &&
1193 uint8_t(StyleTextAlign::Right
) !=
1194 uint8_t(StyleVerticalAlignKeyword::MozMiddleWithBaseline
) &&
1195 uint8_t(StyleTextAlign::Right
) !=
1196 uint8_t(StyleVerticalAlignKeyword::Baseline
) &&
1197 uint8_t(StyleTextAlign::Right
) !=
1198 uint8_t(StyleVerticalAlignKeyword::TextTop
) &&
1199 uint8_t(StyleTextAlign::Right
) !=
1200 uint8_t(StyleVerticalAlignKeyword::Middle
) &&
1201 uint8_t(StyleTextAlign::Right
) !=
1202 uint8_t(StyleVerticalAlignKeyword::Bottom
));
1204 return aResult
.ParseEnumValue(aString
, kAlignTable
, false);
1207 //----------------------------------------
1209 static const nsAttrValue::EnumTable kTableHAlignTable
[] = {
1210 {"left", StyleTextAlign::Left
}, {"right", StyleTextAlign::Right
},
1211 {"center", StyleTextAlign::Center
}, {"char", StyleTextAlign::Char
},
1212 {"justify", StyleTextAlign::Justify
}, {nullptr, 0}};
1214 bool nsGenericHTMLElement::ParseTableHAlignValue(const nsAString
& aString
,
1215 nsAttrValue
& aResult
) {
1216 return aResult
.ParseEnumValue(aString
, kTableHAlignTable
, false);
1219 //----------------------------------------
1221 // This table is used for td, th, tr, col, thead, tbody and tfoot.
1222 static const nsAttrValue::EnumTable kTableCellHAlignTable
[] = {
1223 {"left", StyleTextAlign::MozLeft
},
1224 {"right", StyleTextAlign::MozRight
},
1225 {"center", StyleTextAlign::MozCenter
},
1226 {"char", StyleTextAlign::Char
},
1227 {"justify", StyleTextAlign::Justify
},
1228 {"middle", StyleTextAlign::MozCenter
},
1229 {"absmiddle", StyleTextAlign::Center
},
1232 bool nsGenericHTMLElement::ParseTableCellHAlignValue(const nsAString
& aString
,
1233 nsAttrValue
& aResult
) {
1234 return aResult
.ParseEnumValue(aString
, kTableCellHAlignTable
, false);
1237 //----------------------------------------
1239 bool nsGenericHTMLElement::ParseTableVAlignValue(const nsAString
& aString
,
1240 nsAttrValue
& aResult
) {
1241 return aResult
.ParseEnumValue(aString
, kTableVAlignTable
, false);
1244 bool nsGenericHTMLElement::ParseDivAlignValue(const nsAString
& aString
,
1245 nsAttrValue
& aResult
) {
1246 return aResult
.ParseEnumValue(aString
, kDivAlignTable
, false);
1249 bool nsGenericHTMLElement::ParseImageAttribute(nsAtom
* aAttribute
,
1250 const nsAString
& aString
,
1251 nsAttrValue
& aResult
) {
1252 if (aAttribute
== nsGkAtoms::width
|| aAttribute
== nsGkAtoms::height
||
1253 aAttribute
== nsGkAtoms::hspace
|| aAttribute
== nsGkAtoms::vspace
) {
1254 return aResult
.ParseHTMLDimension(aString
);
1256 if (aAttribute
== nsGkAtoms::border
) {
1257 return aResult
.ParseNonNegativeIntValue(aString
);
1262 bool nsGenericHTMLElement::ParseReferrerAttribute(const nsAString
& aString
,
1263 nsAttrValue
& aResult
) {
1264 using mozilla::dom::ReferrerInfo
;
1265 static const nsAttrValue::EnumTable kReferrerPolicyTable
[] = {
1266 {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::No_referrer
),
1267 static_cast<int16_t>(ReferrerPolicy::No_referrer
)},
1268 {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Origin
),
1269 static_cast<int16_t>(ReferrerPolicy::Origin
)},
1270 {ReferrerInfo::ReferrerPolicyToString(
1271 ReferrerPolicy::Origin_when_cross_origin
),
1272 static_cast<int16_t>(ReferrerPolicy::Origin_when_cross_origin
)},
1273 {ReferrerInfo::ReferrerPolicyToString(
1274 ReferrerPolicy::No_referrer_when_downgrade
),
1275 static_cast<int16_t>(ReferrerPolicy::No_referrer_when_downgrade
)},
1276 {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Unsafe_url
),
1277 static_cast<int16_t>(ReferrerPolicy::Unsafe_url
)},
1278 {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Strict_origin
),
1279 static_cast<int16_t>(ReferrerPolicy::Strict_origin
)},
1280 {ReferrerInfo::ReferrerPolicyToString(ReferrerPolicy::Same_origin
),
1281 static_cast<int16_t>(ReferrerPolicy::Same_origin
)},
1282 {ReferrerInfo::ReferrerPolicyToString(
1283 ReferrerPolicy::Strict_origin_when_cross_origin
),
1284 static_cast<int16_t>(ReferrerPolicy::Strict_origin_when_cross_origin
)},
1285 {nullptr, ReferrerPolicy::_empty
}};
1286 return aResult
.ParseEnumValue(aString
, kReferrerPolicyTable
, false);
1289 bool nsGenericHTMLElement::ParseFrameborderValue(const nsAString
& aString
,
1290 nsAttrValue
& aResult
) {
1291 return aResult
.ParseEnumValue(aString
, kFrameborderTable
, false);
1294 bool nsGenericHTMLElement::ParseScrollingValue(const nsAString
& aString
,
1295 nsAttrValue
& aResult
) {
1296 return aResult
.ParseEnumValue(aString
, kScrollingTable
, false);
1299 static inline void MapLangAttributeInto(MappedDeclarationsBuilder
& aBuilder
) {
1300 const nsAttrValue
* langValue
= aBuilder
.GetAttr(nsGkAtoms::lang
);
1304 MOZ_ASSERT(langValue
->Type() == nsAttrValue::eAtom
);
1305 aBuilder
.SetIdentAtomValueIfUnset(eCSSProperty__x_lang
,
1306 langValue
->GetAtomValue());
1307 if (!aBuilder
.PropertyIsSet(eCSSProperty_text_emphasis_position
)) {
1308 const nsAtom
* lang
= langValue
->GetAtomValue();
1309 if (nsStyleUtil::MatchesLanguagePrefix(lang
, u
"zh")) {
1310 aBuilder
.SetKeywordValue(eCSSProperty_text_emphasis_position
,
1311 StyleTextEmphasisPosition::UNDER
._0
);
1312 } else if (nsStyleUtil::MatchesLanguagePrefix(lang
, u
"ja") ||
1313 nsStyleUtil::MatchesLanguagePrefix(lang
, u
"mn")) {
1314 // This branch is currently no part of the spec.
1315 // See bug 1040668 comment 69 and comment 75.
1316 aBuilder
.SetKeywordValue(eCSSProperty_text_emphasis_position
,
1317 StyleTextEmphasisPosition::OVER
._0
);
1323 * Handle attributes common to all html elements
1325 void nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(
1326 MappedDeclarationsBuilder
& aBuilder
) {
1327 if (!aBuilder
.PropertyIsSet(eCSSProperty__moz_user_modify
)) {
1328 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::contenteditable
);
1330 if (value
->Equals(nsGkAtoms::_empty
, eCaseMatters
) ||
1331 value
->Equals(nsGkAtoms::_true
, eIgnoreCase
)) {
1332 aBuilder
.SetKeywordValue(eCSSProperty__moz_user_modify
,
1333 StyleUserModify::ReadWrite
);
1334 } else if (value
->Equals(nsGkAtoms::_false
, eIgnoreCase
)) {
1335 aBuilder
.SetKeywordValue(eCSSProperty__moz_user_modify
,
1336 StyleUserModify::ReadOnly
);
1341 MapLangAttributeInto(aBuilder
);
1344 void nsGenericHTMLElement::MapCommonAttributesInto(
1345 MappedDeclarationsBuilder
& aBuilder
) {
1346 MapCommonAttributesIntoExceptHidden(aBuilder
);
1347 if (!aBuilder
.PropertyIsSet(eCSSProperty_display
)) {
1348 if (aBuilder
.GetAttr(nsGkAtoms::hidden
)) {
1349 aBuilder
.SetKeywordValue(eCSSProperty_display
, StyleDisplay::None
._0
);
1355 const nsGenericHTMLElement::MappedAttributeEntry
1356 nsGenericHTMLElement::sCommonAttributeMap
[] = {{nsGkAtoms::contenteditable
},
1358 {nsGkAtoms::hidden
},
1362 const Element::MappedAttributeEntry
1363 nsGenericHTMLElement::sImageMarginSizeAttributeMap
[] = {{nsGkAtoms::width
},
1364 {nsGkAtoms::height
},
1365 {nsGkAtoms::hspace
},
1366 {nsGkAtoms::vspace
},
1370 const Element::MappedAttributeEntry
1371 nsGenericHTMLElement::sImageAlignAttributeMap
[] = {{nsGkAtoms::align
},
1375 const Element::MappedAttributeEntry
1376 nsGenericHTMLElement::sDivAlignAttributeMap
[] = {{nsGkAtoms::align
},
1380 const Element::MappedAttributeEntry
1381 nsGenericHTMLElement::sImageBorderAttributeMap
[] = {{nsGkAtoms::border
},
1385 const Element::MappedAttributeEntry
1386 nsGenericHTMLElement::sBackgroundAttributeMap
[] = {
1387 {nsGkAtoms::background
}, {nsGkAtoms::bgcolor
}, {nullptr}};
1390 const Element::MappedAttributeEntry
1391 nsGenericHTMLElement::sBackgroundColorAttributeMap
[] = {
1392 {nsGkAtoms::bgcolor
}, {nullptr}};
1394 void nsGenericHTMLElement::MapImageAlignAttributeInto(
1395 MappedDeclarationsBuilder
& aBuilder
) {
1396 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::align
);
1397 if (value
&& value
->Type() == nsAttrValue::eEnum
) {
1398 int32_t align
= value
->GetEnumValue();
1399 if (!aBuilder
.PropertyIsSet(eCSSProperty_float
)) {
1400 if (align
== uint8_t(StyleTextAlign::Left
)) {
1401 aBuilder
.SetKeywordValue(eCSSProperty_float
, StyleFloat::Left
);
1402 } else if (align
== uint8_t(StyleTextAlign::Right
)) {
1403 aBuilder
.SetKeywordValue(eCSSProperty_float
, StyleFloat::Right
);
1406 if (!aBuilder
.PropertyIsSet(eCSSProperty_vertical_align
)) {
1408 case uint8_t(StyleTextAlign::Left
):
1409 case uint8_t(StyleTextAlign::Right
):
1412 aBuilder
.SetKeywordValue(eCSSProperty_vertical_align
, align
);
1419 void nsGenericHTMLElement::MapDivAlignAttributeInto(
1420 MappedDeclarationsBuilder
& aBuilder
) {
1421 if (!aBuilder
.PropertyIsSet(eCSSProperty_text_align
)) {
1423 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::align
);
1424 if (value
&& value
->Type() == nsAttrValue::eEnum
)
1425 aBuilder
.SetKeywordValue(eCSSProperty_text_align
, value
->GetEnumValue());
1429 void nsGenericHTMLElement::MapVAlignAttributeInto(
1430 MappedDeclarationsBuilder
& aBuilder
) {
1431 if (!aBuilder
.PropertyIsSet(eCSSProperty_vertical_align
)) {
1433 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::valign
);
1434 if (value
&& value
->Type() == nsAttrValue::eEnum
)
1435 aBuilder
.SetKeywordValue(eCSSProperty_vertical_align
,
1436 value
->GetEnumValue());
1440 void nsGenericHTMLElement::MapDimensionAttributeInto(
1441 MappedDeclarationsBuilder
& aBuilder
, nsCSSPropertyID aProp
,
1442 const nsAttrValue
& aValue
) {
1443 MOZ_ASSERT(!aBuilder
.PropertyIsSet(aProp
),
1444 "Why mapping the same property twice?");
1445 if (aValue
.Type() == nsAttrValue::eInteger
) {
1446 return aBuilder
.SetPixelValue(aProp
, aValue
.GetIntegerValue());
1448 if (aValue
.Type() == nsAttrValue::ePercent
) {
1449 return aBuilder
.SetPercentValue(aProp
, aValue
.GetPercentValue());
1451 if (aValue
.Type() == nsAttrValue::eDoubleValue
) {
1452 return aBuilder
.SetPixelValue(aProp
, aValue
.GetDoubleValue());
1456 void nsGenericHTMLElement::MapImageMarginAttributeInto(
1457 MappedDeclarationsBuilder
& aBuilder
) {
1459 if (const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::hspace
)) {
1460 MapDimensionAttributeInto(aBuilder
, eCSSProperty_margin_left
, *value
);
1461 MapDimensionAttributeInto(aBuilder
, eCSSProperty_margin_right
, *value
);
1465 if (const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::vspace
)) {
1466 MapDimensionAttributeInto(aBuilder
, eCSSProperty_margin_top
, *value
);
1467 MapDimensionAttributeInto(aBuilder
, eCSSProperty_margin_bottom
, *value
);
1471 void nsGenericHTMLElement::MapWidthAttributeInto(
1472 MappedDeclarationsBuilder
& aBuilder
) {
1473 if (const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::width
)) {
1474 MapDimensionAttributeInto(aBuilder
, eCSSProperty_width
, *value
);
1478 void nsGenericHTMLElement::MapHeightAttributeInto(
1479 MappedDeclarationsBuilder
& aBuilder
) {
1480 if (const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::height
)) {
1481 MapDimensionAttributeInto(aBuilder
, eCSSProperty_height
, *value
);
1485 void nsGenericHTMLElement::DoMapAspectRatio(
1486 const nsAttrValue
& aWidth
, const nsAttrValue
& aHeight
,
1487 MappedDeclarationsBuilder
& aBuilder
) {
1489 if (aWidth
.Type() == nsAttrValue::eInteger
) {
1490 w
.emplace(aWidth
.GetIntegerValue());
1491 } else if (aWidth
.Type() == nsAttrValue::eDoubleValue
) {
1492 w
.emplace(aWidth
.GetDoubleValue());
1496 if (aHeight
.Type() == nsAttrValue::eInteger
) {
1497 h
.emplace(aHeight
.GetIntegerValue());
1498 } else if (aHeight
.Type() == nsAttrValue::eDoubleValue
) {
1499 h
.emplace(aHeight
.GetDoubleValue());
1503 aBuilder
.SetAspectRatio(*w
, *h
);
1507 void nsGenericHTMLElement::MapImageSizeAttributesInto(
1508 MappedDeclarationsBuilder
& aBuilder
, MapAspectRatio aMapAspectRatio
) {
1509 auto* width
= aBuilder
.GetAttr(nsGkAtoms::width
);
1510 auto* height
= aBuilder
.GetAttr(nsGkAtoms::height
);
1512 MapDimensionAttributeInto(aBuilder
, eCSSProperty_width
, *width
);
1515 MapDimensionAttributeInto(aBuilder
, eCSSProperty_height
, *height
);
1517 if (aMapAspectRatio
== MapAspectRatio::Yes
&& width
&& height
) {
1518 DoMapAspectRatio(*width
, *height
, aBuilder
);
1522 void nsGenericHTMLElement::MapAspectRatioInto(
1523 MappedDeclarationsBuilder
& aBuilder
) {
1524 auto* width
= aBuilder
.GetAttr(nsGkAtoms::width
);
1525 auto* height
= aBuilder
.GetAttr(nsGkAtoms::height
);
1526 if (width
&& height
) {
1527 DoMapAspectRatio(*width
, *height
, aBuilder
);
1531 void nsGenericHTMLElement::MapImageBorderAttributeInto(
1532 MappedDeclarationsBuilder
& aBuilder
) {
1534 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::border
);
1538 if (value
->Type() == nsAttrValue::eInteger
) val
= value
->GetIntegerValue();
1540 aBuilder
.SetPixelValueIfUnset(eCSSProperty_border_top_width
, (float)val
);
1541 aBuilder
.SetPixelValueIfUnset(eCSSProperty_border_right_width
, (float)val
);
1542 aBuilder
.SetPixelValueIfUnset(eCSSProperty_border_bottom_width
, (float)val
);
1543 aBuilder
.SetPixelValueIfUnset(eCSSProperty_border_left_width
, (float)val
);
1545 aBuilder
.SetKeywordValueIfUnset(eCSSProperty_border_top_style
,
1546 StyleBorderStyle::Solid
);
1547 aBuilder
.SetKeywordValueIfUnset(eCSSProperty_border_right_style
,
1548 StyleBorderStyle::Solid
);
1549 aBuilder
.SetKeywordValueIfUnset(eCSSProperty_border_bottom_style
,
1550 StyleBorderStyle::Solid
);
1551 aBuilder
.SetKeywordValueIfUnset(eCSSProperty_border_left_style
,
1552 StyleBorderStyle::Solid
);
1554 aBuilder
.SetCurrentColorIfUnset(eCSSProperty_border_top_color
);
1555 aBuilder
.SetCurrentColorIfUnset(eCSSProperty_border_right_color
);
1556 aBuilder
.SetCurrentColorIfUnset(eCSSProperty_border_bottom_color
);
1557 aBuilder
.SetCurrentColorIfUnset(eCSSProperty_border_left_color
);
1560 void nsGenericHTMLElement::MapBackgroundInto(
1561 MappedDeclarationsBuilder
& aBuilder
) {
1562 if (!aBuilder
.PropertyIsSet(eCSSProperty_background_image
)) {
1564 if (const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::background
)) {
1565 aBuilder
.SetBackgroundImage(*value
);
1570 void nsGenericHTMLElement::MapBGColorInto(MappedDeclarationsBuilder
& aBuilder
) {
1571 if (aBuilder
.PropertyIsSet(eCSSProperty_background_color
)) {
1574 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::bgcolor
);
1576 if (value
&& value
->GetColorValue(color
)) {
1577 aBuilder
.SetColorValue(eCSSProperty_background_color
, color
);
1581 void nsGenericHTMLElement::MapBackgroundAttributesInto(
1582 MappedDeclarationsBuilder
& aBuilder
) {
1583 MapBackgroundInto(aBuilder
);
1584 MapBGColorInto(aBuilder
);
1587 //----------------------------------------------------------------------
1589 int32_t nsGenericHTMLElement::GetIntAttr(nsAtom
* aAttr
,
1590 int32_t aDefault
) const {
1591 const nsAttrValue
* attrVal
= mAttrs
.GetAttr(aAttr
);
1592 if (attrVal
&& attrVal
->Type() == nsAttrValue::eInteger
) {
1593 return attrVal
->GetIntegerValue();
1598 nsresult
nsGenericHTMLElement::SetIntAttr(nsAtom
* aAttr
, int32_t aValue
) {
1600 value
.AppendInt(aValue
);
1602 return SetAttr(kNameSpaceID_None
, aAttr
, value
, true);
1605 uint32_t nsGenericHTMLElement::GetUnsignedIntAttr(nsAtom
* aAttr
,
1606 uint32_t aDefault
) const {
1607 const nsAttrValue
* attrVal
= mAttrs
.GetAttr(aAttr
);
1608 if (!attrVal
|| attrVal
->Type() != nsAttrValue::eInteger
) {
1612 return attrVal
->GetIntegerValue();
1615 uint32_t nsGenericHTMLElement::GetDimensionAttrAsUnsignedInt(
1616 nsAtom
* aAttr
, uint32_t aDefault
) const {
1617 const nsAttrValue
* attrVal
= mAttrs
.GetAttr(aAttr
);
1622 if (attrVal
->Type() == nsAttrValue::eInteger
) {
1623 return attrVal
->GetIntegerValue();
1626 if (attrVal
->Type() == nsAttrValue::ePercent
) {
1627 // This is a nasty hack. When we parsed the value, we stored it as an
1628 // ePercent, not eInteger, because there was a '%' after it in the string.
1629 // But the spec says to basically re-parse the string as an integer.
1630 // Luckily, we can just return the value we have stored. But
1631 // GetPercentValue() divides it by 100, so we need to multiply it back.
1632 return uint32_t(attrVal
->GetPercentValue() * 100.0f
);
1635 if (attrVal
->Type() == nsAttrValue::eDoubleValue
) {
1636 return uint32_t(attrVal
->GetDoubleValue());
1639 // Unfortunately, the set of values that are valid dimensions is not a
1640 // superset of values that are valid unsigned ints. In particular "+100" is
1641 // not a valid dimension, but should parse as the unsigned int "100". So if
1642 // we got here and we don't have a valid dimension value, just try re-parsing
1643 // the string we have as an integer.
1645 attrVal
->ToString(val
);
1646 nsContentUtils::ParseHTMLIntegerResultFlags result
;
1647 int32_t parsedInt
= nsContentUtils::ParseHTMLInteger(val
, &result
);
1648 if ((result
& nsContentUtils::eParseHTMLInteger_Error
) || parsedInt
< 0) {
1655 void nsGenericHTMLElement::GetURIAttr(nsAtom
* aAttr
, nsAtom
* aBaseAttr
,
1656 nsAString
& aResult
) const {
1657 nsCOMPtr
<nsIURI
> uri
;
1658 bool hadAttr
= GetURIAttr(aAttr
, aBaseAttr
, getter_AddRefs(uri
));
1665 // Just return the attr value
1666 GetAttr(aAttr
, aResult
);
1672 CopyUTF8toUTF16(spec
, aResult
);
1675 bool nsGenericHTMLElement::GetURIAttr(nsAtom
* aAttr
, nsAtom
* aBaseAttr
,
1676 nsIURI
** aURI
) const {
1679 const nsAttrValue
* attr
= mAttrs
.GetAttr(aAttr
);
1684 nsCOMPtr
<nsIURI
> baseURI
= GetBaseURI();
1687 nsAutoString baseAttrValue
;
1688 if (GetAttr(aBaseAttr
, baseAttrValue
)) {
1689 nsCOMPtr
<nsIURI
> baseAttrURI
;
1690 nsresult rv
= nsContentUtils::NewURIWithDocumentCharset(
1691 getter_AddRefs(baseAttrURI
), baseAttrValue
, OwnerDoc(), baseURI
);
1692 if (NS_FAILED(rv
)) {
1695 baseURI
.swap(baseAttrURI
);
1699 // Don't care about return value. If it fails, we still want to
1700 // return true, and *aURI will be null.
1701 nsContentUtils::NewURIWithDocumentCharset(aURI
, attr
->GetStringValue(),
1702 OwnerDoc(), baseURI
);
1706 bool nsGenericHTMLElement::IsLabelable() const {
1707 return IsAnyOfHTMLElements(nsGkAtoms::progress
, nsGkAtoms::meter
);
1711 bool nsGenericHTMLElement::MatchLabelsElement(Element
* aElement
,
1712 int32_t aNamespaceID
,
1713 nsAtom
* aAtom
, void* aData
) {
1714 HTMLLabelElement
* element
= HTMLLabelElement::FromNode(aElement
);
1715 return element
&& element
->GetControl() == aData
;
1718 already_AddRefed
<nsINodeList
> nsGenericHTMLElement::Labels() {
1719 MOZ_ASSERT(IsLabelable(),
1720 "Labels() only allow labelable elements to use it.");
1721 nsExtendedDOMSlots
* slots
= ExtendedDOMSlots();
1723 if (!slots
->mLabelsList
) {
1724 slots
->mLabelsList
=
1725 new nsLabelsNodeList(SubtreeRoot(), MatchLabelsElement
, nullptr, this);
1728 RefPtr
<nsLabelsNodeList
> labels
= slots
->mLabelsList
;
1729 return labels
.forget();
1733 bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext
* aCx
,
1734 JSObject
* aGlobal
) {
1735 return TouchEvent::LegacyAPIEnabled(aCx
, aGlobal
);
1738 bool nsGenericHTMLElement::IsFormControlDefaultFocusable(
1739 bool aWithMouse
) const {
1743 switch (StaticPrefs::accessibility_mouse_focuses_formcontrol()) {
1749 return !IsInChromeDocument();
1753 //----------------------------------------------------------------------
1755 nsGenericHTMLFormElement::nsGenericHTMLFormElement(
1756 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
1757 : nsGenericHTMLElement(std::move(aNodeInfo
)) {
1758 // We should add the ElementState::ENABLED bit here as needed, but that
1759 // depends on our type, which is not initialized yet. So we have to do this
1760 // in subclasses. Same for a couple other bits.
1763 void nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm
,
1764 bool aUnbindOrDelete
) {
1765 MOZ_ASSERT(IsFormAssociatedElement());
1767 HTMLFormElement
* form
= GetFormInternal();
1768 NS_ASSERTION((form
!= nullptr) == HasFlag(ADDED_TO_FORM
),
1769 "Form control should have had flag set correctly");
1775 if (aRemoveFromForm
) {
1776 nsAutoString nameVal
, idVal
;
1777 GetAttr(nsGkAtoms::name
, nameVal
);
1778 GetAttr(nsGkAtoms::id
, idVal
);
1780 form
->RemoveElement(this, true);
1782 if (!nameVal
.IsEmpty()) {
1783 form
->RemoveElementFromTable(this, nameVal
);
1786 if (!idVal
.IsEmpty()) {
1787 form
->RemoveElementFromTable(this, idVal
);
1791 UnsetFlags(ADDED_TO_FORM
);
1792 SetFormInternal(nullptr, false);
1793 AfterClearForm(aUnbindOrDelete
);
1794 UpdateValidityElementStates(true);
1797 nsresult
nsGenericHTMLFormElement::BindToTree(BindContext
& aContext
,
1799 nsresult rv
= nsGenericHTMLElement::BindToTree(aContext
, aParent
);
1800 NS_ENSURE_SUCCESS(rv
, rv
);
1802 if (IsFormAssociatedElement()) {
1803 // If @form is set, the element *has* to be in a composed document,
1804 // otherwise it wouldn't be possible to find an element with the
1805 // corresponding id. If @form isn't set, the element *has* to have a parent,
1806 // otherwise it wouldn't be possible to find a form ancestor. We should not
1807 // call UpdateFormOwner if none of these conditions are fulfilled.
1808 if (HasAttr(nsGkAtoms::form
) ? IsInComposedDoc() : aParent
.IsContent()) {
1809 UpdateFormOwner(true, nullptr);
1813 // Set parent fieldset which should be used for the disabled state.
1814 UpdateFieldSet(false);
1818 void nsGenericHTMLFormElement::UnbindFromTree(bool aNullParent
) {
1819 // Save state before doing anything else.
1822 if (IsFormAssociatedElement()) {
1823 if (HTMLFormElement
* form
= GetFormInternal()) {
1824 // Might need to unset form
1826 // No more parent means no more form
1827 ClearForm(true, true);
1829 // Recheck whether we should still have an form.
1830 if (HasAttr(nsGkAtoms::form
) || !FindAncestorForm(form
)) {
1831 ClearForm(true, true);
1833 UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT
);
1838 // We have to remove the form id observer if there was one.
1839 // We will re-add one later if needed (during bind to tree).
1840 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None
,
1842 RemoveFormIdObserver();
1846 nsGenericHTMLElement::UnbindFromTree(aNullParent
);
1848 // The element might not have a fieldset anymore.
1849 UpdateFieldSet(false);
1852 void nsGenericHTMLFormElement::BeforeSetAttr(int32_t aNameSpaceID
,
1854 const nsAttrValue
* aValue
,
1856 if (aNameSpaceID
== kNameSpaceID_None
&& IsFormAssociatedElement()) {
1858 HTMLFormElement
* form
= GetFormInternal();
1860 // remove the control from the hashtable as needed
1862 if (form
&& (aName
== nsGkAtoms::name
|| aName
== nsGkAtoms::id
)) {
1863 GetAttr(aName
, tmp
);
1865 if (!tmp
.IsEmpty()) {
1866 form
->RemoveElementFromTable(this, tmp
);
1870 if (form
&& aName
== nsGkAtoms::type
) {
1871 GetAttr(nsGkAtoms::name
, tmp
);
1873 if (!tmp
.IsEmpty()) {
1874 form
->RemoveElementFromTable(this, tmp
);
1877 GetAttr(nsGkAtoms::id
, tmp
);
1879 if (!tmp
.IsEmpty()) {
1880 form
->RemoveElementFromTable(this, tmp
);
1883 form
->RemoveElement(this, false);
1886 if (aName
== nsGkAtoms::form
) {
1887 // If @form isn't set or set to the empty string, there were no observer
1888 // so we don't have to remove it.
1889 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None
,
1891 // The current form id observer is no longer needed.
1892 // A new one may be added in AfterSetAttr.
1893 RemoveFormIdObserver();
1898 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID
, aName
, aValue
,
1902 void nsGenericHTMLFormElement::AfterSetAttr(
1903 int32_t aNameSpaceID
, nsAtom
* aName
, const nsAttrValue
* aValue
,
1904 const nsAttrValue
* aOldValue
, nsIPrincipal
* aMaybeScriptedPrincipal
,
1906 if (aNameSpaceID
== kNameSpaceID_None
&& IsFormAssociatedElement()) {
1907 HTMLFormElement
* form
= GetFormInternal();
1909 // add the control to the hashtable as needed
1910 if (form
&& (aName
== nsGkAtoms::name
|| aName
== nsGkAtoms::id
) &&
1911 aValue
&& !aValue
->IsEmptyString()) {
1912 MOZ_ASSERT(aValue
->Type() == nsAttrValue::eAtom
,
1913 "Expected atom value for name/id");
1914 form
->AddElementToTable(this,
1915 nsDependentAtomString(aValue
->GetAtomValue()));
1918 if (form
&& aName
== nsGkAtoms::type
) {
1921 GetAttr(nsGkAtoms::name
, tmp
);
1923 if (!tmp
.IsEmpty()) {
1924 form
->AddElementToTable(this, tmp
);
1927 GetAttr(nsGkAtoms::id
, tmp
);
1929 if (!tmp
.IsEmpty()) {
1930 form
->AddElementToTable(this, tmp
);
1933 form
->AddElement(this, false, aNotify
);
1936 if (aName
== nsGkAtoms::form
) {
1937 // We need a new form id observer.
1938 DocumentOrShadowRoot
* docOrShadow
=
1939 GetUncomposedDocOrConnectedShadowRoot();
1941 Element
* formIdElement
= nullptr;
1942 if (aValue
&& !aValue
->IsEmptyString()) {
1943 formIdElement
= AddFormIdObserver();
1946 // Because we have a new @form value (or no more @form), we have to
1947 // update our form owner.
1948 UpdateFormOwner(false, formIdElement
);
1953 return nsGenericHTMLElement::AfterSetAttr(
1954 aNameSpaceID
, aName
, aValue
, aOldValue
, aMaybeScriptedPrincipal
, aNotify
);
1957 void nsGenericHTMLFormElement::ForgetFieldSet(nsIContent
* aFieldset
) {
1958 MOZ_DIAGNOSTIC_ASSERT(IsFormAssociatedElement());
1959 if (GetFieldSetInternal() == aFieldset
) {
1960 SetFieldSetInternal(nullptr);
1964 Element
* nsGenericHTMLFormElement::AddFormIdObserver() {
1965 MOZ_ASSERT(IsFormAssociatedElement());
1967 nsAutoString formId
;
1968 DocumentOrShadowRoot
* docOrShadow
= GetUncomposedDocOrConnectedShadowRoot();
1969 GetAttr(nsGkAtoms::form
, formId
);
1970 NS_ASSERTION(!formId
.IsEmpty(),
1971 "@form value should not be the empty string!");
1972 RefPtr
<nsAtom
> atom
= NS_Atomize(formId
);
1974 return docOrShadow
->AddIDTargetObserver(atom
, FormIdUpdated
, this, false);
1977 void nsGenericHTMLFormElement::RemoveFormIdObserver() {
1978 MOZ_ASSERT(IsFormAssociatedElement());
1980 DocumentOrShadowRoot
* docOrShadow
= GetUncomposedDocOrConnectedShadowRoot();
1985 nsAutoString formId
;
1986 GetAttr(nsGkAtoms::form
, formId
);
1987 NS_ASSERTION(!formId
.IsEmpty(),
1988 "@form value should not be the empty string!");
1989 RefPtr
<nsAtom
> atom
= NS_Atomize(formId
);
1991 docOrShadow
->RemoveIDTargetObserver(atom
, FormIdUpdated
, this, false);
1995 bool nsGenericHTMLFormElement::FormIdUpdated(Element
* aOldElement
,
1996 Element
* aNewElement
,
1998 nsGenericHTMLFormElement
* element
=
1999 static_cast<nsGenericHTMLFormElement
*>(aData
);
2001 NS_ASSERTION(element
->IsHTMLElement(), "aData should be an HTML element");
2003 element
->UpdateFormOwner(false, aNewElement
);
2008 bool nsGenericHTMLFormElement::IsElementDisabledForEvents(WidgetEvent
* aEvent
,
2012 // Allow dispatch of CustomEvent and untrusted Events.
2013 if (!aEvent
->IsTrusted()) {
2017 switch (aEvent
->mMessage
) {
2018 case eAnimationStart
:
2020 case eAnimationIteration
:
2021 case eAnimationCancel
:
2033 case eTransitionCancel
:
2034 case eTransitionEnd
:
2035 case eTransitionRun
:
2036 case eTransitionStart
:
2038 case eLegacyMouseLineOrPageScroll
:
2039 case eLegacyMousePixelScroll
:
2048 if (StaticPrefs::dom_forms_always_allow_key_and_focus_events_enabled()) {
2054 case ePointerCancel
:
2055 case ePointerGotCapture
:
2056 case ePointerLostCapture
:
2057 if (StaticPrefs::dom_forms_always_allow_pointer_events_enabled()) {
2065 if (aEvent
->mSpecifiedEventType
== nsGkAtoms::oninput
) {
2069 // FIXME(emilio): This poking at the style of the frame is slightly bogus
2070 // unless we flush before every event, which we don't really want to do.
2071 if (aFrame
&& aFrame
->StyleUI()->UserInput() == StyleUserInput::None
) {
2075 return IsDisabled();
2078 void nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree
,
2079 Element
* aFormIdElement
) {
2080 MOZ_ASSERT(IsFormAssociatedElement());
2081 MOZ_ASSERT(!aBindToTree
|| !aFormIdElement
,
2082 "aFormIdElement shouldn't be set if aBindToTree is true!");
2084 HTMLFormElement
* form
= GetFormInternal();
2086 ClearForm(true, false);
2090 HTMLFormElement
* oldForm
= form
;
2092 // If @form is set, we have to use that to find the form.
2093 nsAutoString formId
;
2094 if (GetAttr(nsGkAtoms::form
, formId
)) {
2095 if (!formId
.IsEmpty()) {
2096 Element
* element
= nullptr;
2099 element
= AddFormIdObserver();
2101 element
= aFormIdElement
;
2104 NS_ASSERTION(!IsInComposedDoc() ||
2105 element
== GetUncomposedDocOrConnectedShadowRoot()
2106 ->GetElementById(formId
),
2107 "element should be equals to the current element "
2108 "associated with the id in @form!");
2110 if (element
&& element
->IsHTMLElement(nsGkAtoms::form
) &&
2111 nsContentUtils::IsInSameAnonymousTree(this, element
)) {
2112 form
= static_cast<HTMLFormElement
*>(element
);
2113 SetFormInternal(form
, aBindToTree
);
2117 // We now have a parent, so we may have picked up an ancestor form. Search
2118 // for it. Note that if form is already set we don't want to do this,
2119 // because that means someone (probably the content sink) has already set
2120 // it to the right value. Also note that even if being bound here didn't
2121 // change our parent, we still need to search, since our parent chain
2122 // probably changed _somewhere_.
2123 form
= FindAncestorForm();
2124 SetFormInternal(form
, aBindToTree
);
2128 if (form
&& !HasFlag(ADDED_TO_FORM
)) {
2129 // Now we need to add ourselves to the form
2130 nsAutoString nameVal
, idVal
;
2131 GetAttr(nsGkAtoms::name
, nameVal
);
2132 GetAttr(nsGkAtoms::id
, idVal
);
2134 SetFlags(ADDED_TO_FORM
);
2136 // Notify only if we just found this form.
2137 form
->AddElement(this, true, oldForm
== nullptr);
2139 if (!nameVal
.IsEmpty()) {
2140 form
->AddElementToTable(this, nameVal
);
2143 if (!idVal
.IsEmpty()) {
2144 form
->AddElementToTable(this, idVal
);
2148 if (form
!= oldForm
) {
2149 // ui-valid / invalid depends on the form for some elements
2150 UpdateValidityElementStates(true);
2154 void nsGenericHTMLFormElement::UpdateFieldSet(bool aNotify
) {
2155 if (IsInNativeAnonymousSubtree() || !IsFormAssociatedElement()) {
2156 MOZ_ASSERT_IF(IsFormAssociatedElement(), !GetFieldSetInternal());
2160 nsIContent
* parent
= nullptr;
2161 nsIContent
* prev
= nullptr;
2162 HTMLFieldSetElement
* fieldset
= GetFieldSetInternal();
2164 for (parent
= GetParent(); parent
;
2165 prev
= parent
, parent
= parent
->GetParent()) {
2166 HTMLFieldSetElement
* parentFieldset
= HTMLFieldSetElement::FromNode(parent
);
2167 if (parentFieldset
&& (!prev
|| parentFieldset
->GetFirstLegend() != prev
)) {
2168 if (fieldset
== parentFieldset
) {
2169 // We already have the right fieldset;
2174 fieldset
->RemoveElement(this);
2176 SetFieldSetInternal(parentFieldset
);
2177 parentFieldset
->AddElement(this);
2179 // The disabled state may have changed
2180 FieldSetDisabledChanged(aNotify
);
2185 // No fieldset found.
2187 fieldset
->RemoveElement(this);
2188 SetFieldSetInternal(nullptr);
2189 // The disabled state may have changed
2190 FieldSetDisabledChanged(aNotify
);
2194 void nsGenericHTMLFormElement::UpdateDisabledState(bool aNotify
) {
2195 if (!CanBeDisabled()) {
2199 HTMLFieldSetElement
* fieldset
= GetFieldSetInternal();
2200 const bool isDisabled
=
2201 HasAttr(nsGkAtoms::disabled
) || (fieldset
&& fieldset
->IsDisabled());
2203 const ElementState disabledStates
=
2204 isDisabled
? ElementState::DISABLED
: ElementState::ENABLED
;
2206 ElementState oldDisabledStates
= State() & ElementState::DISABLED_STATES
;
2207 ElementState changedStates
= disabledStates
^ oldDisabledStates
;
2209 if (!changedStates
.IsEmpty()) {
2210 ToggleStates(changedStates
, aNotify
);
2211 if (DoesReadOnlyApply()) {
2212 // :disabled influences :read-only / :read-write.
2213 UpdateReadOnlyState(aNotify
);
2218 bool nsGenericHTMLFormElement::IsReadOnlyInternal() const {
2219 if (DoesReadOnlyApply()) {
2220 return IsDisabled() || GetBoolAttr(nsGkAtoms::readonly
);
2222 return nsGenericHTMLElement::IsReadOnlyInternal();
2225 void nsGenericHTMLFormElement::FieldSetDisabledChanged(bool aNotify
) {
2226 UpdateDisabledState(aNotify
);
2229 void nsGenericHTMLFormElement::SaveSubtreeState() {
2232 nsGenericHTMLElement::SaveSubtreeState();
2235 //----------------------------------------------------------------------
2237 void nsGenericHTMLElement::Click(CallerType aCallerType
) {
2238 if (HandlingClick()) {
2242 // There are two notions of disabled.
2244 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled
2245 // "actually disabled":
2246 // https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled
2247 // click() reads the former but IsDisabled() is for the latter. <fieldset> is
2248 // included only in the latter, so we exclude it here.
2249 // XXX(krosylight): What about <optgroup>? And should we add a separate method
2252 !(mNodeInfo
->Equals(nsGkAtoms::fieldset
) &&
2253 StaticPrefs::dom_forms_fieldset_disable_only_descendants_enabled())) {
2257 // Strong in case the event kills it
2258 nsCOMPtr
<Document
> doc
= GetComposedDoc();
2260 RefPtr
<nsPresContext
> context
;
2262 PresShell
* presShell
= doc
->GetPresShell();
2264 // We need the nsPresContext for dispatching the click event. In some
2265 // rare cases we need to flush notifications to force creation of the
2266 // nsPresContext here (for example when a script calls button.click()
2267 // from script early during page load). We only flush the notifications
2268 // if the PresShell hasn't been created yet, to limit the performance
2270 doc
->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames
);
2271 presShell
= doc
->GetPresShell();
2274 context
= presShell
->GetPresContext();
2280 // Mark this event trusted if Click() is called from system code.
2281 WidgetMouseEvent
event(aCallerType
== CallerType::System
, eMouseClick
,
2282 nullptr, WidgetMouseEvent::eReal
);
2283 event
.mFlags
.mIsPositionless
= true;
2284 event
.mInputSource
= MouseEvent_Binding::MOZ_SOURCE_UNKNOWN
;
2286 EventDispatcher::Dispatch(this, context
, &event
);
2288 ClearHandlingClick();
2291 bool nsGenericHTMLElement::IsHTMLFocusable(bool aWithMouse
, bool* aIsFocusable
,
2292 int32_t* aTabIndex
) {
2293 MOZ_ASSERT(aIsFocusable
);
2294 MOZ_ASSERT(aTabIndex
);
2295 if (ShadowRoot
* root
= GetShadowRoot()) {
2296 if (root
->DelegatesFocus()) {
2297 *aIsFocusable
= false;
2302 if (!IsInComposedDoc() || IsInDesignMode()) {
2303 // In designMode documents we only allow focusing the document.
2305 *aIsFocusable
= false;
2309 *aTabIndex
= TabIndex();
2310 bool disabled
= false;
2311 bool disallowOverridingFocusability
= true;
2312 Maybe
<int32_t> attrVal
= GetTabIndexAttrValue();
2313 if (IsEditingHost()) {
2314 // Editable roots should always be focusable.
2315 disallowOverridingFocusability
= true;
2317 // Ignore the disabled attribute in editable contentEditable/designMode
2319 if (attrVal
.isNothing()) {
2320 // The default value for tabindex should be 0 for editable
2321 // contentEditable roots.
2325 disallowOverridingFocusability
= false;
2327 // Just check for disabled attribute on form controls
2328 disabled
= IsDisabled();
2334 // If a tabindex is specified at all, or the default tabindex is 0, we're
2336 *aIsFocusable
= (*aTabIndex
>= 0 || (!disabled
&& attrVal
.isSome()));
2337 return disallowOverridingFocusability
;
2340 Result
<bool, nsresult
> nsGenericHTMLElement::PerformAccesskey(
2341 bool aKeyCausesActivation
, bool aIsTrustedEvent
) {
2342 RefPtr
<nsPresContext
> presContext
= GetPresContext(eForComposedDoc
);
2344 return Err(NS_ERROR_UNEXPECTED
);
2347 // It's hard to say what HTML4 wants us to do in all cases.
2348 bool focused
= true;
2349 if (RefPtr
<nsFocusManager
> fm
= nsFocusManager::GetFocusManager()) {
2350 fm
->SetFocus(this, nsIFocusManager::FLAG_BYKEY
);
2352 // Return true if the element became the current focus within its window.
2353 nsPIDOMWindowOuter
* window
= OwnerDoc()->GetWindow();
2354 focused
= window
&& window
->GetFocusedElement() == this;
2357 if (aKeyCausesActivation
) {
2358 // Click on it if the users prefs indicate to do so.
2359 AutoHandlingUserInputStatePusher
userInputStatePusher(aIsTrustedEvent
);
2360 AutoPopupStatePusher
popupStatePusher(
2361 aIsTrustedEvent
? PopupBlocker::openAllowed
: PopupBlocker::openAbused
);
2362 DispatchSimulatedClick(this, aIsTrustedEvent
, presContext
);
2366 // If the accesskey won't cause the activation and the focus isn't changed,
2367 // either. Return error so EventStateManager would try to find next element
2368 // to handle the accesskey.
2369 return focused
? Result
<bool, nsresult
>{focused
} : Err(NS_ERROR_ABORT
);
2372 void nsGenericHTMLElement::HandleKeyboardActivation(
2373 EventChainPostVisitor
& aVisitor
) {
2374 MOZ_ASSERT(aVisitor
.mEvent
->HasKeyEventMessage());
2375 MOZ_ASSERT(aVisitor
.mEvent
->IsTrusted());
2377 // If focused element is different from this element, it may be editable.
2378 // In that case, associated editor for the element should handle the keyboard
2379 // instead. Therefore, if this is not the focused element, we should not
2380 // handle the event here. Note that this element may be an editing host,
2381 // i.e., focused and editable. In the case, keyboard events should be
2382 // handled by the focused element instead of associated editor because
2383 // Chrome handles the case so. For compatibility with Chrome, we follow them.
2384 if (nsFocusManager::GetFocusedElementStatic() != this) {
2388 const auto message
= aVisitor
.mEvent
->mMessage
;
2389 const WidgetKeyboardEvent
* keyEvent
= aVisitor
.mEvent
->AsKeyboardEvent();
2390 if (nsEventStatus_eIgnore
!= aVisitor
.mEventStatus
) {
2391 if (message
== eKeyUp
&& keyEvent
->mKeyCode
== NS_VK_SPACE
) {
2392 // Unset the flag even if the event is default-prevented or something.
2393 UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD
);
2398 bool shouldActivate
= false;
2401 if (keyEvent
->ShouldWorkAsSpaceKey()) {
2402 SetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD
);
2406 shouldActivate
= keyEvent
->mKeyCode
== NS_VK_RETURN
;
2407 if (keyEvent
->ShouldWorkAsSpaceKey()) {
2408 // Consume 'space' key to prevent scrolling the page down.
2409 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
2413 shouldActivate
= keyEvent
->ShouldWorkAsSpaceKey() &&
2414 HasFlag(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD
);
2415 if (shouldActivate
) {
2416 UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD
);
2420 MOZ_ASSERT_UNREACHABLE("why didn't we bail out earlier?");
2424 if (!shouldActivate
) {
2428 RefPtr
<nsPresContext
> presContext
= aVisitor
.mPresContext
;
2429 DispatchSimulatedClick(this, aVisitor
.mEvent
->IsTrusted(), presContext
);
2430 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
2433 nsresult
nsGenericHTMLElement::DispatchSimulatedClick(
2434 nsGenericHTMLElement
* aElement
, bool aIsTrusted
,
2435 nsPresContext
* aPresContext
) {
2436 WidgetMouseEvent
event(aIsTrusted
, eMouseClick
, nullptr,
2437 WidgetMouseEvent::eReal
);
2438 event
.mInputSource
= MouseEvent_Binding::MOZ_SOURCE_KEYBOARD
;
2439 event
.mFlags
.mIsPositionless
= true;
2440 return EventDispatcher::Dispatch(aElement
, aPresContext
, &event
);
2443 already_AddRefed
<EditorBase
> nsGenericHTMLElement::GetAssociatedEditor() {
2444 // If contenteditable is ever implemented, it might need to do something
2447 RefPtr
<TextEditor
> textEditor
= GetTextEditorInternal();
2448 return textEditor
.forget();
2452 void nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent
* content
) {
2453 /* Sync this node */
2454 nsGenericHTMLElement
* element
= FromNode(content
);
2456 if (RefPtr
<EditorBase
> editorBase
= element
->GetAssociatedEditor()) {
2457 editorBase
->SyncRealTimeSpell();
2461 /* Sync all children */
2462 for (nsIContent
* child
= content
->GetFirstChild(); child
;
2463 child
= child
->GetNextSibling()) {
2464 SyncEditorsOnSubtree(child
);
2468 static void MakeContentDescendantsEditable(nsIContent
* aContent
) {
2469 // If aContent is not an element, we just need to update its
2470 // internal editable state and don't need to notify anyone about
2471 // that. For elements, we need to send a ElementStateChanged
2473 if (!aContent
->IsElement()) {
2474 aContent
->UpdateEditableState(false);
2478 Element
* element
= aContent
->AsElement();
2480 element
->UpdateEditableState(true);
2482 for (nsIContent
* child
= aContent
->GetFirstChild(); child
;
2483 child
= child
->GetNextSibling()) {
2484 if (!child
->IsElement() ||
2485 !child
->AsElement()->HasAttr(nsGkAtoms::contenteditable
)) {
2486 MakeContentDescendantsEditable(child
);
2491 void nsGenericHTMLElement::ChangeEditableState(int32_t aChange
) {
2492 Document
* document
= GetComposedDoc();
2497 Document::EditingState previousEditingState
= Document::EditingState::eOff
;
2499 document
->ChangeContentEditableCount(this, aChange
);
2500 previousEditingState
= document
->GetEditingState();
2503 // MakeContentDescendantsEditable is going to call ElementStateChanged for
2504 // this element and all descendants if editable state has changed.
2505 // We might as well wrap it all in one script blocker.
2506 nsAutoScriptBlocker scriptBlocker
;
2507 MakeContentDescendantsEditable(this);
2509 // If the document already had contenteditable and JS adds new
2510 // contenteditable, that might cause changing editing host to current editing
2511 // host's ancestor. In such case, HTMLEditor needs to know that
2512 // synchronously to update selection limitter.
2513 // Additionally, elements in shadow DOM is not editable in the normal cases,
2514 // but if its content has `contenteditable`, only in it can be ediable.
2515 // So we don't need to notify HTMLEditor of this change only when we're not
2516 // in shadow DOM and the composed document is in design mode.
2517 if (IsInDesignMode() && !IsInShadowTree() && aChange
> 0 &&
2518 previousEditingState
== Document::EditingState::eContentEditable
) {
2519 if (HTMLEditor
* htmlEditor
=
2520 nsContentUtils::GetHTMLEditor(document
->GetPresContext())) {
2521 htmlEditor
->NotifyEditingHostMaybeChanged();
2526 //----------------------------------------------------------------------
2528 nsGenericHTMLFormControlElement::nsGenericHTMLFormControlElement(
2529 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
, FormControlType aType
)
2530 : nsGenericHTMLFormElement(std::move(aNodeInfo
)),
2531 nsIFormControl(aType
),
2533 mFieldSet(nullptr) {}
2535 nsGenericHTMLFormControlElement::~nsGenericHTMLFormControlElement() {
2537 mFieldSet
->RemoveElement(this);
2540 // Check that this element doesn't know anything about its form at this point.
2541 NS_ASSERTION(!mForm
, "mForm should be null at this point!");
2544 NS_IMPL_ISUPPORTS_INHERITED(nsGenericHTMLFormControlElement
,
2545 nsGenericHTMLFormElement
, nsIFormControl
)
2547 nsINode
* nsGenericHTMLFormControlElement::GetScopeChainParent() const {
2548 return mForm
? mForm
: nsGenericHTMLElement::GetScopeChainParent();
2551 nsIContent::IMEState
nsGenericHTMLFormControlElement::GetDesiredIMEState() {
2552 TextEditor
* textEditor
= GetTextEditorInternal();
2554 return nsGenericHTMLFormElement::GetDesiredIMEState();
2557 nsresult rv
= textEditor
->GetPreferredIMEState(&state
);
2558 if (NS_FAILED(rv
)) {
2559 return nsGenericHTMLFormElement::GetDesiredIMEState();
2564 void nsGenericHTMLFormControlElement::GetAutocapitalize(
2565 nsAString
& aValue
) const {
2566 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None
,
2567 nsGkAtoms::autocapitalize
)) {
2568 nsGenericHTMLFormElement::GetAutocapitalize(aValue
);
2572 if (mForm
&& IsAutocapitalizeInheriting()) {
2573 mForm
->GetAutocapitalize(aValue
);
2577 bool nsGenericHTMLFormControlElement::IsHTMLFocusable(bool aWithMouse
,
2579 int32_t* aTabIndex
) {
2580 if (nsGenericHTMLFormElement::IsHTMLFocusable(aWithMouse
, aIsFocusable
,
2585 *aIsFocusable
= *aIsFocusable
&& IsFormControlDefaultFocusable(aWithMouse
);
2589 void nsGenericHTMLFormControlElement::GetEventTargetParent(
2590 EventChainPreVisitor
& aVisitor
) {
2591 if (aVisitor
.mEvent
->IsTrusted() && (aVisitor
.mEvent
->mMessage
== eFocus
||
2592 aVisitor
.mEvent
->mMessage
== eBlur
)) {
2593 // We have to handle focus/blur event to change focus states in
2594 // PreHandleEvent to prevent it breaks event target chain creation.
2595 aVisitor
.mWantsPreHandleEvent
= true;
2597 nsGenericHTMLFormElement::GetEventTargetParent(aVisitor
);
2600 nsresult
nsGenericHTMLFormControlElement::PreHandleEvent(
2601 EventChainVisitor
& aVisitor
) {
2602 if (aVisitor
.mEvent
->IsTrusted()) {
2603 switch (aVisitor
.mEvent
->mMessage
) {
2605 // Check to see if focus has bubbled up from a form control's
2606 // child textfield or button. If that's the case, don't focus
2607 // this parent file control -- leave focus on the child.
2608 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(true);
2609 if (formControlFrame
&&
2610 aVisitor
.mEvent
->mOriginalTarget
== static_cast<nsINode
*>(this)) {
2611 formControlFrame
->SetFocus(true, true);
2616 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(true);
2617 if (formControlFrame
) {
2618 formControlFrame
->SetFocus(false, false);
2626 return nsGenericHTMLFormElement::PreHandleEvent(aVisitor
);
2629 HTMLFieldSetElement
* nsGenericHTMLFormControlElement::GetFieldSet() {
2630 return GetFieldSetInternal();
2633 void nsGenericHTMLFormControlElement::SetForm(HTMLFormElement
* aForm
) {
2634 MOZ_ASSERT(aForm
, "Don't pass null here");
2635 NS_ASSERTION(!mForm
,
2636 "We don't support switching from one non-null form to another.");
2638 SetFormInternal(aForm
, false);
2641 void nsGenericHTMLFormControlElement::ClearForm(bool aRemoveFromForm
,
2642 bool aUnbindOrDelete
) {
2643 nsGenericHTMLFormElement::ClearForm(aRemoveFromForm
, aUnbindOrDelete
);
2646 bool nsGenericHTMLFormControlElement::IsLabelable() const {
2647 auto type
= ControlType();
2648 return (IsInputElement(type
) && type
!= FormControlType::InputHidden
) ||
2649 IsButtonElement(type
) || type
== FormControlType::Output
||
2650 type
== FormControlType::Select
|| type
== FormControlType::Textarea
;
2653 bool nsGenericHTMLFormControlElement::CanBeDisabled() const {
2654 auto type
= ControlType();
2655 // It's easier to test the types that _cannot_ be disabled
2656 return type
!= FormControlType::Object
&& type
!= FormControlType::Output
;
2659 bool nsGenericHTMLFormControlElement::DoesReadOnlyApply() const {
2660 auto type
= ControlType();
2661 if (!IsInputElement(type
) && type
!= FormControlType::Textarea
) {
2666 case FormControlType::InputHidden
:
2667 case FormControlType::InputButton
:
2668 case FormControlType::InputImage
:
2669 case FormControlType::InputReset
:
2670 case FormControlType::InputSubmit
:
2671 case FormControlType::InputRadio
:
2672 case FormControlType::InputFile
:
2673 case FormControlType::InputCheckbox
:
2674 case FormControlType::InputRange
:
2675 case FormControlType::InputColor
:
2678 case FormControlType::Textarea
:
2679 case FormControlType::InputText
:
2680 case FormControlType::InputPassword
:
2681 case FormControlType::InputSearch
:
2682 case FormControlType::InputTel
:
2683 case FormControlType::InputEmail
:
2684 case FormControlType::InputUrl
:
2685 case FormControlType::InputNumber
:
2686 case FormControlType::InputDate
:
2687 case FormControlType::InputTime
:
2688 case FormControlType::InputMonth
:
2689 case FormControlType::InputWeek
:
2690 case FormControlType::InputDatetimeLocal
:
2693 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesReadOnlyApply()");
2702 void nsGenericHTMLFormControlElement::SetFormInternal(HTMLFormElement
* aForm
,
2705 BeforeSetForm(aForm
, aBindToTree
);
2708 // keep a *weak* ref to the form here
2712 HTMLFormElement
* nsGenericHTMLFormControlElement::GetFormInternal() const {
2716 HTMLFieldSetElement
* nsGenericHTMLFormControlElement::GetFieldSetInternal()
2721 void nsGenericHTMLFormControlElement::SetFieldSetInternal(
2722 HTMLFieldSetElement
* aFieldset
) {
2723 mFieldSet
= aFieldset
;
2726 void nsGenericHTMLFormControlElement::UpdateRequiredState(bool aIsRequired
,
2729 auto type
= ControlType();
2731 MOZ_ASSERT(IsInputElement(type
) || type
== FormControlType::Select
||
2732 type
== FormControlType::Textarea
,
2733 "This should be called only on types that @required applies");
2736 if (HTMLInputElement
* input
= HTMLInputElement::FromNode(this)) {
2738 input
->DoesRequiredApply(),
2739 "This should be called only on input types that @required applies");
2743 ElementState requiredStates
;
2745 requiredStates
|= ElementState::REQUIRED
;
2747 requiredStates
|= ElementState::OPTIONAL_
;
2750 ElementState oldRequiredStates
= State() & ElementState::REQUIRED_STATES
;
2751 ElementState changedStates
= requiredStates
^ oldRequiredStates
;
2753 if (!changedStates
.IsEmpty()) {
2754 ToggleStates(changedStates
, aNotify
);
2758 bool nsGenericHTMLFormControlElement::IsAutocapitalizeInheriting() const {
2759 auto type
= ControlType();
2760 return IsInputElement(type
) || IsButtonElement(type
) ||
2761 type
== FormControlType::Fieldset
|| type
== FormControlType::Output
||
2762 type
== FormControlType::Select
|| type
== FormControlType::Textarea
;
2765 nsresult
nsGenericHTMLFormControlElement::SubmitDirnameDir(
2766 FormData
* aFormData
) {
2767 // Submit dirname=dir if element has non-empty dirname attribute
2768 if (HasAttr(nsGkAtoms::dirname
)) {
2769 nsAutoString dirname
;
2770 GetAttr(nsGkAtoms::dirname
, dirname
);
2771 if (!dirname
.IsEmpty()) {
2772 const Directionality eDir
= GetDirectionality();
2773 MOZ_ASSERT(eDir
== eDir_RTL
|| eDir
== eDir_LTR
,
2774 "The directionality of an element is either ltr or rtl");
2775 const nsString dir
= eDir
== eDir_LTR
? u
"ltr"_ns
: u
"rtl"_ns
;
2776 return aFormData
->AddNameValuePair(dirname
, dir
);
2782 //----------------------------------------------------------------------
2784 static const nsAttrValue::EnumTable kPopoverTargetActionTable
[] = {
2785 {"toggle", PopoverTargetAction::Toggle
},
2786 {"show", PopoverTargetAction::Show
},
2787 {"hide", PopoverTargetAction::Hide
},
2790 static const nsAttrValue::EnumTable
* kPopoverTargetActionDefault
=
2791 &kPopoverTargetActionTable
[0];
2793 nsGenericHTMLFormControlElementWithState::
2794 nsGenericHTMLFormControlElementWithState(
2795 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
2796 FromParser aFromParser
, FormControlType aType
)
2797 : nsGenericHTMLFormControlElement(std::move(aNodeInfo
), aType
),
2798 mControlNumber(!!(aFromParser
& FROM_PARSER_NETWORK
)
2799 ? OwnerDoc()->GetNextControlNumber()
2801 mStateKey
.SetIsVoid(true);
2804 bool nsGenericHTMLFormControlElementWithState::ParseAttribute(
2805 int32_t aNamespaceID
, nsAtom
* aAttribute
, const nsAString
& aValue
,
2806 nsIPrincipal
* aMaybeScriptedPrincipal
, nsAttrValue
& aResult
) {
2807 if (aNamespaceID
== kNameSpaceID_None
) {
2808 if (StaticPrefs::dom_element_popover_enabled()) {
2809 if (aAttribute
== nsGkAtoms::popovertargetaction
) {
2810 return aResult
.ParseEnumValue(aValue
, kPopoverTargetActionTable
, false,
2811 kPopoverTargetActionDefault
);
2813 if (aAttribute
== nsGkAtoms::popovertarget
) {
2814 aResult
.ParseAtom(aValue
);
2819 if (StaticPrefs::dom_element_invokers_enabled()) {
2820 if (aAttribute
== nsGkAtoms::invokeaction
) {
2821 aResult
.ParseAtom(aValue
);
2824 if (aAttribute
== nsGkAtoms::invoketarget
) {
2825 aResult
.ParseAtom(aValue
);
2831 return nsGenericHTMLFormControlElement::ParseAttribute(
2832 aNamespaceID
, aAttribute
, aValue
, aMaybeScriptedPrincipal
, aResult
);
2835 mozilla::dom::Element
*
2836 nsGenericHTMLFormControlElementWithState::GetPopoverTargetElement() const {
2837 return GetAttrAssociatedElement(nsGkAtoms::popovertarget
);
2840 void nsGenericHTMLFormControlElementWithState::SetPopoverTargetElement(
2841 mozilla::dom::Element
* aElement
) {
2842 ExplicitlySetAttrElement(nsGkAtoms::popovertarget
, aElement
);
2845 void nsGenericHTMLFormControlElementWithState::HandlePopoverTargetAction() {
2846 RefPtr
<nsGenericHTMLElement
> target
= GetEffectivePopoverTargetElement();
2851 auto action
= PopoverTargetAction::Toggle
;
2852 if (const nsAttrValue
* value
=
2853 GetParsedAttr(nsGkAtoms::popovertargetaction
)) {
2854 MOZ_ASSERT(value
->Type() == nsAttrValue::eEnum
);
2855 action
= static_cast<PopoverTargetAction
>(value
->GetEnumValue());
2858 bool canHide
= action
== PopoverTargetAction::Hide
||
2859 action
== PopoverTargetAction::Toggle
;
2860 bool shouldHide
= canHide
&& target
->IsPopoverOpen();
2861 bool canShow
= action
== PopoverTargetAction::Show
||
2862 action
== PopoverTargetAction::Toggle
;
2863 bool shouldShow
= canShow
&& !target
->IsPopoverOpen();
2866 target
->HidePopover(IgnoreErrors());
2867 } else if (shouldShow
) {
2868 target
->ShowPopoverInternal(this, IgnoreErrors());
2870 #ifdef ACCESSIBILITY
2871 // Notify the accessibility service about the change.
2872 if (shouldHide
|| shouldShow
) {
2873 if (RefPtr
<Document
> doc
= GetComposedDoc()) {
2874 if (PresShell
* presShell
= doc
->GetPresShell()) {
2875 if (nsAccessibilityService
* accService
= GetAccService()) {
2876 accService
->PopovertargetMaybeChanged(presShell
, this);
2884 void nsGenericHTMLFormControlElementWithState::GetInvokeAction(
2885 nsAString
& aValue
) const {
2886 GetInvokeAction()->ToString(aValue
);
2889 nsAtom
* nsGenericHTMLFormControlElementWithState::GetInvokeAction() const {
2890 const nsAttrValue
* attr
= GetParsedAttr(nsGkAtoms::invokeaction
);
2891 if (attr
&& attr
->GetAtomValue() != nsGkAtoms::_empty
) {
2892 return attr
->GetAtomValue();
2894 return nsGkAtoms::_auto
;
2897 mozilla::dom::Element
*
2898 nsGenericHTMLFormControlElementWithState::GetInvokeTargetElement() const {
2899 if (StaticPrefs::dom_element_invokers_enabled()) {
2900 return GetAttrAssociatedElement(nsGkAtoms::invoketarget
);
2905 void nsGenericHTMLFormControlElementWithState::SetInvokeTargetElement(
2906 mozilla::dom::Element
* aElement
) {
2907 ExplicitlySetAttrElement(nsGkAtoms::invoketarget
, aElement
);
2910 void nsGenericHTMLFormControlElementWithState::HandleInvokeTargetAction() {
2911 // 1. Let invokee be node's invoke target element.
2912 RefPtr
<Element
> invokee
= GetInvokeTargetElement();
2914 // 2. If invokee is null, then return.
2919 // 3. Let action be node's invokeaction attribute
2920 // 4. If action is null or empty, then let action be the string "auto".
2921 RefPtr
<nsAtom
> aAction
= GetInvokeAction();
2922 MOZ_ASSERT(!aAction
->IsEmpty(), "Action should not be empty");
2924 // 5. Let notCancelled be the result of firing an event named invoke at
2925 // invokee with its action set to action, its invoker set to node,
2926 // and its cancelable attribute initialized to true.
2927 InvokeEventInit init
;
2928 aAction
->ToString(init
.mAction
);
2929 init
.mInvoker
= this;
2930 init
.mCancelable
= true;
2931 init
.mComposed
= true;
2932 RefPtr
<Event
> event
= InvokeEvent::Constructor(this, u
"invoke"_ns
, init
);
2933 event
->SetTrusted(true);
2934 event
->SetTarget(invokee
);
2936 EventDispatcher::DispatchDOMEvent(invokee
, nullptr, event
, nullptr, nullptr);
2938 // 6. If notCancelled is true and invokee has an associated invocation action
2939 // algorithm then run the invokee's invocation action algorithm given action.
2940 if (event
->DefaultPrevented()) {
2944 invokee
->HandleInvokeInternal(aAction
, IgnoreErrors());
2947 void nsGenericHTMLFormControlElementWithState::GenerateStateKey() {
2948 // Keep the key if already computed
2949 if (!mStateKey
.IsVoid()) {
2953 Document
* doc
= GetUncomposedDoc();
2955 mStateKey
.Truncate();
2959 // Generate the state key
2960 nsContentUtils::GenerateStateKey(this, doc
, mStateKey
);
2962 // If the state key is blank, this is anonymous content or for whatever
2963 // reason we are not supposed to save/restore state: keep it as such.
2964 if (!mStateKey
.IsEmpty()) {
2965 // Add something unique to content so layout doesn't muck us up.
2970 PresState
* nsGenericHTMLFormControlElementWithState::GetPrimaryPresState() {
2971 if (mStateKey
.IsEmpty()) {
2975 nsCOMPtr
<nsILayoutHistoryState
> history
= GetLayoutHistory(false);
2981 // Get the pres state for this key, if it doesn't exist, create one.
2982 PresState
* result
= history
->GetState(mStateKey
);
2984 UniquePtr
<PresState
> newState
= NewPresState();
2985 result
= newState
.get();
2986 history
->AddState(mStateKey
, std::move(newState
));
2992 already_AddRefed
<nsILayoutHistoryState
>
2993 nsGenericHTMLFormElement::GetLayoutHistory(bool aRead
) {
2994 nsCOMPtr
<Document
> doc
= GetUncomposedDoc();
3002 nsCOMPtr
<nsILayoutHistoryState
> history
= doc
->GetLayoutHistoryState();
3007 if (aRead
&& !history
->HasStates()) {
3011 return history
.forget();
3014 bool nsGenericHTMLFormControlElementWithState::RestoreFormControlState() {
3015 MOZ_ASSERT(!mStateKey
.IsVoid(),
3016 "GenerateStateKey must already have been called");
3018 if (mStateKey
.IsEmpty()) {
3022 nsCOMPtr
<nsILayoutHistoryState
> history
= GetLayoutHistory(true);
3027 // Get the pres state for this key
3028 PresState
* state
= history
->GetState(mStateKey
);
3030 bool result
= RestoreState(state
);
3031 history
->RemoveState(mStateKey
);
3038 void nsGenericHTMLFormControlElementWithState::NodeInfoChanged(
3039 Document
* aOldDoc
) {
3040 nsGenericHTMLFormControlElement::NodeInfoChanged(aOldDoc
);
3042 // We need to regenerate the state key now we're in a new document. Clearing
3043 // mControlNumber means we stop considering this control to be parser
3044 // inserted, and we'll generate a state key based on its position in the
3045 // document rather than the order it was inserted into the document.
3046 mControlNumber
= -1;
3047 mStateKey
.SetIsVoid(true);
3050 void nsGenericHTMLFormControlElementWithState::GetFormAction(nsString
& aValue
) {
3051 auto type
= ControlType();
3052 if (!IsInputElement(type
) && !IsButtonElement(type
)) {
3056 if (!GetAttr(nsGkAtoms::formaction
, aValue
) || aValue
.IsEmpty()) {
3057 Document
* document
= OwnerDoc();
3058 nsIURI
* docURI
= document
->GetDocumentURI();
3061 nsresult rv
= docURI
->GetSpec(spec
);
3062 if (NS_FAILED(rv
)) {
3066 CopyUTF8toUTF16(spec
, aValue
);
3069 GetURIAttr(nsGkAtoms::formaction
, nullptr, aValue
);
3073 bool nsGenericHTMLElement::IsEventAttributeNameInternal(nsAtom
* aName
) {
3074 return nsContentUtils::IsEventAttributeName(aName
, EventNameType_HTML
);
3078 * Construct a URI from a string, as an element.src attribute
3079 * would be set to. Helper for the media elements.
3081 nsresult
nsGenericHTMLElement::NewURIFromString(const nsAString
& aURISpec
,
3083 NS_ENSURE_ARG_POINTER(aURI
);
3087 nsCOMPtr
<Document
> doc
= OwnerDoc();
3089 nsresult rv
= nsContentUtils::NewURIWithDocumentCharset(aURI
, aURISpec
, doc
,
3091 NS_ENSURE_SUCCESS(rv
, rv
);
3094 if (aURISpec
.IsEmpty() && doc
->GetDocumentURI() &&
3095 NS_SUCCEEDED(doc
->GetDocumentURI()->Equals(*aURI
, &equal
)) && equal
) {
3096 // Assume an element can't point to a fragment of its embedding
3097 // document. Fail here instead of returning the recursive URI
3098 // and waiting for the subsequent load to fail.
3100 return NS_ERROR_DOM_INVALID_STATE_ERR
;
3106 void nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString
& aValue
,
3107 mozilla::ErrorResult
& aError
) {
3108 // innerText depends on layout. For example, white space processing is
3109 // something that happens during reflow and which must be reflected by
3110 // innerText. So for:
3112 // <div style="white-space:normal"> A B C </div>
3114 // innerText should give "A B C".
3116 // The approach taken here to avoid the expense of reflow is to flush style
3117 // and then see whether it's necessary to flush layout afterwards. Flushing
3118 // layout can be skipped if we can detect that the element or its descendants
3121 // Obtain the composed doc to handle elements in Shadow DOM.
3122 Document
* doc
= GetComposedDoc();
3124 doc
->FlushPendingNotifications(FlushType::Style
);
3127 // Elements with `display: content` will not have a frame. To handle Shadow
3128 // DOM, walk the flattened tree looking for parent frame.
3129 nsIFrame
* frame
= GetPrimaryFrame();
3130 if (IsDisplayContents()) {
3131 for (Element
* parent
= GetFlattenedTreeParentElement(); parent
;
3132 parent
= parent
->GetFlattenedTreeParentElement()) {
3133 frame
= parent
->GetPrimaryFrame();
3140 // Check for dirty reflow roots in the subtree from targetFrame; this requires
3142 bool dirty
= frame
&& frame
->PresShell()->FrameIsAncestorOfDirtyRoot(frame
);
3144 // The way we do that is by checking whether the element has either of the two
3145 // dirty bits (NS_FRAME_IS_DIRTY or NS_FRAME_HAS_DIRTY_DESCENDANTS) or if any
3146 // ancestor has NS_FRAME_IS_DIRTY. We need to check for NS_FRAME_IS_DIRTY on
3147 // ancestors since that is something that implies NS_FRAME_IS_DIRTY on all
3149 dirty
|= frame
&& frame
->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN
);
3150 while (!dirty
&& frame
) {
3151 dirty
|= frame
->HasAnyStateBits(NS_FRAME_IS_DIRTY
);
3152 frame
= frame
->GetInFlowParent();
3155 // Flush layout if we determined a reflow is required.
3157 doc
->FlushPendingNotifications(FlushType::Layout
);
3160 if (!IsRendered()) {
3161 GetTextContentInternal(aValue
, aError
);
3163 nsRange::GetInnerTextNoFlush(aValue
, aError
, this);
3167 static already_AddRefed
<nsINode
> TextToNode(const nsAString
& aString
,
3168 nsNodeInfoManager
* aNim
) {
3170 const char16_t
* s
= aString
.BeginReading();
3171 const char16_t
* end
= aString
.EndReading();
3172 RefPtr
<DocumentFragment
> fragment
;
3174 if (s
!= end
&& *s
== '\r' && s
+ 1 != end
&& s
[1] == '\n') {
3175 // a \r\n pair should only generate one <br>, so just skip the \r
3178 if (s
== end
|| *s
== '\r' || *s
== '\n') {
3179 if (!str
.IsEmpty()) {
3180 RefPtr
<nsTextNode
> textContent
= new (aNim
) nsTextNode(aNim
);
3181 textContent
->SetText(str
, true);
3184 return textContent
.forget();
3186 fragment
= new (aNim
) DocumentFragment(aNim
);
3188 fragment
->AppendChildTo(textContent
, true, IgnoreErrors());
3194 RefPtr
<NodeInfo
> ni
= aNim
->GetNodeInfo(
3195 nsGkAtoms::br
, nullptr, kNameSpaceID_XHTML
, nsINode::ELEMENT_NODE
);
3196 auto* nim
= ni
->NodeInfoManager();
3197 RefPtr
<HTMLBRElement
> br
= new (nim
) HTMLBRElement(ni
.forget());
3202 fragment
= new (aNim
) DocumentFragment(aNim
);
3204 fragment
->AppendChildTo(br
, true, IgnoreErrors());
3210 return fragment
.forget();
3213 void nsGenericHTMLElement::SetInnerText(const nsAString
& aValue
) {
3214 RefPtr
<nsINode
> node
= TextToNode(aValue
, NodeInfo()->NodeInfoManager());
3215 ReplaceChildren(node
, IgnoreErrors());
3218 // https://html.spec.whatwg.org/#merge-with-the-next-text-node
3219 static void MergeWithNextTextNode(Text
& aText
, ErrorResult
& aRv
) {
3220 RefPtr
<Text
> nextSibling
= Text::FromNodeOrNull(aText
.GetNextSibling());
3225 nextSibling
->GetData(data
);
3226 aText
.AppendData(data
, aRv
);
3227 nextSibling
->Remove();
3230 // https://html.spec.whatwg.org/#dom-outertext
3231 void nsGenericHTMLElement::SetOuterText(const nsAString
& aValue
,
3233 nsCOMPtr
<nsINode
> parent
= GetParentNode();
3235 return aRv
.ThrowNoModificationAllowedError("Element has no parent");
3238 RefPtr
<nsINode
> next
= GetNextSibling();
3239 RefPtr
<nsINode
> previous
= GetPreviousSibling();
3241 // Batch possible DOMSubtreeModified events.
3242 mozAutoSubtreeModified
subtree(OwnerDoc(), nullptr);
3244 nsNodeInfoManager
* nim
= NodeInfo()->NodeInfoManager();
3245 RefPtr
<nsINode
> node
= TextToNode(aValue
, nim
);
3247 // This doesn't match the spec, see
3248 // https://github.com/whatwg/html/issues/7508
3249 node
= new (nim
) nsTextNode(nim
);
3251 parent
->ReplaceChild(*node
, *this, aRv
);
3257 if (RefPtr
<Text
> text
= Text::FromNodeOrNull(next
->GetPreviousSibling())) {
3258 MergeWithNextTextNode(*text
, aRv
);
3264 if (auto* text
= Text::FromNodeOrNull(previous
)) {
3265 MergeWithNextTextNode(*text
, aRv
);
3269 // This should be true when `:open` should match.
3270 bool nsGenericHTMLElement::PopoverOpen() const {
3271 if (PopoverData
* popoverData
= GetPopoverData()) {
3272 return popoverData
->GetPopoverVisibilityState() ==
3273 PopoverVisibilityState::Showing
;
3278 // https://html.spec.whatwg.org/#check-popover-validity
3279 bool nsGenericHTMLElement::CheckPopoverValidity(
3280 PopoverVisibilityState aExpectedState
, Document
* aExpectedDocument
,
3282 if (GetPopoverAttributeState() == PopoverAttributeState::None
) {
3283 aRv
.ThrowNotSupportedError("Element is in the no popover state");
3287 if (GetPopoverData()->GetPopoverVisibilityState() != aExpectedState
) {
3291 if (!IsInComposedDoc()) {
3292 aRv
.ThrowInvalidStateError("Element is not connected");
3296 if (aExpectedDocument
&& aExpectedDocument
!= OwnerDoc()) {
3297 aRv
.ThrowInvalidStateError("Element is moved to other document");
3301 if (auto* dialog
= HTMLDialogElement::FromNode(this)) {
3302 if (dialog
->IsInTopLayer()) {
3303 aRv
.ThrowInvalidStateError("Element is a modal <dialog> element");
3308 if (State().HasState(ElementState::FULLSCREEN
)) {
3309 aRv
.ThrowInvalidStateError("Element is fullscreen");
3316 PopoverAttributeState
nsGenericHTMLElement::GetPopoverAttributeState() const {
3317 return GetPopoverData() ? GetPopoverData()->GetPopoverAttributeState()
3318 : PopoverAttributeState::None
;
3321 void nsGenericHTMLElement::PopoverPseudoStateUpdate(bool aOpen
, bool aNotify
) {
3322 SetStates(ElementState::POPOVER_OPEN
, aOpen
, aNotify
);
3325 already_AddRefed
<ToggleEvent
> nsGenericHTMLElement::CreateToggleEvent(
3326 const nsAString
& aEventType
, const nsAString
& aOldState
,
3327 const nsAString
& aNewState
, Cancelable aCancelable
) {
3328 ToggleEventInit init
;
3329 init
.mBubbles
= false;
3330 init
.mOldState
= aOldState
;
3331 init
.mNewState
= aNewState
;
3332 init
.mCancelable
= aCancelable
== Cancelable::eYes
;
3333 RefPtr
<ToggleEvent
> event
= ToggleEvent::Constructor(this, aEventType
, init
);
3334 event
->SetTrusted(true);
3335 event
->SetTarget(this);
3336 return event
.forget();
3339 bool nsGenericHTMLElement::FireToggleEvent(PopoverVisibilityState aOldState
,
3340 PopoverVisibilityState aNewState
,
3341 const nsAString
& aType
) {
3342 auto stringForState
= [](PopoverVisibilityState state
) {
3343 return state
== PopoverVisibilityState::Hidden
? u
"closed"_ns
: u
"open"_ns
;
3345 const auto cancelable
= aType
== u
"beforetoggle"_ns
&&
3346 aNewState
== PopoverVisibilityState::Showing
3349 RefPtr event
= CreateToggleEvent(aType
, stringForState(aOldState
),
3350 stringForState(aNewState
), cancelable
);
3351 EventDispatcher::DispatchDOMEvent(this, nullptr, event
, nullptr, nullptr);
3352 return event
->DefaultPrevented();
3355 // https://html.spec.whatwg.org/#queue-a-popover-toggle-event-task
3356 void nsGenericHTMLElement::QueuePopoverEventTask(
3357 PopoverVisibilityState aOldState
) {
3358 auto* data
= GetPopoverData();
3359 MOZ_ASSERT(data
, "Should have popover data");
3361 if (auto* queuedToggleEventTask
= data
->GetToggleEventTask()) {
3362 aOldState
= queuedToggleEventTask
->GetOldState();
3366 MakeRefPtr
<PopoverToggleEventTask
>(do_GetWeakReference(this), aOldState
);
3367 data
->SetToggleEventTask(task
);
3368 OwnerDoc()->Dispatch(task
.forget());
3371 void nsGenericHTMLElement::RunPopoverToggleEventTask(
3372 PopoverToggleEventTask
* aTask
, PopoverVisibilityState aOldState
) {
3373 auto* data
= GetPopoverData();
3378 auto* popoverToggleEventTask
= data
->GetToggleEventTask();
3379 if (!popoverToggleEventTask
|| aTask
!= popoverToggleEventTask
) {
3382 data
->ClearToggleEventTask();
3383 // Intentionally ignore the return value here as only on open event the
3384 // cancelable attribute is initialized to true for beforetoggle event.
3385 FireToggleEvent(aOldState
, data
->GetPopoverVisibilityState(), u
"toggle"_ns
);
3388 // https://html.spec.whatwg.org/#dom-showpopover
3389 void nsGenericHTMLElement::ShowPopover(ErrorResult
& aRv
) {
3390 return ShowPopoverInternal(nullptr, aRv
);
3392 void nsGenericHTMLElement::ShowPopoverInternal(Element
* aInvoker
,
3394 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden
, nullptr, aRv
)) {
3397 RefPtr
<Document
> document
= OwnerDoc();
3399 MOZ_ASSERT(!GetPopoverData() || !GetPopoverData()->GetInvoker());
3400 MOZ_ASSERT(!OwnerDoc()->TopLayerContains(*this));
3402 bool wasShowingOrHiding
= GetPopoverData()->IsShowingOrHiding();
3403 GetPopoverData()->SetIsShowingOrHiding(true);
3404 auto cleanupShowingFlag
= MakeScopeExit([&]() {
3405 if (auto* popoverData
= GetPopoverData()) {
3406 popoverData
->SetIsShowingOrHiding(wasShowingOrHiding
);
3410 // Fire beforetoggle event and re-check popover validity.
3411 if (FireToggleEvent(PopoverVisibilityState::Hidden
,
3412 PopoverVisibilityState::Showing
, u
"beforetoggle"_ns
)) {
3415 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden
, document
, aRv
)) {
3419 bool shouldRestoreFocus
= false;
3420 nsWeakPtr originallyFocusedElement
;
3421 if (IsAutoPopover()) {
3422 auto originalState
= GetPopoverAttributeState();
3423 RefPtr
<nsINode
> ancestor
= GetTopmostPopoverAncestor(aInvoker
);
3425 ancestor
= document
;
3427 document
->HideAllPopoversUntil(*ancestor
, false,
3428 /* aFireEvents = */ !wasShowingOrHiding
);
3429 if (GetPopoverAttributeState() != originalState
) {
3430 aRv
.ThrowInvalidStateError(
3431 "The value of the popover attribute was changed while hiding the "
3436 // TODO: Handle if document changes, see
3437 // https://github.com/whatwg/html/issues/9177
3438 if (!IsAutoPopover() ||
3439 !CheckPopoverValidity(PopoverVisibilityState::Hidden
, document
, aRv
)) {
3443 shouldRestoreFocus
= !document
->GetTopmostAutoPopover();
3444 // Let originallyFocusedElement be document's focused area of the document's
3446 if (nsIContent
* unretargetedFocus
=
3447 document
->GetUnretargetedFocusedContent()) {
3448 originallyFocusedElement
=
3449 do_GetWeakReference(unretargetedFocus
->AsElement());
3453 document
->AddPopoverToTopLayer(*this);
3455 PopoverPseudoStateUpdate(true, true);
3458 auto* popoverData
= GetPopoverData();
3459 popoverData
->SetPopoverVisibilityState(PopoverVisibilityState::Showing
);
3460 popoverData
->SetInvoker(aInvoker
);
3463 // Run the popover focusing steps given element.
3465 if (shouldRestoreFocus
&&
3466 GetPopoverAttributeState() != PopoverAttributeState::None
) {
3467 GetPopoverData()->SetPreviouslyFocusedElement(originallyFocusedElement
);
3470 // Queue popover toggle event task.
3471 QueuePopoverEventTask(PopoverVisibilityState::Hidden
);
3474 void nsGenericHTMLElement::HidePopoverWithoutRunningScript() {
3475 HidePopoverInternal(/* aFocusPreviousElement = */ false,
3476 /* aFireEvents = */ false, IgnoreErrors());
3479 // https://html.spec.whatwg.org/#dom-hidepopover
3480 void nsGenericHTMLElement::HidePopover(ErrorResult
& aRv
) {
3481 HidePopoverInternal(/* aFocusPreviousElement = */ true,
3482 /* aFireEvents = */ true, aRv
);
3485 void nsGenericHTMLElement::HidePopoverInternal(bool aFocusPreviousElement
,
3488 OwnerDoc()->HidePopover(*this, aFocusPreviousElement
, aFireEvents
, aRv
);
3491 void nsGenericHTMLElement::ForgetPreviouslyFocusedElementAfterHidingPopover() {
3492 auto* data
= GetPopoverData();
3493 MOZ_ASSERT(data
, "Should have popover data");
3494 data
->SetPreviouslyFocusedElement(nullptr);
3497 void nsGenericHTMLElement::FocusPreviousElementAfterHidingPopover() {
3498 auto* data
= GetPopoverData();
3499 MOZ_ASSERT(data
, "Should have popover data");
3501 RefPtr
<Element
> control
=
3502 do_QueryReferent(data
->GetPreviouslyFocusedElement().get());
3503 data
->SetPreviouslyFocusedElement(nullptr);
3510 // https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
3511 // If focusPreviousElement is true and document's focused area of the
3512 // document's DOM anchor is a shadow-including inclusive descendant of
3513 // element, then run the focusing steps for previouslyFocusedElement;
3514 nsIContent
* currentFocus
= OwnerDoc()->GetUnretargetedFocusedContent();
3516 currentFocus
->IsShadowIncludingInclusiveDescendantOf(this)) {
3517 FocusOptions options
;
3518 options
.mPreventScroll
= true;
3519 control
->Focus(options
, CallerType::NonSystem
, IgnoreErrors());
3523 // https://html.spec.whatwg.org/multipage/popover.html#dom-togglepopover
3524 bool nsGenericHTMLElement::TogglePopover(const Optional
<bool>& aForce
,
3526 if (PopoverOpen() && (!aForce
.WasPassed() || !aForce
.Value())) {
3528 } else if (!aForce
.WasPassed() || aForce
.Value()) {
3531 CheckPopoverValidity(GetPopoverData()
3532 ? GetPopoverData()->GetPopoverVisibilityState()
3533 : PopoverVisibilityState::Showing
,
3537 return PopoverOpen();
3540 // https://html.spec.whatwg.org/multipage/popover.html#popover-focusing-steps
3541 void nsGenericHTMLElement::FocusPopover() {
3542 if (auto* dialog
= HTMLDialogElement::FromNode(this)) {
3543 return MOZ_KnownLive(dialog
)->FocusDialog();
3546 if (RefPtr
<Document
> doc
= GetComposedDoc()) {
3547 doc
->FlushPendingNotifications(FlushType::Frames
);
3550 RefPtr
<Element
> control
= GetBoolAttr(nsGkAtoms::autofocus
)
3552 : GetAutofocusDelegate(false /* aWithMouse */);
3557 FocusCandidate(control
, false /* aClearUpFocus */);
3560 void nsGenericHTMLElement::FocusCandidate(Element
* aControl
,
3561 bool aClearUpFocus
) {
3562 // 1) Run the focusing steps given control.
3563 IgnoredErrorResult rv
;
3564 if (RefPtr
<Element
> elementToFocus
= nsFocusManager::GetTheFocusableArea(
3565 aControl
, nsFocusManager::ProgrammaticFocusFlags(FocusOptions()))) {
3566 elementToFocus
->Focus(FocusOptions(), CallerType::NonSystem
, rv
);
3570 } else if (aClearUpFocus
) {
3571 if (RefPtr
<nsFocusManager
> fm
= nsFocusManager::GetFocusManager()) {
3572 // Clear the focus which ends up making the body gets focused
3573 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= OwnerDoc()->GetWindow();
3574 fm
->ClearFocus(outerWindow
);
3578 // 2) Let topDocument be the active document of control's node document's
3579 // browsing context's top-level browsing context.
3580 // 3) If control's node document's origin is not the same as the origin of
3581 // topDocument, then return.
3582 BrowsingContext
* bc
= aControl
->OwnerDoc()->GetBrowsingContext();
3583 if (bc
&& bc
->IsInProcess() && bc
->SameOriginWithTop()) {
3584 if (nsCOMPtr
<nsIDocShell
> docShell
= bc
->Top()->GetDocShell()) {
3585 if (Document
* topDocument
= docShell
->GetExtantDocument()) {
3586 // 4) Empty topDocument's autofocus candidates.
3587 // 5) Set topDocument's autofocus processed flag to true.
3588 topDocument
->SetAutoFocusFired();
3594 already_AddRefed
<ElementInternals
> nsGenericHTMLElement::AttachInternals(
3596 // ElementInternals is only available on autonomous custom element, so throws
3597 // an error by default. The spec steps are implemented in HTMLElement because
3598 // ElementInternals needs to hold a pointer to HTMLElement in order to forward
3599 // form operation to it.
3600 aRv
.ThrowNotSupportedError(nsPrintfCString(
3601 "Cannot attach ElementInternals to a customized built-in or non-custom "
3604 NS_ConvertUTF16toUTF8(NodeInfo()->NameAtom()->GetUTF16String()).get()));
3608 ElementInternals
* nsGenericHTMLElement::GetInternals() const {
3609 if (CustomElementData
* data
= GetCustomElementData()) {
3610 return data
->GetElementInternals();
3615 bool nsGenericHTMLElement::IsFormAssociatedCustomElements() const {
3616 if (CustomElementData
* data
= GetCustomElementData()) {
3617 return data
->IsFormAssociated();
3622 void nsGenericHTMLElement::GetAutocapitalize(nsAString
& aValue
) const {
3623 GetEnumAttr(nsGkAtoms::autocapitalize
, nullptr, kDefaultAutocapitalize
->tag
,
3627 bool nsGenericHTMLElement::Translate() const {
3628 if (const nsAttrValue
* attr
= mAttrs
.GetAttr(nsGkAtoms::translate
)) {
3629 if (attr
->IsEmptyString() || attr
->Equals(nsGkAtoms::yes
, eIgnoreCase
)) {
3632 if (attr
->Equals(nsGkAtoms::no
, eIgnoreCase
)) {
3636 return nsGenericHTMLElementBase::Translate();
3639 void nsGenericHTMLElement::GetPopover(nsString
& aPopover
) const {
3640 GetHTMLEnumAttr(nsGkAtoms::popover
, aPopover
);
3641 if (aPopover
.IsEmpty() && !DOMStringIsNull(aPopover
)) {
3642 aPopover
.Assign(NS_ConvertUTF8toUTF16(kPopoverAttributeValueAuto
));