forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			3632 lines
		
	
	
	
		
			126 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3632 lines
		
	
	
	
		
			126 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/EditorBase.h"
 | |
| #include "mozilla/EventDispatcher.h"
 | |
| #include "mozilla/EventListenerManager.h"
 | |
| #include "mozilla/EventStateManager.h"
 | |
| #include "mozilla/HTMLEditor.h"
 | |
| #include "mozilla/IMEContentObserver.h"
 | |
| #include "mozilla/IMEStateManager.h"
 | |
| #include "mozilla/MappedDeclarationsBuilder.h"
 | |
| #include "mozilla/Maybe.h"
 | |
| #include "mozilla/MouseEvents.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/StaticPrefs_dom.h"
 | |
| #include "mozilla/TextEditor.h"
 | |
| #include "mozilla/TextEvents.h"
 | |
| #include "mozilla/StaticPrefs_accessibility.h"
 | |
| #include "mozilla/dom/FetchPriority.h"
 | |
| #include "mozilla/dom/FormData.h"
 | |
| #include "nscore.h"
 | |
| #include "nsGenericHTMLElement.h"
 | |
| #include "nsCOMPtr.h"
 | |
| #include "nsAtom.h"
 | |
| #include "nsQueryObject.h"
 | |
| #include "mozilla/dom/BindContext.h"
 | |
| #include "mozilla/dom/Document.h"
 | |
| #include "mozilla/dom/UnbindContext.h"
 | |
| #include "nsPIDOMWindow.h"
 | |
| #include "nsIFrameInlines.h"
 | |
| #include "nsIScrollableFrame.h"
 | |
| #include "nsView.h"
 | |
| #include "nsViewManager.h"
 | |
| #include "nsIWidget.h"
 | |
| #include "nsRange.h"
 | |
| #include "nsPresContext.h"
 | |
| #include "nsError.h"
 | |
| #include "nsIPrincipal.h"
 | |
| #include "nsContainerFrame.h"
 | |
| #include "nsStyleUtil.h"
 | |
| #include "ReferrerInfo.h"
 | |
| 
 | |
| #include "mozilla/PresState.h"
 | |
| #include "nsILayoutHistoryState.h"
 | |
| 
 | |
| #include "nsHTMLParts.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "mozilla/dom/DirectionalityUtils.h"
 | |
| #include "mozilla/dom/DocumentOrShadowRoot.h"
 | |
| #include "nsString.h"
 | |
| #include "nsGkAtoms.h"
 | |
| #include "nsDOMCSSDeclaration.h"
 | |
| #include "nsITextControlFrame.h"
 | |
| #include "nsIFormControl.h"
 | |
| #include "mozilla/dom/HTMLFormElement.h"
 | |
| #include "nsFocusManager.h"
 | |
| 
 | |
| #include "nsDOMStringMap.h"
 | |
| #include "nsDOMString.h"
 | |
| 
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "mozilla/dom/DocumentInlines.h"
 | |
| #include "HTMLFieldSetElement.h"
 | |
| #include "nsTextNode.h"
 | |
| #include "HTMLBRElement.h"
 | |
| #include "nsDOMMutationObserver.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/dom/FromParser.h"
 | |
| #include "mozilla/dom/Link.h"
 | |
| #include "mozilla/dom/ScriptLoader.h"
 | |
| 
 | |
| #include "nsDOMTokenList.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "mozilla/dom/BindingUtils.h"
 | |
| #include "mozilla/dom/MouseEventBinding.h"
 | |
| #include "mozilla/dom/ToggleEvent.h"
 | |
| #include "mozilla/dom/TouchEvent.h"
 | |
| #include "mozilla/dom/InputEvent.h"
 | |
| #include "mozilla/dom/InvokeEvent.h"
 | |
| #include "mozilla/ErrorResult.h"
 | |
| #include "nsHTMLDocument.h"
 | |
| #include "nsGlobalWindowInner.h"
 | |
| #include "mozilla/dom/HTMLBodyElement.h"
 | |
| #include "imgIContainer.h"
 | |
| #include "nsComputedDOMStyle.h"
 | |
| #include "mozilla/dom/HTMLDialogElement.h"
 | |
| #include "mozilla/dom/HTMLLabelElement.h"
 | |
| #include "mozilla/dom/HTMLInputElement.h"
 | |
| #include "mozilla/dom/CustomElementRegistry.h"
 | |
| #include "mozilla/dom/ElementBinding.h"
 | |
| #include "mozilla/dom/ElementInternals.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::dom;
 | |
| 
 | |
| static const uint8_t NS_INPUTMODE_NONE = 1;
 | |
| static const uint8_t NS_INPUTMODE_TEXT = 2;
 | |
| static const uint8_t NS_INPUTMODE_TEL = 3;
 | |
| static const uint8_t NS_INPUTMODE_URL = 4;
 | |
| static const uint8_t NS_INPUTMODE_EMAIL = 5;
 | |
| static const uint8_t NS_INPUTMODE_NUMERIC = 6;
 | |
| static const uint8_t NS_INPUTMODE_DECIMAL = 7;
 | |
| static const uint8_t NS_INPUTMODE_SEARCH = 8;
 | |
| 
 | |
| static const nsAttrValue::EnumTable kInputmodeTable[] = {
 | |
|     {"none", NS_INPUTMODE_NONE},
 | |
|     {"text", NS_INPUTMODE_TEXT},
 | |
|     {"tel", NS_INPUTMODE_TEL},
 | |
|     {"url", NS_INPUTMODE_URL},
 | |
|     {"email", NS_INPUTMODE_EMAIL},
 | |
|     {"numeric", NS_INPUTMODE_NUMERIC},
 | |
|     {"decimal", NS_INPUTMODE_DECIMAL},
 | |
|     {"search", NS_INPUTMODE_SEARCH},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| static const uint8_t NS_ENTERKEYHINT_ENTER = 1;
 | |
| static const uint8_t NS_ENTERKEYHINT_DONE = 2;
 | |
| static const uint8_t NS_ENTERKEYHINT_GO = 3;
 | |
| static const uint8_t NS_ENTERKEYHINT_NEXT = 4;
 | |
| static const uint8_t NS_ENTERKEYHINT_PREVIOUS = 5;
 | |
| static const uint8_t NS_ENTERKEYHINT_SEARCH = 6;
 | |
| static const uint8_t NS_ENTERKEYHINT_SEND = 7;
 | |
| 
 | |
| static const nsAttrValue::EnumTable kEnterKeyHintTable[] = {
 | |
|     {"enter", NS_ENTERKEYHINT_ENTER},
 | |
|     {"done", NS_ENTERKEYHINT_DONE},
 | |
|     {"go", NS_ENTERKEYHINT_GO},
 | |
|     {"next", NS_ENTERKEYHINT_NEXT},
 | |
|     {"previous", NS_ENTERKEYHINT_PREVIOUS},
 | |
|     {"search", NS_ENTERKEYHINT_SEARCH},
 | |
|     {"send", NS_ENTERKEYHINT_SEND},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| static const uint8_t NS_AUTOCAPITALIZE_NONE = 1;
 | |
| static const uint8_t NS_AUTOCAPITALIZE_SENTENCES = 2;
 | |
| static const uint8_t NS_AUTOCAPITALIZE_WORDS = 3;
 | |
| static const uint8_t NS_AUTOCAPITALIZE_CHARACTERS = 4;
 | |
| 
 | |
| static const nsAttrValue::EnumTable kAutocapitalizeTable[] = {
 | |
|     {"none", NS_AUTOCAPITALIZE_NONE},
 | |
|     {"sentences", NS_AUTOCAPITALIZE_SENTENCES},
 | |
|     {"words", NS_AUTOCAPITALIZE_WORDS},
 | |
|     {"characters", NS_AUTOCAPITALIZE_CHARACTERS},
 | |
|     {"off", NS_AUTOCAPITALIZE_NONE},
 | |
|     {"on", NS_AUTOCAPITALIZE_SENTENCES},
 | |
|     {"", 0},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| static const nsAttrValue::EnumTable* kDefaultAutocapitalize =
 | |
|     &kAutocapitalizeTable[1];
 | |
| 
 | |
| nsresult nsGenericHTMLElement::CopyInnerTo(Element* aDst) {
 | |
|   MOZ_ASSERT(!aDst->GetUncomposedDoc(),
 | |
|              "Should not CopyInnerTo an Element in a document");
 | |
| 
 | |
|   auto reparse = aDst->OwnerDoc() == OwnerDoc() ? ReparseAttributes::No
 | |
|                                                 : ReparseAttributes::Yes;
 | |
|   nsresult rv = Element::CopyInnerTo(aDst, reparse);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // cloning a node must retain its internal nonce slot
 | |
|   nsString* nonce = static_cast<nsString*>(GetProperty(nsGkAtoms::nonce));
 | |
|   if (nonce) {
 | |
|     static_cast<nsGenericHTMLElement*>(aDst)->SetNonce(*nonce);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| static const nsAttrValue::EnumTable kDirTable[] = {
 | |
|     {"ltr", Directionality::Ltr},
 | |
|     {"rtl", Directionality::Rtl},
 | |
|     {"auto", Directionality::Auto},
 | |
|     {nullptr, 0},
 | |
| };
 | |
| 
 | |
| namespace {
 | |
| // See <https://html.spec.whatwg.org/#the-popover-attribute>.
 | |
| enum class PopoverAttributeKeyword : uint8_t { Auto, EmptyString, Manual };
 | |
| 
 | |
| static const char* kPopoverAttributeValueAuto = "auto";
 | |
| static const char* kPopoverAttributeValueEmptyString = "";
 | |
| static const char* kPopoverAttributeValueManual = "manual";
 | |
| 
 | |
| static const nsAttrValue::EnumTable kPopoverTable[] = {
 | |
|     {kPopoverAttributeValueAuto, PopoverAttributeKeyword::Auto},
 | |
|     {kPopoverAttributeValueEmptyString, PopoverAttributeKeyword::EmptyString},
 | |
|     {kPopoverAttributeValueManual, PopoverAttributeKeyword::Manual},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| // See <https://html.spec.whatwg.org/#the-popover-attribute>.
 | |
| static const nsAttrValue::EnumTable* kPopoverTableInvalidValueDefault =
 | |
|     &kPopoverTable[2];
 | |
| }  // namespace
 | |
| 
 | |
| void nsGenericHTMLElement::GetFetchPriority(nsAString& aFetchPriority) const {
 | |
|   // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
 | |
|   GetEnumAttr(nsGkAtoms::fetchpriority, kFetchPriorityAttributeValueAuto,
 | |
|               aFetchPriority);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| FetchPriority nsGenericHTMLElement::ToFetchPriority(const nsAString& aValue) {
 | |
|   nsAttrValue attrValue;
 | |
|   ParseFetchPriority(aValue, attrValue);
 | |
|   MOZ_ASSERT(attrValue.Type() == nsAttrValue::eEnum);
 | |
|   return FetchPriority(attrValue.GetEnumValue());
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
 | |
| static const nsAttrValue::EnumTable kFetchPriorityEnumTable[] = {
 | |
|     {kFetchPriorityAttributeValueHigh, FetchPriority::High},
 | |
|     {kFetchPriorityAttributeValueLow, FetchPriority::Low},
 | |
|     {kFetchPriorityAttributeValueAuto, FetchPriority::Auto},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>.
 | |
| static const nsAttrValue::EnumTable*
 | |
|     kFetchPriorityEnumTableInvalidValueDefault = &kFetchPriorityEnumTable[2];
 | |
| }  // namespace
 | |
| 
 | |
| FetchPriority nsGenericHTMLElement::GetFetchPriority() const {
 | |
|   const nsAttrValue* fetchpriorityAttribute =
 | |
|       GetParsedAttr(nsGkAtoms::fetchpriority);
 | |
|   if (fetchpriorityAttribute) {
 | |
|     MOZ_ASSERT(fetchpriorityAttribute->Type() == nsAttrValue::eEnum);
 | |
|     return FetchPriority(fetchpriorityAttribute->GetEnumValue());
 | |
|   }
 | |
| 
 | |
|   return FetchPriority::Auto;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void nsGenericHTMLElement::ParseFetchPriority(const nsAString& aValue,
 | |
|                                               nsAttrValue& aResult) {
 | |
|   aResult.ParseEnumValue(aValue, kFetchPriorityEnumTable,
 | |
|                          false /* aCaseSensitive */,
 | |
|                          kFetchPriorityEnumTableInvalidValueDefault);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::AddToNameTable(nsAtom* aName) {
 | |
|   MOZ_ASSERT(HasName(), "Node doesn't have name?");
 | |
|   Document* doc = GetUncomposedDoc();
 | |
|   if (doc && !IsInNativeAnonymousSubtree()) {
 | |
|     doc->AddToNameTable(this, aName);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::RemoveFromNameTable() {
 | |
|   if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
 | |
|     if (Document* doc = GetUncomposedDoc()) {
 | |
|       doc->RemoveFromNameTable(this,
 | |
|                                GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::GetAccessKeyLabel(nsString& aLabel) {
 | |
|   nsAutoString suffix;
 | |
|   GetAccessKey(suffix);
 | |
|   if (!suffix.IsEmpty()) {
 | |
|     EventStateManager::GetAccessKeyLabelPrefix(this, aLabel);
 | |
|     aLabel.Append(suffix);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool IsOffsetParent(nsIFrame* aFrame) {
 | |
|   LayoutFrameType frameType = aFrame->Type();
 | |
| 
 | |
|   if (frameType == LayoutFrameType::TableCell ||
 | |
|       frameType == LayoutFrameType::TableWrapper) {
 | |
|     // Per the IDL for Element, only td, th, and table are acceptable
 | |
|     // offsetParents apart from body or positioned elements; we need to check
 | |
|     // the content type as well as the frame type so we ignore anonymous tables
 | |
|     // created by an element with display: table-cell with no actual table
 | |
|     nsIContent* content = aFrame->GetContent();
 | |
| 
 | |
|     return content->IsAnyOfHTMLElements(nsGkAtoms::table, nsGkAtoms::td,
 | |
|                                         nsGkAtoms::th);
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| struct OffsetResult {
 | |
|   Element* mParent = nullptr;
 | |
|   CSSIntRect mRect;
 | |
| };
 | |
| 
 | |
| static OffsetResult GetUnretargetedOffsetsFor(const Element& aElement) {
 | |
|   nsIFrame* frame = aElement.GetPrimaryFrame();
 | |
|   if (!frame) {
 | |
|     return {};
 | |
|   }
 | |
| 
 | |
|   nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(frame);
 | |
| 
 | |
|   nsIFrame* parent = frame->GetParent();
 | |
|   nsPoint origin(0, 0);
 | |
| 
 | |
|   nsIContent* offsetParent = nullptr;
 | |
|   Element* docElement = aElement.GetComposedDoc()->GetRootElement();
 | |
|   nsIContent* content = frame->GetContent();
 | |
| 
 | |
|   if (content &&
 | |
|       (content->IsHTMLElement(nsGkAtoms::body) || content == docElement)) {
 | |
|     parent = frame;
 | |
|   } else {
 | |
|     const bool isPositioned = styleFrame->IsAbsPosContainingBlock();
 | |
|     const bool isAbsolutelyPositioned = frame->IsAbsolutelyPositioned();
 | |
|     origin += frame->GetPositionIgnoringScrolling();
 | |
| 
 | |
|     for (; parent; parent = parent->GetParent()) {
 | |
|       content = parent->GetContent();
 | |
| 
 | |
|       // Stop at the first ancestor that is positioned.
 | |
|       if (parent->IsAbsPosContainingBlock()) {
 | |
|         offsetParent = content;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       // Add the parent's origin to our own to get to the
 | |
|       // right coordinate system.
 | |
|       const bool isOffsetParent = !isPositioned && IsOffsetParent(parent);
 | |
|       if (!isOffsetParent) {
 | |
|         origin += parent->GetPositionIgnoringScrolling();
 | |
|       }
 | |
| 
 | |
|       if (content) {
 | |
|         // If we've hit the document element, break here.
 | |
|         if (content == docElement) {
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         // Break if the ancestor frame type makes it suitable as offset parent
 | |
|         // and this element is *not* positioned or if we found the body element.
 | |
|         if (isOffsetParent || content->IsHTMLElement(nsGkAtoms::body)) {
 | |
|           offsetParent = content;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (isAbsolutelyPositioned && !offsetParent) {
 | |
|       // If this element is absolutely positioned, but we don't have
 | |
|       // an offset parent it means this element is an absolutely
 | |
|       // positioned child that's not nested inside another positioned
 | |
|       // element, in this case the element's frame's parent is the
 | |
|       // frame for the HTML element so we fail to find the body in the
 | |
|       // parent chain. We want the offset parent in this case to be
 | |
|       // the body, so we just get the body element from the document.
 | |
|       //
 | |
|       // We use GetBodyElement() here, not GetBody(), because we don't want to
 | |
|       // end up with framesets here.
 | |
|       offsetParent = aElement.GetComposedDoc()->GetBodyElement();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Make the position relative to the padding edge.
 | |
|   if (parent) {
 | |
|     const nsStyleBorder* border = parent->StyleBorder();
 | |
|     origin.x -= border->GetComputedBorderWidth(eSideLeft);
 | |
|     origin.y -= border->GetComputedBorderWidth(eSideTop);
 | |
|   }
 | |
| 
 | |
|   // Get the union of all rectangles in this and continuation frames.
 | |
|   // It doesn't really matter what we use as aRelativeTo here, since
 | |
|   // we only care about the size. We just have to use something non-null.
 | |
|   nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
 | |
|   rcFrame.MoveTo(origin);
 | |
|   return {Element::FromNodeOrNull(offsetParent),
 | |
|           CSSIntRect::FromAppUnitsRounded(rcFrame)};
 | |
| }
 | |
| 
 | |
| static bool ShouldBeRetargeted(const Element& aReferenceElement,
 | |
|                                const Element& aElementToMaybeRetarget) {
 | |
|   ShadowRoot* shadow = aElementToMaybeRetarget.GetContainingShadow();
 | |
|   if (!shadow) {
 | |
|     return false;
 | |
|   }
 | |
|   for (ShadowRoot* scope = aReferenceElement.GetContainingShadow(); scope;
 | |
|        scope = scope->Host()->GetContainingShadow()) {
 | |
|     if (scope == shadow) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| Element* nsGenericHTMLElement::GetOffsetRect(CSSIntRect& aRect) {
 | |
|   aRect = CSSIntRect();
 | |
| 
 | |
|   if (!GetPrimaryFrame(FlushType::Layout)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   OffsetResult thisResult = GetUnretargetedOffsetsFor(*this);
 | |
|   aRect = thisResult.mRect;
 | |
| 
 | |
|   Element* parent = thisResult.mParent;
 | |
|   while (parent && ShouldBeRetargeted(*this, *parent)) {
 | |
|     OffsetResult result = GetUnretargetedOffsetsFor(*parent);
 | |
|     aRect += result.mRect.TopLeft();
 | |
|     parent = result.mParent;
 | |
|   }
 | |
| 
 | |
|   return parent;
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::Spellcheck() {
 | |
|   // Has the state has been explicitly set?
 | |
|   nsIContent* node;
 | |
|   for (node = this; node; node = node->GetParent()) {
 | |
|     if (node->IsHTMLElement()) {
 | |
|       static Element::AttrValuesArray strings[] = {nsGkAtoms::_true,
 | |
|                                                    nsGkAtoms::_false, nullptr};
 | |
|       switch (node->AsElement()->FindAttrValueIn(
 | |
|           kNameSpaceID_None, nsGkAtoms::spellcheck, strings, eCaseMatters)) {
 | |
|         case 0:  // spellcheck = "true"
 | |
|           return true;
 | |
|         case 1:  // spellcheck = "false"
 | |
|           return false;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // contenteditable/designMode are spellchecked by default
 | |
|   if (IsEditable()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Is this a chrome element?
 | |
|   if (nsContentUtils::IsChromeDoc(OwnerDoc())) {
 | |
|     return false;  // Not spellchecked by default
 | |
|   }
 | |
| 
 | |
|   // Anything else that's not a form control is not spellchecked by default
 | |
|   nsCOMPtr<nsIFormControl> formControl = do_QueryObject(this);
 | |
|   if (!formControl) {
 | |
|     return false;  // Not spellchecked by default
 | |
|   }
 | |
| 
 | |
|   // Is this a multiline plaintext input?
 | |
|   auto controlType = formControl->ControlType();
 | |
|   if (controlType == FormControlType::Textarea) {
 | |
|     return true;  // Spellchecked by default
 | |
|   }
 | |
| 
 | |
|   // Is this anything other than an input text?
 | |
|   // Other inputs are not spellchecked.
 | |
|   if (controlType != FormControlType::InputText) {
 | |
|     return false;  // Not spellchecked by default
 | |
|   }
 | |
| 
 | |
|   // Does the user want input text spellchecked by default?
 | |
|   // NOTE: Do not reflect a pref value of 0 back to the DOM getter.
 | |
|   // The web page should not know if the user has disabled spellchecking.
 | |
|   // We'll catch this in the editor itself.
 | |
|   int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1);
 | |
|   return spellcheckLevel == 2;  // "Spellcheck multi- and single-line"
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::InNavQuirksMode(Document* aDoc) {
 | |
|   return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::UpdateEditableState(bool aNotify) {
 | |
|   // XXX Should we do this only when in a document?
 | |
|   ContentEditableTristate value = GetContentEditableValue();
 | |
|   if (value != eInherit) {
 | |
|     SetEditableFlag(!!value);
 | |
|     UpdateReadOnlyState(aNotify);
 | |
|     return;
 | |
|   }
 | |
|   nsStyledElement::UpdateEditableState(aNotify);
 | |
| }
 | |
| 
 | |
| nsresult nsGenericHTMLElement::BindToTree(BindContext& aContext,
 | |
|                                           nsINode& aParent) {
 | |
|   nsresult rv = nsGenericHTMLElementBase::BindToTree(aContext, aParent);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (IsInComposedDoc()) {
 | |
|     RegUnRegAccessKey(true);
 | |
|   }
 | |
| 
 | |
|   if (IsInUncomposedDoc()) {
 | |
|     if (HasName() && CanHaveName(NodeInfo()->NameAtom())) {
 | |
|       aContext.OwnerDoc().AddToNameTable(
 | |
|           this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue &&
 | |
|       IsInComposedDoc()) {
 | |
|     aContext.OwnerDoc().ChangeContentEditableCount(this, +1);
 | |
|   }
 | |
| 
 | |
|   // Hide any nonce from the DOM, but keep the internal value of the
 | |
|   // nonce by copying and resetting the internal nonce value.
 | |
|   if (HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && IsInComposedDoc() &&
 | |
|       OwnerDoc()->GetBrowsingContext()) {
 | |
|     nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
 | |
|         "nsGenericHTMLElement::ResetNonce::Runnable",
 | |
|         [self = RefPtr<nsGenericHTMLElement>(this)]() {
 | |
|           nsAutoString nonce;
 | |
|           self->GetNonce(nonce);
 | |
|           self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, u""_ns, true);
 | |
|           self->SetNonce(nonce);
 | |
|         }));
 | |
|   }
 | |
| 
 | |
|   // We need to consider a labels element is moved to another subtree
 | |
|   // with different root, it needs to update labels list and its root
 | |
|   // as well.
 | |
|   nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
 | |
|   if (slots && slots->mLabelsList) {
 | |
|     slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::UnbindFromTree(UnbindContext& aContext) {
 | |
|   if (IsInComposedDoc()) {
 | |
|     // https://html.spec.whatwg.org/#dom-trees:hide-popover-algorithm
 | |
|     // If removedNode's popover attribute is not in the no popover state, then
 | |
|     // run the hide popover algorithm given removedNode, false, false, and
 | |
|     // false.
 | |
|     if (GetPopoverData()) {
 | |
|       HidePopoverWithoutRunningScript();
 | |
|     }
 | |
|     RegUnRegAccessKey(false);
 | |
|   }
 | |
| 
 | |
|   RemoveFromNameTable();
 | |
| 
 | |
|   if (GetContentEditableValue() == eTrue) {
 | |
|     if (Document* doc = GetComposedDoc()) {
 | |
|       doc->ChangeContentEditableCount(this, -1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsStyledElement::UnbindFromTree(aContext);
 | |
| 
 | |
|   // Invalidate .labels list. It will be repopulated when used the next time.
 | |
|   nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
 | |
|   if (slots && slots->mLabelsList) {
 | |
|     slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
 | |
|   }
 | |
| }
 | |
| 
 | |
| HTMLFormElement* nsGenericHTMLElement::FindAncestorForm(
 | |
|     HTMLFormElement* aCurrentForm) {
 | |
|   NS_ASSERTION(!HasAttr(nsGkAtoms::form) || IsHTMLElement(nsGkAtoms::img),
 | |
|                "FindAncestorForm should not be called if @form is set!");
 | |
|   if (IsInNativeAnonymousSubtree()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsIContent* content = this;
 | |
|   while (content) {
 | |
|     // If the current ancestor is a form, return it as our form
 | |
|     if (content->IsHTMLElement(nsGkAtoms::form)) {
 | |
| #ifdef DEBUG
 | |
|       if (!nsContentUtils::IsInSameAnonymousTree(this, content)) {
 | |
|         // It's possible that we started unbinding at |content| or
 | |
|         // some ancestor of it, and |content| and |this| used to all be
 | |
|         // anonymous.  Check for this the hard way.
 | |
|         for (nsIContent* child = this; child != content;
 | |
|              child = child->GetParent()) {
 | |
|           NS_ASSERTION(child->ComputeIndexInParentContent().isSome(),
 | |
|                        "Walked too far?");
 | |
|         }
 | |
|       }
 | |
| #endif
 | |
|       return static_cast<HTMLFormElement*>(content);
 | |
|     }
 | |
| 
 | |
|     nsIContent* prevContent = content;
 | |
|     content = prevContent->GetParent();
 | |
| 
 | |
|     if (!content && aCurrentForm) {
 | |
|       // We got to the root of the subtree we're in, and we're being removed
 | |
|       // from the DOM (the only time we get into this method with a non-null
 | |
|       // aCurrentForm).  Check whether aCurrentForm is in the same subtree.  If
 | |
|       // it is, we want to return aCurrentForm, since this case means that
 | |
|       // we're one of those inputs-in-a-table that have a hacked mForm pointer
 | |
|       // and a subtree containing both us and the form got removed from the
 | |
|       // DOM.
 | |
|       if (aCurrentForm->IsInclusiveDescendantOf(prevContent)) {
 | |
|         return aCurrentForm;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::CheckHandleEventForAnchorsPreconditions(
 | |
|     EventChainVisitor& aVisitor) {
 | |
|   MOZ_ASSERT(nsCOMPtr<Link>(do_QueryObject(this)),
 | |
|              "should be called only when |this| implements |Link|");
 | |
|   // When disconnected, only <a> should navigate away per
 | |
|   // https://html.spec.whatwg.org/#cannot-navigate
 | |
|   return IsInComposedDoc() || IsHTMLElement(nsGkAtoms::a);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::GetEventTargetParentForAnchors(
 | |
|     EventChainPreVisitor& aVisitor) {
 | |
|   nsGenericHTMLElementBase::GetEventTargetParent(aVisitor);
 | |
| 
 | |
|   if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GetEventTargetParentForLinks(aVisitor);
 | |
| }
 | |
| 
 | |
| nsresult nsGenericHTMLElement::PostHandleEventForAnchors(
 | |
|     EventChainPostVisitor& aVisitor) {
 | |
|   if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   return PostHandleEventForLinks(aVisitor);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::IsHTMLLink(nsIURI** aURI) const {
 | |
|   MOZ_ASSERT(aURI, "Must provide aURI out param");
 | |
| 
 | |
|   *aURI = GetHrefURIForAnchors().take();
 | |
|   // We promise out param is non-null if we return true, so base rv on it
 | |
|   return *aURI != nullptr;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIURI> nsGenericHTMLElement::GetHrefURIForAnchors() const {
 | |
|   // This is used by the three Link implementations and
 | |
|   // nsHTMLStyleElement.
 | |
| 
 | |
|   // Get href= attribute (relative URI).
 | |
| 
 | |
|   // We use the nsAttrValue's copy of the URI string to avoid copying.
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri));
 | |
| 
 | |
|   return uri.forget();
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
 | |
|                                          const nsAttrValue* aValue,
 | |
|                                          bool aNotify) {
 | |
|   if (aNamespaceID == kNameSpaceID_None) {
 | |
|     if (aName == nsGkAtoms::accesskey) {
 | |
|       // Have to unregister before clearing flag. See UnregAccessKey
 | |
|       RegUnRegAccessKey(false);
 | |
|       if (!aValue) {
 | |
|         UnsetFlags(NODE_HAS_ACCESSKEY);
 | |
|       }
 | |
|     } else if (aName == nsGkAtoms::name) {
 | |
|       // Have to do this before clearing flag. See RemoveFromNameTable
 | |
|       RemoveFromNameTable();
 | |
|       if (!aValue || aValue->IsEmptyString()) {
 | |
|         ClearHasName();
 | |
|       }
 | |
|     } else if (aName == nsGkAtoms::contenteditable) {
 | |
|       if (aValue) {
 | |
|         // Set this before the attribute is set so that any subclass code that
 | |
|         // runs before the attribute is set won't think we're missing a
 | |
|         // contenteditable attr when we actually have one.
 | |
|         SetMayHaveContentEditableAttr();
 | |
|       }
 | |
|     }
 | |
|     if (!aValue && IsEventAttributeName(aName)) {
 | |
|       if (EventListenerManager* manager = GetExistingListenerManager()) {
 | |
|         manager->RemoveEventHandler(GetEventNameForAttr(aName));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nsGenericHTMLElementBase::BeforeSetAttr(aNamespaceID, aName, aValue,
 | |
|                                                  aNotify);
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| constexpr PopoverAttributeState ToPopoverAttributeState(
 | |
|     PopoverAttributeKeyword aPopoverAttributeKeyword) {
 | |
|   // See <https://html.spec.whatwg.org/#the-popover-attribute>.
 | |
|   switch (aPopoverAttributeKeyword) {
 | |
|     case PopoverAttributeKeyword::Auto:
 | |
|       return PopoverAttributeState::Auto;
 | |
|     case PopoverAttributeKeyword::EmptyString:
 | |
|       return PopoverAttributeState::Auto;
 | |
|     case PopoverAttributeKeyword::Manual:
 | |
|       return PopoverAttributeState::Manual;
 | |
|     default: {
 | |
|       MOZ_ASSERT_UNREACHABLE();
 | |
|       return PopoverAttributeState::None;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| }  // namespace
 | |
| 
 | |
| void nsGenericHTMLElement::AfterSetPopoverAttr() {
 | |
|   auto mapPopoverState = [](const nsAttrValue* value) -> PopoverAttributeState {
 | |
|     if (value) {
 | |
|       MOZ_ASSERT(value->Type() == nsAttrValue::eEnum);
 | |
|       const auto popoverAttributeKeyword =
 | |
|           static_cast<PopoverAttributeKeyword>(value->GetEnumValue());
 | |
|       return ToPopoverAttributeState(popoverAttributeKeyword);
 | |
|     }
 | |
| 
 | |
|     // The missing value default is the no popover state, see
 | |
|     // <https://html.spec.whatwg.org/multipage/popover.html#attr-popover>.
 | |
|     return PopoverAttributeState::None;
 | |
|   };
 | |
| 
 | |
|   PopoverAttributeState newState =
 | |
|       mapPopoverState(GetParsedAttr(nsGkAtoms::popover));
 | |
| 
 | |
|   const PopoverAttributeState oldState = GetPopoverAttributeState();
 | |
| 
 | |
|   if (newState != oldState) {
 | |
|     PopoverPseudoStateUpdate(false, true);
 | |
| 
 | |
|     if (IsPopoverOpen()) {
 | |
|       HidePopoverInternal(/* aFocusPreviousElement = */ true,
 | |
|                           /* aFireEvents = */ true, IgnoreErrors());
 | |
|       // Event handlers could have removed the popover attribute, or changed
 | |
|       // its value.
 | |
|       // https://github.com/whatwg/html/issues/9034
 | |
|       newState = mapPopoverState(GetParsedAttr(nsGkAtoms::popover));
 | |
|     }
 | |
| 
 | |
|     if (newState == PopoverAttributeState::None) {
 | |
|       ClearPopoverData();
 | |
|       RemoveStates(ElementState::POPOVER_OPEN);
 | |
|     } else {
 | |
|       // TODO: what if `HidePopoverInternal` called `ShowPopup()`?
 | |
|       EnsurePopoverData().SetPopoverAttributeState(newState);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::OnAttrSetButNotChanged(
 | |
|     int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue,
 | |
|     bool aNotify) {
 | |
|   if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::popovertarget) {
 | |
|     ClearExplicitlySetAttrElement(aName);
 | |
|   }
 | |
|   return nsGenericHTMLElementBase::OnAttrSetButNotChanged(aNamespaceID, aName,
 | |
|                                                           aValue, aNotify);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
 | |
|                                         const nsAttrValue* aValue,
 | |
|                                         const nsAttrValue* aOldValue,
 | |
|                                         nsIPrincipal* aMaybeScriptedPrincipal,
 | |
|                                         bool aNotify) {
 | |
|   if (aNamespaceID == kNameSpaceID_None) {
 | |
|     if (IsEventAttributeName(aName) && aValue) {
 | |
|       MOZ_ASSERT(aValue->Type() == nsAttrValue::eString,
 | |
|                  "Expected string value for script body");
 | |
|       SetEventHandler(GetEventNameForAttr(aName), aValue->GetStringValue());
 | |
|     } else if (aNotify && aName == nsGkAtoms::spellcheck) {
 | |
|       SyncEditorsOnSubtree(this);
 | |
|     } else if (aName == nsGkAtoms::popover &&
 | |
|                StaticPrefs::dom_element_popover_enabled()) {
 | |
|       nsContentUtils::AddScriptRunner(
 | |
|           NewRunnableMethod("nsGenericHTMLElement::AfterSetPopoverAttr", this,
 | |
|                             &nsGenericHTMLElement::AfterSetPopoverAttr));
 | |
|     } else if (aName == nsGkAtoms::popovertarget) {
 | |
|       ClearExplicitlySetAttrElement(aName);
 | |
|     } else if (aName == nsGkAtoms::dir) {
 | |
|       auto dir = Directionality::Ltr;
 | |
|       // A boolean tracking whether we need to recompute our directionality.
 | |
|       // This needs to happen after we update our internal "dir" attribute
 | |
|       // state but before we call SetDirectionalityOnDescendants.
 | |
|       bool recomputeDirectionality = false;
 | |
|       ElementState dirStates;
 | |
|       if (aValue && aValue->Type() == nsAttrValue::eEnum) {
 | |
|         SetHasValidDir();
 | |
|         dirStates |= ElementState::HAS_DIR_ATTR;
 | |
|         auto dirValue = Directionality(aValue->GetEnumValue());
 | |
|         if (dirValue == Directionality::Auto) {
 | |
|           dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO;
 | |
|         } else {
 | |
|           dir = dirValue;
 | |
|           SetDirectionality(dir, aNotify);
 | |
|           if (dirValue == Directionality::Ltr) {
 | |
|             dirStates |= ElementState::HAS_DIR_ATTR_LTR;
 | |
|           } else {
 | |
|             MOZ_ASSERT(dirValue == Directionality::Rtl);
 | |
|             dirStates |= ElementState::HAS_DIR_ATTR_RTL;
 | |
|           }
 | |
|         }
 | |
|       } else {
 | |
|         if (aValue) {
 | |
|           // We have a value, just not a valid one.
 | |
|           dirStates |= ElementState::HAS_DIR_ATTR;
 | |
|         }
 | |
|         ClearHasValidDir();
 | |
|         if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
 | |
|           dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO;
 | |
|         } else {
 | |
|           recomputeDirectionality = true;
 | |
|         }
 | |
|       }
 | |
|       // Now figure out what's changed about our dir states.
 | |
|       ElementState oldDirStates = State() & ElementState::DIR_ATTR_STATES;
 | |
|       ElementState changedStates = dirStates ^ oldDirStates;
 | |
|       if (!changedStates.IsEmpty()) {
 | |
|         ToggleStates(changedStates, aNotify);
 | |
|       }
 | |
|       if (recomputeDirectionality) {
 | |
|         dir = RecomputeDirectionality(this, aNotify);
 | |
|       }
 | |
|       SetDirectionalityOnDescendants(this, dir, aNotify);
 | |
|     } else if (aName == nsGkAtoms::contenteditable) {
 | |
|       int32_t editableCountDelta = 0;
 | |
|       if (aOldValue && (aOldValue->Equals(u"true"_ns, eIgnoreCase) ||
 | |
|                         aOldValue->Equals(u""_ns, eIgnoreCase))) {
 | |
|         editableCountDelta = -1;
 | |
|       }
 | |
|       if (aValue && (aValue->Equals(u"true"_ns, eIgnoreCase) ||
 | |
|                      aValue->Equals(u""_ns, eIgnoreCase))) {
 | |
|         ++editableCountDelta;
 | |
|       }
 | |
|       ChangeEditableState(editableCountDelta);
 | |
|     } else if (aName == nsGkAtoms::accesskey) {
 | |
|       if (aValue && !aValue->Equals(u""_ns, eIgnoreCase)) {
 | |
|         SetFlags(NODE_HAS_ACCESSKEY);
 | |
|         RegUnRegAccessKey(true);
 | |
|       }
 | |
|     } else if (aName == nsGkAtoms::inert) {
 | |
|       if (aValue) {
 | |
|         AddStates(ElementState::INERT);
 | |
|       } else {
 | |
|         RemoveStates(ElementState::INERT);
 | |
|       }
 | |
|     } else if (aName == nsGkAtoms::name) {
 | |
|       if (aValue && !aValue->Equals(u""_ns, eIgnoreCase)) {
 | |
|         // This may not be quite right because we can have subclass code run
 | |
|         // before here. But in practice subclasses don't care about this flag,
 | |
|         // and in particular selector matching does not care.  Otherwise we'd
 | |
|         // want to handle it like we handle id attributes (in PreIdMaybeChange
 | |
|         // and PostIdMaybeChange).
 | |
|         SetHasName();
 | |
|         if (CanHaveName(NodeInfo()->NameAtom())) {
 | |
|           AddToNameTable(aValue->GetAtomValue());
 | |
|         }
 | |
|       }
 | |
|     } else if (aName == nsGkAtoms::inputmode ||
 | |
|                aName == nsGkAtoms::enterkeyhint) {
 | |
|       if (nsFocusManager::GetFocusedElementStatic() == this) {
 | |
|         if (const nsPresContext* presContext =
 | |
|                 GetPresContext(eForComposedDoc)) {
 | |
|           IMEContentObserver* observer =
 | |
|               IMEStateManager::GetActiveContentObserver();
 | |
|           if (observer && observer->IsObserving(*presContext, this)) {
 | |
|             if (RefPtr<EditorBase> editorBase = GetEditorWithoutCreation()) {
 | |
|               IMEState newState;
 | |
|               editorBase->GetPreferredIMEState(&newState);
 | |
|               OwningNonNull<nsGenericHTMLElement> kungFuDeathGrip(*this);
 | |
|               IMEStateManager::UpdateIMEState(
 | |
|                   newState, kungFuDeathGrip, *editorBase,
 | |
|                   {IMEStateManager::UpdateIMEStateOption::ForceUpdate,
 | |
|                    IMEStateManager::UpdateIMEStateOption::
 | |
|                        DontCommitComposition});
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // The nonce will be copied over to an internal slot and cleared from the
 | |
|     // Element within BindToTree to avoid CSS Selector nonce exfiltration if
 | |
|     // the CSP list contains a header-delivered CSP.
 | |
|     if (nsGkAtoms::nonce == aName) {
 | |
|       if (aValue) {
 | |
|         SetNonce(aValue->GetStringValue());
 | |
|         if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) {
 | |
|           SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP);
 | |
|         }
 | |
|       } else {
 | |
|         RemoveNonce();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nsGenericHTMLElementBase::AfterSetAttr(
 | |
|       aNamespaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
 | |
| }
 | |
| 
 | |
| EventListenerManager* nsGenericHTMLElement::GetEventListenerManagerForAttr(
 | |
|     nsAtom* aAttrName, bool* aDefer) {
 | |
|   // Attributes on the body and frameset tags get set on the global object
 | |
|   if ((mNodeInfo->Equals(nsGkAtoms::body) ||
 | |
|        mNodeInfo->Equals(nsGkAtoms::frameset)) &&
 | |
|       // We only forward some event attributes from body/frameset to window
 | |
|       (0
 | |
| #define EVENT(name_, id_, type_, struct_) /* nothing */
 | |
| #define FORWARDED_EVENT(name_, id_, type_, struct_) \
 | |
|   || nsGkAtoms::on##name_ == aAttrName
 | |
| #define WINDOW_EVENT FORWARDED_EVENT
 | |
| #include "mozilla/EventNameList.h"  // IWYU pragma: keep
 | |
| #undef WINDOW_EVENT
 | |
| #undef FORWARDED_EVENT
 | |
| #undef EVENT
 | |
|        )) {
 | |
|     nsPIDOMWindowInner* win;
 | |
| 
 | |
|     // If we have a document, and it has a window, add the event
 | |
|     // listener on the window (the inner window). If not, proceed as
 | |
|     // normal.
 | |
|     // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() here,
 | |
|     // override BindToTree for those classes and munge event listeners there?
 | |
|     Document* document = OwnerDoc();
 | |
| 
 | |
|     *aDefer = false;
 | |
|     if ((win = document->GetInnerWindow())) {
 | |
|       nsCOMPtr<EventTarget> piTarget(do_QueryInterface(win));
 | |
| 
 | |
|       return piTarget->GetOrCreateListenerManager();
 | |
|     }
 | |
| 
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName,
 | |
|                                                                   aDefer);
 | |
| }
 | |
| 
 | |
| #define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
 | |
| #define FORWARDED_EVENT(name_, id_, type_, struct_)                       \
 | |
|   EventHandlerNonNull* nsGenericHTMLElement::GetOn##name_() {             \
 | |
|     if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {      \
 | |
|       /* XXXbz note to self: add tests for this! */                       \
 | |
|       if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) {       \
 | |
|         nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win);  \
 | |
|         return globalWin->GetOn##name_();                                 \
 | |
|       }                                                                   \
 | |
|       return nullptr;                                                     \
 | |
|     }                                                                     \
 | |
|                                                                           \
 | |
|     return nsINode::GetOn##name_();                                       \
 | |
|   }                                                                       \
 | |
|   void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \
 | |
|     if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {      \
 | |
|       nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();             \
 | |
|       if (!win) {                                                         \
 | |
|         return;                                                           \
 | |
|       }                                                                   \
 | |
|                                                                           \
 | |
|       nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win);    \
 | |
|       return globalWin->SetOn##name_(handler);                            \
 | |
|     }                                                                     \
 | |
|                                                                           \
 | |
|     return nsINode::SetOn##name_(handler);                                \
 | |
|   }
 | |
| #define ERROR_EVENT(name_, id_, type_, struct_)                                \
 | |
|   already_AddRefed<EventHandlerNonNull> nsGenericHTMLElement::GetOn##name_() { \
 | |
|     if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {           \
 | |
|       /* XXXbz note to self: add tests for this! */                            \
 | |
|       if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) {            \
 | |
|         nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win);       \
 | |
|         OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_();  \
 | |
|         if (errorHandler) {                                                    \
 | |
|           RefPtr<EventHandlerNonNull> handler =                                \
 | |
|               new EventHandlerNonNull(errorHandler);                           \
 | |
|           return handler.forget();                                             \
 | |
|         }                                                                      \
 | |
|       }                                                                        \
 | |
|       return nullptr;                                                          \
 | |
|     }                                                                          \
 | |
|                                                                                \
 | |
|     RefPtr<EventHandlerNonNull> handler = nsINode::GetOn##name_();             \
 | |
|     return handler.forget();                                                   \
 | |
|   }                                                                            \
 | |
|   void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) {      \
 | |
|     if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {           \
 | |
|       nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();                  \
 | |
|       if (!win) {                                                              \
 | |
|         return;                                                                \
 | |
|       }                                                                        \
 | |
|                                                                                \
 | |
|       nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win);         \
 | |
|       RefPtr<OnErrorEventHandlerNonNull> errorHandler;                         \
 | |
|       if (handler) {                                                           \
 | |
|         errorHandler = new OnErrorEventHandlerNonNull(handler);                \
 | |
|       }                                                                        \
 | |
|       return globalWin->SetOn##name_(errorHandler);                            \
 | |
|     }                                                                          \
 | |
|                                                                                \
 | |
|     return nsINode::SetOn##name_(handler);                                     \
 | |
|   }
 | |
| #include "mozilla/EventNameList.h"  // IWYU pragma: keep
 | |
| #undef ERROR_EVENT
 | |
| #undef FORWARDED_EVENT
 | |
| #undef EVENT
 | |
| 
 | |
| void nsGenericHTMLElement::GetBaseTarget(nsAString& aBaseTarget) const {
 | |
|   OwnerDoc()->GetBaseTarget(aBaseTarget);
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID,
 | |
|                                           nsAtom* aAttribute,
 | |
|                                           const nsAString& aValue,
 | |
|                                           nsIPrincipal* aMaybeScriptedPrincipal,
 | |
|                                           nsAttrValue& aResult) {
 | |
|   if (aNamespaceID == kNameSpaceID_None) {
 | |
|     if (aAttribute == nsGkAtoms::dir) {
 | |
|       return aResult.ParseEnumValue(aValue, kDirTable, false);
 | |
|     }
 | |
| 
 | |
|     if (aAttribute == nsGkAtoms::popover &&
 | |
|         StaticPrefs::dom_element_popover_enabled()) {
 | |
|       return aResult.ParseEnumValue(aValue, kPopoverTable, false,
 | |
|                                     kPopoverTableInvalidValueDefault);
 | |
|     }
 | |
| 
 | |
|     if (aAttribute == nsGkAtoms::tabindex) {
 | |
|       return aResult.ParseIntValue(aValue);
 | |
|     }
 | |
| 
 | |
|     if (aAttribute == nsGkAtoms::referrerpolicy) {
 | |
|       return ParseReferrerAttribute(aValue, aResult);
 | |
|     }
 | |
| 
 | |
|     if (aAttribute == nsGkAtoms::name) {
 | |
|       // Store name as an atom.  name="" means that the element has no name,
 | |
|       // not that it has an empty string as the name.
 | |
|       if (aValue.IsEmpty()) {
 | |
|         return false;
 | |
|       }
 | |
|       aResult.ParseAtom(aValue);
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (aAttribute == nsGkAtoms::contenteditable ||
 | |
|         aAttribute == nsGkAtoms::translate) {
 | |
|       aResult.ParseAtom(aValue);
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (aAttribute == nsGkAtoms::rel) {
 | |
|       aResult.ParseAtomArray(aValue);
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (aAttribute == nsGkAtoms::inputmode) {
 | |
|       return aResult.ParseEnumValue(aValue, kInputmodeTable, false);
 | |
|     }
 | |
| 
 | |
|     if (aAttribute == nsGkAtoms::enterkeyhint) {
 | |
|       return aResult.ParseEnumValue(aValue, kEnterKeyHintTable, false);
 | |
|     }
 | |
| 
 | |
|     if (aAttribute == nsGkAtoms::autocapitalize) {
 | |
|       return aResult.ParseEnumValue(aValue, kAutocapitalizeTable, false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nsGenericHTMLElementBase::ParseAttribute(
 | |
|       aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID,
 | |
|                                                     nsAtom* aAttribute,
 | |
|                                                     const nsAString& aValue,
 | |
|                                                     nsAttrValue& aResult) {
 | |
|   if (aNamespaceID == kNameSpaceID_None &&
 | |
|       aAttribute == nsGkAtoms::background && !aValue.IsEmpty()) {
 | |
|     // Resolve url to an absolute url
 | |
|     Document* doc = OwnerDoc();
 | |
|     nsCOMPtr<nsIURI> uri;
 | |
|     nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
 | |
|         getter_AddRefs(uri), aValue, doc, GetBaseURI());
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return false;
 | |
|     }
 | |
|     aResult.SetTo(uri, &aValue);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::IsAttributeMapped(const nsAtom* aAttribute) const {
 | |
|   static const MappedAttributeEntry* const map[] = {sCommonAttributeMap};
 | |
| 
 | |
|   return FindAttributeDependence(aAttribute, map);
 | |
| }
 | |
| 
 | |
| nsMapRuleToAttributesFunc nsGenericHTMLElement::GetAttributeMappingFunction()
 | |
|     const {
 | |
|   return &MapCommonAttributesInto;
 | |
| }
 | |
| 
 | |
| nsIFormControlFrame* nsGenericHTMLElement::GetFormControlFrame(
 | |
|     bool aFlushFrames) {
 | |
|   auto flushType = aFlushFrames ? FlushType::Frames : FlushType::None;
 | |
|   nsIFrame* frame = GetPrimaryFrame(flushType);
 | |
|   if (!frame) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (nsIFormControlFrame* f = do_QueryFrame(frame)) {
 | |
|     return f;
 | |
|   }
 | |
| 
 | |
|   // If we have generated content, the primary frame will be a wrapper frame...
 | |
|   // Our real frame will be in its child list.
 | |
|   //
 | |
|   // FIXME(emilio): I don't think that's true... See bug 155957 for test-cases
 | |
|   // though, we should figure out whether this is still needed.
 | |
|   for (nsIFrame* kid : frame->PrincipalChildList()) {
 | |
|     if (nsIFormControlFrame* f = do_QueryFrame(kid)) {
 | |
|       return f;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| static const nsAttrValue::EnumTable kDivAlignTable[] = {
 | |
|     {"left", StyleTextAlign::MozLeft},
 | |
|     {"right", StyleTextAlign::MozRight},
 | |
|     {"center", StyleTextAlign::MozCenter},
 | |
|     {"middle", StyleTextAlign::MozCenter},
 | |
|     {"justify", StyleTextAlign::Justify},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| static const nsAttrValue::EnumTable kFrameborderTable[] = {
 | |
|     {"yes", FrameBorderProperty::Yes},
 | |
|     {"no", FrameBorderProperty::No},
 | |
|     {"1", FrameBorderProperty::One},
 | |
|     {"0", FrameBorderProperty::Zero},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| // TODO(emilio): Nobody uses the parsed attribute here.
 | |
| static const nsAttrValue::EnumTable kScrollingTable[] = {
 | |
|     {"yes", ScrollingAttribute::Yes},
 | |
|     {"no", ScrollingAttribute::No},
 | |
|     {"on", ScrollingAttribute::On},
 | |
|     {"off", ScrollingAttribute::Off},
 | |
|     {"scroll", ScrollingAttribute::Scroll},
 | |
|     {"noscroll", ScrollingAttribute::Noscroll},
 | |
|     {"auto", ScrollingAttribute::Auto},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| static const nsAttrValue::EnumTable kTableVAlignTable[] = {
 | |
|     {"top", StyleVerticalAlignKeyword::Top},
 | |
|     {"middle", StyleVerticalAlignKeyword::Middle},
 | |
|     {"bottom", StyleVerticalAlignKeyword::Bottom},
 | |
|     {"baseline", StyleVerticalAlignKeyword::Baseline},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseAlignValue(const nsAString& aString,
 | |
|                                            nsAttrValue& aResult) {
 | |
|   static const nsAttrValue::EnumTable kAlignTable[] = {
 | |
|       {"left", StyleTextAlign::Left},
 | |
|       {"right", StyleTextAlign::Right},
 | |
| 
 | |
|       {"top", StyleVerticalAlignKeyword::Top},
 | |
|       {"middle", StyleVerticalAlignKeyword::MozMiddleWithBaseline},
 | |
| 
 | |
|       // Intentionally not bottom.
 | |
|       {"bottom", StyleVerticalAlignKeyword::Baseline},
 | |
| 
 | |
|       {"center", StyleVerticalAlignKeyword::MozMiddleWithBaseline},
 | |
|       {"baseline", StyleVerticalAlignKeyword::Baseline},
 | |
| 
 | |
|       {"texttop", StyleVerticalAlignKeyword::TextTop},
 | |
|       {"absmiddle", StyleVerticalAlignKeyword::Middle},
 | |
|       {"abscenter", StyleVerticalAlignKeyword::Middle},
 | |
|       {"absbottom", StyleVerticalAlignKeyword::Bottom},
 | |
|       {nullptr, 0}};
 | |
| 
 | |
|   static_assert(uint8_t(StyleTextAlign::Left) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::Top) &&
 | |
|                 uint8_t(StyleTextAlign::Left) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::MozMiddleWithBaseline) &&
 | |
|                 uint8_t(StyleTextAlign::Left) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::Baseline) &&
 | |
|                 uint8_t(StyleTextAlign::Left) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::TextTop) &&
 | |
|                 uint8_t(StyleTextAlign::Left) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::Middle) &&
 | |
|                 uint8_t(StyleTextAlign::Left) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::Bottom));
 | |
| 
 | |
|   static_assert(uint8_t(StyleTextAlign::Right) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::Top) &&
 | |
|                 uint8_t(StyleTextAlign::Right) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::MozMiddleWithBaseline) &&
 | |
|                 uint8_t(StyleTextAlign::Right) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::Baseline) &&
 | |
|                 uint8_t(StyleTextAlign::Right) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::TextTop) &&
 | |
|                 uint8_t(StyleTextAlign::Right) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::Middle) &&
 | |
|                 uint8_t(StyleTextAlign::Right) !=
 | |
|                     uint8_t(StyleVerticalAlignKeyword::Bottom));
 | |
| 
 | |
|   return aResult.ParseEnumValue(aString, kAlignTable, false);
 | |
| }
 | |
| 
 | |
| //----------------------------------------
 | |
| 
 | |
| static const nsAttrValue::EnumTable kTableHAlignTable[] = {
 | |
|     {"left", StyleTextAlign::Left},       {"right", StyleTextAlign::Right},
 | |
|     {"center", StyleTextAlign::Center},   {"char", StyleTextAlign::Char},
 | |
|     {"justify", StyleTextAlign::Justify}, {nullptr, 0}};
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseTableHAlignValue(const nsAString& aString,
 | |
|                                                  nsAttrValue& aResult) {
 | |
|   return aResult.ParseEnumValue(aString, kTableHAlignTable, false);
 | |
| }
 | |
| 
 | |
| //----------------------------------------
 | |
| 
 | |
| // This table is used for td, th, tr, col, thead, tbody and tfoot.
 | |
| static const nsAttrValue::EnumTable kTableCellHAlignTable[] = {
 | |
|     {"left", StyleTextAlign::MozLeft},
 | |
|     {"right", StyleTextAlign::MozRight},
 | |
|     {"center", StyleTextAlign::MozCenter},
 | |
|     {"char", StyleTextAlign::Char},
 | |
|     {"justify", StyleTextAlign::Justify},
 | |
|     {"middle", StyleTextAlign::MozCenter},
 | |
|     {"absmiddle", StyleTextAlign::Center},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseTableCellHAlignValue(const nsAString& aString,
 | |
|                                                      nsAttrValue& aResult) {
 | |
|   return aResult.ParseEnumValue(aString, kTableCellHAlignTable, false);
 | |
| }
 | |
| 
 | |
| //----------------------------------------
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseTableVAlignValue(const nsAString& aString,
 | |
|                                                  nsAttrValue& aResult) {
 | |
|   return aResult.ParseEnumValue(aString, kTableVAlignTable, false);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseDivAlignValue(const nsAString& aString,
 | |
|                                               nsAttrValue& aResult) {
 | |
|   return aResult.ParseEnumValue(aString, kDivAlignTable, false);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseImageAttribute(nsAtom* aAttribute,
 | |
|                                                const nsAString& aString,
 | |
|                                                nsAttrValue& aResult) {
 | |
|   if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
 | |
|       aAttribute == nsGkAtoms::hspace || aAttribute == nsGkAtoms::vspace) {
 | |
|     return aResult.ParseHTMLDimension(aString);
 | |
|   }
 | |
|   if (aAttribute == nsGkAtoms::border) {
 | |
|     return aResult.ParseNonNegativeIntValue(aString);
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString,
 | |
|                                                   nsAttrValue& aResult) {
 | |
|   using mozilla::dom::ReferrerInfo;
 | |
|   // This is a bit sketchy, we assume GetEnumString(…).get() points to a static
 | |
|   // buffer, relying on the fact that GetEnumString(…) returns a literal string.
 | |
|   static const nsAttrValue::EnumTable kReferrerPolicyTable[] = {
 | |
|       {GetEnumString(ReferrerPolicy::No_referrer).get(),
 | |
|        static_cast<int16_t>(ReferrerPolicy::No_referrer)},
 | |
|       {GetEnumString(ReferrerPolicy::Origin).get(),
 | |
|        static_cast<int16_t>(ReferrerPolicy::Origin)},
 | |
|       {GetEnumString(ReferrerPolicy::Origin_when_cross_origin).get(),
 | |
|        static_cast<int16_t>(ReferrerPolicy::Origin_when_cross_origin)},
 | |
|       {GetEnumString(ReferrerPolicy::No_referrer_when_downgrade).get(),
 | |
|        static_cast<int16_t>(ReferrerPolicy::No_referrer_when_downgrade)},
 | |
|       {GetEnumString(ReferrerPolicy::Unsafe_url).get(),
 | |
|        static_cast<int16_t>(ReferrerPolicy::Unsafe_url)},
 | |
|       {GetEnumString(ReferrerPolicy::Strict_origin).get(),
 | |
|        static_cast<int16_t>(ReferrerPolicy::Strict_origin)},
 | |
|       {GetEnumString(ReferrerPolicy::Same_origin).get(),
 | |
|        static_cast<int16_t>(ReferrerPolicy::Same_origin)},
 | |
|       {GetEnumString(ReferrerPolicy::Strict_origin_when_cross_origin).get(),
 | |
|        static_cast<int16_t>(ReferrerPolicy::Strict_origin_when_cross_origin)},
 | |
|       {nullptr, ReferrerPolicy::_empty}};
 | |
|   return aResult.ParseEnumValue(aString, kReferrerPolicyTable, false);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseFrameborderValue(const nsAString& aString,
 | |
|                                                  nsAttrValue& aResult) {
 | |
|   return aResult.ParseEnumValue(aString, kFrameborderTable, false);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::ParseScrollingValue(const nsAString& aString,
 | |
|                                                nsAttrValue& aResult) {
 | |
|   return aResult.ParseEnumValue(aString, kScrollingTable, false);
 | |
| }
 | |
| 
 | |
| static inline void MapLangAttributeInto(MappedDeclarationsBuilder& aBuilder) {
 | |
|   const nsAttrValue* langValue = aBuilder.GetAttr(nsGkAtoms::lang);
 | |
|   if (!langValue) {
 | |
|     return;
 | |
|   }
 | |
|   MOZ_ASSERT(langValue->Type() == nsAttrValue::eAtom);
 | |
|   aBuilder.SetIdentAtomValueIfUnset(eCSSProperty__x_lang,
 | |
|                                     langValue->GetAtomValue());
 | |
|   if (!aBuilder.PropertyIsSet(eCSSProperty_text_emphasis_position)) {
 | |
|     const nsAtom* lang = langValue->GetAtomValue();
 | |
|     if (nsStyleUtil::MatchesLanguagePrefix(lang, u"zh")) {
 | |
|       aBuilder.SetKeywordValue(eCSSProperty_text_emphasis_position,
 | |
|                                StyleTextEmphasisPosition::UNDER._0);
 | |
|     } else if (nsStyleUtil::MatchesLanguagePrefix(lang, u"ja") ||
 | |
|                nsStyleUtil::MatchesLanguagePrefix(lang, u"mn")) {
 | |
|       // This branch is currently no part of the spec.
 | |
|       // See bug 1040668 comment 69 and comment 75.
 | |
|       aBuilder.SetKeywordValue(eCSSProperty_text_emphasis_position,
 | |
|                                StyleTextEmphasisPosition::OVER._0);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Handle attributes common to all html elements
 | |
|  */
 | |
| void nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   if (!aBuilder.PropertyIsSet(eCSSProperty__moz_user_modify)) {
 | |
|     const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::contenteditable);
 | |
|     if (value) {
 | |
|       if (value->Equals(nsGkAtoms::_empty, eCaseMatters) ||
 | |
|           value->Equals(nsGkAtoms::_true, eIgnoreCase)) {
 | |
|         aBuilder.SetKeywordValue(eCSSProperty__moz_user_modify,
 | |
|                                  StyleUserModify::ReadWrite);
 | |
|       } else if (value->Equals(nsGkAtoms::_false, eIgnoreCase)) {
 | |
|         aBuilder.SetKeywordValue(eCSSProperty__moz_user_modify,
 | |
|                                  StyleUserModify::ReadOnly);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   MapLangAttributeInto(aBuilder);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapCommonAttributesInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   MapCommonAttributesIntoExceptHidden(aBuilder);
 | |
|   if (!aBuilder.PropertyIsSet(eCSSProperty_display)) {
 | |
|     if (aBuilder.GetAttr(nsGkAtoms::hidden)) {
 | |
|       aBuilder.SetKeywordValue(eCSSProperty_display, StyleDisplay::None._0);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| const nsGenericHTMLElement::MappedAttributeEntry
 | |
|     nsGenericHTMLElement::sCommonAttributeMap[] = {{nsGkAtoms::contenteditable},
 | |
|                                                    {nsGkAtoms::lang},
 | |
|                                                    {nsGkAtoms::hidden},
 | |
|                                                    {nullptr}};
 | |
| 
 | |
| /* static */
 | |
| const Element::MappedAttributeEntry
 | |
|     nsGenericHTMLElement::sImageMarginSizeAttributeMap[] = {{nsGkAtoms::width},
 | |
|                                                             {nsGkAtoms::height},
 | |
|                                                             {nsGkAtoms::hspace},
 | |
|                                                             {nsGkAtoms::vspace},
 | |
|                                                             {nullptr}};
 | |
| 
 | |
| /* static */
 | |
| const Element::MappedAttributeEntry
 | |
|     nsGenericHTMLElement::sImageAlignAttributeMap[] = {{nsGkAtoms::align},
 | |
|                                                        {nullptr}};
 | |
| 
 | |
| /* static */
 | |
| const Element::MappedAttributeEntry
 | |
|     nsGenericHTMLElement::sDivAlignAttributeMap[] = {{nsGkAtoms::align},
 | |
|                                                      {nullptr}};
 | |
| 
 | |
| /* static */
 | |
| const Element::MappedAttributeEntry
 | |
|     nsGenericHTMLElement::sImageBorderAttributeMap[] = {{nsGkAtoms::border},
 | |
|                                                         {nullptr}};
 | |
| 
 | |
| /* static */
 | |
| const Element::MappedAttributeEntry
 | |
|     nsGenericHTMLElement::sBackgroundAttributeMap[] = {
 | |
|         {nsGkAtoms::background}, {nsGkAtoms::bgcolor}, {nullptr}};
 | |
| 
 | |
| /* static */
 | |
| const Element::MappedAttributeEntry
 | |
|     nsGenericHTMLElement::sBackgroundColorAttributeMap[] = {
 | |
|         {nsGkAtoms::bgcolor}, {nullptr}};
 | |
| 
 | |
| void nsGenericHTMLElement::MapImageAlignAttributeInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align);
 | |
|   if (value && value->Type() == nsAttrValue::eEnum) {
 | |
|     int32_t align = value->GetEnumValue();
 | |
|     if (!aBuilder.PropertyIsSet(eCSSProperty_float)) {
 | |
|       if (align == uint8_t(StyleTextAlign::Left)) {
 | |
|         aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Left);
 | |
|       } else if (align == uint8_t(StyleTextAlign::Right)) {
 | |
|         aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Right);
 | |
|       }
 | |
|     }
 | |
|     if (!aBuilder.PropertyIsSet(eCSSProperty_vertical_align)) {
 | |
|       switch (align) {
 | |
|         case uint8_t(StyleTextAlign::Left):
 | |
|         case uint8_t(StyleTextAlign::Right):
 | |
|           break;
 | |
|         default:
 | |
|           aBuilder.SetKeywordValue(eCSSProperty_vertical_align, align);
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapDivAlignAttributeInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   if (!aBuilder.PropertyIsSet(eCSSProperty_text_align)) {
 | |
|     // align: enum
 | |
|     const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align);
 | |
|     if (value && value->Type() == nsAttrValue::eEnum)
 | |
|       aBuilder.SetKeywordValue(eCSSProperty_text_align, value->GetEnumValue());
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapVAlignAttributeInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   if (!aBuilder.PropertyIsSet(eCSSProperty_vertical_align)) {
 | |
|     // align: enum
 | |
|     const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::valign);
 | |
|     if (value && value->Type() == nsAttrValue::eEnum)
 | |
|       aBuilder.SetKeywordValue(eCSSProperty_vertical_align,
 | |
|                                value->GetEnumValue());
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapDimensionAttributeInto(
 | |
|     MappedDeclarationsBuilder& aBuilder, nsCSSPropertyID aProp,
 | |
|     const nsAttrValue& aValue) {
 | |
|   MOZ_ASSERT(!aBuilder.PropertyIsSet(aProp),
 | |
|              "Why mapping the same property twice?");
 | |
|   if (aValue.Type() == nsAttrValue::eInteger) {
 | |
|     return aBuilder.SetPixelValue(aProp, aValue.GetIntegerValue());
 | |
|   }
 | |
|   if (aValue.Type() == nsAttrValue::ePercent) {
 | |
|     return aBuilder.SetPercentValue(aProp, aValue.GetPercentValue());
 | |
|   }
 | |
|   if (aValue.Type() == nsAttrValue::eDoubleValue) {
 | |
|     return aBuilder.SetPixelValue(aProp, aValue.GetDoubleValue());
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapImageMarginAttributeInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   // hspace: value
 | |
|   if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::hspace)) {
 | |
|     MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_left, *value);
 | |
|     MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_right, *value);
 | |
|   }
 | |
| 
 | |
|   // vspace: value
 | |
|   if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::vspace)) {
 | |
|     MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_top, *value);
 | |
|     MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_bottom, *value);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapWidthAttributeInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::width)) {
 | |
|     MapDimensionAttributeInto(aBuilder, eCSSProperty_width, *value);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapHeightAttributeInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::height)) {
 | |
|     MapDimensionAttributeInto(aBuilder, eCSSProperty_height, *value);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::DoMapAspectRatio(
 | |
|     const nsAttrValue& aWidth, const nsAttrValue& aHeight,
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   Maybe<double> w;
 | |
|   if (aWidth.Type() == nsAttrValue::eInteger) {
 | |
|     w.emplace(aWidth.GetIntegerValue());
 | |
|   } else if (aWidth.Type() == nsAttrValue::eDoubleValue) {
 | |
|     w.emplace(aWidth.GetDoubleValue());
 | |
|   }
 | |
| 
 | |
|   Maybe<double> h;
 | |
|   if (aHeight.Type() == nsAttrValue::eInteger) {
 | |
|     h.emplace(aHeight.GetIntegerValue());
 | |
|   } else if (aHeight.Type() == nsAttrValue::eDoubleValue) {
 | |
|     h.emplace(aHeight.GetDoubleValue());
 | |
|   }
 | |
| 
 | |
|   if (w && h) {
 | |
|     aBuilder.SetAspectRatio(*w, *h);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapImageSizeAttributesInto(
 | |
|     MappedDeclarationsBuilder& aBuilder, MapAspectRatio aMapAspectRatio) {
 | |
|   auto* width = aBuilder.GetAttr(nsGkAtoms::width);
 | |
|   auto* height = aBuilder.GetAttr(nsGkAtoms::height);
 | |
|   if (width) {
 | |
|     MapDimensionAttributeInto(aBuilder, eCSSProperty_width, *width);
 | |
|   }
 | |
|   if (height) {
 | |
|     MapDimensionAttributeInto(aBuilder, eCSSProperty_height, *height);
 | |
|   }
 | |
|   if (aMapAspectRatio == MapAspectRatio::Yes && width && height) {
 | |
|     DoMapAspectRatio(*width, *height, aBuilder);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapAspectRatioInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   auto* width = aBuilder.GetAttr(nsGkAtoms::width);
 | |
|   auto* height = aBuilder.GetAttr(nsGkAtoms::height);
 | |
|   if (width && height) {
 | |
|     DoMapAspectRatio(*width, *height, aBuilder);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapImageBorderAttributeInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   // border: pixels
 | |
|   const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::border);
 | |
|   if (!value) return;
 | |
| 
 | |
|   nscoord val = 0;
 | |
|   if (value->Type() == nsAttrValue::eInteger) val = value->GetIntegerValue();
 | |
| 
 | |
|   aBuilder.SetPixelValueIfUnset(eCSSProperty_border_top_width, (float)val);
 | |
|   aBuilder.SetPixelValueIfUnset(eCSSProperty_border_right_width, (float)val);
 | |
|   aBuilder.SetPixelValueIfUnset(eCSSProperty_border_bottom_width, (float)val);
 | |
|   aBuilder.SetPixelValueIfUnset(eCSSProperty_border_left_width, (float)val);
 | |
| 
 | |
|   aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_top_style,
 | |
|                                   StyleBorderStyle::Solid);
 | |
|   aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_right_style,
 | |
|                                   StyleBorderStyle::Solid);
 | |
|   aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_bottom_style,
 | |
|                                   StyleBorderStyle::Solid);
 | |
|   aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_left_style,
 | |
|                                   StyleBorderStyle::Solid);
 | |
| 
 | |
|   aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_top_color);
 | |
|   aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_right_color);
 | |
|   aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_bottom_color);
 | |
|   aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_left_color);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapBackgroundInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   if (!aBuilder.PropertyIsSet(eCSSProperty_background_image)) {
 | |
|     // background
 | |
|     if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::background)) {
 | |
|       aBuilder.SetBackgroundImage(*value);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapBGColorInto(MappedDeclarationsBuilder& aBuilder) {
 | |
|   if (aBuilder.PropertyIsSet(eCSSProperty_background_color)) {
 | |
|     return;
 | |
|   }
 | |
|   const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::bgcolor);
 | |
|   nscolor color;
 | |
|   if (value && value->GetColorValue(color)) {
 | |
|     aBuilder.SetColorValue(eCSSProperty_background_color, color);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::MapBackgroundAttributesInto(
 | |
|     MappedDeclarationsBuilder& aBuilder) {
 | |
|   MapBackgroundInto(aBuilder);
 | |
|   MapBGColorInto(aBuilder);
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| 
 | |
| int32_t nsGenericHTMLElement::GetIntAttr(nsAtom* aAttr,
 | |
|                                          int32_t aDefault) const {
 | |
|   const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
 | |
|   if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
 | |
|     return attrVal->GetIntegerValue();
 | |
|   }
 | |
|   return aDefault;
 | |
| }
 | |
| 
 | |
| nsresult nsGenericHTMLElement::SetIntAttr(nsAtom* aAttr, int32_t aValue) {
 | |
|   nsAutoString value;
 | |
|   value.AppendInt(aValue);
 | |
| 
 | |
|   return SetAttr(kNameSpaceID_None, aAttr, value, true);
 | |
| }
 | |
| 
 | |
| uint32_t nsGenericHTMLElement::GetUnsignedIntAttr(nsAtom* aAttr,
 | |
|                                                   uint32_t aDefault) const {
 | |
|   const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
 | |
|   if (!attrVal || attrVal->Type() != nsAttrValue::eInteger) {
 | |
|     return aDefault;
 | |
|   }
 | |
| 
 | |
|   return attrVal->GetIntegerValue();
 | |
| }
 | |
| 
 | |
| uint32_t nsGenericHTMLElement::GetDimensionAttrAsUnsignedInt(
 | |
|     nsAtom* aAttr, uint32_t aDefault) const {
 | |
|   const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr);
 | |
|   if (!attrVal) {
 | |
|     return aDefault;
 | |
|   }
 | |
| 
 | |
|   if (attrVal->Type() == nsAttrValue::eInteger) {
 | |
|     return attrVal->GetIntegerValue();
 | |
|   }
 | |
| 
 | |
|   if (attrVal->Type() == nsAttrValue::ePercent) {
 | |
|     // This is a nasty hack.  When we parsed the value, we stored it as an
 | |
|     // ePercent, not eInteger, because there was a '%' after it in the string.
 | |
|     // But the spec says to basically re-parse the string as an integer.
 | |
|     // Luckily, we can just return the value we have stored.  But
 | |
|     // GetPercentValue() divides it by 100, so we need to multiply it back.
 | |
|     return uint32_t(attrVal->GetPercentValue() * 100.0f);
 | |
|   }
 | |
| 
 | |
|   if (attrVal->Type() == nsAttrValue::eDoubleValue) {
 | |
|     return uint32_t(attrVal->GetDoubleValue());
 | |
|   }
 | |
| 
 | |
|   // Unfortunately, the set of values that are valid dimensions is not a
 | |
|   // superset of values that are valid unsigned ints.  In particular "+100" is
 | |
|   // not a valid dimension, but should parse as the unsigned int "100".  So if
 | |
|   // we got here and we don't have a valid dimension value, just try re-parsing
 | |
|   // the string we have as an integer.
 | |
|   nsAutoString val;
 | |
|   attrVal->ToString(val);
 | |
|   nsContentUtils::ParseHTMLIntegerResultFlags result;
 | |
|   int32_t parsedInt = nsContentUtils::ParseHTMLInteger(val, &result);
 | |
|   if ((result & nsContentUtils::eParseHTMLInteger_Error) || parsedInt < 0) {
 | |
|     return aDefault;
 | |
|   }
 | |
| 
 | |
|   return parsedInt;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
 | |
|                                       nsAString& aResult) const {
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   bool hadAttr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri));
 | |
|   if (!hadAttr) {
 | |
|     aResult.Truncate();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!uri) {
 | |
|     // Just return the attr value
 | |
|     GetAttr(aAttr, aResult);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString spec;
 | |
|   uri->GetSpec(spec);
 | |
|   CopyUTF8toUTF16(spec, aResult);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr,
 | |
|                                       nsIURI** aURI) const {
 | |
|   *aURI = nullptr;
 | |
| 
 | |
|   const nsAttrValue* attr = mAttrs.GetAttr(aAttr);
 | |
|   if (!attr) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> baseURI = GetBaseURI();
 | |
| 
 | |
|   if (aBaseAttr) {
 | |
|     nsAutoString baseAttrValue;
 | |
|     if (GetAttr(aBaseAttr, baseAttrValue)) {
 | |
|       nsCOMPtr<nsIURI> baseAttrURI;
 | |
|       nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
 | |
|           getter_AddRefs(baseAttrURI), baseAttrValue, OwnerDoc(), baseURI);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return true;
 | |
|       }
 | |
|       baseURI.swap(baseAttrURI);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Don't care about return value.  If it fails, we still want to
 | |
|   // return true, and *aURI will be null.
 | |
|   nsContentUtils::NewURIWithDocumentCharset(aURI, attr->GetStringValue(),
 | |
|                                             OwnerDoc(), baseURI);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::IsLabelable() const {
 | |
|   return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool nsGenericHTMLElement::MatchLabelsElement(Element* aElement,
 | |
|                                               int32_t aNamespaceID,
 | |
|                                               nsAtom* aAtom, void* aData) {
 | |
|   HTMLLabelElement* element = HTMLLabelElement::FromNode(aElement);
 | |
|   return element && element->GetControl() == aData;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsINodeList> nsGenericHTMLElement::Labels() {
 | |
|   MOZ_ASSERT(IsLabelable(),
 | |
|              "Labels() only allow labelable elements to use it.");
 | |
|   nsExtendedDOMSlots* slots = ExtendedDOMSlots();
 | |
| 
 | |
|   if (!slots->mLabelsList) {
 | |
|     slots->mLabelsList =
 | |
|         new nsLabelsNodeList(SubtreeRoot(), MatchLabelsElement, nullptr, this);
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsLabelsNodeList> labels = slots->mLabelsList;
 | |
|   return labels.forget();
 | |
| }
 | |
| 
 | |
| // static
 | |
| bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext* aCx,
 | |
|                                                  JSObject* aGlobal) {
 | |
|   return TouchEvent::LegacyAPIEnabled(aCx, aGlobal);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::IsFormControlDefaultFocusable(
 | |
|     bool aWithMouse) const {
 | |
|   if (!aWithMouse) {
 | |
|     return true;
 | |
|   }
 | |
|   switch (StaticPrefs::accessibility_mouse_focuses_formcontrol()) {
 | |
|     case 0:
 | |
|       return false;
 | |
|     case 1:
 | |
|       return true;
 | |
|     default:
 | |
|       return !IsInChromeDocument();
 | |
|   }
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| 
 | |
| nsGenericHTMLFormElement::nsGenericHTMLFormElement(
 | |
|     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
 | |
|     : nsGenericHTMLElement(std::move(aNodeInfo)) {
 | |
|   // We should add the ElementState::ENABLED bit here as needed, but that
 | |
|   // depends on our type, which is not initialized yet.  So we have to do this
 | |
|   // in subclasses. Same for a couple other bits.
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm,
 | |
|                                          bool aUnbindOrDelete) {
 | |
|   MOZ_ASSERT(IsFormAssociatedElement());
 | |
| 
 | |
|   HTMLFormElement* form = GetFormInternal();
 | |
|   NS_ASSERTION((form != nullptr) == HasFlag(ADDED_TO_FORM),
 | |
|                "Form control should have had flag set correctly");
 | |
| 
 | |
|   if (!form) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aRemoveFromForm) {
 | |
|     nsAutoString nameVal, idVal;
 | |
|     GetAttr(nsGkAtoms::name, nameVal);
 | |
|     GetAttr(nsGkAtoms::id, idVal);
 | |
| 
 | |
|     form->RemoveElement(this, true);
 | |
| 
 | |
|     if (!nameVal.IsEmpty()) {
 | |
|       form->RemoveElementFromTable(this, nameVal);
 | |
|     }
 | |
| 
 | |
|     if (!idVal.IsEmpty()) {
 | |
|       form->RemoveElementFromTable(this, idVal);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   UnsetFlags(ADDED_TO_FORM);
 | |
|   SetFormInternal(nullptr, false);
 | |
|   AfterClearForm(aUnbindOrDelete);
 | |
| }
 | |
| 
 | |
| nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext,
 | |
|                                               nsINode& aParent) {
 | |
|   nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (IsFormAssociatedElement()) {
 | |
|     // If @form is set, the element *has* to be in a composed document,
 | |
|     // otherwise it wouldn't be possible to find an element with the
 | |
|     // corresponding id. If @form isn't set, the element *has* to have a parent,
 | |
|     // otherwise it wouldn't be possible to find a form ancestor. We should not
 | |
|     // call UpdateFormOwner if none of these conditions are fulfilled.
 | |
|     if (HasAttr(nsGkAtoms::form) ? IsInComposedDoc() : aParent.IsContent()) {
 | |
|       UpdateFormOwner(true, nullptr);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Set parent fieldset which should be used for the disabled state.
 | |
|   UpdateFieldSet(false);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::UnbindFromTree(UnbindContext& aContext) {
 | |
|   // Save state before doing anything else.
 | |
|   SaveState();
 | |
| 
 | |
|   if (IsFormAssociatedElement()) {
 | |
|     if (HTMLFormElement* form = GetFormInternal()) {
 | |
|       // Might need to unset form
 | |
|       if (aContext.IsUnbindRoot(this)) {
 | |
|         // No more parent means no more form
 | |
|         ClearForm(true, true);
 | |
|       } else {
 | |
|         // Recheck whether we should still have an form.
 | |
|         if (HasAttr(nsGkAtoms::form) || !FindAncestorForm(form)) {
 | |
|           ClearForm(true, true);
 | |
|         } else {
 | |
|           UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // We have to remove the form id observer if there was one.
 | |
|     // We will re-add one later if needed (during bind to tree).
 | |
|     if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
 | |
|                                         nsGkAtoms::form)) {
 | |
|       RemoveFormIdObserver();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsGenericHTMLElement::UnbindFromTree(aContext);
 | |
| 
 | |
|   // The element might not have a fieldset anymore.
 | |
|   UpdateFieldSet(false);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::BeforeSetAttr(int32_t aNameSpaceID,
 | |
|                                              nsAtom* aName,
 | |
|                                              const nsAttrValue* aValue,
 | |
|                                              bool aNotify) {
 | |
|   if (aNameSpaceID == kNameSpaceID_None && IsFormAssociatedElement()) {
 | |
|     nsAutoString tmp;
 | |
|     HTMLFormElement* form = GetFormInternal();
 | |
| 
 | |
|     // remove the control from the hashtable as needed
 | |
| 
 | |
|     if (form && (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
 | |
|       GetAttr(aName, tmp);
 | |
| 
 | |
|       if (!tmp.IsEmpty()) {
 | |
|         form->RemoveElementFromTable(this, tmp);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (form && aName == nsGkAtoms::type) {
 | |
|       GetAttr(nsGkAtoms::name, tmp);
 | |
| 
 | |
|       if (!tmp.IsEmpty()) {
 | |
|         form->RemoveElementFromTable(this, tmp);
 | |
|       }
 | |
| 
 | |
|       GetAttr(nsGkAtoms::id, tmp);
 | |
| 
 | |
|       if (!tmp.IsEmpty()) {
 | |
|         form->RemoveElementFromTable(this, tmp);
 | |
|       }
 | |
| 
 | |
|       form->RemoveElement(this, false);
 | |
|     }
 | |
| 
 | |
|     if (aName == nsGkAtoms::form) {
 | |
|       // If @form isn't set or set to the empty string, there were no observer
 | |
|       // so we don't have to remove it.
 | |
|       if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
 | |
|                                           nsGkAtoms::form)) {
 | |
|         // The current form id observer is no longer needed.
 | |
|         // A new one may be added in AfterSetAttr.
 | |
|         RemoveFormIdObserver();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
 | |
|                                              aNotify);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::AfterSetAttr(
 | |
|     int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue,
 | |
|     const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal,
 | |
|     bool aNotify) {
 | |
|   if (aNameSpaceID == kNameSpaceID_None && IsFormAssociatedElement()) {
 | |
|     HTMLFormElement* form = GetFormInternal();
 | |
| 
 | |
|     // add the control to the hashtable as needed
 | |
|     if (form && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
 | |
|         aValue && !aValue->IsEmptyString()) {
 | |
|       MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
 | |
|                  "Expected atom value for name/id");
 | |
|       form->AddElementToTable(this,
 | |
|                               nsDependentAtomString(aValue->GetAtomValue()));
 | |
|     }
 | |
| 
 | |
|     if (form && aName == nsGkAtoms::type) {
 | |
|       nsAutoString tmp;
 | |
| 
 | |
|       GetAttr(nsGkAtoms::name, tmp);
 | |
| 
 | |
|       if (!tmp.IsEmpty()) {
 | |
|         form->AddElementToTable(this, tmp);
 | |
|       }
 | |
| 
 | |
|       GetAttr(nsGkAtoms::id, tmp);
 | |
| 
 | |
|       if (!tmp.IsEmpty()) {
 | |
|         form->AddElementToTable(this, tmp);
 | |
|       }
 | |
| 
 | |
|       form->AddElement(this, false, aNotify);
 | |
|     }
 | |
| 
 | |
|     if (aName == nsGkAtoms::form) {
 | |
|       // We need a new form id observer.
 | |
|       DocumentOrShadowRoot* docOrShadow =
 | |
|           GetUncomposedDocOrConnectedShadowRoot();
 | |
|       if (docOrShadow) {
 | |
|         Element* formIdElement = nullptr;
 | |
|         if (aValue && !aValue->IsEmptyString()) {
 | |
|           formIdElement = AddFormIdObserver();
 | |
|         }
 | |
| 
 | |
|         // Because we have a new @form value (or no more @form), we have to
 | |
|         // update our form owner.
 | |
|         UpdateFormOwner(false, formIdElement);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nsGenericHTMLElement::AfterSetAttr(
 | |
|       aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::ForgetFieldSet(nsIContent* aFieldset) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(IsFormAssociatedElement());
 | |
|   if (GetFieldSetInternal() == aFieldset) {
 | |
|     SetFieldSetInternal(nullptr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| Element* nsGenericHTMLFormElement::AddFormIdObserver() {
 | |
|   MOZ_ASSERT(IsFormAssociatedElement());
 | |
| 
 | |
|   nsAutoString formId;
 | |
|   DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
 | |
|   GetAttr(nsGkAtoms::form, formId);
 | |
|   NS_ASSERTION(!formId.IsEmpty(),
 | |
|                "@form value should not be the empty string!");
 | |
|   RefPtr<nsAtom> atom = NS_Atomize(formId);
 | |
| 
 | |
|   return docOrShadow->AddIDTargetObserver(atom, FormIdUpdated, this, false);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::RemoveFormIdObserver() {
 | |
|   MOZ_ASSERT(IsFormAssociatedElement());
 | |
| 
 | |
|   DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
 | |
|   if (!docOrShadow) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoString formId;
 | |
|   GetAttr(nsGkAtoms::form, formId);
 | |
|   NS_ASSERTION(!formId.IsEmpty(),
 | |
|                "@form value should not be the empty string!");
 | |
|   RefPtr<nsAtom> atom = NS_Atomize(formId);
 | |
| 
 | |
|   docOrShadow->RemoveIDTargetObserver(atom, FormIdUpdated, this, false);
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool nsGenericHTMLFormElement::FormIdUpdated(Element* aOldElement,
 | |
|                                              Element* aNewElement,
 | |
|                                              void* aData) {
 | |
|   nsGenericHTMLFormElement* element =
 | |
|       static_cast<nsGenericHTMLFormElement*>(aData);
 | |
| 
 | |
|   NS_ASSERTION(element->IsHTMLElement(), "aData should be an HTML element");
 | |
| 
 | |
|   element->UpdateFormOwner(false, aNewElement);
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLFormElement::IsElementDisabledForEvents(WidgetEvent* aEvent,
 | |
|                                                           nsIFrame* aFrame) {
 | |
|   MOZ_ASSERT(aEvent);
 | |
| 
 | |
|   // Allow dispatch of CustomEvent and untrusted Events.
 | |
|   if (!aEvent->IsTrusted()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   switch (aEvent->mMessage) {
 | |
|     case eAnimationStart:
 | |
|     case eAnimationEnd:
 | |
|     case eAnimationIteration:
 | |
|     case eAnimationCancel:
 | |
|     case eFormChange:
 | |
|     case eMouseMove:
 | |
|     case eMouseOver:
 | |
|     case eMouseOut:
 | |
|     case eMouseEnter:
 | |
|     case eMouseLeave:
 | |
|     case ePointerMove:
 | |
|     case ePointerOver:
 | |
|     case ePointerOut:
 | |
|     case ePointerEnter:
 | |
|     case ePointerLeave:
 | |
|     case eTransitionCancel:
 | |
|     case eTransitionEnd:
 | |
|     case eTransitionRun:
 | |
|     case eTransitionStart:
 | |
|     case eWheel:
 | |
|     case eLegacyMouseLineOrPageScroll:
 | |
|     case eLegacyMousePixelScroll:
 | |
|       return false;
 | |
|     case eFocus:
 | |
|     case eBlur:
 | |
|     case eFocusIn:
 | |
|     case eFocusOut:
 | |
|     case eKeyPress:
 | |
|     case eKeyUp:
 | |
|     case eKeyDown:
 | |
|       if (StaticPrefs::dom_forms_always_allow_key_and_focus_events_enabled()) {
 | |
|         return false;
 | |
|       }
 | |
|       [[fallthrough]];
 | |
|     case ePointerDown:
 | |
|     case ePointerUp:
 | |
|     case ePointerCancel:
 | |
|     case ePointerGotCapture:
 | |
|     case ePointerLostCapture:
 | |
|       if (StaticPrefs::dom_forms_always_allow_pointer_events_enabled()) {
 | |
|         return false;
 | |
|       }
 | |
|       [[fallthrough]];
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   if (aEvent->mSpecifiedEventType == nsGkAtoms::oninput) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // FIXME(emilio): This poking at the style of the frame is slightly bogus
 | |
|   // unless we flush before every event, which we don't really want to do.
 | |
|   if (aFrame && aFrame->StyleUI()->UserInput() == StyleUserInput::None) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return IsDisabled();
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree,
 | |
|                                                Element* aFormIdElement) {
 | |
|   MOZ_ASSERT(IsFormAssociatedElement());
 | |
|   MOZ_ASSERT(!aBindToTree || !aFormIdElement,
 | |
|              "aFormIdElement shouldn't be set if aBindToTree is true!");
 | |
| 
 | |
|   HTMLFormElement* form = GetFormInternal();
 | |
|   if (!aBindToTree) {
 | |
|     ClearForm(true, false);
 | |
|     form = nullptr;
 | |
|   }
 | |
| 
 | |
|   HTMLFormElement* oldForm = form;
 | |
|   if (!form) {
 | |
|     // If @form is set, we have to use that to find the form.
 | |
|     nsAutoString formId;
 | |
|     if (GetAttr(nsGkAtoms::form, formId)) {
 | |
|       if (!formId.IsEmpty()) {
 | |
|         Element* element = nullptr;
 | |
| 
 | |
|         if (aBindToTree) {
 | |
|           element = AddFormIdObserver();
 | |
|         } else {
 | |
|           element = aFormIdElement;
 | |
|         }
 | |
| 
 | |
|         NS_ASSERTION(!IsInComposedDoc() ||
 | |
|                          element == GetUncomposedDocOrConnectedShadowRoot()
 | |
|                                         ->GetElementById(formId),
 | |
|                      "element should be equals to the current element "
 | |
|                      "associated with the id in @form!");
 | |
| 
 | |
|         if (element && element->IsHTMLElement(nsGkAtoms::form) &&
 | |
|             nsContentUtils::IsInSameAnonymousTree(this, element)) {
 | |
|           form = static_cast<HTMLFormElement*>(element);
 | |
|           SetFormInternal(form, aBindToTree);
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       // We now have a parent, so we may have picked up an ancestor form. Search
 | |
|       // for it.  Note that if form is already set we don't want to do this,
 | |
|       // because that means someone (probably the content sink) has already set
 | |
|       // it to the right value.  Also note that even if being bound here didn't
 | |
|       // change our parent, we still need to search, since our parent chain
 | |
|       // probably changed _somewhere_.
 | |
|       form = FindAncestorForm();
 | |
|       SetFormInternal(form, aBindToTree);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (form && !HasFlag(ADDED_TO_FORM)) {
 | |
|     // Now we need to add ourselves to the form
 | |
|     nsAutoString nameVal, idVal;
 | |
|     GetAttr(nsGkAtoms::name, nameVal);
 | |
|     GetAttr(nsGkAtoms::id, idVal);
 | |
| 
 | |
|     SetFlags(ADDED_TO_FORM);
 | |
| 
 | |
|     // Notify only if we just found this form.
 | |
|     form->AddElement(this, true, oldForm == nullptr);
 | |
| 
 | |
|     if (!nameVal.IsEmpty()) {
 | |
|       form->AddElementToTable(this, nameVal);
 | |
|     }
 | |
| 
 | |
|     if (!idVal.IsEmpty()) {
 | |
|       form->AddElementToTable(this, idVal);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::UpdateFieldSet(bool aNotify) {
 | |
|   if (IsInNativeAnonymousSubtree() || !IsFormAssociatedElement()) {
 | |
|     MOZ_ASSERT_IF(IsFormAssociatedElement(), !GetFieldSetInternal());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsIContent* parent = nullptr;
 | |
|   nsIContent* prev = nullptr;
 | |
|   HTMLFieldSetElement* fieldset = GetFieldSetInternal();
 | |
| 
 | |
|   for (parent = GetParent(); parent;
 | |
|        prev = parent, parent = parent->GetParent()) {
 | |
|     HTMLFieldSetElement* parentFieldset = HTMLFieldSetElement::FromNode(parent);
 | |
|     if (parentFieldset && (!prev || parentFieldset->GetFirstLegend() != prev)) {
 | |
|       if (fieldset == parentFieldset) {
 | |
|         // We already have the right fieldset;
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       if (fieldset) {
 | |
|         fieldset->RemoveElement(this);
 | |
|       }
 | |
|       SetFieldSetInternal(parentFieldset);
 | |
|       parentFieldset->AddElement(this);
 | |
| 
 | |
|       // The disabled state may have changed
 | |
|       FieldSetDisabledChanged(aNotify);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // No fieldset found.
 | |
|   if (fieldset) {
 | |
|     fieldset->RemoveElement(this);
 | |
|     SetFieldSetInternal(nullptr);
 | |
|     // The disabled state may have changed
 | |
|     FieldSetDisabledChanged(aNotify);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::UpdateDisabledState(bool aNotify) {
 | |
|   if (!CanBeDisabled()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   HTMLFieldSetElement* fieldset = GetFieldSetInternal();
 | |
|   const bool isDisabled =
 | |
|       HasAttr(nsGkAtoms::disabled) || (fieldset && fieldset->IsDisabled());
 | |
| 
 | |
|   const ElementState disabledStates =
 | |
|       isDisabled ? ElementState::DISABLED : ElementState::ENABLED;
 | |
| 
 | |
|   ElementState oldDisabledStates = State() & ElementState::DISABLED_STATES;
 | |
|   ElementState changedStates = disabledStates ^ oldDisabledStates;
 | |
| 
 | |
|   if (!changedStates.IsEmpty()) {
 | |
|     ToggleStates(changedStates, aNotify);
 | |
|     if (DoesReadOnlyApply()) {
 | |
|       // :disabled influences :read-only / :read-write.
 | |
|       UpdateReadOnlyState(aNotify);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLFormElement::IsReadOnlyInternal() const {
 | |
|   if (DoesReadOnlyApply()) {
 | |
|     return IsDisabled() || GetBoolAttr(nsGkAtoms::readonly);
 | |
|   }
 | |
|   return nsGenericHTMLElement::IsReadOnlyInternal();
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::FieldSetDisabledChanged(bool aNotify) {
 | |
|   UpdateDisabledState(aNotify);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormElement::SaveSubtreeState() {
 | |
|   SaveState();
 | |
| 
 | |
|   nsGenericHTMLElement::SaveSubtreeState();
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| 
 | |
| void nsGenericHTMLElement::Click(CallerType aCallerType) {
 | |
|   if (HandlingClick()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // There are two notions of disabled.
 | |
|   // "disabled":
 | |
|   // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled
 | |
|   // "actually disabled":
 | |
|   // https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled
 | |
|   // click() reads the former but IsDisabled() is for the latter. <fieldset> is
 | |
|   // included only in the latter, so we exclude it here.
 | |
|   // XXX(krosylight): What about <optgroup>? And should we add a separate method
 | |
|   // for this?
 | |
|   if (IsDisabled() &&
 | |
|       !(mNodeInfo->Equals(nsGkAtoms::fieldset) &&
 | |
|         StaticPrefs::dom_forms_fieldset_disable_only_descendants_enabled())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Strong in case the event kills it
 | |
|   nsCOMPtr<Document> doc = GetComposedDoc();
 | |
| 
 | |
|   RefPtr<nsPresContext> context;
 | |
|   if (doc) {
 | |
|     PresShell* presShell = doc->GetPresShell();
 | |
|     if (!presShell) {
 | |
|       // We need the nsPresContext for dispatching the click event. In some
 | |
|       // rare cases we need to flush notifications to force creation of the
 | |
|       // nsPresContext here (for example when a script calls button.click()
 | |
|       // from script early during page load). We only flush the notifications
 | |
|       // if the PresShell hasn't been created yet, to limit the performance
 | |
|       // impact.
 | |
|       doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
 | |
|       presShell = doc->GetPresShell();
 | |
|     }
 | |
|     if (presShell) {
 | |
|       context = presShell->GetPresContext();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   SetHandlingClick();
 | |
| 
 | |
|   // Mark this event trusted if Click() is called from system code.
 | |
|   WidgetMouseEvent event(aCallerType == CallerType::System, eMouseClick,
 | |
|                          nullptr, WidgetMouseEvent::eReal);
 | |
|   event.mFlags.mIsPositionless = true;
 | |
|   event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
 | |
| 
 | |
|   EventDispatcher::Dispatch(this, context, &event);
 | |
| 
 | |
|   ClearHandlingClick();
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
 | |
|                                            int32_t* aTabIndex) {
 | |
|   MOZ_ASSERT(aIsFocusable);
 | |
|   MOZ_ASSERT(aTabIndex);
 | |
|   if (ShadowRoot* root = GetShadowRoot()) {
 | |
|     if (root->DelegatesFocus()) {
 | |
|       *aIsFocusable = false;
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!IsInComposedDoc() || IsInDesignMode()) {
 | |
|     // In designMode documents we only allow focusing the document.
 | |
|     *aTabIndex = -1;
 | |
|     *aIsFocusable = false;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   *aTabIndex = TabIndex();
 | |
|   bool disabled = false;
 | |
|   bool disallowOverridingFocusability = true;
 | |
|   Maybe<int32_t> attrVal = GetTabIndexAttrValue();
 | |
|   if (IsEditingHost()) {
 | |
|     // Editable roots should always be focusable.
 | |
|     disallowOverridingFocusability = true;
 | |
| 
 | |
|     // Ignore the disabled attribute in editable contentEditable/designMode
 | |
|     // roots.
 | |
|     if (attrVal.isNothing()) {
 | |
|       // The default value for tabindex should be 0 for editable
 | |
|       // contentEditable roots.
 | |
|       *aTabIndex = 0;
 | |
|     }
 | |
|   } else {
 | |
|     disallowOverridingFocusability = false;
 | |
| 
 | |
|     // Just check for disabled attribute on form controls
 | |
|     disabled = IsDisabled();
 | |
|     if (disabled) {
 | |
|       *aTabIndex = -1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If a tabindex is specified at all, or the default tabindex is 0, we're
 | |
|   // focusable.
 | |
|   *aIsFocusable = (*aTabIndex >= 0 || (!disabled && attrVal.isSome()));
 | |
|   return disallowOverridingFocusability;
 | |
| }
 | |
| 
 | |
| Result<bool, nsresult> nsGenericHTMLElement::PerformAccesskey(
 | |
|     bool aKeyCausesActivation, bool aIsTrustedEvent) {
 | |
|   RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc);
 | |
|   if (!presContext) {
 | |
|     return Err(NS_ERROR_UNEXPECTED);
 | |
|   }
 | |
| 
 | |
|   // It's hard to say what HTML4 wants us to do in all cases.
 | |
|   bool focused = true;
 | |
|   if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
 | |
|     fm->SetFocus(this, nsIFocusManager::FLAG_BYKEY);
 | |
| 
 | |
|     // Return true if the element became the current focus within its window.
 | |
|     nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
 | |
|     focused = window && window->GetFocusedElement() == this;
 | |
|   }
 | |
| 
 | |
|   if (aKeyCausesActivation) {
 | |
|     // Click on it if the users prefs indicate to do so.
 | |
|     AutoHandlingUserInputStatePusher userInputStatePusher(aIsTrustedEvent);
 | |
|     AutoPopupStatePusher popupStatePusher(
 | |
|         aIsTrustedEvent ? PopupBlocker::openAllowed : PopupBlocker::openAbused);
 | |
|     DispatchSimulatedClick(this, aIsTrustedEvent, presContext);
 | |
|     return focused;
 | |
|   }
 | |
| 
 | |
|   // If the accesskey won't cause the activation and the focus isn't changed,
 | |
|   // either. Return error so EventStateManager would try to find next element
 | |
|   // to handle the accesskey.
 | |
|   return focused ? Result<bool, nsresult>{focused} : Err(NS_ERROR_ABORT);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::HandleKeyboardActivation(
 | |
|     EventChainPostVisitor& aVisitor) {
 | |
|   MOZ_ASSERT(aVisitor.mEvent->HasKeyEventMessage());
 | |
|   MOZ_ASSERT(aVisitor.mEvent->IsTrusted());
 | |
| 
 | |
|   // If focused element is different from this element, it may be editable.
 | |
|   // In that case, associated editor for the element should handle the keyboard
 | |
|   // instead.  Therefore, if this is not the focused element, we should not
 | |
|   // handle the event here.  Note that this element may be an editing host,
 | |
|   // i.e., focused and editable.  In the case, keyboard events should be
 | |
|   // handled by the focused element instead of associated editor because
 | |
|   // Chrome handles the case so.  For compatibility with Chrome, we follow them.
 | |
|   if (nsFocusManager::GetFocusedElementStatic() != this) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const auto message = aVisitor.mEvent->mMessage;
 | |
|   const WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
 | |
|   if (nsEventStatus_eIgnore != aVisitor.mEventStatus) {
 | |
|     if (message == eKeyUp && keyEvent->mKeyCode == NS_VK_SPACE) {
 | |
|       // Unset the flag even if the event is default-prevented or something.
 | |
|       UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool shouldActivate = false;
 | |
|   switch (message) {
 | |
|     case eKeyDown:
 | |
|       if (keyEvent->ShouldWorkAsSpaceKey()) {
 | |
|         SetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
 | |
|       }
 | |
|       return;
 | |
|     case eKeyPress:
 | |
|       shouldActivate = keyEvent->mKeyCode == NS_VK_RETURN;
 | |
|       if (keyEvent->ShouldWorkAsSpaceKey()) {
 | |
|         // Consume 'space' key to prevent scrolling the page down.
 | |
|         aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
 | |
|       }
 | |
|       break;
 | |
|     case eKeyUp:
 | |
|       shouldActivate = keyEvent->ShouldWorkAsSpaceKey() &&
 | |
|                        HasFlag(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
 | |
|       if (shouldActivate) {
 | |
|         UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
 | |
|       }
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE("why didn't we bail out earlier?");
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   if (!shouldActivate) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsPresContext> presContext = aVisitor.mPresContext;
 | |
|   DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(), presContext);
 | |
|   aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
 | |
| }
 | |
| 
 | |
| nsresult nsGenericHTMLElement::DispatchSimulatedClick(
 | |
|     nsGenericHTMLElement* aElement, bool aIsTrusted,
 | |
|     nsPresContext* aPresContext) {
 | |
|   WidgetMouseEvent event(aIsTrusted, eMouseClick, nullptr,
 | |
|                          WidgetMouseEvent::eReal);
 | |
|   event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_KEYBOARD;
 | |
|   event.mFlags.mIsPositionless = true;
 | |
|   return EventDispatcher::Dispatch(aElement, aPresContext, &event);
 | |
| }
 | |
| 
 | |
| already_AddRefed<EditorBase> nsGenericHTMLElement::GetAssociatedEditor() {
 | |
|   // If contenteditable is ever implemented, it might need to do something
 | |
|   // different here?
 | |
| 
 | |
|   RefPtr<TextEditor> textEditor = GetTextEditorInternal();
 | |
|   return textEditor.forget();
 | |
| }
 | |
| 
 | |
| // static
 | |
| void nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent* content) {
 | |
|   /* Sync this node */
 | |
|   nsGenericHTMLElement* element = FromNode(content);
 | |
|   if (element) {
 | |
|     if (RefPtr<EditorBase> editorBase = element->GetAssociatedEditor()) {
 | |
|       editorBase->SyncRealTimeSpell();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Sync all children */
 | |
|   for (nsIContent* child = content->GetFirstChild(); child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     SyncEditorsOnSubtree(child);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void MakeContentDescendantsEditable(nsIContent* aContent) {
 | |
|   // If aContent is not an element, we just need to update its
 | |
|   // internal editable state and don't need to notify anyone about
 | |
|   // that.  For elements, we need to send a ElementStateChanged
 | |
|   // notification.
 | |
|   if (!aContent->IsElement()) {
 | |
|     aContent->UpdateEditableState(false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Element* element = aContent->AsElement();
 | |
| 
 | |
|   element->UpdateEditableState(true);
 | |
| 
 | |
|   for (nsIContent* child = aContent->GetFirstChild(); child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     if (!child->IsElement() ||
 | |
|         !child->AsElement()->HasAttr(nsGkAtoms::contenteditable)) {
 | |
|       MakeContentDescendantsEditable(child);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) {
 | |
|   Document* document = GetComposedDoc();
 | |
|   if (!document) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Document::EditingState previousEditingState = Document::EditingState::eOff;
 | |
|   if (aChange != 0) {
 | |
|     document->ChangeContentEditableCount(this, aChange);
 | |
|     previousEditingState = document->GetEditingState();
 | |
|   }
 | |
| 
 | |
|   // MakeContentDescendantsEditable is going to call ElementStateChanged for
 | |
|   // this element and all descendants if editable state has changed.
 | |
|   // We might as well wrap it all in one script blocker.
 | |
|   nsAutoScriptBlocker scriptBlocker;
 | |
|   MakeContentDescendantsEditable(this);
 | |
| 
 | |
|   // If the document already had contenteditable and JS adds new
 | |
|   // contenteditable, that might cause changing editing host to current editing
 | |
|   // host's ancestor.  In such case, HTMLEditor needs to know that
 | |
|   // synchronously to update selection limitter.
 | |
|   // Additionally, elements in shadow DOM is not editable in the normal cases,
 | |
|   // but if its content has `contenteditable`, only in it can be ediable.
 | |
|   // So we don't need to notify HTMLEditor of this change only when we're not
 | |
|   // in shadow DOM and the composed document is in design mode.
 | |
|   if (IsInDesignMode() && !IsInShadowTree() && aChange > 0 &&
 | |
|       previousEditingState == Document::EditingState::eContentEditable) {
 | |
|     if (HTMLEditor* htmlEditor =
 | |
|             nsContentUtils::GetHTMLEditor(document->GetPresContext())) {
 | |
|       htmlEditor->NotifyEditingHostMaybeChanged();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| 
 | |
| nsGenericHTMLFormControlElement::nsGenericHTMLFormControlElement(
 | |
|     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, FormControlType aType)
 | |
|     : nsGenericHTMLFormElement(std::move(aNodeInfo)),
 | |
|       nsIFormControl(aType),
 | |
|       mForm(nullptr),
 | |
|       mFieldSet(nullptr) {}
 | |
| 
 | |
| nsGenericHTMLFormControlElement::~nsGenericHTMLFormControlElement() {
 | |
|   if (mFieldSet) {
 | |
|     mFieldSet->RemoveElement(this);
 | |
|   }
 | |
| 
 | |
|   // Check that this element doesn't know anything about its form at this point.
 | |
|   NS_ASSERTION(!mForm, "mForm should be null at this point!");
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS_INHERITED(nsGenericHTMLFormControlElement,
 | |
|                             nsGenericHTMLFormElement, nsIFormControl)
 | |
| 
 | |
| nsINode* nsGenericHTMLFormControlElement::GetScopeChainParent() const {
 | |
|   return mForm ? mForm : nsGenericHTMLElement::GetScopeChainParent();
 | |
| }
 | |
| 
 | |
| nsIContent::IMEState nsGenericHTMLFormControlElement::GetDesiredIMEState() {
 | |
|   TextEditor* textEditor = GetTextEditorInternal();
 | |
|   if (!textEditor) {
 | |
|     return nsGenericHTMLFormElement::GetDesiredIMEState();
 | |
|   }
 | |
|   IMEState state;
 | |
|   nsresult rv = textEditor->GetPreferredIMEState(&state);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return nsGenericHTMLFormElement::GetDesiredIMEState();
 | |
|   }
 | |
|   return state;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElement::GetAutocapitalize(
 | |
|     nsAString& aValue) const {
 | |
|   if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
 | |
|                                       nsGkAtoms::autocapitalize)) {
 | |
|     nsGenericHTMLFormElement::GetAutocapitalize(aValue);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mForm && IsAutocapitalizeInheriting()) {
 | |
|     mForm->GetAutocapitalize(aValue);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLFormControlElement::IsHTMLFocusable(bool aWithMouse,
 | |
|                                                       bool* aIsFocusable,
 | |
|                                                       int32_t* aTabIndex) {
 | |
|   if (nsGenericHTMLFormElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
 | |
|                                                 aTabIndex)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   *aIsFocusable = *aIsFocusable && IsFormControlDefaultFocusable(aWithMouse);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElement::GetEventTargetParent(
 | |
|     EventChainPreVisitor& aVisitor) {
 | |
|   if (aVisitor.mEvent->IsTrusted() && (aVisitor.mEvent->mMessage == eFocus ||
 | |
|                                        aVisitor.mEvent->mMessage == eBlur)) {
 | |
|     // We have to handle focus/blur event to change focus states in
 | |
|     // PreHandleEvent to prevent it breaks event target chain creation.
 | |
|     aVisitor.mWantsPreHandleEvent = true;
 | |
|   }
 | |
|   nsGenericHTMLFormElement::GetEventTargetParent(aVisitor);
 | |
| }
 | |
| 
 | |
| nsresult nsGenericHTMLFormControlElement::PreHandleEvent(
 | |
|     EventChainVisitor& aVisitor) {
 | |
|   if (aVisitor.mEvent->IsTrusted()) {
 | |
|     switch (aVisitor.mEvent->mMessage) {
 | |
|       case eFocus: {
 | |
|         // Check to see if focus has bubbled up from a form control's
 | |
|         // child textfield or button.  If that's the case, don't focus
 | |
|         // this parent file control -- leave focus on the child.
 | |
|         nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
 | |
|         if (formControlFrame &&
 | |
|             aVisitor.mEvent->mOriginalTarget == static_cast<nsINode*>(this)) {
 | |
|           formControlFrame->SetFocus(true, true);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       case eBlur: {
 | |
|         nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
 | |
|         if (formControlFrame) {
 | |
|           formControlFrame->SetFocus(false, false);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       default:
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
|   return nsGenericHTMLFormElement::PreHandleEvent(aVisitor);
 | |
| }
 | |
| 
 | |
| HTMLFieldSetElement* nsGenericHTMLFormControlElement::GetFieldSet() {
 | |
|   return GetFieldSetInternal();
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElement::SetForm(HTMLFormElement* aForm) {
 | |
|   MOZ_ASSERT(aForm, "Don't pass null here");
 | |
|   NS_ASSERTION(!mForm,
 | |
|                "We don't support switching from one non-null form to another.");
 | |
| 
 | |
|   SetFormInternal(aForm, false);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElement::ClearForm(bool aRemoveFromForm,
 | |
|                                                 bool aUnbindOrDelete) {
 | |
|   nsGenericHTMLFormElement::ClearForm(aRemoveFromForm, aUnbindOrDelete);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLFormControlElement::IsLabelable() const {
 | |
|   auto type = ControlType();
 | |
|   return (IsInputElement(type) && type != FormControlType::InputHidden) ||
 | |
|          IsButtonElement(type) || type == FormControlType::Output ||
 | |
|          type == FormControlType::Select || type == FormControlType::Textarea;
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLFormControlElement::CanBeDisabled() const {
 | |
|   auto type = ControlType();
 | |
|   // It's easier to test the types that _cannot_ be disabled
 | |
|   return type != FormControlType::Object && type != FormControlType::Output;
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLFormControlElement::DoesReadOnlyApply() const {
 | |
|   auto type = ControlType();
 | |
|   if (!IsInputElement(type) && type != FormControlType::Textarea) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   switch (type) {
 | |
|     case FormControlType::InputHidden:
 | |
|     case FormControlType::InputButton:
 | |
|     case FormControlType::InputImage:
 | |
|     case FormControlType::InputReset:
 | |
|     case FormControlType::InputSubmit:
 | |
|     case FormControlType::InputRadio:
 | |
|     case FormControlType::InputFile:
 | |
|     case FormControlType::InputCheckbox:
 | |
|     case FormControlType::InputRange:
 | |
|     case FormControlType::InputColor:
 | |
|       return false;
 | |
| #ifdef DEBUG
 | |
|     case FormControlType::Textarea:
 | |
|     case FormControlType::InputText:
 | |
|     case FormControlType::InputPassword:
 | |
|     case FormControlType::InputSearch:
 | |
|     case FormControlType::InputTel:
 | |
|     case FormControlType::InputEmail:
 | |
|     case FormControlType::InputUrl:
 | |
|     case FormControlType::InputNumber:
 | |
|     case FormControlType::InputDate:
 | |
|     case FormControlType::InputTime:
 | |
|     case FormControlType::InputMonth:
 | |
|     case FormControlType::InputWeek:
 | |
|     case FormControlType::InputDatetimeLocal:
 | |
|       return true;
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesReadOnlyApply()");
 | |
|       return true;
 | |
| #else   // DEBUG
 | |
|     default:
 | |
|       return true;
 | |
| #endif  // DEBUG
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElement::SetFormInternal(HTMLFormElement* aForm,
 | |
|                                                       bool aBindToTree) {
 | |
|   if (aForm) {
 | |
|     BeforeSetForm(aForm, aBindToTree);
 | |
|   }
 | |
| 
 | |
|   // keep a *weak* ref to the form here
 | |
|   mForm = aForm;
 | |
| }
 | |
| 
 | |
| HTMLFormElement* nsGenericHTMLFormControlElement::GetFormInternal() const {
 | |
|   return mForm;
 | |
| }
 | |
| 
 | |
| HTMLFieldSetElement* nsGenericHTMLFormControlElement::GetFieldSetInternal()
 | |
|     const {
 | |
|   return mFieldSet;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElement::SetFieldSetInternal(
 | |
|     HTMLFieldSetElement* aFieldset) {
 | |
|   mFieldSet = aFieldset;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElement::UpdateRequiredState(bool aIsRequired,
 | |
|                                                           bool aNotify) {
 | |
| #ifdef DEBUG
 | |
|   auto type = ControlType();
 | |
| #endif
 | |
|   MOZ_ASSERT(IsInputElement(type) || type == FormControlType::Select ||
 | |
|                  type == FormControlType::Textarea,
 | |
|              "This should be called only on types that @required applies");
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   if (HTMLInputElement* input = HTMLInputElement::FromNode(this)) {
 | |
|     MOZ_ASSERT(
 | |
|         input->DoesRequiredApply(),
 | |
|         "This should be called only on input types that @required applies");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   ElementState requiredStates;
 | |
|   if (aIsRequired) {
 | |
|     requiredStates |= ElementState::REQUIRED;
 | |
|   } else {
 | |
|     requiredStates |= ElementState::OPTIONAL_;
 | |
|   }
 | |
| 
 | |
|   ElementState oldRequiredStates = State() & ElementState::REQUIRED_STATES;
 | |
|   ElementState changedStates = requiredStates ^ oldRequiredStates;
 | |
| 
 | |
|   if (!changedStates.IsEmpty()) {
 | |
|     ToggleStates(changedStates, aNotify);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLFormControlElement::IsAutocapitalizeInheriting() const {
 | |
|   auto type = ControlType();
 | |
|   return IsInputElement(type) || IsButtonElement(type) ||
 | |
|          type == FormControlType::Fieldset || type == FormControlType::Output ||
 | |
|          type == FormControlType::Select || type == FormControlType::Textarea;
 | |
| }
 | |
| 
 | |
| nsresult nsGenericHTMLFormControlElement::SubmitDirnameDir(
 | |
|     FormData* aFormData) {
 | |
|   // Submit dirname=dir if element has non-empty dirname attribute
 | |
|   if (HasAttr(nsGkAtoms::dirname)) {
 | |
|     nsAutoString dirname;
 | |
|     GetAttr(nsGkAtoms::dirname, dirname);
 | |
|     if (!dirname.IsEmpty()) {
 | |
|       const Directionality dir = GetDirectionality();
 | |
|       MOZ_ASSERT(dir == Directionality::Ltr || dir == Directionality::Rtl,
 | |
|                  "The directionality of an element is either ltr or rtl");
 | |
|       return aFormData->AddNameValuePair(
 | |
|           dirname, dir == Directionality::Ltr ? u"ltr"_ns : u"rtl"_ns);
 | |
|     }
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| 
 | |
| static const nsAttrValue::EnumTable kPopoverTargetActionTable[] = {
 | |
|     {"toggle", PopoverTargetAction::Toggle},
 | |
|     {"show", PopoverTargetAction::Show},
 | |
|     {"hide", PopoverTargetAction::Hide},
 | |
|     {nullptr, 0}};
 | |
| 
 | |
| static const nsAttrValue::EnumTable* kPopoverTargetActionDefault =
 | |
|     &kPopoverTargetActionTable[0];
 | |
| 
 | |
| nsGenericHTMLFormControlElementWithState::
 | |
|     nsGenericHTMLFormControlElementWithState(
 | |
|         already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
 | |
|         FromParser aFromParser, FormControlType aType)
 | |
|     : nsGenericHTMLFormControlElement(std::move(aNodeInfo), aType),
 | |
|       mControlNumber(!!(aFromParser & FROM_PARSER_NETWORK)
 | |
|                          ? OwnerDoc()->GetNextControlNumber()
 | |
|                          : -1) {
 | |
|   mStateKey.SetIsVoid(true);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLFormControlElementWithState::ParseAttribute(
 | |
|     int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue,
 | |
|     nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) {
 | |
|   if (aNamespaceID == kNameSpaceID_None) {
 | |
|     if (StaticPrefs::dom_element_popover_enabled()) {
 | |
|       if (aAttribute == nsGkAtoms::popovertargetaction) {
 | |
|         return aResult.ParseEnumValue(aValue, kPopoverTargetActionTable, false,
 | |
|                                       kPopoverTargetActionDefault);
 | |
|       }
 | |
|       if (aAttribute == nsGkAtoms::popovertarget) {
 | |
|         aResult.ParseAtom(aValue);
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (StaticPrefs::dom_element_invokers_enabled()) {
 | |
|       if (aAttribute == nsGkAtoms::invokeaction) {
 | |
|         aResult.ParseAtom(aValue);
 | |
|         return true;
 | |
|       }
 | |
|       if (aAttribute == nsGkAtoms::invoketarget) {
 | |
|         aResult.ParseAtom(aValue);
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nsGenericHTMLFormControlElement::ParseAttribute(
 | |
|       aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
 | |
| }
 | |
| 
 | |
| mozilla::dom::Element*
 | |
| nsGenericHTMLFormControlElementWithState::GetPopoverTargetElement() const {
 | |
|   return GetAttrAssociatedElement(nsGkAtoms::popovertarget);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElementWithState::SetPopoverTargetElement(
 | |
|     mozilla::dom::Element* aElement) {
 | |
|   ExplicitlySetAttrElement(nsGkAtoms::popovertarget, aElement);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElementWithState::HandlePopoverTargetAction() {
 | |
|   RefPtr<nsGenericHTMLElement> target = GetEffectivePopoverTargetElement();
 | |
|   if (!target) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto action = PopoverTargetAction::Toggle;
 | |
|   if (const nsAttrValue* value =
 | |
|           GetParsedAttr(nsGkAtoms::popovertargetaction)) {
 | |
|     MOZ_ASSERT(value->Type() == nsAttrValue::eEnum);
 | |
|     action = static_cast<PopoverTargetAction>(value->GetEnumValue());
 | |
|   }
 | |
| 
 | |
|   bool canHide = action == PopoverTargetAction::Hide ||
 | |
|                  action == PopoverTargetAction::Toggle;
 | |
|   bool shouldHide = canHide && target->IsPopoverOpen();
 | |
|   bool canShow = action == PopoverTargetAction::Show ||
 | |
|                  action == PopoverTargetAction::Toggle;
 | |
|   bool shouldShow = canShow && !target->IsPopoverOpen();
 | |
| 
 | |
|   if (shouldHide) {
 | |
|     target->HidePopover(IgnoreErrors());
 | |
|   } else if (shouldShow) {
 | |
|     target->ShowPopoverInternal(this, IgnoreErrors());
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElementWithState::GetInvokeAction(
 | |
|     nsAString& aValue) const {
 | |
|   GetInvokeAction()->ToString(aValue);
 | |
| }
 | |
| 
 | |
| nsAtom* nsGenericHTMLFormControlElementWithState::GetInvokeAction() const {
 | |
|   const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::invokeaction);
 | |
|   if (attr && attr->GetAtomValue() != nsGkAtoms::_empty) {
 | |
|     return attr->GetAtomValue();
 | |
|   }
 | |
|   return nsGkAtoms::_auto;
 | |
| }
 | |
| 
 | |
| mozilla::dom::Element*
 | |
| nsGenericHTMLFormControlElementWithState::GetInvokeTargetElement() const {
 | |
|   if (StaticPrefs::dom_element_invokers_enabled()) {
 | |
|     return GetAttrAssociatedElement(nsGkAtoms::invoketarget);
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElementWithState::SetInvokeTargetElement(
 | |
|     mozilla::dom::Element* aElement) {
 | |
|   ExplicitlySetAttrElement(nsGkAtoms::invoketarget, aElement);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElementWithState::HandleInvokeTargetAction() {
 | |
|   // 1. Let invokee be node's invoke target element.
 | |
|   RefPtr<Element> invokee = GetInvokeTargetElement();
 | |
| 
 | |
|   // 2. If invokee is null, then return.
 | |
|   if (!invokee) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // 3. Let action be node's invokeaction attribute
 | |
|   // 4. If action is null or empty, then let action be the string "auto".
 | |
|   RefPtr<nsAtom> aAction = GetInvokeAction();
 | |
|   MOZ_ASSERT(!aAction->IsEmpty(), "Action should not be empty");
 | |
| 
 | |
|   // 5. Let notCancelled be the result of firing an event named invoke at
 | |
|   // invokee with its action set to action, its invoker set to node,
 | |
|   // and its cancelable attribute initialized to true.
 | |
|   InvokeEventInit init;
 | |
|   aAction->ToString(init.mAction);
 | |
|   init.mInvoker = this;
 | |
|   init.mCancelable = true;
 | |
|   init.mComposed = true;
 | |
|   RefPtr<Event> event = InvokeEvent::Constructor(this, u"invoke"_ns, init);
 | |
|   event->SetTrusted(true);
 | |
|   event->SetTarget(invokee);
 | |
| 
 | |
|   EventDispatcher::DispatchDOMEvent(invokee, nullptr, event, nullptr, nullptr);
 | |
| 
 | |
|   // 6. If notCancelled is true and invokee has an associated invocation action
 | |
|   // algorithm then run the invokee's invocation action algorithm given action.
 | |
|   if (event->DefaultPrevented()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   invokee->HandleInvokeInternal(aAction, IgnoreErrors());
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElementWithState::GenerateStateKey() {
 | |
|   // Keep the key if already computed
 | |
|   if (!mStateKey.IsVoid()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Document* doc = GetUncomposedDoc();
 | |
|   if (!doc) {
 | |
|     mStateKey.Truncate();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Generate the state key
 | |
|   nsContentUtils::GenerateStateKey(this, doc, mStateKey);
 | |
| 
 | |
|   // If the state key is blank, this is anonymous content or for whatever
 | |
|   // reason we are not supposed to save/restore state: keep it as such.
 | |
|   if (!mStateKey.IsEmpty()) {
 | |
|     // Add something unique to content so layout doesn't muck us up.
 | |
|     mStateKey += "-C";
 | |
|   }
 | |
| }
 | |
| 
 | |
| PresState* nsGenericHTMLFormControlElementWithState::GetPrimaryPresState() {
 | |
|   if (mStateKey.IsEmpty()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false);
 | |
| 
 | |
|   if (!history) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Get the pres state for this key, if it doesn't exist, create one.
 | |
|   PresState* result = history->GetState(mStateKey);
 | |
|   if (!result) {
 | |
|     UniquePtr<PresState> newState = NewPresState();
 | |
|     result = newState.get();
 | |
|     history->AddState(mStateKey, std::move(newState));
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsILayoutHistoryState>
 | |
| nsGenericHTMLFormElement::GetLayoutHistory(bool aRead) {
 | |
|   nsCOMPtr<Document> doc = GetUncomposedDoc();
 | |
|   if (!doc) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Get the history
 | |
|   //
 | |
|   nsCOMPtr<nsILayoutHistoryState> history = doc->GetLayoutHistoryState();
 | |
|   if (!history) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (aRead && !history->HasStates()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return history.forget();
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLFormControlElementWithState::RestoreFormControlState() {
 | |
|   MOZ_ASSERT(!mStateKey.IsVoid(),
 | |
|              "GenerateStateKey must already have been called");
 | |
| 
 | |
|   if (mStateKey.IsEmpty()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true);
 | |
|   if (!history) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Get the pres state for this key
 | |
|   PresState* state = history->GetState(mStateKey);
 | |
|   if (state) {
 | |
|     bool result = RestoreState(state);
 | |
|     history->RemoveState(mStateKey);
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElementWithState::NodeInfoChanged(
 | |
|     Document* aOldDoc) {
 | |
|   nsGenericHTMLFormControlElement::NodeInfoChanged(aOldDoc);
 | |
| 
 | |
|   // We need to regenerate the state key now we're in a new document.  Clearing
 | |
|   // mControlNumber means we stop considering this control to be parser
 | |
|   // inserted, and we'll generate a state key based on its position in the
 | |
|   // document rather than the order it was inserted into the document.
 | |
|   mControlNumber = -1;
 | |
|   mStateKey.SetIsVoid(true);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLFormControlElementWithState::GetFormAction(nsString& aValue) {
 | |
|   auto type = ControlType();
 | |
|   if (!IsInputElement(type) && !IsButtonElement(type)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!GetAttr(nsGkAtoms::formaction, aValue) || aValue.IsEmpty()) {
 | |
|     Document* document = OwnerDoc();
 | |
|     nsIURI* docURI = document->GetDocumentURI();
 | |
|     if (docURI) {
 | |
|       nsAutoCString spec;
 | |
|       nsresult rv = docURI->GetSpec(spec);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       CopyUTF8toUTF16(spec, aValue);
 | |
|     }
 | |
|   } else {
 | |
|     GetURIAttr(nsGkAtoms::formaction, nullptr, aValue);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::IsEventAttributeNameInternal(nsAtom* aName) {
 | |
|   return nsContentUtils::IsEventAttributeName(aName, EventNameType_HTML);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Construct a URI from a string, as an element.src attribute
 | |
|  * would be set to. Helper for the media elements.
 | |
|  */
 | |
| nsresult nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec,
 | |
|                                                 nsIURI** aURI) {
 | |
|   NS_ENSURE_ARG_POINTER(aURI);
 | |
| 
 | |
|   *aURI = nullptr;
 | |
| 
 | |
|   nsCOMPtr<Document> doc = OwnerDoc();
 | |
| 
 | |
|   nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI, aURISpec, doc,
 | |
|                                                           GetBaseURI());
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   bool equal;
 | |
|   if (aURISpec.IsEmpty() && doc->GetDocumentURI() &&
 | |
|       NS_SUCCEEDED(doc->GetDocumentURI()->Equals(*aURI, &equal)) && equal) {
 | |
|     // Assume an element can't point to a fragment of its embedding
 | |
|     // document. Fail here instead of returning the recursive URI
 | |
|     // and waiting for the subsequent load to fail.
 | |
|     NS_RELEASE(*aURI);
 | |
|     return NS_ERROR_DOM_INVALID_STATE_ERR;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue,
 | |
|                                         mozilla::ErrorResult& aError) {
 | |
|   // innerText depends on layout. For example, white space processing is
 | |
|   // something that happens during reflow and which must be reflected by
 | |
|   // innerText.  So for:
 | |
|   //
 | |
|   //   <div style="white-space:normal"> A     B C </div>
 | |
|   //
 | |
|   // innerText should give "A B C".
 | |
|   //
 | |
|   // The approach taken here to avoid the expense of reflow is to flush style
 | |
|   // and then see whether it's necessary to flush layout afterwards. Flushing
 | |
|   // layout can be skipped if we can detect that the element or its descendants
 | |
|   // are not dirty.
 | |
| 
 | |
|   // Obtain the composed doc to handle elements in Shadow DOM.
 | |
|   Document* doc = GetComposedDoc();
 | |
|   if (doc) {
 | |
|     doc->FlushPendingNotifications(FlushType::Style);
 | |
|   }
 | |
| 
 | |
|   // Elements with `display: content` will not have a frame. To handle Shadow
 | |
|   // DOM, walk the flattened tree looking for parent frame.
 | |
|   nsIFrame* frame = GetPrimaryFrame();
 | |
|   if (IsDisplayContents()) {
 | |
|     for (Element* parent = GetFlattenedTreeParentElement(); parent;
 | |
|          parent = parent->GetFlattenedTreeParentElement()) {
 | |
|       frame = parent->GetPrimaryFrame();
 | |
|       if (frame) {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Check for dirty reflow roots in the subtree from targetFrame; this requires
 | |
|   // a reflow flush.
 | |
|   bool dirty = frame && frame->PresShell()->FrameIsAncestorOfDirtyRoot(frame);
 | |
| 
 | |
|   // The way we do that is by checking whether the element has either of the two
 | |
|   // dirty bits (NS_FRAME_IS_DIRTY or NS_FRAME_HAS_DIRTY_DESCENDANTS) or if any
 | |
|   // ancestor has NS_FRAME_IS_DIRTY.  We need to check for NS_FRAME_IS_DIRTY on
 | |
|   // ancestors since that is something that implies NS_FRAME_IS_DIRTY on all
 | |
|   // descendants.
 | |
|   dirty |= frame && frame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
 | |
|   while (!dirty && frame) {
 | |
|     dirty |= frame->HasAnyStateBits(NS_FRAME_IS_DIRTY);
 | |
|     frame = frame->GetInFlowParent();
 | |
|   }
 | |
| 
 | |
|   // Flush layout if we determined a reflow is required.
 | |
|   if (dirty && doc) {
 | |
|     doc->FlushPendingNotifications(FlushType::Layout);
 | |
|   }
 | |
| 
 | |
|   if (!IsRendered()) {
 | |
|     GetTextContentInternal(aValue, aError);
 | |
|   } else {
 | |
|     nsRange::GetInnerTextNoFlush(aValue, aError, this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static already_AddRefed<nsINode> TextToNode(const nsAString& aString,
 | |
|                                             nsNodeInfoManager* aNim) {
 | |
|   nsString str;
 | |
|   const char16_t* s = aString.BeginReading();
 | |
|   const char16_t* end = aString.EndReading();
 | |
|   RefPtr<DocumentFragment> fragment;
 | |
|   while (true) {
 | |
|     if (s != end && *s == '\r' && s + 1 != end && s[1] == '\n') {
 | |
|       // a \r\n pair should only generate one <br>, so just skip the \r
 | |
|       ++s;
 | |
|     }
 | |
|     if (s == end || *s == '\r' || *s == '\n') {
 | |
|       if (!str.IsEmpty()) {
 | |
|         RefPtr<nsTextNode> textContent = new (aNim) nsTextNode(aNim);
 | |
|         textContent->SetText(str, true);
 | |
|         if (!fragment) {
 | |
|           if (s == end) {
 | |
|             return textContent.forget();
 | |
|           }
 | |
|           fragment = new (aNim) DocumentFragment(aNim);
 | |
|         }
 | |
|         fragment->AppendChildTo(textContent, true, IgnoreErrors());
 | |
|       }
 | |
|       if (s == end) {
 | |
|         break;
 | |
|       }
 | |
|       str.Truncate();
 | |
|       RefPtr<NodeInfo> ni = aNim->GetNodeInfo(
 | |
|           nsGkAtoms::br, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
 | |
|       auto* nim = ni->NodeInfoManager();
 | |
|       RefPtr<HTMLBRElement> br = new (nim) HTMLBRElement(ni.forget());
 | |
|       if (!fragment) {
 | |
|         if (s + 1 == end) {
 | |
|           return br.forget();
 | |
|         }
 | |
|         fragment = new (aNim) DocumentFragment(aNim);
 | |
|       }
 | |
|       fragment->AppendChildTo(br, true, IgnoreErrors());
 | |
|     } else {
 | |
|       str.Append(*s);
 | |
|     }
 | |
|     ++s;
 | |
|   }
 | |
|   return fragment.forget();
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::SetInnerText(const nsAString& aValue) {
 | |
|   RefPtr<nsINode> node = TextToNode(aValue, NodeInfo()->NodeInfoManager());
 | |
|   ReplaceChildren(node, IgnoreErrors());
 | |
| }
 | |
| 
 | |
| // https://html.spec.whatwg.org/#merge-with-the-next-text-node
 | |
| static void MergeWithNextTextNode(Text& aText, ErrorResult& aRv) {
 | |
|   RefPtr<Text> nextSibling = Text::FromNodeOrNull(aText.GetNextSibling());
 | |
|   if (!nextSibling) {
 | |
|     return;
 | |
|   }
 | |
|   nsAutoString data;
 | |
|   nextSibling->GetData(data);
 | |
|   aText.AppendData(data, aRv);
 | |
|   nextSibling->Remove();
 | |
| }
 | |
| 
 | |
| // https://html.spec.whatwg.org/#dom-outertext
 | |
| void nsGenericHTMLElement::SetOuterText(const nsAString& aValue,
 | |
|                                         ErrorResult& aRv) {
 | |
|   nsCOMPtr<nsINode> parent = GetParentNode();
 | |
|   if (!parent) {
 | |
|     return aRv.ThrowNoModificationAllowedError("Element has no parent");
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsINode> next = GetNextSibling();
 | |
|   RefPtr<nsINode> previous = GetPreviousSibling();
 | |
| 
 | |
|   // Batch possible DOMSubtreeModified events.
 | |
|   mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
 | |
| 
 | |
|   nsNodeInfoManager* nim = NodeInfo()->NodeInfoManager();
 | |
|   RefPtr<nsINode> node = TextToNode(aValue, nim);
 | |
|   if (!node) {
 | |
|     // This doesn't match the spec, see
 | |
|     // https://github.com/whatwg/html/issues/7508
 | |
|     node = new (nim) nsTextNode(nim);
 | |
|   }
 | |
|   parent->ReplaceChild(*node, *this, aRv);
 | |
|   if (aRv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (next) {
 | |
|     if (RefPtr<Text> text = Text::FromNodeOrNull(next->GetPreviousSibling())) {
 | |
|       MergeWithNextTextNode(*text, aRv);
 | |
|       if (aRv.Failed()) {
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (auto* text = Text::FromNodeOrNull(previous)) {
 | |
|     MergeWithNextTextNode(*text, aRv);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // This should be true when `:open` should match.
 | |
| bool nsGenericHTMLElement::PopoverOpen() const {
 | |
|   if (PopoverData* popoverData = GetPopoverData()) {
 | |
|     return popoverData->GetPopoverVisibilityState() ==
 | |
|            PopoverVisibilityState::Showing;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // https://html.spec.whatwg.org/#check-popover-validity
 | |
| bool nsGenericHTMLElement::CheckPopoverValidity(
 | |
|     PopoverVisibilityState aExpectedState, Document* aExpectedDocument,
 | |
|     ErrorResult& aRv) {
 | |
|   if (GetPopoverAttributeState() == PopoverAttributeState::None) {
 | |
|     aRv.ThrowNotSupportedError("Element is in the no popover state");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (GetPopoverData()->GetPopoverVisibilityState() != aExpectedState) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!IsInComposedDoc()) {
 | |
|     aRv.ThrowInvalidStateError("Element is not connected");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (aExpectedDocument && aExpectedDocument != OwnerDoc()) {
 | |
|     aRv.ThrowInvalidStateError("Element is moved to other document");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (auto* dialog = HTMLDialogElement::FromNode(this)) {
 | |
|     if (dialog->IsInTopLayer()) {
 | |
|       aRv.ThrowInvalidStateError("Element is a modal <dialog> element");
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (State().HasState(ElementState::FULLSCREEN)) {
 | |
|     aRv.ThrowInvalidStateError("Element is fullscreen");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| PopoverAttributeState nsGenericHTMLElement::GetPopoverAttributeState() const {
 | |
|   return GetPopoverData() ? GetPopoverData()->GetPopoverAttributeState()
 | |
|                           : PopoverAttributeState::None;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::PopoverPseudoStateUpdate(bool aOpen, bool aNotify) {
 | |
|   SetStates(ElementState::POPOVER_OPEN, aOpen, aNotify);
 | |
| }
 | |
| 
 | |
| already_AddRefed<ToggleEvent> nsGenericHTMLElement::CreateToggleEvent(
 | |
|     const nsAString& aEventType, const nsAString& aOldState,
 | |
|     const nsAString& aNewState, Cancelable aCancelable) {
 | |
|   ToggleEventInit init;
 | |
|   init.mBubbles = false;
 | |
|   init.mOldState = aOldState;
 | |
|   init.mNewState = aNewState;
 | |
|   init.mCancelable = aCancelable == Cancelable::eYes;
 | |
|   RefPtr<ToggleEvent> event = ToggleEvent::Constructor(this, aEventType, init);
 | |
|   event->SetTrusted(true);
 | |
|   event->SetTarget(this);
 | |
|   return event.forget();
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::FireToggleEvent(PopoverVisibilityState aOldState,
 | |
|                                            PopoverVisibilityState aNewState,
 | |
|                                            const nsAString& aType) {
 | |
|   auto stringForState = [](PopoverVisibilityState state) {
 | |
|     return state == PopoverVisibilityState::Hidden ? u"closed"_ns : u"open"_ns;
 | |
|   };
 | |
|   const auto cancelable = aType == u"beforetoggle"_ns &&
 | |
|                                   aNewState == PopoverVisibilityState::Showing
 | |
|                               ? Cancelable::eYes
 | |
|                               : Cancelable::eNo;
 | |
|   RefPtr event = CreateToggleEvent(aType, stringForState(aOldState),
 | |
|                                    stringForState(aNewState), cancelable);
 | |
|   EventDispatcher::DispatchDOMEvent(this, nullptr, event, nullptr, nullptr);
 | |
|   return event->DefaultPrevented();
 | |
| }
 | |
| 
 | |
| // https://html.spec.whatwg.org/#queue-a-popover-toggle-event-task
 | |
| void nsGenericHTMLElement::QueuePopoverEventTask(
 | |
|     PopoverVisibilityState aOldState) {
 | |
|   auto* data = GetPopoverData();
 | |
|   MOZ_ASSERT(data, "Should have popover data");
 | |
| 
 | |
|   if (auto* queuedToggleEventTask = data->GetToggleEventTask()) {
 | |
|     aOldState = queuedToggleEventTask->GetOldState();
 | |
|   }
 | |
| 
 | |
|   auto task =
 | |
|       MakeRefPtr<PopoverToggleEventTask>(do_GetWeakReference(this), aOldState);
 | |
|   data->SetToggleEventTask(task);
 | |
|   OwnerDoc()->Dispatch(task.forget());
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::RunPopoverToggleEventTask(
 | |
|     PopoverToggleEventTask* aTask, PopoverVisibilityState aOldState) {
 | |
|   auto* data = GetPopoverData();
 | |
|   if (!data) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto* popoverToggleEventTask = data->GetToggleEventTask();
 | |
|   if (!popoverToggleEventTask || aTask != popoverToggleEventTask) {
 | |
|     return;
 | |
|   }
 | |
|   data->ClearToggleEventTask();
 | |
|   // Intentionally ignore the return value here as only on open event the
 | |
|   // cancelable attribute is initialized to true for beforetoggle event.
 | |
|   FireToggleEvent(aOldState, data->GetPopoverVisibilityState(), u"toggle"_ns);
 | |
| }
 | |
| 
 | |
| // https://html.spec.whatwg.org/#dom-showpopover
 | |
| void nsGenericHTMLElement::ShowPopover(ErrorResult& aRv) {
 | |
|   return ShowPopoverInternal(nullptr, aRv);
 | |
| }
 | |
| void nsGenericHTMLElement::ShowPopoverInternal(Element* aInvoker,
 | |
|                                                ErrorResult& aRv) {
 | |
|   if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, nullptr, aRv)) {
 | |
|     return;
 | |
|   }
 | |
|   RefPtr<Document> document = OwnerDoc();
 | |
| 
 | |
|   MOZ_ASSERT(!GetPopoverData() || !GetPopoverData()->GetInvoker());
 | |
|   MOZ_ASSERT(!OwnerDoc()->TopLayerContains(*this));
 | |
| 
 | |
|   bool wasShowingOrHiding = GetPopoverData()->IsShowingOrHiding();
 | |
|   GetPopoverData()->SetIsShowingOrHiding(true);
 | |
|   auto cleanupShowingFlag = MakeScopeExit([&]() {
 | |
|     if (auto* popoverData = GetPopoverData()) {
 | |
|       popoverData->SetIsShowingOrHiding(wasShowingOrHiding);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   // Fire beforetoggle event and re-check popover validity.
 | |
|   if (FireToggleEvent(PopoverVisibilityState::Hidden,
 | |
|                       PopoverVisibilityState::Showing, u"beforetoggle"_ns)) {
 | |
|     return;
 | |
|   }
 | |
|   if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool shouldRestoreFocus = false;
 | |
|   nsWeakPtr originallyFocusedElement;
 | |
|   if (IsAutoPopover()) {
 | |
|     auto originalState = GetPopoverAttributeState();
 | |
|     RefPtr<nsINode> ancestor = GetTopmostPopoverAncestor(aInvoker, true);
 | |
|     if (!ancestor) {
 | |
|       ancestor = document;
 | |
|     }
 | |
|     document->HideAllPopoversUntil(*ancestor, false,
 | |
|                                    /* aFireEvents = */ !wasShowingOrHiding);
 | |
|     if (GetPopoverAttributeState() != originalState) {
 | |
|       aRv.ThrowInvalidStateError(
 | |
|           "The value of the popover attribute was changed while hiding the "
 | |
|           "popover.");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // TODO: Handle if document changes, see
 | |
|     // https://github.com/whatwg/html/issues/9177
 | |
|     if (!IsAutoPopover() ||
 | |
|         !CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     shouldRestoreFocus = !document->GetTopmostAutoPopover();
 | |
|     // Let originallyFocusedElement be document's focused area of the document's
 | |
|     // DOM anchor.
 | |
|     if (nsIContent* unretargetedFocus =
 | |
|             document->GetUnretargetedFocusedContent()) {
 | |
|       originallyFocusedElement =
 | |
|           do_GetWeakReference(unretargetedFocus->AsElement());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   document->AddPopoverToTopLayer(*this);
 | |
| 
 | |
|   PopoverPseudoStateUpdate(true, true);
 | |
| 
 | |
|   {
 | |
|     auto* popoverData = GetPopoverData();
 | |
|     popoverData->SetPopoverVisibilityState(PopoverVisibilityState::Showing);
 | |
|     popoverData->SetInvoker(aInvoker);
 | |
|   }
 | |
| 
 | |
|   // Run the popover focusing steps given element.
 | |
|   FocusPopover();
 | |
|   if (shouldRestoreFocus &&
 | |
|       GetPopoverAttributeState() != PopoverAttributeState::None) {
 | |
|     GetPopoverData()->SetPreviouslyFocusedElement(originallyFocusedElement);
 | |
|   }
 | |
| 
 | |
|   // Queue popover toggle event task.
 | |
|   QueuePopoverEventTask(PopoverVisibilityState::Hidden);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::HidePopoverWithoutRunningScript() {
 | |
|   HidePopoverInternal(/* aFocusPreviousElement = */ false,
 | |
|                       /* aFireEvents = */ false, IgnoreErrors());
 | |
| }
 | |
| 
 | |
| // https://html.spec.whatwg.org/#dom-hidepopover
 | |
| void nsGenericHTMLElement::HidePopover(ErrorResult& aRv) {
 | |
|   HidePopoverInternal(/* aFocusPreviousElement = */ true,
 | |
|                       /* aFireEvents = */ true, aRv);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::HidePopoverInternal(bool aFocusPreviousElement,
 | |
|                                                bool aFireEvents,
 | |
|                                                ErrorResult& aRv) {
 | |
|   OwnerDoc()->HidePopover(*this, aFocusPreviousElement, aFireEvents, aRv);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::ForgetPreviouslyFocusedElementAfterHidingPopover() {
 | |
|   auto* data = GetPopoverData();
 | |
|   MOZ_ASSERT(data, "Should have popover data");
 | |
|   data->SetPreviouslyFocusedElement(nullptr);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::FocusPreviousElementAfterHidingPopover() {
 | |
|   auto* data = GetPopoverData();
 | |
|   MOZ_ASSERT(data, "Should have popover data");
 | |
| 
 | |
|   RefPtr<Element> control =
 | |
|       do_QueryReferent(data->GetPreviouslyFocusedElement().get());
 | |
|   data->SetPreviouslyFocusedElement(nullptr);
 | |
| 
 | |
|   if (!control) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Step 14.2 at
 | |
|   // https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
 | |
|   // If focusPreviousElement is true and document's focused area of the
 | |
|   // document's DOM anchor is a shadow-including inclusive descendant of
 | |
|   // element, then run the focusing steps for previouslyFocusedElement;
 | |
|   nsIContent* currentFocus = OwnerDoc()->GetUnretargetedFocusedContent();
 | |
|   if (currentFocus &&
 | |
|       currentFocus->IsShadowIncludingInclusiveDescendantOf(this)) {
 | |
|     FocusOptions options;
 | |
|     options.mPreventScroll = true;
 | |
|     control->Focus(options, CallerType::NonSystem, IgnoreErrors());
 | |
|   }
 | |
| }
 | |
| 
 | |
| // https://html.spec.whatwg.org/multipage/popover.html#dom-togglepopover
 | |
| bool nsGenericHTMLElement::TogglePopover(const Optional<bool>& aForce,
 | |
|                                          ErrorResult& aRv) {
 | |
|   if (PopoverOpen() && (!aForce.WasPassed() || !aForce.Value())) {
 | |
|     HidePopover(aRv);
 | |
|   } else if (!aForce.WasPassed() || aForce.Value()) {
 | |
|     ShowPopover(aRv);
 | |
|   } else {
 | |
|     CheckPopoverValidity(GetPopoverData()
 | |
|                              ? GetPopoverData()->GetPopoverVisibilityState()
 | |
|                              : PopoverVisibilityState::Showing,
 | |
|                          nullptr, aRv);
 | |
|   }
 | |
| 
 | |
|   return PopoverOpen();
 | |
| }
 | |
| 
 | |
| // https://html.spec.whatwg.org/multipage/popover.html#popover-focusing-steps
 | |
| void nsGenericHTMLElement::FocusPopover() {
 | |
|   if (auto* dialog = HTMLDialogElement::FromNode(this)) {
 | |
|     return MOZ_KnownLive(dialog)->FocusDialog();
 | |
|   }
 | |
| 
 | |
|   if (RefPtr<Document> doc = GetComposedDoc()) {
 | |
|     doc->FlushPendingNotifications(FlushType::Frames);
 | |
|   }
 | |
| 
 | |
|   RefPtr<Element> control = GetBoolAttr(nsGkAtoms::autofocus)
 | |
|                                 ? this
 | |
|                                 : GetAutofocusDelegate(false /* aWithMouse */);
 | |
| 
 | |
|   if (!control) {
 | |
|     return;
 | |
|   }
 | |
|   FocusCandidate(control, false /* aClearUpFocus */);
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::FocusCandidate(Element* aControl,
 | |
|                                           bool aClearUpFocus) {
 | |
|   // 1) Run the focusing steps given control.
 | |
|   IgnoredErrorResult rv;
 | |
|   if (RefPtr<Element> elementToFocus = nsFocusManager::GetTheFocusableArea(
 | |
|           aControl, nsFocusManager::ProgrammaticFocusFlags(FocusOptions()))) {
 | |
|     elementToFocus->Focus(FocusOptions(), CallerType::NonSystem, rv);
 | |
|     if (rv.Failed()) {
 | |
|       return;
 | |
|     }
 | |
|   } else if (aClearUpFocus) {
 | |
|     if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
 | |
|       // Clear the focus which ends up making the body gets focused
 | |
|       nsCOMPtr<nsPIDOMWindowOuter> outerWindow = OwnerDoc()->GetWindow();
 | |
|       fm->ClearFocus(outerWindow);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // 2) Let topDocument be the active document of control's node document's
 | |
|   // browsing context's top-level browsing context.
 | |
|   // 3) If control's node document's origin is not the same as the origin of
 | |
|   // topDocument, then return.
 | |
|   BrowsingContext* bc = aControl->OwnerDoc()->GetBrowsingContext();
 | |
|   if (bc && bc->IsInProcess() && bc->SameOriginWithTop()) {
 | |
|     if (nsCOMPtr<nsIDocShell> docShell = bc->Top()->GetDocShell()) {
 | |
|       if (Document* topDocument = docShell->GetExtantDocument()) {
 | |
|         // 4) Empty topDocument's autofocus candidates.
 | |
|         // 5) Set topDocument's autofocus processed flag to true.
 | |
|         topDocument->SetAutoFocusFired();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<ElementInternals> nsGenericHTMLElement::AttachInternals(
 | |
|     ErrorResult& aRv) {
 | |
|   // ElementInternals is only available on autonomous custom element, so throws
 | |
|   // an error by default. The spec steps are implemented in HTMLElement because
 | |
|   // ElementInternals needs to hold a pointer to HTMLElement in order to forward
 | |
|   // form operation to it.
 | |
|   aRv.ThrowNotSupportedError(nsPrintfCString(
 | |
|       "Cannot attach ElementInternals to a customized built-in or non-custom "
 | |
|       "element "
 | |
|       "'%s'",
 | |
|       NS_ConvertUTF16toUTF8(NodeInfo()->NameAtom()->GetUTF16String()).get()));
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| ElementInternals* nsGenericHTMLElement::GetInternals() const {
 | |
|   if (CustomElementData* data = GetCustomElementData()) {
 | |
|     return data->GetElementInternals();
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::IsFormAssociatedCustomElements() const {
 | |
|   if (CustomElementData* data = GetCustomElementData()) {
 | |
|     return data->IsFormAssociated();
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::GetAutocapitalize(nsAString& aValue) const {
 | |
|   GetEnumAttr(nsGkAtoms::autocapitalize, nullptr, kDefaultAutocapitalize->tag,
 | |
|               aValue);
 | |
| }
 | |
| 
 | |
| bool nsGenericHTMLElement::Translate() const {
 | |
|   if (const nsAttrValue* attr = mAttrs.GetAttr(nsGkAtoms::translate)) {
 | |
|     if (attr->IsEmptyString() || attr->Equals(nsGkAtoms::yes, eIgnoreCase)) {
 | |
|       return true;
 | |
|     }
 | |
|     if (attr->Equals(nsGkAtoms::no, eIgnoreCase)) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   return nsGenericHTMLElementBase::Translate();
 | |
| }
 | |
| 
 | |
| void nsGenericHTMLElement::GetPopover(nsString& aPopover) const {
 | |
|   GetHTMLEnumAttr(nsGkAtoms::popover, aPopover);
 | |
|   if (aPopover.IsEmpty() && !DOMStringIsNull(aPopover)) {
 | |
|     aPopover.Assign(NS_ConvertUTF8toUTF16(kPopoverAttributeValueAuto));
 | |
|   }
 | |
| }
 | 
