Backed out changeset 51d87c2129d2 (bug 1865372) for causing RunWatchdog crashes in...
[gecko.git] / dom / html / nsGenericHTMLElement.cpp
blobb3ca51b64818980e7ddad4038813886f6a84d8cc
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"
25 #include "nscore.h"
26 #include "nsGenericHTMLElement.h"
27 #include "nsCOMPtr.h"
28 #include "nsAtom.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"
35 #include "nsView.h"
36 #include "nsViewManager.h"
37 #include "nsIWidget.h"
38 #include "nsRange.h"
39 #include "nsPresContext.h"
40 #include "nsError.h"
41 #include "nsIPrincipal.h"
42 #include "nsContainerFrame.h"
43 #include "nsStyleUtil.h"
44 #include "ReferrerInfo.h"
46 #include "mozilla/PresState.h"
47 #include "nsILayoutHistoryState.h"
49 #include "nsHTMLParts.h"
50 #include "nsContentUtils.h"
51 #include "mozilla/dom/DirectionalityUtils.h"
52 #include "mozilla/dom/DocumentOrShadowRoot.h"
53 #include "nsString.h"
54 #include "nsGkAtoms.h"
55 #include "nsDOMCSSDeclaration.h"
56 #include "nsITextControlFrame.h"
57 #include "nsIFormControl.h"
58 #include "mozilla/dom/HTMLFormElement.h"
59 #include "nsFocusManager.h"
61 #include "nsDOMStringMap.h"
62 #include "nsDOMString.h"
64 #include "nsLayoutUtils.h"
65 #include "mozilla/dom/DocumentInlines.h"
66 #include "HTMLFieldSetElement.h"
67 #include "nsTextNode.h"
68 #include "HTMLBRElement.h"
69 #include "nsDOMMutationObserver.h"
70 #include "mozilla/Preferences.h"
71 #include "mozilla/dom/FromParser.h"
72 #include "mozilla/dom/Link.h"
73 #include "mozilla/dom/ScriptLoader.h"
75 #include "nsDOMTokenList.h"
76 #include "nsThreadUtils.h"
77 #include "mozilla/dom/BindingUtils.h"
78 #include "mozilla/dom/MouseEventBinding.h"
79 #include "mozilla/dom/ToggleEvent.h"
80 #include "mozilla/dom/TouchEvent.h"
81 #include "mozilla/dom/InputEvent.h"
82 #include "mozilla/dom/InvokeEvent.h"
83 #include "mozilla/ErrorResult.h"
84 #include "nsHTMLDocument.h"
85 #include "nsGlobalWindowInner.h"
86 #include "mozilla/dom/HTMLBodyElement.h"
87 #include "imgIContainer.h"
88 #include "nsComputedDOMStyle.h"
89 #include "mozilla/dom/HTMLDialogElement.h"
90 #include "mozilla/dom/HTMLLabelElement.h"
91 #include "mozilla/dom/HTMLInputElement.h"
92 #include "mozilla/dom/CustomElementRegistry.h"
93 #include "mozilla/dom/ElementBinding.h"
94 #include "mozilla/dom/ElementInternals.h"
96 #ifdef ACCESSIBILITY
97 # include "nsAccessibilityService.h"
98 #endif
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},
121 {nullptr, 0}};
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},
139 {nullptr, 0}};
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},
153 {"", 0},
154 {nullptr, 0}};
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));
170 if (nonce) {
171 static_cast<nsGenericHTMLElement*>(aDst)->SetNonce(*nonce);
173 return NS_OK;
176 static const nsAttrValue::EnumTable kDirTable[] = {
177 {"ltr", eDir_LTR}, {"rtl", eDir_RTL}, {"auto", eDir_Auto}, {nullptr, 0}};
179 namespace {
180 // See <https://html.spec.whatwg.org/#the-popover-attribute>.
181 enum class PopoverAttributeKeyword : uint8_t { Auto, EmptyString, Manual };
183 static const char* kPopoverAttributeValueAuto = "auto";
184 static const char* kPopoverAttributeValueEmptyString = "";
185 static const char* kPopoverAttributeValueManual = "manual";
187 static const nsAttrValue::EnumTable kPopoverTable[] = {
188 {kPopoverAttributeValueAuto, PopoverAttributeKeyword::Auto},
189 {kPopoverAttributeValueEmptyString, PopoverAttributeKeyword::EmptyString},
190 {kPopoverAttributeValueManual, PopoverAttributeKeyword::Manual},
191 {nullptr, 0}};
193 // See <https://html.spec.whatwg.org/#the-popover-attribute>.
194 static const nsAttrValue::EnumTable* kPopoverTableInvalidValueDefault =
195 &kPopoverTable[2];
196 } // namespace
198 void nsGenericHTMLElement::GetFetchPriority(nsAString& aFetchPriority) const {
199 // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
200 GetEnumAttr(nsGkAtoms::fetchpriority, kFetchPriorityAttributeValueAuto,
201 aFetchPriority);
204 /* static */
205 FetchPriority nsGenericHTMLElement::ToFetchPriority(const nsAString& aValue) {
206 nsAttrValue attrValue;
207 ParseFetchPriority(aValue, attrValue);
208 MOZ_ASSERT(attrValue.Type() == nsAttrValue::eEnum);
209 return FetchPriority(attrValue.GetEnumValue());
212 namespace {
213 // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
214 static const nsAttrValue::EnumTable kFetchPriorityEnumTable[] = {
215 {kFetchPriorityAttributeValueHigh, FetchPriority::High},
216 {kFetchPriorityAttributeValueLow, FetchPriority::Low},
217 {kFetchPriorityAttributeValueAuto, FetchPriority::Auto},
218 {nullptr, 0}};
220 // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
221 static const nsAttrValue::EnumTable*
222 kFetchPriorityEnumTableInvalidValueDefault = &kFetchPriorityEnumTable[2];
223 } // namespace
225 FetchPriority nsGenericHTMLElement::GetFetchPriority() const {
226 const nsAttrValue* fetchpriorityAttribute =
227 GetParsedAttr(nsGkAtoms::fetchpriority);
228 if (fetchpriorityAttribute) {
229 MOZ_ASSERT(fetchpriorityAttribute->Type() == nsAttrValue::eEnum);
230 return FetchPriority(fetchpriorityAttribute->GetEnumValue());
233 return FetchPriority::Auto;
236 /* static */
237 void nsGenericHTMLElement::ParseFetchPriority(const nsAString& aValue,
238 nsAttrValue& aResult) {
239 aResult.ParseEnumValue(aValue, kFetchPriorityEnumTable,
240 false /* aCaseSensitive */,
241 kFetchPriorityEnumTableInvalidValueDefault);
244 void nsGenericHTMLElement::AddToNameTable(nsAtom* aName) {
245 MOZ_ASSERT(HasName(), "Node doesn't have name?");
246 Document* doc = GetUncomposedDoc();
247 if (doc && !IsInNativeAnonymousSubtree()) {
248 doc->AddToNameTable(this, aName);
252 void nsGenericHTMLElement::RemoveFromNameTable() {
253 if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
254 if (Document* doc = GetUncomposedDoc()) {
255 doc->RemoveFromNameTable(this,
256 GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
261 void nsGenericHTMLElement::GetAccessKeyLabel(nsString& aLabel) {
262 nsAutoString suffix;
263 GetAccessKey(suffix);
264 if (!suffix.IsEmpty()) {
265 EventStateManager::GetAccessKeyLabelPrefix(this, aLabel);
266 aLabel.Append(suffix);
270 static bool IsOffsetParent(nsIFrame* aFrame) {
271 LayoutFrameType frameType = aFrame->Type();
273 if (frameType == LayoutFrameType::TableCell ||
274 frameType == LayoutFrameType::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,
282 nsGkAtoms::th);
284 return false;
287 struct OffsetResult {
288 Element* mParent = nullptr;
289 CSSIntRect mRect;
292 static OffsetResult GetUnretargetedOffsetsFor(const Element& aElement) {
293 nsIFrame* frame = aElement.GetPrimaryFrame();
294 if (!frame) {
295 return {};
298 nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(frame);
300 nsIFrame* parent = frame->GetParent();
301 nsPoint origin(0, 0);
303 nsIContent* offsetParent = nullptr;
304 Element* docElement = aElement.GetComposedDoc()->GetRootElement();
305 nsIContent* content = frame->GetContent();
307 if (content &&
308 (content->IsHTMLElement(nsGkAtoms::body) || content == docElement)) {
309 parent = frame;
310 } else {
311 const bool isPositioned = styleFrame->IsAbsPosContainingBlock();
312 const bool isAbsolutelyPositioned = frame->IsAbsolutelyPositioned();
313 origin += frame->GetPositionIgnoringScrolling();
315 for (; parent; parent = parent->GetParent()) {
316 content = parent->GetContent();
318 // Stop at the first ancestor that is positioned.
319 if (parent->IsAbsPosContainingBlock()) {
320 offsetParent = content;
321 break;
324 // Add the parent's origin to our own to get to the
325 // right coordinate system.
326 const bool isOffsetParent = !isPositioned && IsOffsetParent(parent);
327 if (!isOffsetParent) {
328 origin += parent->GetPositionIgnoringScrolling();
331 if (content) {
332 // If we've hit the document element, break here.
333 if (content == docElement) {
334 break;
337 // Break if the ancestor frame type makes it suitable as offset parent
338 // and this element is *not* positioned or if we found the body element.
339 if (isOffsetParent || content->IsHTMLElement(nsGkAtoms::body)) {
340 offsetParent = content;
341 break;
346 if (isAbsolutelyPositioned && !offsetParent) {
347 // If this element is absolutely positioned, but we don't have
348 // an offset parent it means this element is an absolutely
349 // positioned child that's not nested inside another positioned
350 // element, in this case the element's frame's parent is the
351 // frame for the HTML element so we fail to find the body in the
352 // parent chain. We want the offset parent in this case to be
353 // the body, so we just get the body element from the document.
355 // We use GetBodyElement() here, not GetBody(), because we don't want to
356 // end up with framesets here.
357 offsetParent = aElement.GetComposedDoc()->GetBodyElement();
361 // Make the position relative to the padding edge.
362 if (parent) {
363 const nsStyleBorder* border = parent->StyleBorder();
364 origin.x -= border->GetComputedBorderWidth(eSideLeft);
365 origin.y -= border->GetComputedBorderWidth(eSideTop);
368 // Get the union of all rectangles in this and continuation frames.
369 // It doesn't really matter what we use as aRelativeTo here, since
370 // we only care about the size. We just have to use something non-null.
371 nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
372 rcFrame.MoveTo(origin);
373 return {Element::FromNodeOrNull(offsetParent),
374 CSSIntRect::FromAppUnitsRounded(rcFrame)};
377 static bool ShouldBeRetargeted(const Element& aReferenceElement,
378 const Element& aElementToMaybeRetarget) {
379 ShadowRoot* shadow = aElementToMaybeRetarget.GetContainingShadow();
380 if (!shadow) {
381 return false;
383 for (ShadowRoot* scope = aReferenceElement.GetContainingShadow(); scope;
384 scope = scope->Host()->GetContainingShadow()) {
385 if (scope == shadow) {
386 return false;
390 return true;
393 Element* nsGenericHTMLElement::GetOffsetRect(CSSIntRect& aRect) {
394 aRect = CSSIntRect();
396 if (!GetPrimaryFrame(FlushType::Layout)) {
397 return nullptr;
400 OffsetResult thisResult = GetUnretargetedOffsetsFor(*this);
401 aRect = thisResult.mRect;
403 Element* parent = thisResult.mParent;
404 while (parent && ShouldBeRetargeted(*this, *parent)) {
405 OffsetResult result = GetUnretargetedOffsetsFor(*parent);
406 aRect += result.mRect.TopLeft();
407 parent = result.mParent;
410 return parent;
413 bool nsGenericHTMLElement::Spellcheck() {
414 // Has the state has been explicitly set?
415 nsIContent* node;
416 for (node = this; node; node = node->GetParent()) {
417 if (node->IsHTMLElement()) {
418 static Element::AttrValuesArray strings[] = {nsGkAtoms::_true,
419 nsGkAtoms::_false, nullptr};
420 switch (node->AsElement()->FindAttrValueIn(
421 kNameSpaceID_None, nsGkAtoms::spellcheck, strings, eCaseMatters)) {
422 case 0: // spellcheck = "true"
423 return true;
424 case 1: // spellcheck = "false"
425 return false;
430 // contenteditable/designMode are spellchecked by default
431 if (IsEditable()) {
432 return true;
435 // Is this a chrome element?
436 if (nsContentUtils::IsChromeDoc(OwnerDoc())) {
437 return false; // Not spellchecked by default
440 // Anything else that's not a form control is not spellchecked by default
441 nsCOMPtr<nsIFormControl> formControl = do_QueryObject(this);
442 if (!formControl) {
443 return false; // Not spellchecked by default
446 // Is this a multiline plaintext input?
447 auto controlType = formControl->ControlType();
448 if (controlType == FormControlType::Textarea) {
449 return true; // Spellchecked by default
452 // Is this anything other than an input text?
453 // Other inputs are not spellchecked.
454 if (controlType != FormControlType::InputText) {
455 return false; // Not spellchecked by default
458 // Does the user want input text spellchecked by default?
459 // NOTE: Do not reflect a pref value of 0 back to the DOM getter.
460 // The web page should not know if the user has disabled spellchecking.
461 // We'll catch this in the editor itself.
462 int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1);
463 return spellcheckLevel == 2; // "Spellcheck multi- and single-line"
466 bool nsGenericHTMLElement::InNavQuirksMode(Document* aDoc) {
467 return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks;
470 void nsGenericHTMLElement::UpdateEditableState(bool aNotify) {
471 // XXX Should we do this only when in a document?
472 ContentEditableTristate value = GetContentEditableValue();
473 if (value != eInherit) {
474 SetEditableFlag(!!value);
475 UpdateReadOnlyState(aNotify);
476 return;
478 nsStyledElement::UpdateEditableState(aNotify);
481 nsresult nsGenericHTMLElement::BindToTree(BindContext& aContext,
482 nsINode& aParent) {
483 nsresult rv = nsGenericHTMLElementBase::BindToTree(aContext, aParent);
484 NS_ENSURE_SUCCESS(rv, rv);
486 if (IsInComposedDoc()) {
487 RegUnRegAccessKey(true);
490 if (IsInUncomposedDoc()) {
491 if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
492 aContext.OwnerDoc().AddToNameTable(
493 this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
497 if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue &&
498 IsInComposedDoc()) {
499 aContext.OwnerDoc().ChangeContentEditableCount(this, +1);
502 // Hide any nonce from the DOM, but keep the internal value of the
503 // nonce by copying and resetting the internal nonce value.
504 if (HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && IsInComposedDoc() &&
505 OwnerDoc()->GetBrowsingContext()) {
506 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
507 "nsGenericHTMLElement::ResetNonce::Runnable",
508 [self = RefPtr<nsGenericHTMLElement>(this)]() {
509 nsAutoString nonce;
510 self->GetNonce(nonce);
511 self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, u""_ns, true);
512 self->SetNonce(nonce);
513 }));
516 // We need to consider a labels element is moved to another subtree
517 // with different root, it needs to update labels list and its root
518 // as well.
519 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
520 if (slots && slots->mLabelsList) {
521 slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
524 return rv;
527 void nsGenericHTMLElement::UnbindFromTree(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
532 // false.
533 if (GetPopoverData()) {
534 HidePopoverWithoutRunningScript();
536 RegUnRegAccessKey(false);
539 RemoveFromNameTable();
541 if (GetContentEditableValue() == eTrue) {
542 if (Document* doc = GetComposedDoc()) {
543 doc->ChangeContentEditableCount(this, -1);
547 nsStyledElement::UnbindFromTree(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()) {
561 return nullptr;
564 nsIContent* content = this;
565 while (content) {
566 // If the current ancestor is a form, return it as our form
567 if (content->IsHTMLElement(nsGkAtoms::form)) {
568 #ifdef DEBUG
569 if (!nsContentUtils::IsInSameAnonymousTree(this, content)) {
570 // It's possible that we started unbinding at |content| or
571 // some ancestor of it, and |content| and |this| used to all be
572 // anonymous. Check for this the hard way.
573 for (nsIContent* child = this; child != content;
574 child = child->GetParent()) {
575 NS_ASSERTION(child->ComputeIndexInParentContent().isSome(),
576 "Walked too far?");
579 #endif
580 return static_cast<HTMLFormElement*>(content);
583 nsIContent* prevContent = content;
584 content = prevContent->GetParent();
586 if (!content && aCurrentForm) {
587 // We got to the root of the subtree we're in, and we're being removed
588 // from the DOM (the only time we get into this method with a non-null
589 // aCurrentForm). Check whether aCurrentForm is in the same subtree. If
590 // it is, we want to return aCurrentForm, since this case means that
591 // we're one of those inputs-in-a-table that have a hacked mForm pointer
592 // and a subtree containing both us and the form got removed from the
593 // DOM.
594 if (aCurrentForm->IsInclusiveDescendantOf(prevContent)) {
595 return aCurrentForm;
600 return nullptr;
603 bool nsGenericHTMLElement::CheckHandleEventForAnchorsPreconditions(
604 EventChainVisitor& aVisitor) {
605 MOZ_ASSERT(nsCOMPtr<Link>(do_QueryObject(this)),
606 "should be called only when |this| implements |Link|");
607 // When disconnected, only <a> should navigate away per
608 // https://html.spec.whatwg.org/#cannot-navigate
609 return IsInComposedDoc() || IsHTMLElement(nsGkAtoms::a);
612 void nsGenericHTMLElement::GetEventTargetParentForAnchors(
613 EventChainPreVisitor& aVisitor) {
614 nsGenericHTMLElementBase::GetEventTargetParent(aVisitor);
616 if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
617 return;
620 GetEventTargetParentForLinks(aVisitor);
623 nsresult nsGenericHTMLElement::PostHandleEventForAnchors(
624 EventChainPostVisitor& aVisitor) {
625 if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
626 return NS_OK;
629 return PostHandleEventForLinks(aVisitor);
632 bool nsGenericHTMLElement::IsHTMLLink(nsIURI** aURI) const {
633 MOZ_ASSERT(aURI, "Must provide aURI out param");
635 *aURI = GetHrefURIForAnchors().take();
636 // We promise out param is non-null if we return true, so base rv on it
637 return *aURI != nullptr;
640 already_AddRefed<nsIURI> nsGenericHTMLElement::GetHrefURIForAnchors() const {
641 // This is used by the three Link implementations and
642 // nsHTMLStyleElement.
644 // Get href= attribute (relative URI).
646 // We use the nsAttrValue's copy of the URI string to avoid copying.
647 nsCOMPtr<nsIURI> uri;
648 GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri));
650 return uri.forget();
653 void nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
654 const nsAttrValue* aValue,
655 bool aNotify) {
656 if (aNamespaceID == kNameSpaceID_None) {
657 if (aName == nsGkAtoms::accesskey) {
658 // Have to unregister before clearing flag. See UnregAccessKey
659 RegUnRegAccessKey(false);
660 if (!aValue) {
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()) {
667 ClearHasName();
669 } else if (aName == nsGkAtoms::contenteditable) {
670 if (aValue) {
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,
685 aNotify);
688 namespace {
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;
699 default: {
700 MOZ_ASSERT_UNREACHABLE();
701 return PopoverAttributeState::None;
705 } // namespace
707 void nsGenericHTMLElement::AfterSetPopoverAttr() {
708 auto mapPopoverState = [](const nsAttrValue* value) -> PopoverAttributeState {
709 if (value) {
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
733 // its value.
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
740 // layer.
741 if (GetPopoverData()) {
742 OwnerDoc()->RemovePopoverFromTopLayer(*this);
744 ClearPopoverData();
745 RemoveStates(ElementState::POPOVER_OPEN);
746 } else {
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,
757 bool aNotify) {
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) {
778 SetHasValidDir();
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;
783 } else {
784 dir = dirValue;
785 SetDirectionality(dir, aNotify);
786 if (dirValue == eDir_LTR) {
787 dirStates |= ElementState::HAS_DIR_ATTR_LTR;
788 } else {
789 MOZ_ASSERT(dirValue == eDir_RTL);
790 dirStates |= ElementState::HAS_DIR_ATTR_RTL;
793 } else {
794 if (aValue) {
795 // We have a value, just not a valid one.
796 dirStates |= ElementState::HAS_DIR_ATTR;
798 ClearHasValidDir();
799 if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
800 dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO;
801 } else {
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) {
832 if (aValue) {
833 AddStates(ElementState::INERT);
834 } else {
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).
844 SetHasName();
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()) {
858 IMEState newState;
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) {
876 if (aValue) {
877 SetNonce(aValue->GetStringValue());
878 if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) {
879 SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP);
881 } else {
882 RemoveNonce();
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
903 #undef WINDOW_EVENT
904 #undef FORWARDED_EVENT
905 #undef EVENT
906 )) {
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
911 // normal.
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();
916 *aDefer = false;
917 if ((win = document->GetInnerWindow())) {
918 nsCOMPtr<EventTarget> piTarget(do_QueryInterface(win));
920 return piTarget->GetOrCreateListenerManager();
923 return nullptr;
926 return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName,
927 aDefer);
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_(); \
939 return nullptr; \
942 return nsINode::GetOn##name_(); \
944 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \
945 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
946 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
947 if (!win) { \
948 return; \
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(); \
970 return nullptr; \
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(); \
979 if (!win) { \
980 return; \
983 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
984 RefPtr<OnErrorEventHandlerNonNull> errorHandler; \
985 if (handler) { \
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
994 #undef ERROR_EVENT
995 #undef FORWARDED_EVENT
996 #undef EVENT
998 void nsGenericHTMLElement::GetBaseTarget(nsAString& aBaseTarget) const {
999 OwnerDoc()->GetBaseTarget(aBaseTarget);
1002 //----------------------------------------------------------------------
1004 bool nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID,
1005 nsAtom* aAttribute,
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()) {
1032 return false;
1034 aResult.ParseAtom(aValue);
1035 return true;
1038 if (aAttribute == nsGkAtoms::contenteditable ||
1039 aAttribute == nsGkAtoms::translate) {
1040 aResult.ParseAtom(aValue);
1041 return true;
1044 if (aAttribute == nsGkAtoms::rel) {
1045 aResult.ParseAtomArray(aValue);
1046 return true;
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,
1067 nsAtom* aAttribute,
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)) {
1078 return false;
1080 aResult.SetTo(uri, &aValue);
1081 return true;
1084 return false;
1087 bool nsGenericHTMLElement::IsAttributeMapped(const nsAtom* aAttribute) const {
1088 static const MappedAttributeEntry* const map[] = {sCommonAttributeMap};
1090 return FindAttributeDependence(aAttribute, map);
1093 nsMapRuleToAttributesFunc nsGenericHTMLElement::GetAttributeMappingFunction()
1094 const {
1095 return &MapCommonAttributesInto;
1098 nsIFormControlFrame* nsGenericHTMLElement::GetFormControlFrame(
1099 bool aFlushFrames) {
1100 auto flushType = aFlushFrames ? FlushType::Frames : FlushType::None;
1101 nsIFrame* frame = GetPrimaryFrame(flushType);
1102 if (!frame) {
1103 return nullptr;
1106 if (nsIFormControlFrame* f = do_QueryFrame(frame)) {
1107 return f;
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)) {
1117 return f;
1121 return nullptr;
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},
1130 {nullptr, 0}};
1132 static const nsAttrValue::EnumTable kFrameborderTable[] = {
1133 {"yes", FrameBorderProperty::Yes},
1134 {"no", FrameBorderProperty::No},
1135 {"1", FrameBorderProperty::One},
1136 {"0", FrameBorderProperty::Zero},
1137 {nullptr, 0}};
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},
1148 {nullptr, 0}};
1150 static const nsAttrValue::EnumTable kTableVAlignTable[] = {
1151 {"top", StyleVerticalAlignKeyword::Top},
1152 {"middle", StyleVerticalAlignKeyword::Middle},
1153 {"bottom", StyleVerticalAlignKeyword::Bottom},
1154 {"baseline", StyleVerticalAlignKeyword::Baseline},
1155 {nullptr, 0}};
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},
1176 {nullptr, 0}};
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},
1230 {nullptr, 0}};
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);
1259 return false;
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);
1301 if (!langValue) {
1302 return;
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);
1329 if (value) {
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);
1354 /* static */
1355 const nsGenericHTMLElement::MappedAttributeEntry
1356 nsGenericHTMLElement::sCommonAttributeMap[] = {{nsGkAtoms::contenteditable},
1357 {nsGkAtoms::lang},
1358 {nsGkAtoms::hidden},
1359 {nullptr}};
1361 /* static */
1362 const Element::MappedAttributeEntry
1363 nsGenericHTMLElement::sImageMarginSizeAttributeMap[] = {{nsGkAtoms::width},
1364 {nsGkAtoms::height},
1365 {nsGkAtoms::hspace},
1366 {nsGkAtoms::vspace},
1367 {nullptr}};
1369 /* static */
1370 const Element::MappedAttributeEntry
1371 nsGenericHTMLElement::sImageAlignAttributeMap[] = {{nsGkAtoms::align},
1372 {nullptr}};
1374 /* static */
1375 const Element::MappedAttributeEntry
1376 nsGenericHTMLElement::sDivAlignAttributeMap[] = {{nsGkAtoms::align},
1377 {nullptr}};
1379 /* static */
1380 const Element::MappedAttributeEntry
1381 nsGenericHTMLElement::sImageBorderAttributeMap[] = {{nsGkAtoms::border},
1382 {nullptr}};
1384 /* static */
1385 const Element::MappedAttributeEntry
1386 nsGenericHTMLElement::sBackgroundAttributeMap[] = {
1387 {nsGkAtoms::background}, {nsGkAtoms::bgcolor}, {nullptr}};
1389 /* static */
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)) {
1407 switch (align) {
1408 case uint8_t(StyleTextAlign::Left):
1409 case uint8_t(StyleTextAlign::Right):
1410 break;
1411 default:
1412 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, align);
1413 break;
1419 void nsGenericHTMLElement::MapDivAlignAttributeInto(
1420 MappedDeclarationsBuilder& aBuilder) {
1421 if (!aBuilder.PropertyIsSet(eCSSProperty_text_align)) {
1422 // align: enum
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)) {
1432 // align: enum
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) {
1458 // hspace: value
1459 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::hspace)) {
1460 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_left, *value);
1461 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_right, *value);
1464 // vspace: 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) {
1488 Maybe<double> w;
1489 if (aWidth.Type() == nsAttrValue::eInteger) {
1490 w.emplace(aWidth.GetIntegerValue());
1491 } else if (aWidth.Type() == nsAttrValue::eDoubleValue) {
1492 w.emplace(aWidth.GetDoubleValue());
1495 Maybe<double> h;
1496 if (aHeight.Type() == nsAttrValue::eInteger) {
1497 h.emplace(aHeight.GetIntegerValue());
1498 } else if (aHeight.Type() == nsAttrValue::eDoubleValue) {
1499 h.emplace(aHeight.GetDoubleValue());
1502 if (w && h) {
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);
1511 if (width) {
1512 MapDimensionAttributeInto(aBuilder, eCSSProperty_width, *width);
1514 if (height) {
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) {
1533 // border: pixels
1534 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::border);
1535 if (!value) return;
1537 nscoord val = 0;
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)) {
1563 // background
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)) {
1572 return;
1574 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::bgcolor);
1575 nscolor color;
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();
1595 return aDefault;
1598 nsresult nsGenericHTMLElement::SetIntAttr(nsAtom* aAttr, int32_t aValue) {
1599 nsAutoString value;
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) {
1609 return aDefault;
1612 return attrVal->GetIntegerValue();
1615 uint32_t nsGenericHTMLElement::GetDimensionAttrAsUnsignedInt(
1616 nsAtom* aAttr, uint32_t aDefault) const {
1617 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
1618 if (!attrVal) {
1619 return aDefault;
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.
1644 nsAutoString val;
1645 attrVal->ToString(val);
1646 nsContentUtils::ParseHTMLIntegerResultFlags result;
1647 int32_t parsedInt = nsContentUtils::ParseHTMLInteger(val, &result);
1648 if ((result & nsContentUtils::eParseHTMLInteger_Error) || parsedInt < 0) {
1649 return aDefault;
1652 return parsedInt;
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));
1659 if (!hadAttr) {
1660 aResult.Truncate();
1661 return;
1664 if (!uri) {
1665 // Just return the attr value
1666 GetAttr(aAttr, aResult);
1667 return;
1670 nsAutoCString spec;
1671 uri->GetSpec(spec);
1672 CopyUTF8toUTF16(spec, aResult);
1675 bool nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
1676 nsIURI** aURI) const {
1677 *aURI = nullptr;
1679 const nsAttrValue* attr = mAttrs.GetAttr(aAttr);
1680 if (!attr) {
1681 return false;
1684 nsCOMPtr<nsIURI> baseURI = GetBaseURI();
1686 if (aBaseAttr) {
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)) {
1693 return true;
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);
1703 return true;
1706 bool nsGenericHTMLElement::IsLabelable() const {
1707 return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter);
1710 /* static */
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();
1732 // static
1733 bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext* aCx,
1734 JSObject* aGlobal) {
1735 return TouchEvent::LegacyAPIEnabled(aCx, aGlobal);
1738 bool nsGenericHTMLElement::IsFormControlDefaultFocusable(
1739 bool aWithMouse) const {
1740 if (!aWithMouse) {
1741 return true;
1743 switch (StaticPrefs::accessibility_mouse_focuses_formcontrol()) {
1744 case 0:
1745 return false;
1746 case 1:
1747 return true;
1748 default:
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");
1771 if (!form) {
1772 return;
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,
1798 nsINode& aParent) {
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);
1815 return NS_OK;
1818 void nsGenericHTMLFormElement::UnbindFromTree(bool aNullParent) {
1819 // Save state before doing anything else.
1820 SaveState();
1822 if (IsFormAssociatedElement()) {
1823 if (HTMLFormElement* form = GetFormInternal()) {
1824 // Might need to unset form
1825 if (aNullParent) {
1826 // No more parent means no more form
1827 ClearForm(true, true);
1828 } else {
1829 // Recheck whether we should still have an form.
1830 if (HasAttr(nsGkAtoms::form) || !FindAncestorForm(form)) {
1831 ClearForm(true, true);
1832 } else {
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,
1841 nsGkAtoms::form)) {
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,
1853 nsAtom* aName,
1854 const nsAttrValue* aValue,
1855 bool aNotify) {
1856 if (aNameSpaceID == kNameSpaceID_None && IsFormAssociatedElement()) {
1857 nsAutoString tmp;
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,
1890 nsGkAtoms::form)) {
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,
1899 aNotify);
1902 void nsGenericHTMLFormElement::AfterSetAttr(
1903 int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue,
1904 const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal,
1905 bool aNotify) {
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) {
1919 nsAutoString tmp;
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();
1940 if (docOrShadow) {
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();
1981 if (!docOrShadow) {
1982 return;
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);
1994 /* static */
1995 bool nsGenericHTMLFormElement::FormIdUpdated(Element* aOldElement,
1996 Element* aNewElement,
1997 void* aData) {
1998 nsGenericHTMLFormElement* element =
1999 static_cast<nsGenericHTMLFormElement*>(aData);
2001 NS_ASSERTION(element->IsHTMLElement(), "aData should be an HTML element");
2003 element->UpdateFormOwner(false, aNewElement);
2005 return true;
2008 bool nsGenericHTMLFormElement::IsElementDisabledForEvents(WidgetEvent* aEvent,
2009 nsIFrame* aFrame) {
2010 MOZ_ASSERT(aEvent);
2012 // Allow dispatch of CustomEvent and untrusted Events.
2013 if (!aEvent->IsTrusted()) {
2014 return false;
2017 switch (aEvent->mMessage) {
2018 case eAnimationStart:
2019 case eAnimationEnd:
2020 case eAnimationIteration:
2021 case eAnimationCancel:
2022 case eFormChange:
2023 case eMouseMove:
2024 case eMouseOver:
2025 case eMouseOut:
2026 case eMouseEnter:
2027 case eMouseLeave:
2028 case ePointerMove:
2029 case ePointerOver:
2030 case ePointerOut:
2031 case ePointerEnter:
2032 case ePointerLeave:
2033 case eTransitionCancel:
2034 case eTransitionEnd:
2035 case eTransitionRun:
2036 case eTransitionStart:
2037 case eWheel:
2038 case eLegacyMouseLineOrPageScroll:
2039 case eLegacyMousePixelScroll:
2040 return false;
2041 case eFocus:
2042 case eBlur:
2043 case eFocusIn:
2044 case eFocusOut:
2045 case eKeyPress:
2046 case eKeyUp:
2047 case eKeyDown:
2048 if (StaticPrefs::dom_forms_always_allow_key_and_focus_events_enabled()) {
2049 return false;
2051 [[fallthrough]];
2052 case ePointerDown:
2053 case ePointerUp:
2054 case ePointerCancel:
2055 case ePointerGotCapture:
2056 case ePointerLostCapture:
2057 if (StaticPrefs::dom_forms_always_allow_pointer_events_enabled()) {
2058 return false;
2060 [[fallthrough]];
2061 default:
2062 break;
2065 if (aEvent->mSpecifiedEventType == nsGkAtoms::oninput) {
2066 return false;
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) {
2072 return true;
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();
2085 if (!aBindToTree) {
2086 ClearForm(true, false);
2087 form = nullptr;
2090 HTMLFormElement* oldForm = form;
2091 if (!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;
2098 if (aBindToTree) {
2099 element = AddFormIdObserver();
2100 } else {
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);
2116 } else {
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());
2157 return;
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;
2170 return;
2173 if (fieldset) {
2174 fieldset->RemoveElement(this);
2176 SetFieldSetInternal(parentFieldset);
2177 parentFieldset->AddElement(this);
2179 // The disabled state may have changed
2180 FieldSetDisabledChanged(aNotify);
2181 return;
2185 // No fieldset found.
2186 if (fieldset) {
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()) {
2196 return;
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() {
2230 SaveState();
2232 nsGenericHTMLElement::SaveSubtreeState();
2235 //----------------------------------------------------------------------
2237 void nsGenericHTMLElement::Click(CallerType aCallerType) {
2238 if (HandlingClick()) {
2239 return;
2242 // There are two notions of disabled.
2243 // "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
2250 // for this?
2251 if (IsDisabled() &&
2252 !(mNodeInfo->Equals(nsGkAtoms::fieldset) &&
2253 StaticPrefs::dom_forms_fieldset_disable_only_descendants_enabled())) {
2254 return;
2257 // Strong in case the event kills it
2258 nsCOMPtr<Document> doc = GetComposedDoc();
2260 RefPtr<nsPresContext> context;
2261 if (doc) {
2262 PresShell* presShell = doc->GetPresShell();
2263 if (!presShell) {
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
2269 // impact.
2270 doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
2271 presShell = doc->GetPresShell();
2273 if (presShell) {
2274 context = presShell->GetPresContext();
2278 SetHandlingClick();
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;
2298 return true;
2302 if (!IsInComposedDoc() || IsInDesignMode()) {
2303 // In designMode documents we only allow focusing the document.
2304 *aTabIndex = -1;
2305 *aIsFocusable = false;
2306 return true;
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
2318 // roots.
2319 if (attrVal.isNothing()) {
2320 // The default value for tabindex should be 0 for editable
2321 // contentEditable roots.
2322 *aTabIndex = 0;
2324 } else {
2325 disallowOverridingFocusability = false;
2327 // Just check for disabled attribute on form controls
2328 disabled = IsDisabled();
2329 if (disabled) {
2330 *aTabIndex = -1;
2334 // If a tabindex is specified at all, or the default tabindex is 0, we're
2335 // focusable.
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);
2343 if (!presContext) {
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);
2363 return focused;
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) {
2385 return;
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);
2395 return;
2398 bool shouldActivate = false;
2399 switch (message) {
2400 case eKeyDown:
2401 if (keyEvent->ShouldWorkAsSpaceKey()) {
2402 SetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
2404 return;
2405 case eKeyPress:
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;
2411 break;
2412 case eKeyUp:
2413 shouldActivate = keyEvent->ShouldWorkAsSpaceKey() &&
2414 HasFlag(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
2415 if (shouldActivate) {
2416 UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
2418 break;
2419 default:
2420 MOZ_ASSERT_UNREACHABLE("why didn't we bail out earlier?");
2421 break;
2424 if (!shouldActivate) {
2425 return;
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
2445 // different here?
2447 RefPtr<TextEditor> textEditor = GetTextEditorInternal();
2448 return textEditor.forget();
2451 // static
2452 void nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent* content) {
2453 /* Sync this node */
2454 nsGenericHTMLElement* element = FromNode(content);
2455 if (element) {
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
2472 // notification.
2473 if (!aContent->IsElement()) {
2474 aContent->UpdateEditableState(false);
2475 return;
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();
2493 if (!document) {
2494 return;
2497 Document::EditingState previousEditingState = Document::EditingState::eOff;
2498 if (aChange != 0) {
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),
2532 mForm(nullptr),
2533 mFieldSet(nullptr) {}
2535 nsGenericHTMLFormControlElement::~nsGenericHTMLFormControlElement() {
2536 if (mFieldSet) {
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();
2553 if (!textEditor) {
2554 return nsGenericHTMLFormElement::GetDesiredIMEState();
2556 IMEState state;
2557 nsresult rv = textEditor->GetPreferredIMEState(&state);
2558 if (NS_FAILED(rv)) {
2559 return nsGenericHTMLFormElement::GetDesiredIMEState();
2561 return state;
2564 void nsGenericHTMLFormControlElement::GetAutocapitalize(
2565 nsAString& aValue) const {
2566 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
2567 nsGkAtoms::autocapitalize)) {
2568 nsGenericHTMLFormElement::GetAutocapitalize(aValue);
2569 return;
2572 if (mForm && IsAutocapitalizeInheriting()) {
2573 mForm->GetAutocapitalize(aValue);
2577 bool nsGenericHTMLFormControlElement::IsHTMLFocusable(bool aWithMouse,
2578 bool* aIsFocusable,
2579 int32_t* aTabIndex) {
2580 if (nsGenericHTMLFormElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
2581 aTabIndex)) {
2582 return true;
2585 *aIsFocusable = *aIsFocusable && IsFormControlDefaultFocusable(aWithMouse);
2586 return false;
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) {
2604 case eFocus: {
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);
2613 break;
2615 case eBlur: {
2616 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
2617 if (formControlFrame) {
2618 formControlFrame->SetFocus(false, false);
2620 break;
2622 default:
2623 break;
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) {
2662 return false;
2665 switch (type) {
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:
2676 return false;
2677 #ifdef DEBUG
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:
2691 return true;
2692 default:
2693 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesReadOnlyApply()");
2694 return true;
2695 #else // DEBUG
2696 default:
2697 return true;
2698 #endif // DEBUG
2702 void nsGenericHTMLFormControlElement::SetFormInternal(HTMLFormElement* aForm,
2703 bool aBindToTree) {
2704 if (aForm) {
2705 BeforeSetForm(aForm, aBindToTree);
2708 // keep a *weak* ref to the form here
2709 mForm = aForm;
2712 HTMLFormElement* nsGenericHTMLFormControlElement::GetFormInternal() const {
2713 return mForm;
2716 HTMLFieldSetElement* nsGenericHTMLFormControlElement::GetFieldSetInternal()
2717 const {
2718 return mFieldSet;
2721 void nsGenericHTMLFormControlElement::SetFieldSetInternal(
2722 HTMLFieldSetElement* aFieldset) {
2723 mFieldSet = aFieldset;
2726 void nsGenericHTMLFormControlElement::UpdateRequiredState(bool aIsRequired,
2727 bool aNotify) {
2728 #ifdef DEBUG
2729 auto type = ControlType();
2730 #endif
2731 MOZ_ASSERT(IsInputElement(type) || type == FormControlType::Select ||
2732 type == FormControlType::Textarea,
2733 "This should be called only on types that @required applies");
2735 #ifdef DEBUG
2736 if (HTMLInputElement* input = HTMLInputElement::FromNode(this)) {
2737 MOZ_ASSERT(
2738 input->DoesRequiredApply(),
2739 "This should be called only on input types that @required applies");
2741 #endif
2743 ElementState requiredStates;
2744 if (aIsRequired) {
2745 requiredStates |= ElementState::REQUIRED;
2746 } else {
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);
2779 return NS_OK;
2782 //----------------------------------------------------------------------
2784 static const nsAttrValue::EnumTable kPopoverTargetActionTable[] = {
2785 {"toggle", PopoverTargetAction::Toggle},
2786 {"show", PopoverTargetAction::Show},
2787 {"hide", PopoverTargetAction::Hide},
2788 {nullptr, 0}};
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()
2800 : -1) {
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);
2815 return true;
2819 if (StaticPrefs::dom_element_invokers_enabled()) {
2820 if (aAttribute == nsGkAtoms::invokeaction) {
2821 aResult.ParseAtom(aValue);
2822 return true;
2824 if (aAttribute == nsGkAtoms::invoketarget) {
2825 aResult.ParseAtom(aValue);
2826 return true;
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();
2847 if (!target) {
2848 return;
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();
2865 if (shouldHide) {
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);
2881 #endif
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);
2902 return nullptr;
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.
2915 if (!invokee) {
2916 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()) {
2941 return;
2944 invokee->HandleInvokeInternal(aAction, IgnoreErrors());
2947 void nsGenericHTMLFormControlElementWithState::GenerateStateKey() {
2948 // Keep the key if already computed
2949 if (!mStateKey.IsVoid()) {
2950 return;
2953 Document* doc = GetUncomposedDoc();
2954 if (!doc) {
2955 mStateKey.Truncate();
2956 return;
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.
2966 mStateKey += "-C";
2970 PresState* nsGenericHTMLFormControlElementWithState::GetPrimaryPresState() {
2971 if (mStateKey.IsEmpty()) {
2972 return nullptr;
2975 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false);
2977 if (!history) {
2978 return nullptr;
2981 // Get the pres state for this key, if it doesn't exist, create one.
2982 PresState* result = history->GetState(mStateKey);
2983 if (!result) {
2984 UniquePtr<PresState> newState = NewPresState();
2985 result = newState.get();
2986 history->AddState(mStateKey, std::move(newState));
2989 return result;
2992 already_AddRefed<nsILayoutHistoryState>
2993 nsGenericHTMLFormElement::GetLayoutHistory(bool aRead) {
2994 nsCOMPtr<Document> doc = GetUncomposedDoc();
2995 if (!doc) {
2996 return nullptr;
3000 // Get the history
3002 nsCOMPtr<nsILayoutHistoryState> history = doc->GetLayoutHistoryState();
3003 if (!history) {
3004 return nullptr;
3007 if (aRead && !history->HasStates()) {
3008 return nullptr;
3011 return history.forget();
3014 bool nsGenericHTMLFormControlElementWithState::RestoreFormControlState() {
3015 MOZ_ASSERT(!mStateKey.IsVoid(),
3016 "GenerateStateKey must already have been called");
3018 if (mStateKey.IsEmpty()) {
3019 return false;
3022 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true);
3023 if (!history) {
3024 return false;
3027 // Get the pres state for this key
3028 PresState* state = history->GetState(mStateKey);
3029 if (state) {
3030 bool result = RestoreState(state);
3031 history->RemoveState(mStateKey);
3032 return result;
3035 return false;
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)) {
3053 return;
3056 if (!GetAttr(nsGkAtoms::formaction, aValue) || aValue.IsEmpty()) {
3057 Document* document = OwnerDoc();
3058 nsIURI* docURI = document->GetDocumentURI();
3059 if (docURI) {
3060 nsAutoCString spec;
3061 nsresult rv = docURI->GetSpec(spec);
3062 if (NS_FAILED(rv)) {
3063 return;
3066 CopyUTF8toUTF16(spec, aValue);
3068 } else {
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,
3082 nsIURI** aURI) {
3083 NS_ENSURE_ARG_POINTER(aURI);
3085 *aURI = nullptr;
3087 nsCOMPtr<Document> doc = OwnerDoc();
3089 nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI, aURISpec, doc,
3090 GetBaseURI());
3091 NS_ENSURE_SUCCESS(rv, rv);
3093 bool equal;
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.
3099 NS_RELEASE(*aURI);
3100 return NS_ERROR_DOM_INVALID_STATE_ERR;
3103 return NS_OK;
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
3119 // are not dirty.
3121 // Obtain the composed doc to handle elements in Shadow DOM.
3122 Document* doc = GetComposedDoc();
3123 if (doc) {
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();
3134 if (frame) {
3135 break;
3140 // Check for dirty reflow roots in the subtree from targetFrame; this requires
3141 // a reflow flush.
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
3148 // descendants.
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.
3156 if (dirty && doc) {
3157 doc->FlushPendingNotifications(FlushType::Layout);
3160 if (!IsRendered()) {
3161 GetTextContentInternal(aValue, aError);
3162 } else {
3163 nsRange::GetInnerTextNoFlush(aValue, aError, this);
3167 static already_AddRefed<nsINode> TextToNode(const nsAString& aString,
3168 nsNodeInfoManager* aNim) {
3169 nsString str;
3170 const char16_t* s = aString.BeginReading();
3171 const char16_t* end = aString.EndReading();
3172 RefPtr<DocumentFragment> fragment;
3173 while (true) {
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
3176 ++s;
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);
3182 if (!fragment) {
3183 if (s == end) {
3184 return textContent.forget();
3186 fragment = new (aNim) DocumentFragment(aNim);
3188 fragment->AppendChildTo(textContent, true, IgnoreErrors());
3190 if (s == end) {
3191 break;
3193 str.Truncate();
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());
3198 if (!fragment) {
3199 if (s + 1 == end) {
3200 return br.forget();
3202 fragment = new (aNim) DocumentFragment(aNim);
3204 fragment->AppendChildTo(br, true, IgnoreErrors());
3205 } else {
3206 str.Append(*s);
3208 ++s;
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());
3221 if (!nextSibling) {
3222 return;
3224 nsAutoString data;
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,
3232 ErrorResult& aRv) {
3233 nsCOMPtr<nsINode> parent = GetParentNode();
3234 if (!parent) {
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);
3246 if (!node) {
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);
3252 if (aRv.Failed()) {
3253 return;
3256 if (next) {
3257 if (RefPtr<Text> text = Text::FromNodeOrNull(next->GetPreviousSibling())) {
3258 MergeWithNextTextNode(*text, aRv);
3259 if (aRv.Failed()) {
3260 return;
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;
3275 return false;
3278 // https://html.spec.whatwg.org/#check-popover-validity
3279 bool nsGenericHTMLElement::CheckPopoverValidity(
3280 PopoverVisibilityState aExpectedState, Document* aExpectedDocument,
3281 ErrorResult& aRv) {
3282 if (GetPopoverAttributeState() == PopoverAttributeState::None) {
3283 aRv.ThrowNotSupportedError("Element is in the no popover state");
3284 return false;
3287 if (GetPopoverData()->GetPopoverVisibilityState() != aExpectedState) {
3288 return false;
3291 if (!IsInComposedDoc()) {
3292 aRv.ThrowInvalidStateError("Element is not connected");
3293 return false;
3296 if (aExpectedDocument && aExpectedDocument != OwnerDoc()) {
3297 aRv.ThrowInvalidStateError("Element is moved to other document");
3298 return false;
3301 if (auto* dialog = HTMLDialogElement::FromNode(this)) {
3302 if (dialog->IsInTopLayer()) {
3303 aRv.ThrowInvalidStateError("Element is a modal <dialog> element");
3304 return false;
3308 if (State().HasState(ElementState::FULLSCREEN)) {
3309 aRv.ThrowInvalidStateError("Element is fullscreen");
3310 return false;
3313 return true;
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
3347 ? Cancelable::eYes
3348 : Cancelable::eNo;
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();
3365 auto task =
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();
3374 if (!data) {
3375 return;
3378 auto* popoverToggleEventTask = data->GetToggleEventTask();
3379 if (!popoverToggleEventTask || aTask != popoverToggleEventTask) {
3380 return;
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,
3393 ErrorResult& aRv) {
3394 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, nullptr, aRv)) {
3395 return;
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)) {
3413 return;
3415 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) {
3416 return;
3419 bool shouldRestoreFocus = false;
3420 nsWeakPtr originallyFocusedElement;
3421 if (IsAutoPopover()) {
3422 auto originalState = GetPopoverAttributeState();
3423 RefPtr<nsINode> ancestor = GetTopmostPopoverAncestor(aInvoker);
3424 if (!ancestor) {
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 "
3432 "popover.");
3433 return;
3436 // TODO: Handle if document changes, see
3437 // https://github.com/whatwg/html/issues/9177
3438 if (!IsAutoPopover() ||
3439 !CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) {
3440 return;
3443 shouldRestoreFocus = !document->GetTopmostAutoPopover();
3444 // Let originallyFocusedElement be document's focused area of the document's
3445 // DOM anchor.
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.
3464 FocusPopover();
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,
3486 bool aFireEvents,
3487 ErrorResult& aRv) {
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);
3505 if (!control) {
3506 return;
3509 // Step 14.2 at
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();
3515 if (currentFocus &&
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,
3525 ErrorResult& aRv) {
3526 if (PopoverOpen() && (!aForce.WasPassed() || !aForce.Value())) {
3527 HidePopover(aRv);
3528 } else if (!aForce.WasPassed() || aForce.Value()) {
3529 ShowPopover(aRv);
3530 } else {
3531 CheckPopoverValidity(GetPopoverData()
3532 ? GetPopoverData()->GetPopoverVisibilityState()
3533 : PopoverVisibilityState::Showing,
3534 nullptr, aRv);
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)
3551 ? this
3552 : GetAutofocusDelegate(false /* aWithMouse */);
3554 if (!control) {
3555 return;
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);
3567 if (rv.Failed()) {
3568 return;
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(
3595 ErrorResult& aRv) {
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 "
3602 "element "
3603 "'%s'",
3604 NS_ConvertUTF16toUTF8(NodeInfo()->NameAtom()->GetUTF16String()).get()));
3605 return nullptr;
3608 ElementInternals* nsGenericHTMLElement::GetInternals() const {
3609 if (CustomElementData* data = GetCustomElementData()) {
3610 return data->GetElementInternals();
3612 return nullptr;
3615 bool nsGenericHTMLElement::IsFormAssociatedCustomElements() const {
3616 if (CustomElementData* data = GetCustomElementData()) {
3617 return data->IsFormAssociated();
3619 return false;
3622 void nsGenericHTMLElement::GetAutocapitalize(nsAString& aValue) const {
3623 GetEnumAttr(nsGkAtoms::autocapitalize, nullptr, kDefaultAutocapitalize->tag,
3624 aValue);
3627 bool nsGenericHTMLElement::Translate() const {
3628 if (const nsAttrValue* attr = mAttrs.GetAttr(nsGkAtoms::translate)) {
3629 if (attr->IsEmptyString() || attr->Equals(nsGkAtoms::yes, eIgnoreCase)) {
3630 return true;
3632 if (attr->Equals(nsGkAtoms::no, eIgnoreCase)) {
3633 return false;
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));