Bug 1560374 - Set testharness and reftest web-platform-tests to Tier-1; r=jmaher...
[gecko.git] / dom / html / nsGenericHTMLElement.cpp
blob51bc874692d86ba4840601dfa3163b4e39128a7b
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/ArrayUtils.h"
8 #include "mozilla/DeclarationBlock.h"
9 #include "mozilla/EventDispatcher.h"
10 #include "mozilla/EventListenerManager.h"
11 #include "mozilla/EventStateManager.h"
12 #include "mozilla/EventStates.h"
13 #include "mozilla/HTMLEditor.h"
14 #include "mozilla/MappedDeclarations.h"
15 #include "mozilla/Likely.h"
16 #include "mozilla/MouseEvents.h"
17 #include "mozilla/PresShell.h"
18 #include "mozilla/TextEditor.h"
19 #include "mozilla/StaticPrefs.h"
21 #include "nscore.h"
22 #include "nsGenericHTMLElement.h"
23 #include "nsAttrValueInlines.h"
24 #include "nsCOMPtr.h"
25 #include "nsAtom.h"
26 #include "nsQueryObject.h"
27 #include "nsIContentInlines.h"
28 #include "nsIContentViewer.h"
29 #include "mozilla/dom/BindContext.h"
30 #include "mozilla/dom/Document.h"
31 #include "nsIDocumentEncoder.h"
32 #include "nsIDOMWindow.h"
33 #include "nsMappedAttributes.h"
34 #include "nsHTMLStyleSheet.h"
35 #include "nsPIDOMWindow.h"
36 #include "nsIURL.h"
37 #include "nsEscape.h"
38 #include "nsIFrameInlines.h"
39 #include "nsIScrollableFrame.h"
40 #include "nsView.h"
41 #include "nsViewManager.h"
42 #include "nsIWidget.h"
43 #include "nsRange.h"
44 #include "nsPresContext.h"
45 #include "nsIDocShell.h"
46 #include "nsNameSpaceManager.h"
47 #include "nsError.h"
48 #include "nsIPrincipal.h"
49 #include "nsContainerFrame.h"
50 #include "nsStyleUtil.h"
52 #include "mozilla/PresState.h"
53 #include "nsILayoutHistoryState.h"
55 #include "nsHTMLParts.h"
56 #include "nsContentUtils.h"
57 #include "mozilla/dom/DirectionalityUtils.h"
58 #include "mozilla/dom/DocumentOrShadowRoot.h"
59 #include "nsString.h"
60 #include "nsUnicharUtils.h"
61 #include "nsGkAtoms.h"
62 #include "nsDOMCSSDeclaration.h"
63 #include "nsITextControlFrame.h"
64 #include "nsIForm.h"
65 #include "nsIFormControl.h"
66 #include "mozilla/dom/HTMLFormElement.h"
67 #include "nsFocusManager.h"
68 #include "nsAttrValueOrString.h"
70 #include "mozilla/InternalMutationEvent.h"
71 #include "nsDOMStringMap.h"
73 #include "nsLayoutUtils.h"
74 #include "mozAutoDocUpdate.h"
75 #include "nsHtml5Module.h"
76 #include "nsITextControlElement.h"
77 #include "mozilla/dom/ElementInlines.h"
78 #include "HTMLFieldSetElement.h"
79 #include "nsTextNode.h"
80 #include "HTMLBRElement.h"
81 #include "HTMLMenuElement.h"
82 #include "nsDOMMutationObserver.h"
83 #include "mozilla/Preferences.h"
84 #include "mozilla/dom/FromParser.h"
85 #include "mozilla/dom/Link.h"
86 #include "mozilla/BloomFilter.h"
87 #include "mozilla/dom/ScriptLoader.h"
89 #include "nsVariant.h"
90 #include "nsDOMTokenList.h"
91 #include "nsThreadUtils.h"
92 #include "nsTextFragment.h"
93 #include "mozilla/dom/BindingUtils.h"
94 #include "mozilla/dom/MouseEventBinding.h"
95 #include "mozilla/dom/TouchEvent.h"
96 #include "mozilla/ErrorResult.h"
97 #include "nsHTMLDocument.h"
98 #include "nsGlobalWindow.h"
99 #include "mozilla/dom/HTMLBodyElement.h"
100 #include "imgIContainer.h"
101 #include "nsComputedDOMStyle.h"
102 #include "ReferrerPolicy.h"
103 #include "mozilla/dom/HTMLLabelElement.h"
104 #include "mozilla/dom/HTMLInputElement.h"
106 using namespace mozilla;
107 using namespace mozilla::dom;
109 nsresult nsGenericHTMLElement::CopyInnerTo(Element* aDst) {
110 MOZ_ASSERT(!aDst->GetUncomposedDoc(),
111 "Should not CopyInnerTo an Element in a document");
113 bool reparse = (aDst->OwnerDoc() != OwnerDoc());
115 nsresult rv =
116 static_cast<nsGenericHTMLElement*>(aDst)->mAttrs.EnsureCapacityToClone(
117 mAttrs);
118 NS_ENSURE_SUCCESS(rv, rv);
120 int32_t i, count = GetAttrCount();
121 for (i = 0; i < count; ++i) {
122 const nsAttrName* name = mAttrs.AttrNameAt(i);
123 const nsAttrValue* value = mAttrs.AttrAt(i);
125 if (name->Equals(nsGkAtoms::style, kNameSpaceID_None) &&
126 value->Type() == nsAttrValue::eCSSDeclaration) {
127 // We still clone CSS attributes, even in the cross-document case.
128 // https://github.com/w3c/webappsec-csp/issues/212
130 // We can't just set this as a string, because that will fail
131 // to reparse the string into style data until the node is
132 // inserted into the document. Clone the Rule instead.
133 nsAttrValue valueCopy(*value);
134 rv = aDst->SetParsedAttr(name->NamespaceID(), name->LocalName(),
135 name->GetPrefix(), valueCopy, false);
136 NS_ENSURE_SUCCESS(rv, rv);
138 DeclarationBlock* cssDeclaration = value->GetCSSDeclarationValue();
139 cssDeclaration->SetImmutable();
140 } else if (reparse) {
141 nsAutoString valStr;
142 value->ToString(valStr);
144 rv = aDst->SetAttr(name->NamespaceID(), name->LocalName(),
145 name->GetPrefix(), valStr, false);
146 NS_ENSURE_SUCCESS(rv, rv);
147 } else {
148 nsAttrValue valueCopy(*value);
149 rv = aDst->SetParsedAttr(name->NamespaceID(), name->LocalName(),
150 name->GetPrefix(), valueCopy, false);
151 NS_ENSURE_SUCCESS(rv, rv);
155 return NS_OK;
158 static const nsAttrValue::EnumTable kDirTable[] = {
159 {"ltr", eDir_LTR}, {"rtl", eDir_RTL}, {"auto", eDir_Auto}, {nullptr, 0}};
161 void nsGenericHTMLElement::AddToNameTable(nsAtom* aName) {
162 MOZ_ASSERT(HasName(), "Node doesn't have name?");
163 Document* doc = GetUncomposedDoc();
164 if (doc && !IsInAnonymousSubtree()) {
165 doc->AddToNameTable(this, aName);
169 void nsGenericHTMLElement::RemoveFromNameTable() {
170 if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
171 if (Document* doc = GetUncomposedDoc()) {
172 doc->RemoveFromNameTable(this,
173 GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
178 void nsGenericHTMLElement::GetAccessKeyLabel(nsString& aLabel) {
179 nsAutoString suffix;
180 GetAccessKey(suffix);
181 if (!suffix.IsEmpty()) {
182 EventStateManager::GetAccessKeyLabelPrefix(this, aLabel);
183 aLabel.Append(suffix);
187 static bool IsTableCell(LayoutFrameType frameType) {
188 return LayoutFrameType::TableCell == frameType ||
189 LayoutFrameType::BCTableCell == frameType;
192 static bool IsOffsetParent(nsIFrame* aFrame) {
193 LayoutFrameType frameType = aFrame->Type();
195 if (IsTableCell(frameType) || frameType == LayoutFrameType::Table) {
196 // Per the IDL for Element, only td, th, and table are acceptable
197 // offsetParents apart from body or positioned elements; we need to check
198 // the content type as well as the frame type so we ignore anonymous tables
199 // created by an element with display: table-cell with no actual table
200 nsIContent* content = aFrame->GetContent();
202 return content->IsAnyOfHTMLElements(nsGkAtoms::table, nsGkAtoms::td,
203 nsGkAtoms::th);
205 return false;
208 struct OffsetResult {
209 Element* mParent = nullptr;
210 CSSIntRect mRect;
213 static OffsetResult GetUnretargetedOffsetsFor(const Element& aElement) {
214 nsIFrame* frame = aElement.GetPrimaryFrame();
215 if (!frame) {
216 return {};
219 nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(frame);
221 nsIFrame* parent = frame->GetParent();
222 nsPoint origin(0, 0);
224 nsIContent* offsetParent = nullptr;
225 Element* docElement = aElement.GetComposedDoc()->GetRootElement();
226 nsIContent* content = frame->GetContent();
228 if (content &&
229 (content->IsHTMLElement(nsGkAtoms::body) || content == docElement)) {
230 parent = frame;
231 } else {
232 const bool isPositioned = styleFrame->IsAbsPosContainingBlock();
233 const bool isAbsolutelyPositioned = styleFrame->IsAbsolutelyPositioned();
234 origin += frame->GetPositionIgnoringScrolling();
236 for (; parent; parent = parent->GetParent()) {
237 content = parent->GetContent();
239 // Stop at the first ancestor that is positioned.
240 if (parent->IsAbsPosContainingBlock()) {
241 offsetParent = content;
242 break;
245 // Add the parent's origin to our own to get to the
246 // right coordinate system.
247 const bool isOffsetParent = !isPositioned && IsOffsetParent(parent);
248 if (!isOffsetParent) {
249 origin += parent->GetPositionIgnoringScrolling();
252 if (content) {
253 // If we've hit the document element, break here.
254 if (content == docElement) {
255 break;
258 // Break if the ancestor frame type makes it suitable as offset parent
259 // and this element is *not* positioned or if we found the body element.
260 if (isOffsetParent || content->IsHTMLElement(nsGkAtoms::body)) {
261 offsetParent = content;
262 break;
267 if (isAbsolutelyPositioned && !offsetParent) {
268 // If this element is absolutely positioned, but we don't have
269 // an offset parent it means this element is an absolutely
270 // positioned child that's not nested inside another positioned
271 // element, in this case the element's frame's parent is the
272 // frame for the HTML element so we fail to find the body in the
273 // parent chain. We want the offset parent in this case to be
274 // the body, so we just get the body element from the document.
276 // We use GetBodyElement() here, not GetBody(), because we don't want to
277 // end up with framesets here.
278 offsetParent = aElement.GetComposedDoc()->GetBodyElement();
282 // Subtract the parent border unless it uses border-box sizing.
283 if (parent && parent->StylePosition()->mBoxSizing != StyleBoxSizing::Border) {
284 const nsStyleBorder* border = parent->StyleBorder();
285 origin.x -= border->GetComputedBorderWidth(eSideLeft);
286 origin.y -= border->GetComputedBorderWidth(eSideTop);
289 // XXX We should really consider subtracting out padding for
290 // content-box sizing, but we should see what IE does....
292 // Get the union of all rectangles in this and continuation frames.
293 // It doesn't really matter what we use as aRelativeTo here, since
294 // we only care about the size. We just have to use something non-null.
295 nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
296 rcFrame.MoveTo(origin);
297 return {Element::FromNodeOrNull(offsetParent),
298 CSSIntRect::FromAppUnitsRounded(rcFrame)};
301 static bool ShouldBeRetargeted(const Element& aReferenceElement,
302 const Element& aElementToMaybeRetarget) {
303 ShadowRoot* shadow = aElementToMaybeRetarget.GetContainingShadow();
304 if (!shadow) {
305 return false;
307 for (ShadowRoot* scope = aReferenceElement.GetContainingShadow(); scope;
308 scope = scope->Host()->GetContainingShadow()) {
309 if (scope == shadow) {
310 return false;
314 return true;
317 Element* nsGenericHTMLElement::GetOffsetRect(CSSIntRect& aRect) {
318 aRect = CSSIntRect();
320 if (!GetPrimaryFrame(FlushType::Layout)) {
321 return nullptr;
324 OffsetResult thisResult = GetUnretargetedOffsetsFor(*this);
325 aRect = thisResult.mRect;
327 Element* parent = thisResult.mParent;
328 while (parent && ShouldBeRetargeted(*this, *parent)) {
329 OffsetResult result = GetUnretargetedOffsetsFor(*parent);
330 aRect += result.mRect.TopLeft();
331 parent = result.mParent;
334 return parent;
337 bool nsGenericHTMLElement::Spellcheck() {
338 // Has the state has been explicitly set?
339 nsIContent* node;
340 for (node = this; node; node = node->GetParent()) {
341 if (node->IsHTMLElement()) {
342 static Element::AttrValuesArray strings[] = {nsGkAtoms::_true,
343 nsGkAtoms::_false, nullptr};
344 switch (node->AsElement()->FindAttrValueIn(
345 kNameSpaceID_None, nsGkAtoms::spellcheck, strings, eCaseMatters)) {
346 case 0: // spellcheck = "true"
347 return true;
348 case 1: // spellcheck = "false"
349 return false;
354 // contenteditable/designMode are spellchecked by default
355 if (IsEditable()) {
356 return true;
359 // Is this a chrome element?
360 if (nsContentUtils::IsChromeDoc(OwnerDoc())) {
361 return false; // Not spellchecked by default
364 // Anything else that's not a form control is not spellchecked by default
365 nsCOMPtr<nsIFormControl> formControl = do_QueryObject(this);
366 if (!formControl) {
367 return false; // Not spellchecked by default
370 // Is this a multiline plaintext input?
371 int32_t controlType = formControl->ControlType();
372 if (controlType == NS_FORM_TEXTAREA) {
373 return true; // Spellchecked by default
376 // Is this anything other than an input text?
377 // Other inputs are not spellchecked.
378 if (controlType != NS_FORM_INPUT_TEXT) {
379 return false; // Not spellchecked by default
382 // Does the user want input text spellchecked by default?
383 // NOTE: Do not reflect a pref value of 0 back to the DOM getter.
384 // The web page should not know if the user has disabled spellchecking.
385 // We'll catch this in the editor itself.
386 int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1);
387 return spellcheckLevel == 2; // "Spellcheck multi- and single-line"
390 bool nsGenericHTMLElement::InNavQuirksMode(Document* aDoc) {
391 return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks;
394 void nsGenericHTMLElement::UpdateEditableState(bool aNotify) {
395 // XXX Should we do this only when in a document?
396 ContentEditableTristate value = GetContentEditableValue();
397 if (value != eInherit) {
398 DoSetEditableFlag(!!value, aNotify);
399 return;
402 nsStyledElement::UpdateEditableState(aNotify);
405 EventStates nsGenericHTMLElement::IntrinsicState() const {
406 EventStates state = nsGenericHTMLElementBase::IntrinsicState();
408 if (GetDirectionality() == eDir_RTL) {
409 state |= NS_EVENT_STATE_RTL;
410 state &= ~NS_EVENT_STATE_LTR;
411 } else { // at least for HTML, directionality is exclusively LTR or RTL
412 NS_ASSERTION(GetDirectionality() == eDir_LTR,
413 "HTML element's directionality must be either RTL or LTR");
414 state |= NS_EVENT_STATE_LTR;
415 state &= ~NS_EVENT_STATE_RTL;
418 return state;
421 nsresult nsGenericHTMLElement::BindToTree(BindContext& aContext,
422 nsINode& aParent) {
423 nsresult rv = nsGenericHTMLElementBase::BindToTree(aContext, aParent);
424 NS_ENSURE_SUCCESS(rv, rv);
426 if (IsInUncomposedDoc()) {
427 RegAccessKey();
428 if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
429 aContext.OwnerDoc().AddToNameTable(
430 this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
434 if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue &&
435 IsInComposedDoc()) {
436 aContext.OwnerDoc().ChangeContentEditableCount(this, +1);
439 // We need to consider a labels element is moved to another subtree
440 // with different root, it needs to update labels list and its root
441 // as well.
442 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
443 if (slots && slots->mLabelsList) {
444 slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
447 return rv;
450 void nsGenericHTMLElement::UnbindFromTree(bool aNullParent) {
451 if (IsInUncomposedDoc()) {
452 UnregAccessKey();
455 RemoveFromNameTable();
457 if (GetContentEditableValue() == eTrue) {
458 Document* doc = GetComposedDoc();
459 if (doc) {
460 doc->ChangeContentEditableCount(this, -1);
464 nsStyledElement::UnbindFromTree(aNullParent);
466 // Invalidate .labels list. It will be repopulated when used the next time.
467 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
468 if (slots && slots->mLabelsList) {
469 slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
473 HTMLFormElement* nsGenericHTMLElement::FindAncestorForm(
474 HTMLFormElement* aCurrentForm) {
475 NS_ASSERTION(!HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
476 IsHTMLElement(nsGkAtoms::img),
477 "FindAncestorForm should not be called if @form is set!");
479 // Make sure we don't end up finding a form that's anonymous from
480 // our point of view. See also nsGenericHTMLFormElement::UpdateFieldSet.
481 nsIContent* bindingParent = GetBindingParent();
483 nsIContent* content = this;
484 while (content != bindingParent && content) {
485 // If the current ancestor is a form, return it as our form
486 if (content->IsHTMLElement(nsGkAtoms::form)) {
487 #ifdef DEBUG
488 if (!nsContentUtils::IsInSameAnonymousTree(this, content)) {
489 // It's possible that we started unbinding at |content| or
490 // some ancestor of it, and |content| and |this| used to all be
491 // anonymous. Check for this the hard way.
492 for (nsIContent* child = this; child != content;
493 child = child->GetParent()) {
494 NS_ASSERTION(child->GetParent()->ComputeIndexOf(child) != -1,
495 "Walked too far?");
498 #endif
499 return static_cast<HTMLFormElement*>(content);
502 nsIContent* prevContent = content;
503 content = prevContent->GetParent();
505 if (!content && aCurrentForm) {
506 // We got to the root of the subtree we're in, and we're being removed
507 // from the DOM (the only time we get into this method with a non-null
508 // aCurrentForm). Check whether aCurrentForm is in the same subtree. If
509 // it is, we want to return aCurrentForm, since this case means that
510 // we're one of those inputs-in-a-table that have a hacked mForm pointer
511 // and a subtree containing both us and the form got removed from the
512 // DOM.
513 if (aCurrentForm->IsInclusiveDescendantOf(prevContent)) {
514 return aCurrentForm;
519 return nullptr;
522 bool nsGenericHTMLElement::CheckHandleEventForAnchorsPreconditions(
523 EventChainVisitor& aVisitor) {
524 MOZ_ASSERT(nsCOMPtr<Link>(do_QueryObject(this)),
525 "should be called only when |this| implements |Link|");
526 // When disconnected, only <a> should navigate away per
527 // https://html.spec.whatwg.org/#cannot-navigate
528 return IsInComposedDoc() || IsHTMLElement(nsGkAtoms::a);
531 void nsGenericHTMLElement::GetEventTargetParentForAnchors(
532 EventChainPreVisitor& aVisitor) {
533 nsGenericHTMLElementBase::GetEventTargetParent(aVisitor);
535 if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
536 return;
539 GetEventTargetParentForLinks(aVisitor);
542 nsresult nsGenericHTMLElement::PostHandleEventForAnchors(
543 EventChainPostVisitor& aVisitor) {
544 if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
545 return NS_OK;
548 return PostHandleEventForLinks(aVisitor);
551 bool nsGenericHTMLElement::IsHTMLLink(nsIURI** aURI) const {
552 MOZ_ASSERT(aURI, "Must provide aURI out param");
554 *aURI = GetHrefURIForAnchors().take();
555 // We promise out param is non-null if we return true, so base rv on it
556 return *aURI != nullptr;
559 already_AddRefed<nsIURI> nsGenericHTMLElement::GetHrefURIForAnchors() const {
560 // This is used by the three Link implementations and
561 // nsHTMLStyleElement.
563 // Get href= attribute (relative URI).
565 // We use the nsAttrValue's copy of the URI string to avoid copying.
566 nsCOMPtr<nsIURI> uri;
567 GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri));
569 return uri.forget();
572 nsresult nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID,
573 nsAtom* aName,
574 const nsAttrValueOrString* aValue,
575 bool aNotify) {
576 if (aNamespaceID == kNameSpaceID_None) {
577 if (aName == nsGkAtoms::accesskey) {
578 // Have to unregister before clearing flag. See UnregAccessKey
579 UnregAccessKey();
580 if (!aValue) {
581 UnsetFlags(NODE_HAS_ACCESSKEY);
583 } else if (aName == nsGkAtoms::name) {
584 // Have to do this before clearing flag. See RemoveFromNameTable
585 RemoveFromNameTable();
586 if (!aValue || aValue->IsEmpty()) {
587 ClearHasName();
589 } else if (aName == nsGkAtoms::contenteditable) {
590 if (aValue) {
591 // Set this before the attribute is set so that any subclass code that
592 // runs before the attribute is set won't think we're missing a
593 // contenteditable attr when we actually have one.
594 SetMayHaveContentEditableAttr();
597 if (!aValue && IsEventAttributeName(aName)) {
598 if (EventListenerManager* manager = GetExistingListenerManager()) {
599 manager->RemoveEventHandler(aName);
604 return nsGenericHTMLElementBase::BeforeSetAttr(aNamespaceID, aName, aValue,
605 aNotify);
608 nsresult nsGenericHTMLElement::AfterSetAttr(
609 int32_t aNamespaceID, nsAtom* aName, const nsAttrValue* aValue,
610 const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal,
611 bool aNotify) {
612 if (aNamespaceID == kNameSpaceID_None) {
613 if (IsEventAttributeName(aName) && aValue) {
614 MOZ_ASSERT(aValue->Type() == nsAttrValue::eString,
615 "Expected string value for script body");
616 nsresult rv = SetEventHandler(aName, aValue->GetStringValue());
617 NS_ENSURE_SUCCESS(rv, rv);
618 } else if (aNotify && aName == nsGkAtoms::spellcheck) {
619 SyncEditorsOnSubtree(this);
620 } else if (aName == nsGkAtoms::dir) {
621 Directionality dir = eDir_LTR;
622 // A boolean tracking whether we need to recompute our directionality.
623 // This needs to happen after we update our internal "dir" attribute
624 // state but before we call SetDirectionalityOnDescendants.
625 bool recomputeDirectionality = false;
626 // We don't want to have to keep getting the "dir" attribute in
627 // IntrinsicState, so we manually recompute our dir-related event states
628 // here and send the relevant update notifications.
629 EventStates dirStates;
630 if (aValue && aValue->Type() == nsAttrValue::eEnum) {
631 SetHasValidDir();
632 dirStates |= NS_EVENT_STATE_HAS_DIR_ATTR;
633 Directionality dirValue = (Directionality)aValue->GetEnumValue();
634 if (dirValue == eDir_Auto) {
635 dirStates |= NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO;
636 } else {
637 dir = dirValue;
638 SetDirectionality(dir, aNotify);
639 if (dirValue == eDir_LTR) {
640 dirStates |= NS_EVENT_STATE_DIR_ATTR_LTR;
641 } else {
642 MOZ_ASSERT(dirValue == eDir_RTL);
643 dirStates |= NS_EVENT_STATE_DIR_ATTR_RTL;
646 } else {
647 if (aValue) {
648 // We have a value, just not a valid one.
649 dirStates |= NS_EVENT_STATE_HAS_DIR_ATTR;
651 ClearHasValidDir();
652 if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
653 dirStates |= NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO;
654 } else {
655 recomputeDirectionality = true;
658 // Now figure out what's changed about our dir states.
659 EventStates oldDirStates = State() & DIR_ATTR_STATES;
660 EventStates changedStates = dirStates ^ oldDirStates;
661 ToggleStates(changedStates, aNotify);
662 if (recomputeDirectionality) {
663 dir = RecomputeDirectionality(this, aNotify);
665 SetDirectionalityOnDescendants(this, dir, aNotify);
666 } else if (aName == nsGkAtoms::contenteditable) {
667 int32_t editableCountDelta = 0;
668 if (aOldValue &&
669 (aOldValue->Equals(NS_LITERAL_STRING("true"), eIgnoreCase) ||
670 aOldValue->Equals(EmptyString(), eIgnoreCase))) {
671 editableCountDelta = -1;
673 if (aValue && (aValue->Equals(NS_LITERAL_STRING("true"), eIgnoreCase) ||
674 aValue->Equals(EmptyString(), eIgnoreCase))) {
675 ++editableCountDelta;
677 ChangeEditableState(editableCountDelta);
678 } else if (aName == nsGkAtoms::accesskey) {
679 if (aValue && !aValue->Equals(EmptyString(), eIgnoreCase)) {
680 SetFlags(NODE_HAS_ACCESSKEY);
681 RegAccessKey();
683 } else if (aName == nsGkAtoms::name) {
684 if (aValue && !aValue->Equals(EmptyString(), eIgnoreCase)) {
685 // This may not be quite right because we can have subclass code run
686 // before here. But in practice subclasses don't care about this flag,
687 // and in particular selector matching does not care. Otherwise we'd
688 // want to handle it like we handle id attributes (in PreIdMaybeChange
689 // and PostIdMaybeChange).
690 SetHasName();
691 if (CanHaveName(NodeInfo()->NameAtom())) {
692 AddToNameTable(aValue->GetAtomValue());
698 return nsGenericHTMLElementBase::AfterSetAttr(
699 aNamespaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
702 EventListenerManager* nsGenericHTMLElement::GetEventListenerManagerForAttr(
703 nsAtom* aAttrName, bool* aDefer) {
704 // Attributes on the body and frameset tags get set on the global object
705 if ((mNodeInfo->Equals(nsGkAtoms::body) ||
706 mNodeInfo->Equals(nsGkAtoms::frameset)) &&
707 // We only forward some event attributes from body/frameset to window
709 #define EVENT(name_, id_, type_, struct_) /* nothing */
710 #define FORWARDED_EVENT(name_, id_, type_, struct_) \
711 || nsGkAtoms::on##name_ == aAttrName
712 #define WINDOW_EVENT FORWARDED_EVENT
713 #include "mozilla/EventNameList.h" // IWYU pragma: keep
714 #undef WINDOW_EVENT
715 #undef FORWARDED_EVENT
716 #undef EVENT
717 )) {
718 nsPIDOMWindowInner* win;
720 // If we have a document, and it has a window, add the event
721 // listener on the window (the inner window). If not, proceed as
722 // normal.
723 // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() here,
724 // override BindToTree for those classes and munge event listeners there?
725 Document* document = OwnerDoc();
727 *aDefer = false;
728 if ((win = document->GetInnerWindow())) {
729 nsCOMPtr<EventTarget> piTarget(do_QueryInterface(win));
731 return piTarget->GetOrCreateListenerManager();
734 return nullptr;
737 return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName,
738 aDefer);
741 #define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
742 #define FORWARDED_EVENT(name_, id_, type_, struct_) \
743 EventHandlerNonNull* nsGenericHTMLElement::GetOn##name_() { \
744 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
745 /* XXXbz note to self: add tests for this! */ \
746 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
747 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
748 return globalWin->GetOn##name_(); \
750 return nullptr; \
753 return nsINode::GetOn##name_(); \
755 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \
756 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
757 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
758 if (!win) { \
759 return; \
762 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
763 return globalWin->SetOn##name_(handler); \
766 return nsINode::SetOn##name_(handler); \
768 #define ERROR_EVENT(name_, id_, type_, struct_) \
769 already_AddRefed<EventHandlerNonNull> nsGenericHTMLElement::GetOn##name_() { \
770 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
771 /* XXXbz note to self: add tests for this! */ \
772 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
773 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
774 OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_(); \
775 if (errorHandler) { \
776 RefPtr<EventHandlerNonNull> handler = \
777 new EventHandlerNonNull(errorHandler); \
778 return handler.forget(); \
781 return nullptr; \
784 RefPtr<EventHandlerNonNull> handler = nsINode::GetOn##name_(); \
785 return handler.forget(); \
787 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \
788 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
789 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
790 if (!win) { \
791 return; \
794 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
795 RefPtr<OnErrorEventHandlerNonNull> errorHandler; \
796 if (handler) { \
797 errorHandler = new OnErrorEventHandlerNonNull(handler); \
799 return globalWin->SetOn##name_(errorHandler); \
802 return nsINode::SetOn##name_(handler); \
804 #include "mozilla/EventNameList.h" // IWYU pragma: keep
805 #undef ERROR_EVENT
806 #undef FORWARDED_EVENT
807 #undef EVENT
809 void nsGenericHTMLElement::GetBaseTarget(nsAString& aBaseTarget) const {
810 OwnerDoc()->GetBaseTarget(aBaseTarget);
813 //----------------------------------------------------------------------
815 bool nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID,
816 nsAtom* aAttribute,
817 const nsAString& aValue,
818 nsIPrincipal* aMaybeScriptedPrincipal,
819 nsAttrValue& aResult) {
820 if (aNamespaceID == kNameSpaceID_None) {
821 if (aAttribute == nsGkAtoms::dir) {
822 return aResult.ParseEnumValue(aValue, kDirTable, false);
825 if (aAttribute == nsGkAtoms::tabindex) {
826 return aResult.ParseIntValue(aValue);
829 if (aAttribute == nsGkAtoms::referrerpolicy) {
830 return ParseReferrerAttribute(aValue, aResult);
833 if (aAttribute == nsGkAtoms::name) {
834 // Store name as an atom. name="" means that the element has no name,
835 // not that it has an empty string as the name.
836 if (aValue.IsEmpty()) {
837 return false;
839 aResult.ParseAtom(aValue);
840 return true;
843 if (aAttribute == nsGkAtoms::contenteditable) {
844 aResult.ParseAtom(aValue);
845 return true;
848 if (aAttribute == nsGkAtoms::rel) {
849 aResult.ParseAtomArray(aValue);
850 return true;
854 return nsGenericHTMLElementBase::ParseAttribute(
855 aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
858 bool nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID,
859 nsAtom* aAttribute,
860 const nsAString& aValue,
861 nsAttrValue& aResult) {
862 if (aNamespaceID == kNameSpaceID_None &&
863 aAttribute == nsGkAtoms::background && !aValue.IsEmpty()) {
864 // Resolve url to an absolute url
865 Document* doc = OwnerDoc();
866 nsCOMPtr<nsIURI> uri;
867 nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
868 getter_AddRefs(uri), aValue, doc, GetBaseURI());
869 if (NS_FAILED(rv)) {
870 return false;
872 aResult.SetTo(uri, &aValue);
873 return true;
876 return false;
879 bool nsGenericHTMLElement::IsAttributeMapped(const nsAtom* aAttribute) const {
880 static const MappedAttributeEntry* const map[] = {sCommonAttributeMap};
882 return FindAttributeDependence(aAttribute, map);
885 nsMapRuleToAttributesFunc nsGenericHTMLElement::GetAttributeMappingFunction()
886 const {
887 return &MapCommonAttributesInto;
890 nsIFormControlFrame* nsGenericHTMLElement::GetFormControlFrame(
891 bool aFlushFrames) {
892 if (aFlushFrames && IsInComposedDoc()) {
893 // Cause a flush of the frames, so we get up-to-date frame information
894 GetComposedDoc()->FlushPendingNotifications(FlushType::Frames);
896 nsIFrame* frame = GetPrimaryFrame();
897 if (frame) {
898 nsIFormControlFrame* form_frame = do_QueryFrame(frame);
899 if (form_frame) {
900 return form_frame;
903 // If we have generated content, the primary frame will be a
904 // wrapper frame.. out real frame will be in its child list.
905 for (frame = frame->PrincipalChildList().FirstChild(); frame;
906 frame = frame->GetNextSibling()) {
907 form_frame = do_QueryFrame(frame);
908 if (form_frame) {
909 return form_frame;
914 return nullptr;
917 nsPresContext* nsGenericHTMLElement::GetPresContext(PresContextFor aFor) {
918 // Get the document
919 Document* doc =
920 (aFor == eForComposedDoc) ? GetComposedDoc() : GetUncomposedDoc();
921 if (doc) {
922 return doc->GetPresContext();
925 return nullptr;
928 static const nsAttrValue::EnumTable kDivAlignTable[] = {
929 {"left", NS_STYLE_TEXT_ALIGN_MOZ_LEFT},
930 {"right", NS_STYLE_TEXT_ALIGN_MOZ_RIGHT},
931 {"center", NS_STYLE_TEXT_ALIGN_MOZ_CENTER},
932 {"middle", NS_STYLE_TEXT_ALIGN_MOZ_CENTER},
933 {"justify", NS_STYLE_TEXT_ALIGN_JUSTIFY},
934 {nullptr, 0}};
936 static const nsAttrValue::EnumTable kFrameborderTable[] = {
937 {"yes", NS_STYLE_FRAME_YES},
938 {"no", NS_STYLE_FRAME_NO},
939 {"1", NS_STYLE_FRAME_1},
940 {"0", NS_STYLE_FRAME_0},
941 {nullptr, 0}};
943 static const nsAttrValue::EnumTable kScrollingTable[] = {
944 {"yes", NS_STYLE_FRAME_YES}, {"no", NS_STYLE_FRAME_NO},
945 {"on", NS_STYLE_FRAME_ON}, {"off", NS_STYLE_FRAME_OFF},
946 {"scroll", NS_STYLE_FRAME_SCROLL}, {"noscroll", NS_STYLE_FRAME_NOSCROLL},
947 {"auto", NS_STYLE_FRAME_AUTO}, {nullptr, 0}};
949 static const nsAttrValue::EnumTable kTableVAlignTable[] = {
950 {"top", StyleVerticalAlignKeyword::Top},
951 {"middle", StyleVerticalAlignKeyword::Middle},
952 {"bottom", StyleVerticalAlignKeyword::Bottom},
953 {"baseline", StyleVerticalAlignKeyword::Baseline},
954 {nullptr, 0}};
956 bool nsGenericHTMLElement::ParseAlignValue(const nsAString& aString,
957 nsAttrValue& aResult) {
958 static const nsAttrValue::EnumTable kAlignTable[] = {
959 {"left", NS_STYLE_TEXT_ALIGN_LEFT},
960 {"right", NS_STYLE_TEXT_ALIGN_RIGHT},
962 {"top", StyleVerticalAlignKeyword::Top},
963 {"middle", StyleVerticalAlignKeyword::MozMiddleWithBaseline},
964 {"bottom", StyleVerticalAlignKeyword::Bottom},
966 {"center", StyleVerticalAlignKeyword::MozMiddleWithBaseline},
967 {"baseline", StyleVerticalAlignKeyword::Baseline},
969 {"texttop", StyleVerticalAlignKeyword::TextTop},
970 {"absmiddle", StyleVerticalAlignKeyword::Middle},
971 {"abscenter", StyleVerticalAlignKeyword::Middle},
972 {"absbottom", StyleVerticalAlignKeyword::Bottom},
973 {nullptr, 0}};
975 return aResult.ParseEnumValue(aString, kAlignTable, false);
978 //----------------------------------------
980 static const nsAttrValue::EnumTable kTableHAlignTable[] = {
981 {"left", NS_STYLE_TEXT_ALIGN_LEFT},
982 {"right", NS_STYLE_TEXT_ALIGN_RIGHT},
983 {"center", NS_STYLE_TEXT_ALIGN_CENTER},
984 {"char", NS_STYLE_TEXT_ALIGN_CHAR},
985 {"justify", NS_STYLE_TEXT_ALIGN_JUSTIFY},
986 {nullptr, 0}};
988 bool nsGenericHTMLElement::ParseTableHAlignValue(const nsAString& aString,
989 nsAttrValue& aResult) {
990 return aResult.ParseEnumValue(aString, kTableHAlignTable, false);
993 //----------------------------------------
995 // This table is used for td, th, tr, col, thead, tbody and tfoot.
996 static const nsAttrValue::EnumTable kTableCellHAlignTable[] = {
997 {"left", NS_STYLE_TEXT_ALIGN_MOZ_LEFT},
998 {"right", NS_STYLE_TEXT_ALIGN_MOZ_RIGHT},
999 {"center", NS_STYLE_TEXT_ALIGN_MOZ_CENTER},
1000 {"char", NS_STYLE_TEXT_ALIGN_CHAR},
1001 {"justify", NS_STYLE_TEXT_ALIGN_JUSTIFY},
1002 {"middle", NS_STYLE_TEXT_ALIGN_MOZ_CENTER},
1003 {"absmiddle", NS_STYLE_TEXT_ALIGN_CENTER},
1004 {nullptr, 0}};
1006 bool nsGenericHTMLElement::ParseTableCellHAlignValue(const nsAString& aString,
1007 nsAttrValue& aResult) {
1008 return aResult.ParseEnumValue(aString, kTableCellHAlignTable, false);
1011 //----------------------------------------
1013 bool nsGenericHTMLElement::ParseTableVAlignValue(const nsAString& aString,
1014 nsAttrValue& aResult) {
1015 return aResult.ParseEnumValue(aString, kTableVAlignTable, false);
1018 bool nsGenericHTMLElement::ParseDivAlignValue(const nsAString& aString,
1019 nsAttrValue& aResult) {
1020 return aResult.ParseEnumValue(aString, kDivAlignTable, false);
1023 bool nsGenericHTMLElement::ParseImageAttribute(nsAtom* aAttribute,
1024 const nsAString& aString,
1025 nsAttrValue& aResult) {
1026 if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
1027 aAttribute == nsGkAtoms::hspace || aAttribute == nsGkAtoms::vspace) {
1028 return aResult.ParseHTMLDimension(aString);
1030 if (aAttribute == nsGkAtoms::border) {
1031 return aResult.ParseNonNegativeIntValue(aString);
1033 return false;
1036 bool nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString,
1037 nsAttrValue& aResult) {
1038 static const nsAttrValue::EnumTable kReferrerTable[] = {
1039 {ReferrerPolicyToString(net::RP_No_Referrer),
1040 static_cast<int16_t>(net::RP_No_Referrer)},
1041 {ReferrerPolicyToString(net::RP_Origin),
1042 static_cast<int16_t>(net::RP_Origin)},
1043 {ReferrerPolicyToString(net::RP_Origin_When_Crossorigin),
1044 static_cast<int16_t>(net::RP_Origin_When_Crossorigin)},
1045 {ReferrerPolicyToString(net::RP_No_Referrer_When_Downgrade),
1046 static_cast<int16_t>(net::RP_No_Referrer_When_Downgrade)},
1047 {ReferrerPolicyToString(net::RP_Unsafe_URL),
1048 static_cast<int16_t>(net::RP_Unsafe_URL)},
1049 {ReferrerPolicyToString(net::RP_Strict_Origin),
1050 static_cast<int16_t>(net::RP_Strict_Origin)},
1051 {ReferrerPolicyToString(net::RP_Same_Origin),
1052 static_cast<int16_t>(net::RP_Same_Origin)},
1053 {ReferrerPolicyToString(net::RP_Strict_Origin_When_Cross_Origin),
1054 static_cast<int16_t>(net::RP_Strict_Origin_When_Cross_Origin)},
1055 {nullptr, 0}};
1056 return aResult.ParseEnumValue(aString, kReferrerTable, false);
1059 bool nsGenericHTMLElement::ParseFrameborderValue(const nsAString& aString,
1060 nsAttrValue& aResult) {
1061 return aResult.ParseEnumValue(aString, kFrameborderTable, false);
1064 bool nsGenericHTMLElement::ParseScrollingValue(const nsAString& aString,
1065 nsAttrValue& aResult) {
1066 return aResult.ParseEnumValue(aString, kScrollingTable, false);
1069 static inline void MapLangAttributeInto(const nsMappedAttributes* aAttributes,
1070 MappedDeclarations& aDecls) {
1071 const nsAttrValue* langValue = aAttributes->GetAttr(nsGkAtoms::lang);
1072 if (!langValue) {
1073 return;
1075 MOZ_ASSERT(langValue->Type() == nsAttrValue::eAtom);
1076 aDecls.SetIdentAtomValueIfUnset(eCSSProperty__x_lang,
1077 langValue->GetAtomValue());
1078 if (!aDecls.PropertyIsSet(eCSSProperty_text_emphasis_position)) {
1079 const nsAtom* lang = langValue->GetAtomValue();
1080 if (nsStyleUtil::MatchesLanguagePrefix(lang, u"zh")) {
1081 aDecls.SetKeywordValue(eCSSProperty_text_emphasis_position,
1082 NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH);
1083 } else if (nsStyleUtil::MatchesLanguagePrefix(lang, u"ja") ||
1084 nsStyleUtil::MatchesLanguagePrefix(lang, u"mn")) {
1085 // This branch is currently no part of the spec.
1086 // See bug 1040668 comment 69 and comment 75.
1087 aDecls.SetKeywordValue(eCSSProperty_text_emphasis_position,
1088 NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT);
1094 * Handle attributes common to all html elements
1096 void nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(
1097 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1098 if (!aDecls.PropertyIsSet(eCSSProperty__moz_user_modify)) {
1099 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::contenteditable);
1100 if (value) {
1101 if (value->Equals(nsGkAtoms::_empty, eCaseMatters) ||
1102 value->Equals(nsGkAtoms::_true, eIgnoreCase)) {
1103 aDecls.SetKeywordValue(eCSSProperty__moz_user_modify,
1104 StyleUserModify::ReadWrite);
1105 } else if (value->Equals(nsGkAtoms::_false, eIgnoreCase)) {
1106 aDecls.SetKeywordValue(eCSSProperty__moz_user_modify,
1107 StyleUserModify::ReadOnly);
1112 MapLangAttributeInto(aAttributes, aDecls);
1115 void nsGenericHTMLElement::MapCommonAttributesInto(
1116 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1117 MapCommonAttributesIntoExceptHidden(aAttributes, aDecls);
1119 if (!aDecls.PropertyIsSet(eCSSProperty_display)) {
1120 if (aAttributes->IndexOfAttr(nsGkAtoms::hidden) >= 0) {
1121 aDecls.SetKeywordValue(eCSSProperty_display, StyleDisplay::None);
1126 /* static */
1127 const nsGenericHTMLElement::MappedAttributeEntry
1128 nsGenericHTMLElement::sCommonAttributeMap[] = {{nsGkAtoms::contenteditable},
1129 {nsGkAtoms::lang},
1130 {nsGkAtoms::hidden},
1131 {nullptr}};
1133 /* static */
1134 const Element::MappedAttributeEntry
1135 nsGenericHTMLElement::sImageMarginSizeAttributeMap[] = {{nsGkAtoms::width},
1136 {nsGkAtoms::height},
1137 {nsGkAtoms::hspace},
1138 {nsGkAtoms::vspace},
1139 {nullptr}};
1141 /* static */
1142 const Element::MappedAttributeEntry
1143 nsGenericHTMLElement::sImageAlignAttributeMap[] = {{nsGkAtoms::align},
1144 {nullptr}};
1146 /* static */
1147 const Element::MappedAttributeEntry
1148 nsGenericHTMLElement::sDivAlignAttributeMap[] = {{nsGkAtoms::align},
1149 {nullptr}};
1151 /* static */
1152 const Element::MappedAttributeEntry
1153 nsGenericHTMLElement::sImageBorderAttributeMap[] = {{nsGkAtoms::border},
1154 {nullptr}};
1156 /* static */
1157 const Element::MappedAttributeEntry
1158 nsGenericHTMLElement::sBackgroundAttributeMap[] = {
1159 {nsGkAtoms::background}, {nsGkAtoms::bgcolor}, {nullptr}};
1161 /* static */
1162 const Element::MappedAttributeEntry
1163 nsGenericHTMLElement::sBackgroundColorAttributeMap[] = {
1164 {nsGkAtoms::bgcolor}, {nullptr}};
1166 void nsGenericHTMLElement::MapImageAlignAttributeInto(
1167 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1168 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
1169 if (value && value->Type() == nsAttrValue::eEnum) {
1170 int32_t align = value->GetEnumValue();
1171 if (!aDecls.PropertyIsSet(eCSSProperty_float)) {
1172 if (align == NS_STYLE_TEXT_ALIGN_LEFT) {
1173 aDecls.SetKeywordValue(eCSSProperty_float, StyleFloat::Left);
1174 } else if (align == NS_STYLE_TEXT_ALIGN_RIGHT) {
1175 aDecls.SetKeywordValue(eCSSProperty_float, StyleFloat::Right);
1178 if (!aDecls.PropertyIsSet(eCSSProperty_vertical_align)) {
1179 switch (align) {
1180 case NS_STYLE_TEXT_ALIGN_LEFT:
1181 case NS_STYLE_TEXT_ALIGN_RIGHT:
1182 break;
1183 default:
1184 aDecls.SetKeywordValue(eCSSProperty_vertical_align, align);
1185 break;
1191 void nsGenericHTMLElement::MapDivAlignAttributeInto(
1192 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1193 if (!aDecls.PropertyIsSet(eCSSProperty_text_align)) {
1194 // align: enum
1195 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
1196 if (value && value->Type() == nsAttrValue::eEnum)
1197 aDecls.SetKeywordValue(eCSSProperty_text_align, value->GetEnumValue());
1201 void nsGenericHTMLElement::MapVAlignAttributeInto(
1202 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1203 if (!aDecls.PropertyIsSet(eCSSProperty_vertical_align)) {
1204 // align: enum
1205 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::valign);
1206 if (value && value->Type() == nsAttrValue::eEnum)
1207 aDecls.SetKeywordValue(eCSSProperty_vertical_align,
1208 value->GetEnumValue());
1212 static void MapDimensionAttributeInto(MappedDeclarations& aDecls,
1213 nsCSSPropertyID aProp,
1214 const nsAttrValue& aValue) {
1215 MOZ_ASSERT(!aDecls.PropertyIsSet(aProp),
1216 "Why mapping the same property twice?");
1217 if (aValue.Type() == nsAttrValue::eInteger) {
1218 return aDecls.SetPixelValue(aProp, aValue.GetIntegerValue());
1220 if (aValue.Type() == nsAttrValue::ePercent) {
1221 return aDecls.SetPercentValue(aProp, aValue.GetPercentValue());
1223 if (aValue.Type() == nsAttrValue::eDoubleValue) {
1224 return aDecls.SetPixelValue(aProp, aValue.GetDoubleValue());
1228 void nsGenericHTMLElement::MapImageMarginAttributeInto(
1229 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1230 const nsAttrValue* value;
1232 // hspace: value
1233 value = aAttributes->GetAttr(nsGkAtoms::hspace);
1234 if (value) {
1235 MapDimensionAttributeInto(aDecls, eCSSProperty_margin_left, *value);
1236 MapDimensionAttributeInto(aDecls, eCSSProperty_margin_right, *value);
1239 // vspace: value
1240 value = aAttributes->GetAttr(nsGkAtoms::vspace);
1241 if (value) {
1242 MapDimensionAttributeInto(aDecls, eCSSProperty_margin_top, *value);
1243 MapDimensionAttributeInto(aDecls, eCSSProperty_margin_bottom, *value);
1247 void nsGenericHTMLElement::MapWidthAttributeInto(
1248 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1249 if (const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width)) {
1250 MapDimensionAttributeInto(aDecls, eCSSProperty_width, *value);
1254 void nsGenericHTMLElement::MapHeightAttributeInto(
1255 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1256 if (const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height)) {
1257 MapDimensionAttributeInto(aDecls, eCSSProperty_height, *value);
1261 void nsGenericHTMLElement::MapImageSizeAttributesInto(
1262 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1263 auto* width = aAttributes->GetAttr(nsGkAtoms::width);
1264 auto* height = aAttributes->GetAttr(nsGkAtoms::height);
1265 if (width) {
1266 MapDimensionAttributeInto(aDecls, eCSSProperty_width, *width);
1268 if (height) {
1269 MapDimensionAttributeInto(aDecls, eCSSProperty_height, *height);
1271 // NOTE(emilio): If we implement the unrestricted aspect-ratio proposal, we
1272 // probably need to make this attribute mapping not apply to things like
1273 // <marquee> and <table>, which right now can go through this path.
1274 if (StaticPrefs::layout_css_width_and_height_map_to_aspect_ratio_enabled() &&
1275 width && height) {
1276 Maybe<double> w;
1277 if (width->Type() == nsAttrValue::eInteger) {
1278 w.emplace(width->GetIntegerValue());
1279 } else if (width->Type() == nsAttrValue::eDoubleValue) {
1280 w.emplace(width->GetDoubleValue());
1283 Maybe<double> h;
1284 if (height->Type() == nsAttrValue::eInteger) {
1285 h.emplace(height->GetIntegerValue());
1286 } else if (height->Type() == nsAttrValue::eDoubleValue) {
1287 h.emplace(height->GetDoubleValue());
1290 if (w && h && *w != 0 && *h != 0) {
1291 aDecls.SetNumberValue(eCSSProperty_aspect_ratio, *w / *h);
1296 void nsGenericHTMLElement::MapImageBorderAttributeInto(
1297 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1298 // border: pixels
1299 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::border);
1300 if (!value) return;
1302 nscoord val = 0;
1303 if (value->Type() == nsAttrValue::eInteger) val = value->GetIntegerValue();
1305 aDecls.SetPixelValueIfUnset(eCSSProperty_border_top_width, (float)val);
1306 aDecls.SetPixelValueIfUnset(eCSSProperty_border_right_width, (float)val);
1307 aDecls.SetPixelValueIfUnset(eCSSProperty_border_bottom_width, (float)val);
1308 aDecls.SetPixelValueIfUnset(eCSSProperty_border_left_width, (float)val);
1310 aDecls.SetKeywordValueIfUnset(eCSSProperty_border_top_style,
1311 StyleBorderStyle::Solid);
1312 aDecls.SetKeywordValueIfUnset(eCSSProperty_border_right_style,
1313 StyleBorderStyle::Solid);
1314 aDecls.SetKeywordValueIfUnset(eCSSProperty_border_bottom_style,
1315 StyleBorderStyle::Solid);
1316 aDecls.SetKeywordValueIfUnset(eCSSProperty_border_left_style,
1317 StyleBorderStyle::Solid);
1319 aDecls.SetCurrentColorIfUnset(eCSSProperty_border_top_color);
1320 aDecls.SetCurrentColorIfUnset(eCSSProperty_border_right_color);
1321 aDecls.SetCurrentColorIfUnset(eCSSProperty_border_bottom_color);
1322 aDecls.SetCurrentColorIfUnset(eCSSProperty_border_left_color);
1325 void nsGenericHTMLElement::MapBackgroundInto(
1326 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1327 if (!aDecls.PropertyIsSet(eCSSProperty_background_image)) {
1328 // background
1329 nsAttrValue* value =
1330 const_cast<nsAttrValue*>(aAttributes->GetAttr(nsGkAtoms::background));
1331 if (value) {
1332 aDecls.SetBackgroundImage(*value);
1337 void nsGenericHTMLElement::MapBGColorInto(const nsMappedAttributes* aAttributes,
1338 MappedDeclarations& aDecls) {
1339 if (!aDecls.PropertyIsSet(eCSSProperty_background_color)) {
1340 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::bgcolor);
1341 nscolor color;
1342 if (value && value->GetColorValue(color)) {
1343 aDecls.SetColorValue(eCSSProperty_background_color, color);
1348 void nsGenericHTMLElement::MapBackgroundAttributesInto(
1349 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
1350 MapBackgroundInto(aAttributes, aDecls);
1351 MapBGColorInto(aAttributes, aDecls);
1354 //----------------------------------------------------------------------
1356 int32_t nsGenericHTMLElement::GetIntAttr(nsAtom* aAttr,
1357 int32_t aDefault) const {
1358 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
1359 if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
1360 return attrVal->GetIntegerValue();
1362 return aDefault;
1365 nsresult nsGenericHTMLElement::SetIntAttr(nsAtom* aAttr, int32_t aValue) {
1366 nsAutoString value;
1367 value.AppendInt(aValue);
1369 return SetAttr(kNameSpaceID_None, aAttr, value, true);
1372 uint32_t nsGenericHTMLElement::GetUnsignedIntAttr(nsAtom* aAttr,
1373 uint32_t aDefault) const {
1374 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
1375 if (!attrVal || attrVal->Type() != nsAttrValue::eInteger) {
1376 return aDefault;
1379 return attrVal->GetIntegerValue();
1382 uint32_t nsGenericHTMLElement::GetDimensionAttrAsUnsignedInt(
1383 nsAtom* aAttr, uint32_t aDefault) const {
1384 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
1385 if (!attrVal) {
1386 return aDefault;
1389 if (attrVal->Type() == nsAttrValue::eInteger) {
1390 return attrVal->GetIntegerValue();
1393 if (attrVal->Type() == nsAttrValue::ePercent) {
1394 // This is a nasty hack. When we parsed the value, we stored it as an
1395 // ePercent, not eInteger, because there was a '%' after it in the string.
1396 // But the spec says to basically re-parse the string as an integer.
1397 // Luckily, we can just return the value we have stored. But
1398 // GetPercentValue() divides it by 100, so we need to multiply it back.
1399 return uint32_t(attrVal->GetPercentValue() * 100.0f);
1402 if (attrVal->Type() == nsAttrValue::eDoubleValue) {
1403 return uint32_t(attrVal->GetDoubleValue());
1406 // Unfortunately, the set of values that are valid dimensions is not a
1407 // superset of values that are valid unsigned ints. In particular "+100" is
1408 // not a valid dimension, but should parse as the unsigned int "100". So if
1409 // we got here and we don't have a valid dimension value, just try re-parsing
1410 // the string we have as an integer.
1411 nsAutoString val;
1412 attrVal->ToString(val);
1413 nsContentUtils::ParseHTMLIntegerResultFlags result;
1414 int32_t parsedInt = nsContentUtils::ParseHTMLInteger(val, &result);
1415 if ((result & nsContentUtils::eParseHTMLInteger_Error) || parsedInt < 0) {
1416 return aDefault;
1419 return parsedInt;
1422 void nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
1423 nsAString& aResult) const {
1424 nsCOMPtr<nsIURI> uri;
1425 bool hadAttr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri));
1426 if (!hadAttr) {
1427 aResult.Truncate();
1428 return;
1431 if (!uri) {
1432 // Just return the attr value
1433 GetAttr(kNameSpaceID_None, aAttr, aResult);
1434 return;
1437 nsAutoCString spec;
1438 uri->GetSpec(spec);
1439 CopyUTF8toUTF16(spec, aResult);
1442 bool nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
1443 nsIURI** aURI) const {
1444 *aURI = nullptr;
1446 const nsAttrValue* attr = mAttrs.GetAttr(aAttr);
1447 if (!attr) {
1448 return false;
1451 nsCOMPtr<nsIURI> baseURI = GetBaseURI();
1453 if (aBaseAttr) {
1454 nsAutoString baseAttrValue;
1455 if (GetAttr(kNameSpaceID_None, aBaseAttr, baseAttrValue)) {
1456 nsCOMPtr<nsIURI> baseAttrURI;
1457 nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
1458 getter_AddRefs(baseAttrURI), baseAttrValue, OwnerDoc(), baseURI);
1459 if (NS_FAILED(rv)) {
1460 return true;
1462 baseURI.swap(baseAttrURI);
1466 // Don't care about return value. If it fails, we still want to
1467 // return true, and *aURI will be null.
1468 nsContentUtils::NewURIWithDocumentCharset(aURI, attr->GetStringValue(),
1469 OwnerDoc(), baseURI);
1470 return true;
1473 HTMLMenuElement* nsGenericHTMLElement::GetContextMenu() const {
1474 nsAutoString value;
1475 GetHTMLAttr(nsGkAtoms::contextmenu, value);
1476 if (!value.IsEmpty()) {
1477 // XXXsmaug How should this work in Shadow DOM?
1478 Document* doc = GetUncomposedDoc();
1479 if (doc) {
1480 return HTMLMenuElement::FromNodeOrNull(doc->GetElementById(value));
1483 return nullptr;
1486 bool nsGenericHTMLElement::IsLabelable() const {
1487 return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter);
1490 /* static */
1491 bool nsGenericHTMLElement::MatchLabelsElement(Element* aElement,
1492 int32_t aNamespaceID,
1493 nsAtom* aAtom, void* aData) {
1494 HTMLLabelElement* element = HTMLLabelElement::FromNode(aElement);
1495 return element && element->GetControl() == aData;
1498 already_AddRefed<nsINodeList> nsGenericHTMLElement::Labels() {
1499 MOZ_ASSERT(IsLabelable(),
1500 "Labels() only allow labelable elements to use it.");
1501 nsExtendedDOMSlots* slots = ExtendedDOMSlots();
1503 if (!slots->mLabelsList) {
1504 slots->mLabelsList =
1505 new nsLabelsNodeList(SubtreeRoot(), MatchLabelsElement, nullptr, this);
1508 RefPtr<nsLabelsNodeList> labels = slots->mLabelsList;
1509 return labels.forget();
1512 bool nsGenericHTMLElement::IsInteractiveHTMLContent(
1513 bool aIgnoreTabindex) const {
1514 return IsAnyOfHTMLElements(nsGkAtoms::embed) ||
1515 (!aIgnoreTabindex && HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex));
1518 // static
1519 bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext* aCx,
1520 JSObject* aGlobal) {
1521 return TouchEvent::LegacyAPIEnabled(aCx, aGlobal);
1524 //----------------------------------------------------------------------
1526 nsGenericHTMLFormElement::nsGenericHTMLFormElement(
1527 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, uint8_t aType)
1528 : nsGenericHTMLElement(std::move(aNodeInfo)),
1529 nsIFormControl(aType),
1530 mForm(nullptr),
1531 mFieldSet(nullptr) {
1532 // We should add the NS_EVENT_STATE_ENABLED bit here as needed, but
1533 // that depends on our type, which is not initialized yet. So we
1534 // have to do this in subclasses.
1537 nsGenericHTMLFormElement::~nsGenericHTMLFormElement() {
1538 if (mFieldSet) {
1539 mFieldSet->RemoveElement(this);
1542 // Check that this element doesn't know anything about its form at this point.
1543 NS_ASSERTION(!mForm, "mForm should be null at this point!");
1546 NS_IMPL_ISUPPORTS_INHERITED(nsGenericHTMLFormElement, nsGenericHTMLElement,
1547 nsIFormControl)
1549 nsINode* nsGenericHTMLFormElement::GetScopeChainParent() const {
1550 return mForm ? mForm : nsGenericHTMLElement::GetScopeChainParent();
1553 bool nsGenericHTMLFormElement::IsNodeOfType(uint32_t aFlags) const {
1554 return !(aFlags & ~eHTML_FORM_CONTROL);
1557 void nsGenericHTMLFormElement::SaveSubtreeState() {
1558 SaveState();
1560 nsGenericHTMLElement::SaveSubtreeState();
1563 void nsGenericHTMLFormElement::SetForm(HTMLFormElement* aForm) {
1564 MOZ_ASSERT(aForm, "Don't pass null here");
1565 NS_ASSERTION(!mForm,
1566 "We don't support switching from one non-null form to another.");
1568 SetForm(aForm, false);
1571 void nsGenericHTMLFormElement::SetForm(HTMLFormElement* aForm,
1572 bool aBindToTree) {
1573 if (aForm) {
1574 BeforeSetForm(aBindToTree);
1577 // keep a *weak* ref to the form here
1578 mForm = aForm;
1581 void nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm,
1582 bool aUnbindOrDelete) {
1583 NS_ASSERTION((mForm != nullptr) == HasFlag(ADDED_TO_FORM),
1584 "Form control should have had flag set correctly");
1586 if (!mForm) {
1587 return;
1590 if (aRemoveFromForm) {
1591 nsAutoString nameVal, idVal;
1592 GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
1593 GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
1595 mForm->RemoveElement(this, true);
1597 if (!nameVal.IsEmpty()) {
1598 mForm->RemoveElementFromTable(this, nameVal);
1601 if (!idVal.IsEmpty()) {
1602 mForm->RemoveElementFromTable(this, idVal);
1606 UnsetFlags(ADDED_TO_FORM);
1607 mForm = nullptr;
1609 AfterClearForm(aUnbindOrDelete);
1612 HTMLFormElement* nsGenericHTMLFormElement::GetFormElement() { return mForm; }
1614 HTMLFieldSetElement* nsGenericHTMLFormElement::GetFieldSet() {
1615 return mFieldSet;
1618 nsIContent::IMEState nsGenericHTMLFormElement::GetDesiredIMEState() {
1619 TextEditor* textEditor = GetTextEditorInternal();
1620 if (!textEditor) {
1621 return nsGenericHTMLElement::GetDesiredIMEState();
1623 IMEState state;
1624 nsresult rv = textEditor->GetPreferredIMEState(&state);
1625 if (NS_FAILED(rv)) {
1626 return nsGenericHTMLElement::GetDesiredIMEState();
1628 return state;
1631 nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext,
1632 nsINode& aParent) {
1633 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
1634 NS_ENSURE_SUCCESS(rv, rv);
1636 // An autofocus event has to be launched if the autofocus attribute is
1637 // specified and the element accept the autofocus attribute. In addition,
1638 // the document should not be already loaded and the "browser.autofocus"
1639 // preference should be 'true'.
1640 if (IsAutofocusable() && HasAttr(kNameSpaceID_None, nsGkAtoms::autofocus) &&
1641 StaticPrefs::browser_autofocus() && IsInUncomposedDoc()) {
1642 aContext.OwnerDoc().SetAutoFocusElement(this);
1645 // If @form is set, the element *has* to be in a composed document, otherwise
1646 // it wouldn't be possible to find an element with the corresponding id.
1647 // If @form isn't set, the element *has* to have a parent, otherwise it
1648 // wouldn't be possible to find a form ancestor.
1649 // We should not call UpdateFormOwner if none of these conditions are
1650 // fulfilled.
1651 if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ? IsInComposedDoc()
1652 : aParent.IsContent()) {
1653 UpdateFormOwner(true, nullptr);
1656 // Set parent fieldset which should be used for the disabled state.
1657 UpdateFieldSet(false);
1659 return NS_OK;
1662 void nsGenericHTMLFormElement::UnbindFromTree(bool aNullParent) {
1663 // Save state before doing anything
1664 SaveState();
1666 if (mForm) {
1667 // Might need to unset mForm
1668 if (aNullParent) {
1669 // No more parent means no more form
1670 ClearForm(true, true);
1671 } else {
1672 // Recheck whether we should still have an mForm.
1673 if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
1674 !FindAncestorForm(mForm)) {
1675 ClearForm(true, true);
1676 } else {
1677 UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
1681 if (!mForm) {
1682 // Our novalidate state might have changed
1683 UpdateState(false);
1687 // We have to remove the form id observer if there was one.
1688 // We will re-add one later if needed (during bind to tree).
1689 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
1690 nsGkAtoms::form)) {
1691 RemoveFormIdObserver();
1694 nsGenericHTMLElement::UnbindFromTree(aNullParent);
1696 // The element might not have a fieldset anymore.
1697 UpdateFieldSet(false);
1700 nsresult nsGenericHTMLFormElement::BeforeSetAttr(
1701 int32_t aNameSpaceID, nsAtom* aName, const nsAttrValueOrString* aValue,
1702 bool aNotify) {
1703 if (aNameSpaceID == kNameSpaceID_None) {
1704 nsAutoString tmp;
1706 // remove the control from the hashtable as needed
1708 if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
1709 GetAttr(kNameSpaceID_None, aName, tmp);
1711 if (!tmp.IsEmpty()) {
1712 mForm->RemoveElementFromTable(this, tmp);
1716 if (mForm && aName == nsGkAtoms::type) {
1717 GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp);
1719 if (!tmp.IsEmpty()) {
1720 mForm->RemoveElementFromTable(this, tmp);
1723 GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp);
1725 if (!tmp.IsEmpty()) {
1726 mForm->RemoveElementFromTable(this, tmp);
1729 mForm->RemoveElement(this, false);
1732 if (aName == nsGkAtoms::form) {
1733 // If @form isn't set or set to the empty string, there were no observer
1734 // so we don't have to remove it.
1735 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
1736 nsGkAtoms::form)) {
1737 // The current form id observer is no longer needed.
1738 // A new one may be added in AfterSetAttr.
1739 RemoveFormIdObserver();
1744 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
1745 aNotify);
1748 nsresult nsGenericHTMLFormElement::AfterSetAttr(
1749 int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue,
1750 const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal,
1751 bool aNotify) {
1752 if (aNameSpaceID == kNameSpaceID_None) {
1753 // add the control to the hashtable as needed
1755 if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
1756 aValue && !aValue->IsEmptyString()) {
1757 MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
1758 "Expected atom value for name/id");
1759 mForm->AddElementToTable(this,
1760 nsDependentAtomString(aValue->GetAtomValue()));
1763 if (mForm && aName == nsGkAtoms::type) {
1764 nsAutoString tmp;
1766 GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp);
1768 if (!tmp.IsEmpty()) {
1769 mForm->AddElementToTable(this, tmp);
1772 GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp);
1774 if (!tmp.IsEmpty()) {
1775 mForm->AddElementToTable(this, tmp);
1778 mForm->AddElement(this, false, aNotify);
1781 if (aName == nsGkAtoms::form) {
1782 // We need a new form id observer.
1783 DocumentOrShadowRoot* docOrShadow =
1784 GetUncomposedDocOrConnectedShadowRoot();
1785 if (docOrShadow) {
1786 Element* formIdElement = nullptr;
1787 if (aValue && !aValue->IsEmptyString()) {
1788 formIdElement = AddFormIdObserver();
1791 // Because we have a new @form value (or no more @form), we have to
1792 // update our form owner.
1793 UpdateFormOwner(false, formIdElement);
1798 return nsGenericHTMLElement::AfterSetAttr(
1799 aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
1802 void nsGenericHTMLFormElement::GetEventTargetParent(
1803 EventChainPreVisitor& aVisitor) {
1804 if (aVisitor.mEvent->IsTrusted() && (aVisitor.mEvent->mMessage == eFocus ||
1805 aVisitor.mEvent->mMessage == eBlur)) {
1806 // We have to handle focus/blur event to change focus states in
1807 // PreHandleEvent to prevent it breaks event target chain creation.
1808 aVisitor.mWantsPreHandleEvent = true;
1810 nsGenericHTMLElement::GetEventTargetParent(aVisitor);
1813 nsresult nsGenericHTMLFormElement::PreHandleEvent(EventChainVisitor& aVisitor) {
1814 if (aVisitor.mEvent->IsTrusted()) {
1815 switch (aVisitor.mEvent->mMessage) {
1816 case eFocus: {
1817 // Check to see if focus has bubbled up from a form control's
1818 // child textfield or button. If that's the case, don't focus
1819 // this parent file control -- leave focus on the child.
1820 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
1821 if (formControlFrame &&
1822 aVisitor.mEvent->mOriginalTarget == static_cast<nsINode*>(this))
1823 formControlFrame->SetFocus(true, true);
1824 break;
1826 case eBlur: {
1827 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
1828 if (formControlFrame) formControlFrame->SetFocus(false, false);
1829 break;
1831 default:
1832 break;
1835 return nsGenericHTMLElement::PreHandleEvent(aVisitor);
1838 void nsGenericHTMLFormElement::ForgetFieldSet(nsIContent* aFieldset) {
1839 if (mFieldSet == aFieldset) {
1840 mFieldSet = nullptr;
1844 bool nsGenericHTMLFormElement::CanBeDisabled() const {
1845 int32_t type = ControlType();
1846 // It's easier to test the types that _cannot_ be disabled
1847 return type != NS_FORM_OBJECT && type != NS_FORM_OUTPUT;
1850 bool nsGenericHTMLFormElement::DoesReadOnlyApply() const {
1851 int32_t type = ControlType();
1852 if (!(type & NS_FORM_INPUT_ELEMENT) && type != NS_FORM_TEXTAREA) {
1853 return false;
1856 switch (type) {
1857 case NS_FORM_INPUT_HIDDEN:
1858 case NS_FORM_INPUT_BUTTON:
1859 case NS_FORM_INPUT_IMAGE:
1860 case NS_FORM_INPUT_RESET:
1861 case NS_FORM_INPUT_SUBMIT:
1862 case NS_FORM_INPUT_RADIO:
1863 case NS_FORM_INPUT_FILE:
1864 case NS_FORM_INPUT_CHECKBOX:
1865 case NS_FORM_INPUT_RANGE:
1866 case NS_FORM_INPUT_COLOR:
1867 return false;
1868 #ifdef DEBUG
1869 case NS_FORM_TEXTAREA:
1870 case NS_FORM_INPUT_TEXT:
1871 case NS_FORM_INPUT_PASSWORD:
1872 case NS_FORM_INPUT_SEARCH:
1873 case NS_FORM_INPUT_TEL:
1874 case NS_FORM_INPUT_EMAIL:
1875 case NS_FORM_INPUT_URL:
1876 case NS_FORM_INPUT_NUMBER:
1877 case NS_FORM_INPUT_DATE:
1878 case NS_FORM_INPUT_TIME:
1879 case NS_FORM_INPUT_MONTH:
1880 case NS_FORM_INPUT_WEEK:
1881 case NS_FORM_INPUT_DATETIME_LOCAL:
1882 return true;
1883 default:
1884 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesReadOnlyApply()");
1885 return true;
1886 #else // DEBUG
1887 default:
1888 return true;
1889 #endif // DEBUG
1893 bool nsGenericHTMLFormElement::IsHTMLFocusable(bool aWithMouse,
1894 bool* aIsFocusable,
1895 int32_t* aTabIndex) {
1896 if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
1897 aTabIndex)) {
1898 return true;
1901 #ifdef XP_MACOSX
1902 *aIsFocusable = (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) &&
1903 *aIsFocusable;
1904 #endif
1905 return false;
1908 EventStates nsGenericHTMLFormElement::IntrinsicState() const {
1909 // If you add attribute-dependent states here, you need to add them them to
1910 // AfterSetAttr too. And add them to AfterSetAttr for all subclasses that
1911 // implement IntrinsicState() and are affected by that attribute.
1912 EventStates state = nsGenericHTMLElement::IntrinsicState();
1914 if (mForm && mForm->IsDefaultSubmitElement(this)) {
1915 NS_ASSERTION(IsSubmitControl(),
1916 "Default submit element that isn't a submit control.");
1917 // We are the default submit element (:default)
1918 state |= NS_EVENT_STATE_DEFAULT;
1921 // Make the text controls read-write
1922 if (!state.HasState(NS_EVENT_STATE_MOZ_READWRITE) && DoesReadOnlyApply()) {
1923 if (!GetBoolAttr(nsGkAtoms::readonly)) {
1924 state |= NS_EVENT_STATE_MOZ_READWRITE;
1925 state &= ~NS_EVENT_STATE_MOZ_READONLY;
1929 return state;
1932 nsGenericHTMLFormElement::FocusTristate nsGenericHTMLFormElement::FocusState() {
1933 // We can't be focused if we aren't in a (composed) document
1934 Document* doc = GetComposedDoc();
1935 if (!doc) return eUnfocusable;
1937 // first see if we are disabled or not. If disabled then do nothing.
1938 if (IsDisabled()) {
1939 return eUnfocusable;
1942 // If the window is not active, do not allow the focus to bring the
1943 // window to the front. We update the focus controller, but do
1944 // nothing else.
1945 if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
1946 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = win->GetPrivateRoot();
1948 nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
1949 if (fm && rootWindow) {
1950 nsCOMPtr<mozIDOMWindowProxy> activeWindow;
1951 fm->GetActiveWindow(getter_AddRefs(activeWindow));
1952 if (activeWindow == rootWindow) {
1953 return eActiveWindow;
1958 return eInactiveWindow;
1961 Element* nsGenericHTMLFormElement::AddFormIdObserver() {
1962 nsAutoString formId;
1963 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
1964 GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId);
1965 NS_ASSERTION(!formId.IsEmpty(),
1966 "@form value should not be the empty string!");
1967 RefPtr<nsAtom> atom = NS_Atomize(formId);
1969 return docOrShadow->AddIDTargetObserver(atom, FormIdUpdated, this, false);
1972 void nsGenericHTMLFormElement::RemoveFormIdObserver() {
1973 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
1974 if (!docOrShadow) {
1975 return;
1978 nsAutoString formId;
1979 GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId);
1980 NS_ASSERTION(!formId.IsEmpty(),
1981 "@form value should not be the empty string!");
1982 RefPtr<nsAtom> atom = NS_Atomize(formId);
1984 docOrShadow->RemoveIDTargetObserver(atom, FormIdUpdated, this, false);
1987 /* static */
1988 bool nsGenericHTMLFormElement::FormIdUpdated(Element* aOldElement,
1989 Element* aNewElement,
1990 void* aData) {
1991 nsGenericHTMLFormElement* element =
1992 static_cast<nsGenericHTMLFormElement*>(aData);
1994 NS_ASSERTION(element->IsHTMLElement(), "aData should be an HTML element");
1996 element->UpdateFormOwner(false, aNewElement);
1998 return true;
2001 bool nsGenericHTMLFormElement::IsElementDisabledForEvents(WidgetEvent* aEvent,
2002 nsIFrame* aFrame) {
2003 MOZ_ASSERT(aEvent);
2005 // Allow dispatch of CustomEvent and untrusted Events.
2006 if (!aEvent->IsTrusted()) {
2007 return false;
2010 switch (aEvent->mMessage) {
2011 case eAnimationStart:
2012 case eAnimationEnd:
2013 case eAnimationIteration:
2014 case eAnimationCancel:
2015 case eMouseMove:
2016 case eMouseOver:
2017 case eMouseOut:
2018 case eMouseEnter:
2019 case eMouseLeave:
2020 case ePointerMove:
2021 case ePointerOver:
2022 case ePointerOut:
2023 case ePointerEnter:
2024 case ePointerLeave:
2025 case eTransitionCancel:
2026 case eTransitionEnd:
2027 case eTransitionRun:
2028 case eTransitionStart:
2029 case eWheel:
2030 case eLegacyMouseLineOrPageScroll:
2031 case eLegacyMousePixelScroll:
2032 return false;
2033 default:
2034 break;
2037 // FIXME(emilio): This poking at the style of the frame is slightly bogus
2038 // unless we flush before every event, which we don't really want to do.
2039 if (aFrame && aFrame->StyleUI()->mUserInput == StyleUserInput::None) {
2040 return true;
2043 return IsDisabled();
2046 void nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree,
2047 Element* aFormIdElement) {
2048 MOZ_ASSERT(!aBindToTree || !aFormIdElement,
2049 "aFormIdElement shouldn't be set if aBindToTree is true!");
2051 bool needStateUpdate = false;
2052 if (!aBindToTree) {
2053 needStateUpdate = mForm && mForm->IsDefaultSubmitElement(this);
2054 ClearForm(true, false);
2057 HTMLFormElement* oldForm = mForm;
2059 if (!mForm) {
2060 // If @form is set, we have to use that to find the form.
2061 nsAutoString formId;
2062 if (GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId)) {
2063 if (!formId.IsEmpty()) {
2064 Element* element = nullptr;
2066 if (aBindToTree) {
2067 element = AddFormIdObserver();
2068 } else {
2069 element = aFormIdElement;
2072 NS_ASSERTION(!IsInComposedDoc() ||
2073 element == GetUncomposedDocOrConnectedShadowRoot()
2074 ->GetElementById(formId),
2075 "element should be equals to the current element "
2076 "associated with the id in @form!");
2078 if (element && element->IsHTMLElement(nsGkAtoms::form) &&
2079 nsContentUtils::IsInSameAnonymousTree(this, element)) {
2080 SetForm(static_cast<HTMLFormElement*>(element), aBindToTree);
2083 } else {
2084 // We now have a parent, so we may have picked up an ancestor form. Search
2085 // for it. Note that if mForm is already set we don't want to do this,
2086 // because that means someone (probably the content sink) has already set
2087 // it to the right value. Also note that even if being bound here didn't
2088 // change our parent, we still need to search, since our parent chain
2089 // probably changed _somewhere_.
2090 SetForm(FindAncestorForm(), aBindToTree);
2094 if (mForm && !HasFlag(ADDED_TO_FORM)) {
2095 // Now we need to add ourselves to the form
2096 nsAutoString nameVal, idVal;
2097 GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
2098 GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
2100 SetFlags(ADDED_TO_FORM);
2102 // Notify only if we just found this mForm.
2103 mForm->AddElement(this, true, oldForm == nullptr);
2105 if (!nameVal.IsEmpty()) {
2106 mForm->AddElementToTable(this, nameVal);
2109 if (!idVal.IsEmpty()) {
2110 mForm->AddElementToTable(this, idVal);
2114 if (mForm != oldForm || needStateUpdate) {
2115 UpdateState(true);
2119 void nsGenericHTMLFormElement::UpdateFieldSet(bool aNotify) {
2120 nsIContent* parent = nullptr;
2121 nsIContent* prev = nullptr;
2123 // Don't walk out of anonymous subtrees. Note the similar code in
2124 // nsGenericHTMLElement::FindAncestorForm.
2125 nsIContent* bindingParent = GetBindingParent();
2127 for (parent = GetParent(); parent && parent != bindingParent;
2128 prev = parent, parent = parent->GetParent()) {
2129 HTMLFieldSetElement* fieldset = HTMLFieldSetElement::FromNode(parent);
2130 if (fieldset && (!prev || fieldset->GetFirstLegend() != prev)) {
2131 if (mFieldSet == fieldset) {
2132 // We already have the right fieldset;
2133 return;
2136 if (mFieldSet) {
2137 mFieldSet->RemoveElement(this);
2139 mFieldSet = fieldset;
2140 fieldset->AddElement(this);
2142 // The disabled state may have changed
2143 FieldSetDisabledChanged(aNotify);
2144 return;
2148 // No fieldset found.
2149 if (mFieldSet) {
2150 mFieldSet->RemoveElement(this);
2151 mFieldSet = nullptr;
2152 // The disabled state may have changed
2153 FieldSetDisabledChanged(aNotify);
2157 void nsGenericHTMLFormElement::UpdateDisabledState(bool aNotify) {
2158 if (!CanBeDisabled()) {
2159 return;
2162 bool isDisabled = HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
2163 if (!isDisabled && mFieldSet) {
2164 isDisabled = mFieldSet->IsDisabled();
2167 EventStates disabledStates;
2168 if (isDisabled) {
2169 disabledStates |= NS_EVENT_STATE_DISABLED;
2170 } else {
2171 disabledStates |= NS_EVENT_STATE_ENABLED;
2174 EventStates oldDisabledStates = State() & DISABLED_STATES;
2175 EventStates changedStates = disabledStates ^ oldDisabledStates;
2177 if (!changedStates.IsEmpty()) {
2178 ToggleStates(changedStates, aNotify);
2182 void nsGenericHTMLFormElement::UpdateRequiredState(bool aIsRequired,
2183 bool aNotify) {
2184 #ifdef DEBUG
2185 int32_t type = ControlType();
2186 #endif
2187 MOZ_ASSERT((type & NS_FORM_INPUT_ELEMENT) || type == NS_FORM_SELECT ||
2188 type == NS_FORM_TEXTAREA,
2189 "This should be called only on types that @required applies");
2191 #ifdef DEBUG
2192 HTMLInputElement* input = HTMLInputElement::FromNode(this);
2193 if (input) {
2194 MOZ_ASSERT(
2195 input->DoesRequiredApply(),
2196 "This should be called only on input types that @required applies");
2198 #endif
2200 EventStates requiredStates;
2201 if (aIsRequired) {
2202 requiredStates |= NS_EVENT_STATE_REQUIRED;
2203 } else {
2204 requiredStates |= NS_EVENT_STATE_OPTIONAL;
2207 EventStates oldRequiredStates = State() & REQUIRED_STATES;
2208 EventStates changedStates = requiredStates ^ oldRequiredStates;
2210 if (!changedStates.IsEmpty()) {
2211 ToggleStates(changedStates, aNotify);
2215 void nsGenericHTMLFormElement::FieldSetDisabledChanged(bool aNotify) {
2216 UpdateDisabledState(aNotify);
2219 bool nsGenericHTMLFormElement::IsLabelable() const {
2220 uint32_t type = ControlType();
2221 return (type & NS_FORM_INPUT_ELEMENT && type != NS_FORM_INPUT_HIDDEN) ||
2222 type & NS_FORM_BUTTON_ELEMENT || type == NS_FORM_OUTPUT ||
2223 type == NS_FORM_SELECT || type == NS_FORM_TEXTAREA;
2226 void nsGenericHTMLFormElement::GetFormAction(nsString& aValue) {
2227 uint32_t type = ControlType();
2228 if (!(type & NS_FORM_INPUT_ELEMENT) && !(type & NS_FORM_BUTTON_ELEMENT)) {
2229 return;
2232 if (!GetAttr(kNameSpaceID_None, nsGkAtoms::formaction, aValue) ||
2233 aValue.IsEmpty()) {
2234 Document* document = OwnerDoc();
2235 nsIURI* docURI = document->GetDocumentURI();
2236 if (docURI) {
2237 nsAutoCString spec;
2238 nsresult rv = docURI->GetSpec(spec);
2239 if (NS_FAILED(rv)) {
2240 return;
2243 CopyUTF8toUTF16(spec, aValue);
2245 } else {
2246 GetURIAttr(nsGkAtoms::formaction, nullptr, aValue);
2250 //----------------------------------------------------------------------
2252 void nsGenericHTMLElement::Click(CallerType aCallerType) {
2253 if (IsDisabled() || HandlingClick()) {
2254 return;
2257 // Strong in case the event kills it
2258 nsCOMPtr<Document> doc = GetComposedDoc();
2260 RefPtr<nsPresContext> context;
2261 if (doc) {
2262 context = doc->GetPresContext();
2265 SetHandlingClick();
2267 // Mark this event trusted if Click() is called from system code.
2268 WidgetMouseEvent event(aCallerType == CallerType::System, eMouseClick,
2269 nullptr, WidgetMouseEvent::eReal);
2270 event.mFlags.mIsPositionless = true;
2271 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
2273 EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context, &event);
2275 ClearHandlingClick();
2278 bool nsGenericHTMLElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
2279 int32_t* aTabIndex) {
2280 Document* doc = GetComposedDoc();
2281 if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) {
2282 // In designMode documents we only allow focusing the document.
2283 if (aTabIndex) {
2284 *aTabIndex = -1;
2287 *aIsFocusable = false;
2289 return true;
2292 int32_t tabIndex = TabIndex();
2293 bool disabled = false;
2294 bool disallowOverridingFocusability = true;
2296 if (IsEditableRoot()) {
2297 // Editable roots should always be focusable.
2298 disallowOverridingFocusability = true;
2300 // Ignore the disabled attribute in editable contentEditable/designMode
2301 // roots.
2302 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
2303 // The default value for tabindex should be 0 for editable
2304 // contentEditable roots.
2305 tabIndex = 0;
2307 } else {
2308 disallowOverridingFocusability = false;
2310 // Just check for disabled attribute on form controls
2311 disabled = IsDisabled();
2312 if (disabled) {
2313 tabIndex = -1;
2317 if (aTabIndex) {
2318 *aTabIndex = tabIndex;
2321 // If a tabindex is specified at all, or the default tabindex is 0, we're
2322 // focusable
2323 *aIsFocusable =
2324 (tabIndex >= 0 ||
2325 (!disabled && HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)));
2327 return disallowOverridingFocusability;
2330 void nsGenericHTMLElement::RegUnRegAccessKey(bool aDoReg) {
2331 // first check to see if we have an access key
2332 nsAutoString accessKey;
2333 GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
2334 if (accessKey.IsEmpty()) {
2335 return;
2338 // We have an access key, so get the ESM from the pres context.
2339 nsPresContext* presContext = GetPresContext(eForUncomposedDoc);
2341 if (presContext) {
2342 EventStateManager* esm = presContext->EventStateManager();
2344 // Register or unregister as appropriate.
2345 if (aDoReg) {
2346 esm->RegisterAccessKey(this, (uint32_t)accessKey.First());
2347 } else {
2348 esm->UnregisterAccessKey(this, (uint32_t)accessKey.First());
2353 bool nsGenericHTMLElement::PerformAccesskey(bool aKeyCausesActivation,
2354 bool aIsTrustedEvent) {
2355 nsPresContext* presContext = GetPresContext(eForUncomposedDoc);
2356 if (!presContext) {
2357 return false;
2360 // It's hard to say what HTML4 wants us to do in all cases.
2361 bool focused = true;
2362 nsFocusManager* fm = nsFocusManager::GetFocusManager();
2363 if (fm) {
2364 fm->SetFocus(this, nsIFocusManager::FLAG_BYKEY |
2365 nsIFocusManager::FLAG_BYELEMENTFOCUS);
2367 // Return true if the element became the current focus within its window.
2368 nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
2369 focused = (window && window->GetFocusedElement());
2372 if (aKeyCausesActivation) {
2373 // Click on it if the users prefs indicate to do so.
2374 AutoPopupStatePusher popupStatePusher(
2375 aIsTrustedEvent ? PopupBlocker::openAllowed : PopupBlocker::openAbused);
2376 DispatchSimulatedClick(this, aIsTrustedEvent, presContext);
2379 return focused;
2382 nsresult nsGenericHTMLElement::DispatchSimulatedClick(
2383 nsGenericHTMLElement* aElement, bool aIsTrusted,
2384 nsPresContext* aPresContext) {
2385 WidgetMouseEvent event(aIsTrusted, eMouseClick, nullptr,
2386 WidgetMouseEvent::eReal);
2387 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_KEYBOARD;
2388 event.mFlags.mIsPositionless = true;
2389 return EventDispatcher::Dispatch(ToSupports(aElement), aPresContext, &event);
2392 already_AddRefed<TextEditor> nsGenericHTMLElement::GetAssociatedEditor() {
2393 // If contenteditable is ever implemented, it might need to do something
2394 // different here?
2396 RefPtr<TextEditor> textEditor = GetTextEditorInternal();
2397 return textEditor.forget();
2400 // static
2401 void nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent* content) {
2402 /* Sync this node */
2403 nsGenericHTMLElement* element = FromNode(content);
2404 if (element) {
2405 RefPtr<TextEditor> textEditor = element->GetAssociatedEditor();
2406 if (textEditor) {
2407 textEditor->SyncRealTimeSpell();
2411 /* Sync all children */
2412 for (nsIContent* child = content->GetFirstChild(); child;
2413 child = child->GetNextSibling()) {
2414 SyncEditorsOnSubtree(child);
2418 void nsGenericHTMLElement::RecompileScriptEventListeners() {
2419 int32_t i, count = mAttrs.AttrCount();
2420 for (i = 0; i < count; ++i) {
2421 const nsAttrName* name = mAttrs.AttrNameAt(i);
2423 // Eventlistenener-attributes are always in the null namespace
2424 if (!name->IsAtom()) {
2425 continue;
2428 nsAtom* attr = name->Atom();
2429 if (!IsEventAttributeName(attr)) {
2430 continue;
2433 nsAutoString value;
2434 GetAttr(kNameSpaceID_None, attr, value);
2435 SetEventHandler(attr, value, true);
2439 bool nsGenericHTMLElement::IsEditableRoot() const {
2440 Document* document = GetComposedDoc();
2441 if (!document) {
2442 return false;
2445 if (document->HasFlag(NODE_IS_EDITABLE)) {
2446 return false;
2449 if (GetContentEditableValue() != eTrue) {
2450 return false;
2453 nsIContent* parent = GetParent();
2455 return !parent || !parent->HasFlag(NODE_IS_EDITABLE);
2458 static void MakeContentDescendantsEditable(nsIContent* aContent,
2459 Document* aDocument) {
2460 // If aContent is not an element, we just need to update its
2461 // internal editable state and don't need to notify anyone about
2462 // that. For elements, we need to send a ContentStateChanged
2463 // notification.
2464 if (!aContent->IsElement()) {
2465 aContent->UpdateEditableState(false);
2466 return;
2469 Element* element = aContent->AsElement();
2471 element->UpdateEditableState(true);
2473 for (nsIContent* child = aContent->GetFirstChild(); child;
2474 child = child->GetNextSibling()) {
2475 if (!child->IsElement() ||
2476 !child->AsElement()->HasAttr(kNameSpaceID_None,
2477 nsGkAtoms::contenteditable)) {
2478 MakeContentDescendantsEditable(child, aDocument);
2483 void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) {
2484 Document* document = GetComposedDoc();
2485 if (!document) {
2486 return;
2489 Document::EditingState previousEditingState = Document::EditingState::eOff;
2490 if (aChange != 0) {
2491 document->ChangeContentEditableCount(this, aChange);
2492 previousEditingState = document->GetEditingState();
2495 if (document->HasFlag(NODE_IS_EDITABLE)) {
2496 document = nullptr;
2499 // MakeContentDescendantsEditable is going to call ContentStateChanged for
2500 // this element and all descendants if editable state has changed.
2501 // We might as well wrap it all in one script blocker.
2502 nsAutoScriptBlocker scriptBlocker;
2503 MakeContentDescendantsEditable(this, document);
2505 // If the document already had contenteditable and JS adds new
2506 // contenteditable, that might cause changing editing host to current editing
2507 // host's ancestor. In such case, HTMLEditor needs to know that
2508 // synchronously to update selection limitter.
2509 if (document && aChange > 0 &&
2510 previousEditingState == Document::EditingState::eContentEditable) {
2511 if (HTMLEditor* htmlEditor =
2512 nsContentUtils::GetHTMLEditor(document->GetPresContext())) {
2513 htmlEditor->NotifyEditingHostMaybeChanged();
2518 //----------------------------------------------------------------------
2520 nsGenericHTMLFormElementWithState::nsGenericHTMLFormElementWithState(
2521 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
2522 FromParser aFromParser, uint8_t aType)
2523 : nsGenericHTMLFormElement(std::move(aNodeInfo), aType),
2524 mControlNumber(!!(aFromParser & FROM_PARSER_NETWORK)
2525 ? OwnerDoc()->GetNextControlNumber()
2526 : -1) {
2527 mStateKey.SetIsVoid(true);
2530 void nsGenericHTMLFormElementWithState::GenerateStateKey() {
2531 // Keep the key if already computed
2532 if (!mStateKey.IsVoid()) {
2533 return;
2536 Document* doc = GetUncomposedDoc();
2537 if (!doc) {
2538 mStateKey.Truncate();
2539 return;
2542 // Generate the state key
2543 nsContentUtils::GenerateStateKey(this, doc, mStateKey);
2545 // If the state key is blank, this is anonymous content or for whatever
2546 // reason we are not supposed to save/restore state: keep it as such.
2547 if (!mStateKey.IsEmpty()) {
2548 // Add something unique to content so layout doesn't muck us up.
2549 mStateKey += "-C";
2553 PresState* nsGenericHTMLFormElementWithState::GetPrimaryPresState() {
2554 if (mStateKey.IsEmpty()) {
2555 return nullptr;
2558 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false);
2560 if (!history) {
2561 return nullptr;
2564 // Get the pres state for this key, if it doesn't exist, create one.
2565 PresState* result = history->GetState(mStateKey);
2566 if (!result) {
2567 UniquePtr<PresState> newState = NewPresState();
2568 result = newState.get();
2569 history->AddState(mStateKey, std::move(newState));
2572 return result;
2575 already_AddRefed<nsILayoutHistoryState>
2576 nsGenericHTMLFormElementWithState::GetLayoutHistory(bool aRead) {
2577 nsCOMPtr<Document> doc = GetUncomposedDoc();
2578 if (!doc) {
2579 return nullptr;
2583 // Get the history
2585 nsCOMPtr<nsILayoutHistoryState> history = doc->GetLayoutHistoryState();
2586 if (!history) {
2587 return nullptr;
2590 if (aRead && !history->HasStates()) {
2591 return nullptr;
2594 return history.forget();
2597 bool nsGenericHTMLFormElementWithState::RestoreFormControlState() {
2598 MOZ_ASSERT(!mStateKey.IsVoid(),
2599 "GenerateStateKey must already have been called");
2601 if (mStateKey.IsEmpty()) {
2602 return false;
2605 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true);
2606 if (!history) {
2607 return false;
2610 // Get the pres state for this key
2611 PresState* state = history->GetState(mStateKey);
2612 if (state) {
2613 bool result = RestoreState(state);
2614 history->RemoveState(mStateKey);
2615 return result;
2618 return false;
2621 void nsGenericHTMLFormElementWithState::NodeInfoChanged(Document* aOldDoc) {
2622 nsGenericHTMLElement::NodeInfoChanged(aOldDoc);
2624 // We need to regenerate the state key now we're in a new document. Clearing
2625 // mControlNumber means we stop considering this control to be parser
2626 // inserted, and we'll generate a state key based on its position in the
2627 // document rather than the order it was inserted into the document.
2628 mControlNumber = -1;
2629 mStateKey.SetIsVoid(true);
2632 nsSize nsGenericHTMLElement::GetWidthHeightForImage(
2633 RefPtr<imgRequestProxy>& aImageRequest) {
2634 nsSize size(0, 0);
2636 nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
2638 if (frame) {
2639 size = frame->GetContentRect().Size();
2641 size.width = nsPresContext::AppUnitsToIntCSSPixels(size.width);
2642 size.height = nsPresContext::AppUnitsToIntCSSPixels(size.height);
2643 } else {
2644 const nsAttrValue* value;
2645 nsCOMPtr<imgIContainer> image;
2646 if (aImageRequest) {
2647 aImageRequest->GetImage(getter_AddRefs(image));
2650 if ((value = GetParsedAttr(nsGkAtoms::width)) &&
2651 value->Type() == nsAttrValue::eInteger) {
2652 size.width = value->GetIntegerValue();
2653 } else if (image) {
2654 image->GetWidth(&size.width);
2657 if ((value = GetParsedAttr(nsGkAtoms::height)) &&
2658 value->Type() == nsAttrValue::eInteger) {
2659 size.height = value->GetIntegerValue();
2660 } else if (image) {
2661 image->GetHeight(&size.height);
2665 NS_ASSERTION(size.width >= 0, "negative width");
2666 NS_ASSERTION(size.height >= 0, "negative height");
2667 return size;
2670 bool nsGenericHTMLElement::IsEventAttributeNameInternal(nsAtom* aName) {
2671 return nsContentUtils::IsEventAttributeName(aName, EventNameType_HTML);
2675 * Construct a URI from a string, as an element.src attribute
2676 * would be set to. Helper for the media elements.
2678 nsresult nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec,
2679 nsIURI** aURI) {
2680 NS_ENSURE_ARG_POINTER(aURI);
2682 *aURI = nullptr;
2684 nsCOMPtr<Document> doc = OwnerDoc();
2686 nsresult rv =
2687 nsContentUtils::NewURIWithDocumentCharset(aURI, aURISpec, doc,
2688 GetBaseURI());
2689 NS_ENSURE_SUCCESS(rv, rv);
2691 bool equal;
2692 if (aURISpec.IsEmpty() && doc->GetDocumentURI() &&
2693 NS_SUCCEEDED(doc->GetDocumentURI()->Equals(*aURI, &equal)) && equal) {
2694 // Assume an element can't point to a fragment of its embedding
2695 // document. Fail here instead of returning the recursive URI
2696 // and waiting for the subsequent load to fail.
2697 NS_RELEASE(*aURI);
2698 return NS_ERROR_DOM_INVALID_STATE_ERR;
2701 return NS_OK;
2704 void nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue,
2705 mozilla::ErrorResult& aError) {
2706 // innerText depends on layout. For example, white space processing is
2707 // something that happens during reflow and which must be reflected by
2708 // innerText. So for:
2710 // <div style="white-space:normal"> A B C </div>
2712 // innerText should give "A B C".
2714 // The approach taken here to avoid the expense of reflow is to flush style
2715 // and then see whether it's necessary to flush layout afterwards. Flushing
2716 // layout can be skipped if we can detect that the element or its descendants
2717 // are not dirty.
2719 // Obtain the composed doc to handle elements in Shadow DOM.
2720 Document* doc = GetComposedDoc();
2721 if (doc) {
2722 doc->FlushPendingNotifications(FlushType::Style);
2725 // Elements with `display: content` will not have a frame. To handle Shadow
2726 // DOM, walk the flattened tree looking for parent frame.
2727 nsIFrame* frame = GetPrimaryFrame();
2728 if (IsDisplayContents()) {
2729 for (Element* parent = GetFlattenedTreeParentElement(); parent;
2730 parent = parent->GetFlattenedTreeParentElement()) {
2731 frame = parent->GetPrimaryFrame();
2732 if (frame) {
2733 break;
2738 // Check for dirty reflow roots in the subtree from targetFrame; this requires
2739 // a reflow flush.
2740 bool dirty = frame && frame->PresShell()->FrameIsAncestorOfDirtyRoot(frame);
2742 // The way we do that is by checking whether the element has either of the two
2743 // dirty bits (NS_FRAME_IS_DIRTY or NS_FRAME_HAS_DIRTY_DESCENDANTS) or if any
2744 // ancestor has NS_FRAME_IS_DIRTY. We need to check for NS_FRAME_IS_DIRTY on
2745 // ancestors since that is something that implies NS_FRAME_IS_DIRTY on all
2746 // descendants.
2747 dirty |= frame && frame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
2748 while (!dirty && frame) {
2749 dirty |= frame->HasAnyStateBits(NS_FRAME_IS_DIRTY);
2750 frame = frame->GetInFlowParent();
2753 // Flush layout if we determined a reflow is required.
2754 if (dirty && doc) {
2755 doc->FlushPendingNotifications(FlushType::Layout);
2758 if (!IsRendered()) {
2759 GetTextContentInternal(aValue, aError);
2760 } else {
2761 nsRange::GetInnerTextNoFlush(aValue, aError, this);
2765 void nsGenericHTMLElement::SetInnerText(const nsAString& aValue) {
2766 // Batch possible DOMSubtreeModified events.
2767 mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
2768 FireNodeRemovedForChildren();
2770 // Might as well stick a batch around this since we're performing several
2771 // mutations.
2772 mozAutoDocUpdate updateBatch(GetComposedDoc(), true);
2773 nsAutoMutationBatch mb;
2775 mb.Init(this, true, false);
2777 while (HasChildren()) {
2778 RemoveChildNode(nsINode::GetFirstChild(), true);
2781 mb.RemovalDone();
2783 nsString str;
2784 const char16_t* s = aValue.BeginReading();
2785 const char16_t* end = aValue.EndReading();
2786 while (true) {
2787 if (s != end && *s == '\r' && s + 1 != end && s[1] == '\n') {
2788 // a \r\n pair should only generate one <br>, so just skip the \r
2789 ++s;
2791 if (s == end || *s == '\r' || *s == '\n') {
2792 if (!str.IsEmpty()) {
2793 RefPtr<nsTextNode> textContent =
2794 new nsTextNode(NodeInfo()->NodeInfoManager());
2795 textContent->SetText(str, true);
2796 AppendChildTo(textContent, true);
2798 if (s == end) {
2799 break;
2801 str.Truncate();
2802 RefPtr<mozilla::dom::NodeInfo> ni =
2803 NodeInfo()->NodeInfoManager()->GetNodeInfo(
2804 nsGkAtoms::br, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
2805 RefPtr<HTMLBRElement> br = new HTMLBRElement(ni.forget());
2806 AppendChildTo(br, true);
2807 } else {
2808 str.Append(*s);
2810 ++s;
2813 mb.NodesAdded();