nsGenericHTMLElement.cpp (142528B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsGenericHTMLElement.h" 8 9 #include "HTMLBRElement.h" 10 #include "HTMLFieldSetElement.h" 11 #include "ReferrerInfo.h" 12 #include "imgIContainer.h" 13 #include "mozilla/EditorBase.h" 14 #include "mozilla/ErrorResult.h" 15 #include "mozilla/EventDispatcher.h" 16 #include "mozilla/EventListenerManager.h" 17 #include "mozilla/EventStateManager.h" 18 #include "mozilla/FocusModel.h" 19 #include "mozilla/HTMLEditor.h" 20 #include "mozilla/IMEContentObserver.h" 21 #include "mozilla/IMEStateManager.h" 22 #include "mozilla/MappedDeclarationsBuilder.h" 23 #include "mozilla/Maybe.h" 24 #include "mozilla/MouseEvents.h" 25 #include "mozilla/Preferences.h" 26 #include "mozilla/PresShell.h" 27 #include "mozilla/PresState.h" 28 #include "mozilla/StaticPrefs_accessibility.h" 29 #include "mozilla/StaticPrefs_dom.h" 30 #include "mozilla/StaticPrefs_layout.h" 31 #include "mozilla/TextEditor.h" 32 #include "mozilla/TextEvents.h" 33 #include "mozilla/dom/AncestorIterator.h" 34 #include "mozilla/dom/BindContext.h" 35 #include "mozilla/dom/BindingUtils.h" 36 #include "mozilla/dom/CommandEvent.h" 37 #include "mozilla/dom/CustomElementRegistry.h" 38 #include "mozilla/dom/DirectionalityUtils.h" 39 #include "mozilla/dom/Document.h" 40 #include "mozilla/dom/DocumentInlines.h" 41 #include "mozilla/dom/DocumentOrShadowRoot.h" 42 #include "mozilla/dom/ElementBinding.h" 43 #include "mozilla/dom/ElementInternals.h" 44 #include "mozilla/dom/FetchPriority.h" 45 #include "mozilla/dom/FormData.h" 46 #include "mozilla/dom/FromParser.h" 47 #include "mozilla/dom/HTMLBodyElement.h" 48 #include "mozilla/dom/HTMLDialogElement.h" 49 #include "mozilla/dom/HTMLElementBinding.h" 50 #include "mozilla/dom/HTMLFormElement.h" 51 #include "mozilla/dom/HTMLInputElement.h" 52 #include "mozilla/dom/HTMLLabelElement.h" 53 #include "mozilla/dom/InputEvent.h" 54 #include "mozilla/dom/Link.h" 55 #include "mozilla/dom/MouseEventBinding.h" 56 #include "mozilla/dom/ScriptLoader.h" 57 #include "mozilla/dom/ToggleEvent.h" 58 #include "mozilla/dom/TouchEvent.h" 59 #include "mozilla/dom/UnbindContext.h" 60 #include "mozilla/intl/Locale.h" 61 #include "nsAtom.h" 62 #include "nsAttrValueOrString.h" 63 #include "nsCOMPtr.h" 64 #include "nsCaseTreatment.h" 65 #include "nsComputedDOMStyle.h" 66 #include "nsContainerFrame.h" 67 #include "nsContentUtils.h" 68 #include "nsDOMCSSDeclaration.h" 69 #include "nsDOMMutationObserver.h" 70 #include "nsDOMString.h" 71 #include "nsDOMStringMap.h" 72 #include "nsDOMTokenList.h" 73 #include "nsError.h" 74 #include "nsFocusManager.h" 75 #include "nsGkAtoms.h" 76 #include "nsGlobalWindowInner.h" 77 #include "nsHTMLDocument.h" 78 #include "nsHTMLParts.h" 79 #include "nsIFormControl.h" 80 #include "nsIFrameInlines.h" 81 #include "nsILayoutHistoryState.h" 82 #include "nsIPrincipal.h" 83 #include "nsIWidget.h" 84 #include "nsLayoutUtils.h" 85 #include "nsPIDOMWindow.h" 86 #include "nsPresContext.h" 87 #include "nsQueryObject.h" 88 #include "nsRange.h" 89 #include "nsString.h" 90 #include "nsStyleUtil.h" 91 #include "nsTableCellFrame.h" 92 #include "nsTextNode.h" 93 #include "nsThreadUtils.h" 94 #include "nscore.h" 95 96 using namespace mozilla; 97 using namespace mozilla::dom; 98 99 static const uint8_t NS_INPUTMODE_NONE = 1; 100 static const uint8_t NS_INPUTMODE_TEXT = 2; 101 static const uint8_t NS_INPUTMODE_TEL = 3; 102 static const uint8_t NS_INPUTMODE_URL = 4; 103 static const uint8_t NS_INPUTMODE_EMAIL = 5; 104 static const uint8_t NS_INPUTMODE_NUMERIC = 6; 105 static const uint8_t NS_INPUTMODE_DECIMAL = 7; 106 static const uint8_t NS_INPUTMODE_SEARCH = 8; 107 108 static constexpr nsAttrValue::EnumTableEntry kInputmodeTable[] = { 109 {"none", NS_INPUTMODE_NONE}, {"text", NS_INPUTMODE_TEXT}, 110 {"tel", NS_INPUTMODE_TEL}, {"url", NS_INPUTMODE_URL}, 111 {"email", NS_INPUTMODE_EMAIL}, {"numeric", NS_INPUTMODE_NUMERIC}, 112 {"decimal", NS_INPUTMODE_DECIMAL}, {"search", NS_INPUTMODE_SEARCH}, 113 }; 114 115 static const uint8_t NS_ENTERKEYHINT_ENTER = 1; 116 static const uint8_t NS_ENTERKEYHINT_DONE = 2; 117 static const uint8_t NS_ENTERKEYHINT_GO = 3; 118 static const uint8_t NS_ENTERKEYHINT_NEXT = 4; 119 static const uint8_t NS_ENTERKEYHINT_PREVIOUS = 5; 120 static const uint8_t NS_ENTERKEYHINT_SEARCH = 6; 121 static const uint8_t NS_ENTERKEYHINT_SEND = 7; 122 123 static constexpr nsAttrValue::EnumTableEntry kEnterKeyHintTable[] = { 124 {"enter", NS_ENTERKEYHINT_ENTER}, 125 {"done", NS_ENTERKEYHINT_DONE}, 126 {"go", NS_ENTERKEYHINT_GO}, 127 {"next", NS_ENTERKEYHINT_NEXT}, 128 {"previous", NS_ENTERKEYHINT_PREVIOUS}, 129 {"search", NS_ENTERKEYHINT_SEARCH}, 130 {"send", NS_ENTERKEYHINT_SEND}, 131 }; 132 133 static const uint8_t NS_AUTOCAPITALIZE_NONE = 1; 134 static const uint8_t NS_AUTOCAPITALIZE_SENTENCES = 2; 135 static const uint8_t NS_AUTOCAPITALIZE_WORDS = 3; 136 static const uint8_t NS_AUTOCAPITALIZE_CHARACTERS = 4; 137 138 static constexpr nsAttrValue::EnumTableEntry kAutocapitalizeTable[] = { 139 {"none", NS_AUTOCAPITALIZE_NONE}, 140 {"sentences", NS_AUTOCAPITALIZE_SENTENCES}, 141 {"words", NS_AUTOCAPITALIZE_WORDS}, 142 {"characters", NS_AUTOCAPITALIZE_CHARACTERS}, 143 {"off", NS_AUTOCAPITALIZE_NONE}, 144 {"on", NS_AUTOCAPITALIZE_SENTENCES}, 145 {"", 0}, 146 }; 147 148 static constexpr const nsAttrValue::EnumTableEntry* kDefaultAutocapitalize = 149 &kAutocapitalizeTable[1]; 150 151 nsresult nsGenericHTMLElement::CopyInnerTo(Element* aDst) { 152 MOZ_ASSERT(!aDst->GetUncomposedDoc(), 153 "Should not CopyInnerTo an Element in a document"); 154 155 auto reparse = aDst->OwnerDoc() == OwnerDoc() ? ReparseAttributes::No 156 : ReparseAttributes::Yes; 157 nsresult rv = Element::CopyInnerTo(aDst, reparse); 158 NS_ENSURE_SUCCESS(rv, rv); 159 160 // cloning a node must retain its internal nonce slot 161 nsString* nonce = static_cast<nsString*>(GetProperty(nsGkAtoms::nonce)); 162 if (nonce) { 163 static_cast<nsGenericHTMLElement*>(aDst)->SetNonce(*nonce); 164 } 165 return NS_OK; 166 } 167 168 static constexpr nsAttrValue::EnumTableEntry kDirTable[] = { 169 {"ltr", Directionality::Ltr}, 170 {"rtl", Directionality::Rtl}, 171 {"auto", Directionality::Auto}, 172 }; 173 174 namespace { 175 // See <https://html.spec.whatwg.org/#the-popover-attribute>. 176 enum class PopoverAttributeKeyword : uint8_t { Auto, EmptyString, Manual }; 177 178 static constexpr const char kPopoverAttributeValueAuto[] = "auto"; 179 static constexpr const char kPopoverAttributeValueEmptyString[] = ""; 180 static constexpr const char kPopoverAttributeValueManual[] = "manual"; 181 182 static constexpr nsAttrValue::EnumTableEntry kPopoverTable[] = { 183 {kPopoverAttributeValueAuto, PopoverAttributeKeyword::Auto}, 184 {kPopoverAttributeValueEmptyString, PopoverAttributeKeyword::EmptyString}, 185 {kPopoverAttributeValueManual, PopoverAttributeKeyword::Manual}, 186 }; 187 188 // See <https://html.spec.whatwg.org/#the-popover-attribute>. 189 static const nsAttrValue::EnumTableEntry* kPopoverTableInvalidValueDefault = 190 &kPopoverTable[2]; 191 } // namespace 192 193 void nsGenericHTMLElement::GetFetchPriority(nsAString& aFetchPriority) const { 194 // <https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attributes>. 195 GetEnumAttr(nsGkAtoms::fetchpriority, kFetchPriorityAttributeValueAuto, 196 aFetchPriority); 197 } 198 199 /* static */ 200 FetchPriority nsGenericHTMLElement::ToFetchPriority(const nsAString& aValue) { 201 nsAttrValue attrValue; 202 ParseFetchPriority(aValue, attrValue); 203 MOZ_ASSERT(attrValue.Type() == nsAttrValue::eEnum); 204 return FetchPriority(attrValue.GetEnumValue()); 205 } 206 207 void nsGenericHTMLElement::AddToNameTable(nsAtom* aName) { 208 MOZ_ASSERT(HasName(), "Node doesn't have name?"); 209 Document* doc = GetUncomposedDoc(); 210 if (doc && !IsInNativeAnonymousSubtree()) { 211 doc->AddToNameTable(this, aName); 212 } 213 } 214 215 void nsGenericHTMLElement::RemoveFromNameTable() { 216 if (HasName() && CanHaveName(NodeInfo()->NameAtom())) { 217 if (Document* doc = GetUncomposedDoc()) { 218 doc->RemoveFromNameTable(this, 219 GetParsedAttr(nsGkAtoms::name)->GetAtomValue()); 220 } 221 } 222 } 223 224 void nsGenericHTMLElement::GetAccessKeyLabel(nsString& aLabel) { 225 nsAutoString suffix; 226 GetAccessKey(suffix); 227 if (!suffix.IsEmpty()) { 228 EventStateManager::GetAccessKeyLabelPrefix(this, aLabel); 229 aLabel.Append(suffix); 230 } 231 } 232 233 // https://html.spec.whatwg.org/#dom-hidden 234 void nsGenericHTMLElement::GetHidden( 235 Nullable<OwningBooleanOrUnrestrictedDoubleOrString>& aHidden) const { 236 OwningBooleanOrUnrestrictedDoubleOrString value; 237 // 1. If the hidden attribute is in the hidden until found state, then 238 // return "until-found". 239 nsAutoString result; 240 if (GetAttr(kNameSpaceID_None, nsGkAtoms::hidden, result)) { 241 if (StaticPrefs::dom_hidden_until_found_enabled() && 242 result.LowerCaseEqualsLiteral("until-found")) { 243 value.SetStringLiteral(u"until-found"); 244 } else { 245 // 2. If the hidden attribute is set, then return true. 246 value.SetAsBoolean() = true; 247 } 248 } else { 249 // 3. Return false. 250 value.SetAsBoolean() = false; 251 } 252 253 aHidden.SetValue(value); 254 } 255 256 // https://html.spec.whatwg.org/#dom-hidden 257 void nsGenericHTMLElement::SetHidden( 258 const Nullable<BooleanOrUnrestrictedDoubleOrString>& aHidden, 259 ErrorResult& aRv) { 260 // 4. Otherwise, if the given value is null, then remove the hidden attribute. 261 if (aHidden.IsNull()) { 262 return UnsetAttr(nsGkAtoms::hidden, aRv); 263 } 264 bool isHidden = true; 265 const auto& value = aHidden.Value(); 266 // 1. If the given value is a string that is an ASCII case-insensitive match 267 // for "until-found", then set the hidden attribute to "until-found". 268 if (value.IsString()) { 269 const nsAString& stringValue = value.GetAsString(); 270 // 3. Otherwise, if the given value is the empty string, then remove the 271 // hidden attribute. 272 if (stringValue.IsEmpty()) { 273 isHidden = false; 274 } else if (StaticPrefs::dom_hidden_until_found_enabled() && 275 stringValue.LowerCaseEqualsLiteral("until-found")) { 276 return SetAttr(nsGkAtoms::hidden, u"until-found"_ns, aRv); 277 } 278 } 279 // 2. Otherwise, if the given value is false, then remove the hidden 280 // attribute. 281 else if (value.IsBoolean()) { 282 if (!value.GetAsBoolean()) { 283 isHidden = false; 284 } 285 } 286 // 5. Otherwise, if the given value is 0, then remove the hidden attribute. 287 // 6. Otherwise, if the given value is NaN, then remove the hidden attribute. 288 else if (value.IsUnrestrictedDouble()) { 289 double d = value.GetAsUnrestrictedDouble(); 290 if (d == 0.0 || std::isnan(d)) { 291 isHidden = false; 292 } 293 } 294 295 // 7. Otherwise, set the hidden attribute to the empty string. 296 if (isHidden) { 297 aRv = SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, u""_ns, true); 298 } else { 299 aRv = UnsetAttr(kNameSpaceID_None, nsGkAtoms::hidden, true); 300 } 301 } 302 303 bool nsGenericHTMLElement::Spellcheck() { 304 // Has the state has been explicitly set? 305 nsIContent* node; 306 for (node = this; node; node = node->GetParent()) { 307 if (node->IsHTMLElement()) { 308 static Element::AttrValuesArray strings[] = {nsGkAtoms::_true, 309 nsGkAtoms::_false, nullptr}; 310 switch (node->AsElement()->FindAttrValueIn( 311 kNameSpaceID_None, nsGkAtoms::spellcheck, strings, eCaseMatters)) { 312 case 0: // spellcheck = "true" 313 return true; 314 case 1: // spellcheck = "false" 315 return false; 316 } 317 } 318 } 319 320 // contenteditable/designMode are spellchecked by default 321 if (IsEditable()) { 322 return true; 323 } 324 325 // Is this a chrome element? 326 if (nsContentUtils::IsChromeDoc(OwnerDoc())) { 327 return false; // Not spellchecked by default 328 } 329 330 // Anything else that's not a form control is not spellchecked by default 331 const nsIFormControl* formControl = GetAsFormControl(); 332 if (!formControl) { 333 return false; // Not spellchecked by default 334 } 335 336 // Is this a multiline plaintext input? 337 auto controlType = formControl->ControlType(); 338 if (controlType == FormControlType::Textarea) { 339 return true; // Spellchecked by default 340 } 341 342 // Is this anything other than an input text? 343 // Other inputs are not spellchecked. 344 if (controlType != FormControlType::InputText) { 345 return false; // Not spellchecked by default 346 } 347 348 // Does the user want input text spellchecked by default? 349 // NOTE: Do not reflect a pref value of 0 back to the DOM getter. 350 // The web page should not know if the user has disabled spellchecking. 351 // We'll catch this in the editor itself. 352 int32_t spellcheckLevel = StaticPrefs::layout_spellcheckDefault(); 353 return spellcheckLevel == 2; // "Spellcheck multi- and single-line" 354 } 355 356 bool nsGenericHTMLElement::Autocorrect() const { 357 return !AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocorrect, nsGkAtoms::OFF, 358 eIgnoreCase); 359 } 360 361 bool nsGenericHTMLElement::InNavQuirksMode(Document* aDoc) { 362 return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks; 363 } 364 365 void nsGenericHTMLElement::UpdateEditableState(bool aNotify) { 366 // XXX Should we do this only when in a document? 367 ContentEditableState state = GetContentEditableState(); 368 if (state != ContentEditableState::Inherit) { 369 SetEditableFlag(IsEditableState(state)); 370 UpdateReadOnlyState(aNotify); 371 return; 372 } 373 nsStyledElement::UpdateEditableState(aNotify); 374 } 375 376 nsresult nsGenericHTMLElement::BindToTree(BindContext& aContext, 377 nsINode& aParent) { 378 nsresult rv = nsGenericHTMLElementBase::BindToTree(aContext, aParent); 379 NS_ENSURE_SUCCESS(rv, rv); 380 381 if (IsInComposedDoc()) { 382 RegUnRegAccessKey(true); 383 } 384 385 if (IsInUncomposedDoc()) { 386 Document& doc = aContext.OwnerDoc(); 387 if (HasName() && CanHaveName(NodeInfo()->NameAtom())) { 388 doc.AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue()); 389 } 390 391 nsAtom* id = nullptr; 392 if (ShouldExposeIdAsHTMLDocumentProperty(this)) { 393 id = DoGetID(); 394 MOZ_ASSERT(id && id != nsGkAtoms::_empty); 395 doc.AddToDocumentNameTable(this, id); 396 } 397 if (ShouldExposeNameAsHTMLDocumentProperty(this)) { 398 nsAtom* name = GetParsedAttr(nsGkAtoms::name)->GetAtomValue(); 399 MOZ_ASSERT(name && name != nsGkAtoms::_empty); 400 // Make sure not to double-add if id and name are the same. 401 if (id != name) { 402 doc.AddToDocumentNameTable(this, name); 403 } 404 } 405 } 406 407 if (HasFlag(NODE_IS_EDITABLE) && 408 HasContentEditableAttrTrueOrPlainTextOnly() && IsInComposedDoc()) { 409 aContext.OwnerDoc().ChangeContentEditableCount(this, +1); 410 } 411 412 // Hide any nonce from the DOM, but keep the internal value of the 413 // nonce by copying and resetting the internal nonce value. 414 if (!aContext.IsMove() && HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && 415 IsInComposedDoc() && OwnerDoc()->GetBrowsingContext()) { 416 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( 417 "nsGenericHTMLElement::ResetNonce::Runnable", 418 [self = RefPtr<nsGenericHTMLElement>(this)]() { 419 nsAutoString nonce; 420 self->GetNonce(nonce); 421 self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, u""_ns, true); 422 self->SetNonce(nonce); 423 })); 424 } 425 426 // We need to consider a labels element is moved to another subtree 427 // with different root, it needs to update labels list and its root 428 // as well. 429 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); 430 if (slots && slots->mLabelsList) { 431 slots->mLabelsList->MaybeResetRoot(SubtreeRoot()); 432 } 433 434 return rv; 435 } 436 437 void nsGenericHTMLElement::UnbindFromTree(UnbindContext& aContext) { 438 if (IsInComposedDoc()) { 439 // https://html.spec.whatwg.org/#dom-trees:hide-popover-algorithm 440 // If removedNode's popover attribute is not in the no popover state, then 441 // run the hide popover algorithm given removedNode, false, false, and 442 // false. 443 if (!aContext.IsMove() && GetPopoverData()) { 444 HidePopoverWithoutRunningScript(); 445 } 446 RegUnRegAccessKey(false); 447 } 448 449 RemoveFromNameTable(); 450 451 if (Document* doc = GetUncomposedDoc()) { 452 nsAtom* id = nullptr; 453 if (ShouldExposeIdAsHTMLDocumentProperty(this)) { 454 id = DoGetID(); 455 MOZ_ASSERT(id && id != nsGkAtoms::_empty); 456 doc->RemoveFromDocumentNameTable(this, id); 457 } 458 if (ShouldExposeNameAsHTMLDocumentProperty(this)) { 459 nsAtom* name = GetParsedAttr(nsGkAtoms::name)->GetAtomValue(); 460 MOZ_ASSERT(name && name != nsGkAtoms::_empty); 461 // Make sure not to double-remove if id and name are the same. 462 if (id != name) { 463 doc->RemoveFromDocumentNameTable(this, name); 464 } 465 } 466 } 467 468 if (HasContentEditableAttrTrueOrPlainTextOnly()) { 469 if (Document* doc = GetComposedDoc()) { 470 doc->ChangeContentEditableCount(this, -1); 471 } 472 } 473 474 nsStyledElement::UnbindFromTree(aContext); 475 476 // Invalidate .labels list. It will be repopulated when used the next time. 477 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); 478 if (slots && slots->mLabelsList) { 479 slots->mLabelsList->MaybeResetRoot(SubtreeRoot()); 480 } 481 } 482 483 HTMLFormElement* nsGenericHTMLElement::FindAncestorForm( 484 HTMLFormElement* aCurrentForm) { 485 NS_ASSERTION(!HasAttr(nsGkAtoms::form) || IsHTMLElement(nsGkAtoms::img), 486 "FindAncestorForm should not be called if @form is set!"); 487 if (IsInNativeAnonymousSubtree()) { 488 return nullptr; 489 } 490 491 nsIContent* content = this; 492 while (content) { 493 // If the current ancestor is a form, return it as our form 494 if (content->IsHTMLElement(nsGkAtoms::form)) { 495 #ifdef DEBUG 496 if (!nsContentUtils::IsInSameAnonymousTree(this, content)) { 497 // It's possible that we started unbinding at |content| or 498 // some ancestor of it, and |content| and |this| used to all be 499 // anonymous. Check for this the hard way. 500 for (nsIContent* child = this; child != content; 501 child = child->GetParent()) { 502 NS_ASSERTION(child->ComputeIndexInParentContent().isSome(), 503 "Walked too far?"); 504 } 505 } 506 #endif 507 return static_cast<HTMLFormElement*>(content); 508 } 509 510 nsIContent* prevContent = content; 511 content = prevContent->GetParent(); 512 513 if (!content && aCurrentForm) { 514 // We got to the root of the subtree we're in, and we're being removed 515 // from the DOM (the only time we get into this method with a non-null 516 // aCurrentForm). Check whether aCurrentForm is in the same subtree. If 517 // it is, we want to return aCurrentForm, since this case means that 518 // we're one of those inputs-in-a-table that have a hacked mForm pointer 519 // and a subtree containing both us and the form got removed from the 520 // DOM. 521 if (aCurrentForm->IsInclusiveDescendantOf(prevContent)) { 522 return aCurrentForm; 523 } 524 } 525 } 526 527 return nullptr; 528 } 529 530 bool nsGenericHTMLElement::CheckHandleEventForAnchorsPreconditions( 531 EventChainVisitor& aVisitor) { 532 MOZ_ASSERT(nsCOMPtr<Link>(do_QueryObject(this)), 533 "should be called only when |this| implements |Link|"); 534 // When disconnected, only <a> should navigate away per 535 // https://html.spec.whatwg.org/#cannot-navigate 536 return IsInComposedDoc() || IsHTMLElement(nsGkAtoms::a); 537 } 538 539 void nsGenericHTMLElement::GetEventTargetParentForAnchors( 540 EventChainPreVisitor& aVisitor) { 541 nsGenericHTMLElementBase::GetEventTargetParent(aVisitor); 542 543 if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) { 544 return; 545 } 546 547 GetEventTargetParentForLinks(aVisitor); 548 } 549 550 nsresult nsGenericHTMLElement::PostHandleEventForAnchors( 551 EventChainPostVisitor& aVisitor) { 552 if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) { 553 return NS_OK; 554 } 555 556 return PostHandleEventForLinks(aVisitor); 557 } 558 559 bool nsGenericHTMLElement::IsHTMLLink(nsIURI** aURI) const { 560 MOZ_ASSERT(aURI, "Must provide aURI out param"); 561 562 *aURI = GetHrefURIForAnchors().take(); 563 // We promise out param is non-null if we return true, so base rv on it 564 return *aURI != nullptr; 565 } 566 567 already_AddRefed<nsIURI> nsGenericHTMLElement::GetHrefURIForAnchors() const { 568 // This is used by the three Link implementations and 569 // nsHTMLStyleElement. 570 571 // Get href= attribute (relative URI). 572 573 // We use the nsAttrValue's copy of the URI string to avoid copying. 574 nsCOMPtr<nsIURI> uri; 575 GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri)); 576 return uri.forget(); 577 } 578 579 void nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, 580 const nsAttrValue* aValue, 581 bool aNotify) { 582 if (aNamespaceID == kNameSpaceID_None) { 583 if (aName == nsGkAtoms::accesskey) { 584 // Have to unregister before clearing flag. See UnregAccessKey 585 RegUnRegAccessKey(false); 586 if (!aValue) { 587 UnsetFlags(NODE_HAS_ACCESSKEY); 588 } 589 } else if (aName == nsGkAtoms::name) { 590 // Have to do this before clearing flag. See RemoveFromNameTable 591 RemoveFromNameTable(); 592 593 nsAtom* exposedIdOnDocument = nullptr; 594 Document* doc = GetUncomposedDoc(); 595 if (doc) { 596 nsAtom* exposedNameOnDocument = 597 ShouldExposeNameAsHTMLDocumentProperty(this) 598 ? GetParsedAttr(nsGkAtoms::name)->GetAtomValue() 599 : nullptr; 600 exposedIdOnDocument = 601 ShouldExposeIdAsHTMLDocumentProperty(this) ? DoGetID() : nullptr; 602 if (exposedNameOnDocument && 603 exposedNameOnDocument != exposedIdOnDocument) { 604 MOZ_ASSERT(exposedNameOnDocument != nsGkAtoms::_empty); 605 doc->RemoveFromDocumentNameTable(this, exposedNameOnDocument); 606 } 607 } 608 if (!aValue || aValue->IsEmptyString()) { 609 ClearHasName(); 610 // The result of ShouldExposeIdAsHTMLDocumentProperty() might change 611 // after clearing the hasName flag. 612 if (doc && exposedIdOnDocument && 613 !ShouldExposeIdAsHTMLDocumentProperty(this)) { 614 doc->RemoveFromDocumentNameTable(this, exposedIdOnDocument); 615 } 616 } 617 } else if (aName == nsGkAtoms::id) { 618 if (Document* doc = GetUncomposedDoc()) { 619 nsAtom* exposedIdOnDocument = 620 ShouldExposeIdAsHTMLDocumentProperty(this) ? DoGetID() : nullptr; 621 nsAtom* exposedNameOnDocument = 622 ShouldExposeNameAsHTMLDocumentProperty(this) 623 ? GetParsedAttr(nsGkAtoms::name)->GetAtomValue() 624 : nullptr; 625 if (exposedIdOnDocument && 626 exposedIdOnDocument != exposedNameOnDocument) { 627 doc->RemoveFromDocumentNameTable(this, exposedIdOnDocument); 628 } 629 } 630 } else if (aName == nsGkAtoms::contenteditable) { 631 if (aValue) { 632 // Set this before the attribute is set so that any subclass code that 633 // runs before the attribute is set won't think we're missing a 634 // contenteditable attr when we actually have one. 635 SetMayHaveContentEditableAttr(); 636 } 637 } 638 if (!aValue && IsEventAttributeName(aName)) { 639 if (EventListenerManager* manager = GetExistingListenerManager()) { 640 manager->RemoveEventHandler(GetEventNameForAttr(aName)); 641 } 642 } 643 } 644 645 return nsGenericHTMLElementBase::BeforeSetAttr(aNamespaceID, aName, aValue, 646 aNotify); 647 } 648 649 namespace { 650 constexpr PopoverAttributeState ToPopoverAttributeState( 651 PopoverAttributeKeyword aPopoverAttributeKeyword) { 652 // See <https://html.spec.whatwg.org/#the-popover-attribute>. 653 switch (aPopoverAttributeKeyword) { 654 case PopoverAttributeKeyword::Auto: 655 return PopoverAttributeState::Auto; 656 case PopoverAttributeKeyword::EmptyString: 657 return PopoverAttributeState::Auto; 658 case PopoverAttributeKeyword::Manual: 659 return PopoverAttributeState::Manual; 660 default: { 661 MOZ_ASSERT_UNREACHABLE(); 662 return PopoverAttributeState::None; 663 } 664 } 665 } 666 } // namespace 667 668 void nsGenericHTMLElement::AfterSetPopoverAttr() { 669 auto mapPopoverState = [](const nsAttrValue* value) -> PopoverAttributeState { 670 if (value) { 671 MOZ_ASSERT(value->Type() == nsAttrValue::eEnum); 672 const auto popoverAttributeKeyword = 673 static_cast<PopoverAttributeKeyword>(value->GetEnumValue()); 674 return ToPopoverAttributeState(popoverAttributeKeyword); 675 } 676 677 // The missing value default is the no popover state, see 678 // <https://html.spec.whatwg.org/multipage/popover.html#attr-popover>. 679 return PopoverAttributeState::None; 680 }; 681 682 PopoverAttributeState newState = 683 mapPopoverState(GetParsedAttr(nsGkAtoms::popover)); 684 685 const PopoverAttributeState oldState = GetPopoverAttributeState(); 686 687 if (newState != oldState) { 688 PopoverPseudoStateUpdate(false, true); 689 690 if (IsPopoverOpen()) { 691 HidePopoverInternal(/* aFocusPreviousElement = */ true, 692 /* aFireEvents = */ true, /* aSource*/ nullptr, 693 IgnoreErrors()); 694 // Event handlers could have removed the popover attribute, or changed 695 // its value. 696 // https://github.com/whatwg/html/issues/9034 697 newState = mapPopoverState(GetParsedAttr(nsGkAtoms::popover)); 698 } 699 700 if (newState == PopoverAttributeState::None) { 701 ClearPopoverData(); 702 RemoveStates(ElementState::POPOVER_OPEN); 703 } else { 704 // TODO: what if `HidePopoverInternal` called `ShowPopup()`? 705 EnsurePopoverData().SetPopoverAttributeState(newState); 706 } 707 } 708 } 709 710 void nsGenericHTMLElement::OnAttrSetButNotChanged( 711 int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue, 712 bool aNotify) { 713 if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::popovertarget) { 714 ClearExplicitlySetAttrElement(aName); 715 } 716 return nsGenericHTMLElementBase::OnAttrSetButNotChanged(aNamespaceID, aName, 717 aValue, aNotify); 718 } 719 720 void nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, 721 const nsAttrValue* aValue, 722 const nsAttrValue* aOldValue, 723 nsIPrincipal* aMaybeScriptedPrincipal, 724 bool aNotify) { 725 if (aNamespaceID == kNameSpaceID_None) { 726 if (IsEventAttributeName(aName) && aValue) { 727 MOZ_ASSERT(aValue->Type() == nsAttrValue::eString || 728 aValue->Type() == nsAttrValue::eAtom, 729 "Expected string or atom value for script body"); 730 SetEventHandler(GetEventNameForAttr(aName), 731 nsAttrValueOrString(aValue).String()); 732 } else if (aNotify && aName == nsGkAtoms::spellcheck) { 733 SyncEditorsOnSubtree(this); 734 } else if (aName == nsGkAtoms::popover) { 735 nsContentUtils::AddScriptRunner( 736 NewRunnableMethod("nsGenericHTMLElement::AfterSetPopoverAttr", this, 737 &nsGenericHTMLElement::AfterSetPopoverAttr)); 738 } else if (aName == nsGkAtoms::popovertarget) { 739 ClearExplicitlySetAttrElement(aName); 740 } else if (aName == nsGkAtoms::dir) { 741 auto dir = Directionality::Ltr; 742 // A boolean tracking whether we need to recompute our directionality. 743 // This needs to happen after we update our internal "dir" attribute 744 // state but before we call SetDirectionalityOnDescendants. 745 bool recomputeDirectionality = false; 746 ElementState dirStates; 747 if (aValue && aValue->Type() == nsAttrValue::eEnum) { 748 SetHasValidDir(); 749 dirStates |= ElementState::HAS_DIR_ATTR; 750 auto dirValue = Directionality(aValue->GetEnumValue()); 751 if (dirValue == Directionality::Auto) { 752 dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO; 753 } else { 754 dir = dirValue; 755 SetDirectionality(dir, aNotify); 756 if (dirValue == Directionality::Ltr) { 757 dirStates |= ElementState::HAS_DIR_ATTR_LTR; 758 } else { 759 MOZ_ASSERT(dirValue == Directionality::Rtl); 760 dirStates |= ElementState::HAS_DIR_ATTR_RTL; 761 } 762 } 763 } else { 764 if (aValue) { 765 // We have a value, just not a valid one. 766 dirStates |= ElementState::HAS_DIR_ATTR; 767 } 768 ClearHasValidDir(); 769 if (NodeInfo()->Equals(nsGkAtoms::bdi)) { 770 dirStates |= ElementState::HAS_DIR_ATTR_LIKE_AUTO; 771 } else { 772 recomputeDirectionality = true; 773 } 774 } 775 // Now figure out what's changed about our dir states. 776 ElementState oldDirStates = State() & ElementState::DIR_ATTR_STATES; 777 ElementState changedStates = dirStates ^ oldDirStates; 778 if (!changedStates.IsEmpty()) { 779 ToggleStates(changedStates, aNotify); 780 } 781 if (recomputeDirectionality) { 782 dir = RecomputeDirectionality(this, aNotify); 783 } 784 SetDirectionalityOnDescendants(this, dir, aNotify); 785 } else if (aName == nsGkAtoms::contenteditable) { 786 const auto IsEditableExceptInherit = [](const nsAttrValue& aValue) { 787 return aValue.Equals(EmptyString(), eCaseMatters) || 788 aValue.Equals(u"true"_ns, eIgnoreCase) || 789 aValue.Equals(u"plaintext-only"_ns, eIgnoreCase); 790 }; 791 // FYI: Now, both HasContentEditableAttrTrueOrPlainTextOnly() and 792 // HasContentEditableAttrFalse() return true. Therefore, we need to clear 793 // one of them or both of them. 794 int32_t editableCountDelta = 0; 795 if (aOldValue && IsEditableExceptInherit(*aOldValue)) { 796 editableCountDelta = -1; 797 ClearHasContentEditableAttrTrueOrPlainTextOnly(); 798 } 799 if (!aValue) { 800 ClearMayHaveContentEditableAttr(); 801 } else if (IsEditableExceptInherit(*aValue)) { 802 ++editableCountDelta; 803 SetHasContentEditableAttrTrueOrPlainTextOnly(); 804 } 805 ChangeEditableState(editableCountDelta); 806 } else if (aName == nsGkAtoms::accesskey) { 807 if (aValue && !aValue->Equals(u""_ns, eIgnoreCase)) { 808 SetFlags(NODE_HAS_ACCESSKEY); 809 RegUnRegAccessKey(true); 810 } 811 } else if (aName == nsGkAtoms::inert) { 812 if (aValue) { 813 AddStates(ElementState::INERT); 814 } else { 815 RemoveStates(ElementState::INERT); 816 } 817 } else if (aName == nsGkAtoms::name) { 818 if (aValue && !aValue->Equals(u""_ns, eIgnoreCase)) { 819 // This may not be quite right because we can have subclass code run 820 // before here. But in practice subclasses don't care about this flag, 821 // and in particular selector matching does not care. Otherwise we'd 822 // want to handle it like we handle id attributes (in PreIdMaybeChange 823 // and PostIdMaybeChange). 824 SetHasName(); 825 if (CanHaveName(NodeInfo()->NameAtom())) { 826 AddToNameTable(aValue->GetAtomValue()); 827 } 828 if (Document* doc = GetUncomposedDoc()) { 829 if (ShouldExposeNameAsHTMLDocumentProperty(this)) { 830 nsAtom* id = ShouldExposeIdAsHTMLDocumentProperty(this) ? DoGetID() 831 : nullptr; 832 nsAtom* name = aValue->GetAtomValue(); 833 // Make sure not to double-add if id and name are the same 834 if (id != name) { 835 doc->AddToDocumentNameTable(this, name); 836 } 837 } 838 } 839 } 840 } else if (aName == nsGkAtoms::id) { 841 if (Document* doc = GetUncomposedDoc()) { 842 if (ShouldExposeIdAsHTMLDocumentProperty(this)) { 843 nsAtom* id = aValue->GetAtomValue(); 844 nsAtom* name = ShouldExposeNameAsHTMLDocumentProperty(this) 845 ? GetParsedAttr(nsGkAtoms::name)->GetAtomValue() 846 : nullptr; 847 if (id != name) { 848 doc->AddToDocumentNameTable(this, id); 849 } 850 } 851 } 852 } else if (aName == nsGkAtoms::inputmode || 853 aName == nsGkAtoms::enterkeyhint) { 854 if (nsFocusManager::GetFocusedElementStatic() == this) { 855 if (const nsPresContext* presContext = 856 GetPresContext(eForComposedDoc)) { 857 IMEContentObserver* observer = 858 IMEStateManager::GetActiveContentObserver(); 859 if (observer && observer->IsObserving(*presContext, this)) { 860 if (const RefPtr<EditorBase> editorBase = GetExtantEditor()) { 861 // If the TextControlState does not have a bound frame, 862 // TextEditor::GetPreferredIMEState() may fail. In such case, 863 // TextEditor will update IME state when it's (re)initialized 864 // later. 865 Result<IMEState, nsresult> newStateOrError = 866 editorBase->GetPreferredIMEState(); 867 if (MOZ_LIKELY(newStateOrError.isOk())) { 868 OwningNonNull<nsGenericHTMLElement> kungFuDeathGrip(*this); 869 IMEStateManager::UpdateIMEState( 870 newStateOrError.unwrap(), kungFuDeathGrip, *editorBase, 871 {IMEStateManager::UpdateIMEStateOption::ForceUpdate, 872 IMEStateManager::UpdateIMEStateOption:: 873 DontCommitComposition}); 874 } 875 } 876 } 877 } 878 } 879 } 880 881 // The nonce will be copied over to an internal slot and cleared from the 882 // Element within BindToTree to avoid CSS Selector nonce exfiltration if 883 // the CSP list contains a header-delivered CSP. 884 if (nsGkAtoms::nonce == aName) { 885 if (aValue) { 886 SetNonce(nsAttrValueOrString(aValue).String()); 887 if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) { 888 SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP); 889 } 890 } else { 891 RemoveNonce(); 892 } 893 } 894 } 895 896 return nsGenericHTMLElementBase::AfterSetAttr( 897 aNamespaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); 898 } 899 900 EventListenerManager* nsGenericHTMLElement::GetEventListenerManagerForAttr( 901 nsAtom* aAttrName, bool* aDefer) { 902 // Attributes on the body and frameset tags get set on the global object 903 if ((mNodeInfo->Equals(nsGkAtoms::body) || 904 mNodeInfo->Equals(nsGkAtoms::frameset)) && 905 // We only forward some event attributes from body/frameset to window 906 (0 907 #define EVENT(name_, id_, type_, struct_) /* nothing */ 908 #define FORWARDED_EVENT(name_, id_, type_, struct_) \ 909 || nsGkAtoms::on##name_ == aAttrName 910 #define WINDOW_EVENT FORWARDED_EVENT 911 #include "mozilla/EventNameList.h" // IWYU pragma: keep 912 #undef WINDOW_EVENT 913 #undef FORWARDED_EVENT 914 #undef EVENT 915 )) { 916 nsPIDOMWindowInner* win; 917 918 // If we have a document, and it has a window, add the event 919 // listener on the window (the inner window). If not, proceed as 920 // normal. 921 // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() here, 922 // override BindToTree for those classes and munge event listeners there? 923 Document* document = OwnerDoc(); 924 925 *aDefer = false; 926 if ((win = document->GetInnerWindow())) { 927 nsCOMPtr<EventTarget> piTarget(do_QueryInterface(win)); 928 929 return piTarget->GetOrCreateListenerManager(); 930 } 931 932 return nullptr; 933 } 934 935 return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName, 936 aDefer); 937 } 938 939 #define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */ 940 #define FORWARDED_EVENT(name_, id_, type_, struct_) \ 941 EventHandlerNonNull* nsGenericHTMLElement::GetOn##name_() { \ 942 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \ 943 /* XXXbz note to self: add tests for this! */ \ 944 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \ 945 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \ 946 return globalWin->GetOn##name_(); \ 947 } \ 948 return nullptr; \ 949 } \ 950 \ 951 return nsINode::GetOn##name_(); \ 952 } \ 953 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \ 954 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \ 955 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \ 956 if (!win) { \ 957 return; \ 958 } \ 959 \ 960 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \ 961 return globalWin->SetOn##name_(handler); \ 962 } \ 963 \ 964 return nsINode::SetOn##name_(handler); \ 965 } 966 #define ERROR_EVENT(name_, id_, type_, struct_) \ 967 already_AddRefed<EventHandlerNonNull> nsGenericHTMLElement::GetOn##name_() { \ 968 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \ 969 /* XXXbz note to self: add tests for this! */ \ 970 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \ 971 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \ 972 OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_(); \ 973 if (errorHandler) { \ 974 RefPtr<EventHandlerNonNull> handler = \ 975 new EventHandlerNonNull(errorHandler); \ 976 return handler.forget(); \ 977 } \ 978 } \ 979 return nullptr; \ 980 } \ 981 \ 982 RefPtr<EventHandlerNonNull> handler = nsINode::GetOn##name_(); \ 983 return handler.forget(); \ 984 } \ 985 void nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) { \ 986 if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \ 987 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \ 988 if (!win) { \ 989 return; \ 990 } \ 991 \ 992 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \ 993 RefPtr<OnErrorEventHandlerNonNull> errorHandler; \ 994 if (handler) { \ 995 errorHandler = new OnErrorEventHandlerNonNull(handler); \ 996 } \ 997 return globalWin->SetOn##name_(errorHandler); \ 998 } \ 999 \ 1000 return nsINode::SetOn##name_(handler); \ 1001 } 1002 #include "mozilla/EventNameList.h" // IWYU pragma: keep 1003 #undef ERROR_EVENT 1004 #undef FORWARDED_EVENT 1005 #undef EVENT 1006 1007 void nsGenericHTMLElement::GetBaseTarget(nsAString& aBaseTarget) const { 1008 OwnerDoc()->GetBaseTarget(aBaseTarget); 1009 } 1010 1011 //---------------------------------------------------------------------- 1012 1013 bool nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID, 1014 nsAtom* aAttribute, 1015 const nsAString& aValue, 1016 nsIPrincipal* aMaybeScriptedPrincipal, 1017 nsAttrValue& aResult) { 1018 if (aNamespaceID == kNameSpaceID_None) { 1019 if (aAttribute == nsGkAtoms::dir) { 1020 return aResult.ParseEnumValue(aValue, kDirTable, false); 1021 } 1022 1023 if (aAttribute == nsGkAtoms::popover) { 1024 return aResult.ParseEnumValue(aValue, kPopoverTable, false, 1025 kPopoverTableInvalidValueDefault); 1026 } 1027 1028 if (aAttribute == nsGkAtoms::tabindex) { 1029 return aResult.ParseIntValue(aValue); 1030 } 1031 1032 if (aAttribute == nsGkAtoms::referrerpolicy) { 1033 return ParseReferrerAttribute(aValue, aResult); 1034 } 1035 1036 if (aAttribute == nsGkAtoms::name) { 1037 // Store name as an atom. name="" means that the element has no name, 1038 // not that it has an empty string as the name. 1039 if (aValue.IsEmpty()) { 1040 return false; 1041 } 1042 aResult.ParseAtom(aValue); 1043 return true; 1044 } 1045 1046 if (aAttribute == nsGkAtoms::contenteditable || 1047 aAttribute == nsGkAtoms::translate) { 1048 aResult.ParseAtom(aValue); 1049 return true; 1050 } 1051 1052 if (aAttribute == nsGkAtoms::rel) { 1053 aResult.ParseAtomArray(aValue); 1054 return true; 1055 } 1056 1057 if (aAttribute == nsGkAtoms::inputmode) { 1058 return aResult.ParseEnumValue(aValue, kInputmodeTable, false); 1059 } 1060 1061 if (aAttribute == nsGkAtoms::enterkeyhint) { 1062 return aResult.ParseEnumValue(aValue, kEnterKeyHintTable, false); 1063 } 1064 1065 if (aAttribute == nsGkAtoms::autocapitalize) { 1066 return aResult.ParseEnumValue(aValue, kAutocapitalizeTable, false); 1067 } 1068 } 1069 1070 return nsGenericHTMLElementBase::ParseAttribute( 1071 aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); 1072 } 1073 1074 bool nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID, 1075 nsAtom* aAttribute, 1076 const nsAString& aValue, 1077 nsAttrValue& aResult) { 1078 if (aNamespaceID == kNameSpaceID_None && 1079 aAttribute == nsGkAtoms::background && !aValue.IsEmpty()) { 1080 // Resolve url to an absolute url 1081 Document* doc = OwnerDoc(); 1082 nsCOMPtr<nsIURI> uri; 1083 nsresult rv = nsContentUtils::NewURIWithDocumentCharset( 1084 getter_AddRefs(uri), aValue, doc, GetBaseURI()); 1085 if (NS_FAILED(rv)) { 1086 return false; 1087 } 1088 aResult.SetTo(uri, &aValue); 1089 return true; 1090 } 1091 1092 return false; 1093 } 1094 1095 bool nsGenericHTMLElement::IsAttributeMapped(const nsAtom* aAttribute) const { 1096 static const MappedAttributeEntry* const map[] = {sCommonAttributeMap}; 1097 1098 return FindAttributeDependence(aAttribute, map); 1099 } 1100 1101 nsMapRuleToAttributesFunc nsGenericHTMLElement::GetAttributeMappingFunction() 1102 const { 1103 return &MapCommonAttributesInto; 1104 } 1105 1106 // Represents a possible value for the "align" attribute. Different 1107 // elements may support different subsets of these values and map 1108 // them into different CSS properties. 1109 enum class HTMLAlignValue : uint8_t { 1110 Left, 1111 Right, 1112 Top, 1113 Middle, 1114 Bottom, 1115 Center, 1116 Baseline, 1117 TextTop, 1118 AbsMiddle, 1119 AbsCenter, 1120 AbsBottom, 1121 Justify, 1122 }; 1123 1124 // clang-format off 1125 static constexpr nsAttrValue::EnumTableEntry kDivAlignTable[] = { 1126 {"left", HTMLAlignValue::Left}, 1127 {"right", HTMLAlignValue::Right}, 1128 {"center", HTMLAlignValue::Center}, 1129 {"middle", HTMLAlignValue::Middle}, 1130 {"justify", HTMLAlignValue::Justify}, 1131 }; 1132 // clang-format on 1133 1134 static constexpr nsAttrValue::EnumTableEntry kFrameborderTable[] = { 1135 {"yes", FrameBorderProperty::Yes}, 1136 {"no", FrameBorderProperty::No}, 1137 {"1", FrameBorderProperty::One}, 1138 {"0", FrameBorderProperty::Zero}, 1139 }; 1140 1141 // TODO(emilio): Nobody uses the parsed attribute here. 1142 static constexpr nsAttrValue::EnumTableEntry kScrollingTable[] = { 1143 {"yes", ScrollingAttribute::Yes}, 1144 {"no", ScrollingAttribute::No}, 1145 {"on", ScrollingAttribute::On}, 1146 {"off", ScrollingAttribute::Off}, 1147 {"scroll", ScrollingAttribute::Scroll}, 1148 {"noscroll", ScrollingAttribute::Noscroll}, 1149 {"auto", ScrollingAttribute::Auto}, 1150 }; 1151 1152 static constexpr nsAttrValue::EnumTableEntry kTableVAlignTable[] = { 1153 {"top", TableCellAlignment::Top}, 1154 {"middle", TableCellAlignment::Middle}, 1155 {"bottom", TableCellAlignment::Bottom}, 1156 {"baseline", TableCellAlignment::Baseline}, 1157 }; 1158 1159 static constexpr nsAttrValue::EnumTableEntry kAlignTable[] = { 1160 {"left", HTMLAlignValue::Left}, 1161 {"right", HTMLAlignValue::Right}, 1162 {"top", HTMLAlignValue::Top}, 1163 {"middle", HTMLAlignValue::Middle}, 1164 {"bottom", HTMLAlignValue::Bottom}, 1165 {"center", HTMLAlignValue::Center}, 1166 {"baseline", HTMLAlignValue::Baseline}, 1167 {"texttop", HTMLAlignValue::TextTop}, 1168 {"absmiddle", HTMLAlignValue::AbsMiddle}, 1169 {"abscenter", HTMLAlignValue::AbsCenter}, 1170 {"absbottom", HTMLAlignValue::AbsBottom}, 1171 }; 1172 1173 bool nsGenericHTMLElement::ParseAlignValue(const nsAString& aString, 1174 nsAttrValue& aResult) { 1175 return aResult.ParseEnumValue(aString, kAlignTable, false); 1176 } 1177 1178 //---------------------------------------- 1179 1180 static constexpr nsAttrValue::EnumTableEntry kTableHAlignTable[] = { 1181 {"left", HTMLAlignValue::Left}, 1182 {"right", HTMLAlignValue::Right}, 1183 {"center", HTMLAlignValue::Center}, 1184 }; 1185 1186 bool nsGenericHTMLElement::ParseTableHAlignValue(const nsAString& aString, 1187 nsAttrValue& aResult) { 1188 return aResult.ParseEnumValue(aString, kTableHAlignTable, false); 1189 } 1190 1191 //---------------------------------------- 1192 1193 // This table is used for td, th, tr, col, thead, tbody and tfoot. 1194 static constexpr nsAttrValue::EnumTableEntry kTableCellHAlignTable[] = { 1195 {"left", HTMLAlignValue::Left}, 1196 {"right", HTMLAlignValue::Right}, 1197 {"center", HTMLAlignValue::Center}, 1198 {"justify", HTMLAlignValue::Justify}, 1199 {"middle", HTMLAlignValue::Middle}, 1200 {"absmiddle", HTMLAlignValue::AbsMiddle}, 1201 }; 1202 1203 bool nsGenericHTMLElement::ParseTableCellHAlignValue(const nsAString& aString, 1204 nsAttrValue& aResult) { 1205 return aResult.ParseEnumValue(aString, kTableCellHAlignTable, false); 1206 } 1207 1208 //---------------------------------------- 1209 1210 bool nsGenericHTMLElement::ParseTableVAlignValue(const nsAString& aString, 1211 nsAttrValue& aResult) { 1212 return aResult.ParseEnumValue(aString, kTableVAlignTable, false); 1213 } 1214 1215 bool nsGenericHTMLElement::ParseDivAlignValue(const nsAString& aString, 1216 nsAttrValue& aResult) { 1217 return aResult.ParseEnumValue(aString, kDivAlignTable, false); 1218 } 1219 1220 bool nsGenericHTMLElement::ParseImageAttribute(nsAtom* aAttribute, 1221 const nsAString& aString, 1222 nsAttrValue& aResult) { 1223 if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height || 1224 aAttribute == nsGkAtoms::hspace || aAttribute == nsGkAtoms::vspace) { 1225 return aResult.ParseHTMLDimension(aString); 1226 } 1227 if (aAttribute == nsGkAtoms::border) { 1228 return aResult.ParseNonNegativeIntValue(aString); 1229 } 1230 return false; 1231 } 1232 1233 static constexpr nsAttrValue::EnumTableEntry kReferrerPolicyTable[] = { 1234 {GetEnumString(ReferrerPolicy::No_referrer).get(), 1235 static_cast<int16_t>(ReferrerPolicy::No_referrer)}, 1236 {GetEnumString(ReferrerPolicy::Origin).get(), 1237 static_cast<int16_t>(ReferrerPolicy::Origin)}, 1238 {GetEnumString(ReferrerPolicy::Origin_when_cross_origin).get(), 1239 static_cast<int16_t>(ReferrerPolicy::Origin_when_cross_origin)}, 1240 {GetEnumString(ReferrerPolicy::No_referrer_when_downgrade).get(), 1241 static_cast<int16_t>(ReferrerPolicy::No_referrer_when_downgrade)}, 1242 {GetEnumString(ReferrerPolicy::Unsafe_url).get(), 1243 static_cast<int16_t>(ReferrerPolicy::Unsafe_url)}, 1244 {GetEnumString(ReferrerPolicy::Strict_origin).get(), 1245 static_cast<int16_t>(ReferrerPolicy::Strict_origin)}, 1246 {GetEnumString(ReferrerPolicy::Same_origin).get(), 1247 static_cast<int16_t>(ReferrerPolicy::Same_origin)}, 1248 {GetEnumString(ReferrerPolicy::Strict_origin_when_cross_origin).get(), 1249 static_cast<int16_t>(ReferrerPolicy::Strict_origin_when_cross_origin)}, 1250 }; 1251 1252 bool nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString, 1253 nsAttrValue& aResult) { 1254 using mozilla::dom::ReferrerInfo; 1255 return aResult.ParseEnumValue(aString, kReferrerPolicyTable, false); 1256 } 1257 1258 bool nsGenericHTMLElement::ParseFrameborderValue(const nsAString& aString, 1259 nsAttrValue& aResult) { 1260 return aResult.ParseEnumValue(aString, kFrameborderTable, false); 1261 } 1262 1263 bool nsGenericHTMLElement::ParseScrollingValue(const nsAString& aString, 1264 nsAttrValue& aResult) { 1265 return aResult.ParseEnumValue(aString, kScrollingTable, false); 1266 } 1267 1268 static inline void MapLangAttributeInto(MappedDeclarationsBuilder& aBuilder) { 1269 const nsAttrValue* langValue = aBuilder.GetAttr(nsGkAtoms::lang); 1270 if (!langValue) { 1271 return; 1272 } 1273 MOZ_ASSERT(langValue->Type() == nsAttrValue::eAtom); 1274 1275 // Adaptor for nsCString to expose the Buffer interface to Locale::ToString. 1276 class BufferAdaptor { 1277 public: 1278 using CharType = char; 1279 1280 explicit BufferAdaptor(nsCString& aString) : mString(aString) {} 1281 CharType* data() { return mString.BeginWriting(); } 1282 size_t capacity() const { return mString.Length(); } 1283 bool reserve(size_t aLen) { return mString.SetLength(aLen, fallible); } 1284 void written(size_t aLen) { mString.SetLength(aLen); } 1285 1286 private: 1287 nsCString& mString; 1288 }; 1289 1290 // Try parsing lang as a Locale and canonicalizing the subtags; if parsing 1291 // succeeds, we record the canonicalized version rather than the original, 1292 // so that code checking for particular codes can assume canonical casing. 1293 // Note that in some cases this will also map 3-character ISO 639-3 tags to 1294 // their corresponding 2-char ISO 639-1 tags. 1295 RefPtr<nsAtom> lang = langValue->GetAtomValue(); 1296 nsAtomCString langStr(lang); 1297 intl::Locale loc; 1298 if (intl::LocaleParser::TryParse(langStr, loc).isOk() && 1299 loc.Canonicalize().isOk()) { 1300 nsAutoCString canonical; 1301 BufferAdaptor buffer(canonical); 1302 if (loc.ToString(buffer).isOk() && canonical != langStr) { 1303 lang = NS_Atomize(canonical); 1304 } 1305 } 1306 1307 aBuilder.SetIdentAtomValueIfUnset(eCSSProperty__x_lang, lang); 1308 if (!aBuilder.PropertyIsSet(eCSSProperty_text_emphasis_position)) { 1309 if (nsStyleUtil::MatchesLanguagePrefix(lang, u"zh")) { 1310 aBuilder.SetKeywordValue(eCSSProperty_text_emphasis_position, 1311 StyleTextEmphasisPosition::UNDER._0); 1312 } else if (nsStyleUtil::MatchesLanguagePrefix(lang, u"ja") || 1313 nsStyleUtil::MatchesLanguagePrefix(lang, u"mn")) { 1314 // This branch is currently no part of the spec. 1315 // See bug 1040668 comment 69 and comment 75. 1316 aBuilder.SetKeywordValue(eCSSProperty_text_emphasis_position, 1317 StyleTextEmphasisPosition::OVER._0); 1318 } 1319 } 1320 } 1321 1322 /** 1323 * Handle attributes common to all html elements 1324 */ 1325 void nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden( 1326 MappedDeclarationsBuilder& aBuilder) { 1327 MapLangAttributeInto(aBuilder); 1328 } 1329 1330 void nsGenericHTMLElement::MapCommonAttributesInto( 1331 MappedDeclarationsBuilder& aBuilder) { 1332 MapCommonAttributesIntoExceptHidden(aBuilder); 1333 MOZ_ASSERT(!aBuilder.PropertyIsSet(eCSSProperty_display)); 1334 MOZ_ASSERT(!aBuilder.PropertyIsSet(eCSSProperty_content_visibility)); 1335 1336 if (const nsAttrValue* hidden = aBuilder.GetAttr(nsGkAtoms::hidden)) { 1337 if (StaticPrefs::dom_hidden_until_found_enabled() && 1338 hidden->Equals(nsGkAtoms::untilFound, eIgnoreCase)) { 1339 aBuilder.SetKeywordValue(eCSSProperty_content_visibility, 1340 StyleContentVisibility::Hidden); 1341 } else { 1342 aBuilder.SetKeywordValue(eCSSProperty_display, StyleDisplay::None._0); 1343 } 1344 } 1345 } 1346 1347 /* static */ 1348 const nsGenericHTMLElement::MappedAttributeEntry 1349 nsGenericHTMLElement::sCommonAttributeMap[] = {{nsGkAtoms::contenteditable}, 1350 {nsGkAtoms::lang}, 1351 {nsGkAtoms::hidden}, 1352 {nullptr}}; 1353 1354 /* static */ 1355 const Element::MappedAttributeEntry 1356 nsGenericHTMLElement::sImageMarginSizeAttributeMap[] = {{nsGkAtoms::width}, 1357 {nsGkAtoms::height}, 1358 {nsGkAtoms::hspace}, 1359 {nsGkAtoms::vspace}, 1360 {nullptr}}; 1361 1362 /* static */ 1363 const Element::MappedAttributeEntry 1364 nsGenericHTMLElement::sImageAlignAttributeMap[] = {{nsGkAtoms::align}, 1365 {nullptr}}; 1366 1367 /* static */ 1368 const Element::MappedAttributeEntry 1369 nsGenericHTMLElement::sDivAlignAttributeMap[] = {{nsGkAtoms::align}, 1370 {nullptr}}; 1371 1372 /* static */ 1373 const Element::MappedAttributeEntry 1374 nsGenericHTMLElement::sImageBorderAttributeMap[] = {{nsGkAtoms::border}, 1375 {nullptr}}; 1376 1377 /* static */ 1378 const Element::MappedAttributeEntry 1379 nsGenericHTMLElement::sBackgroundAttributeMap[] = { 1380 {nsGkAtoms::background}, {nsGkAtoms::bgcolor}, {nullptr}}; 1381 1382 /* static */ 1383 const Element::MappedAttributeEntry 1384 nsGenericHTMLElement::sBackgroundColorAttributeMap[] = { 1385 {nsGkAtoms::bgcolor}, {nullptr}}; 1386 1387 void nsGenericHTMLElement::MapImageAlignAttributeInto( 1388 MappedDeclarationsBuilder& aBuilder) { 1389 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align); 1390 if (value && value->Type() == nsAttrValue::eEnum) { 1391 switch (HTMLAlignValue(value->GetEnumValue())) { 1392 case HTMLAlignValue::Left: 1393 aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Left); 1394 break; 1395 case HTMLAlignValue::Right: 1396 aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Right); 1397 break; 1398 case HTMLAlignValue::TextTop: 1399 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, 1400 StyleVerticalAlignKeyword::TextTop); 1401 break; 1402 case HTMLAlignValue::Top: 1403 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, 1404 StyleVerticalAlignKeyword::Top); 1405 break; 1406 case HTMLAlignValue::Middle: 1407 case HTMLAlignValue::Center: 1408 aBuilder.SetKeywordValue( 1409 eCSSProperty_vertical_align, 1410 StyleVerticalAlignKeyword::MozMiddleWithBaseline); 1411 break; 1412 case HTMLAlignValue::AbsMiddle: 1413 case HTMLAlignValue::AbsCenter: 1414 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, 1415 StyleVerticalAlignKeyword::Middle); 1416 break; 1417 case HTMLAlignValue::AbsBottom: 1418 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, 1419 StyleVerticalAlignKeyword::Bottom); 1420 break; 1421 case HTMLAlignValue::Bottom: // Intentionally mapped to `baseline` 1422 case HTMLAlignValue::Baseline: 1423 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, 1424 StyleVerticalAlignKeyword::Baseline); 1425 break; 1426 default: 1427 MOZ_ASSERT_UNREACHABLE("Unexpected align value"); 1428 break; 1429 } 1430 } 1431 } 1432 1433 void nsGenericHTMLElement::MapDivAlignAttributeInto( 1434 MappedDeclarationsBuilder& aBuilder) { 1435 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align); 1436 if (value && value->Type() == nsAttrValue::eEnum) { 1437 switch (HTMLAlignValue(value->GetEnumValue())) { 1438 case HTMLAlignValue::Left: 1439 aBuilder.SetKeywordValue(eCSSProperty_text_align, 1440 StyleTextAlign::MozLeft); 1441 break; 1442 case HTMLAlignValue::Right: 1443 aBuilder.SetKeywordValue(eCSSProperty_text_align, 1444 StyleTextAlign::MozRight); 1445 break; 1446 case HTMLAlignValue::Center: 1447 case HTMLAlignValue::Middle: 1448 aBuilder.SetKeywordValue(eCSSProperty_text_align, 1449 StyleTextAlign::MozCenter); 1450 break; 1451 case HTMLAlignValue::Justify: 1452 aBuilder.SetKeywordValue(eCSSProperty_text_align, 1453 StyleTextAlign::Justify); 1454 break; 1455 default: 1456 MOZ_ASSERT_UNREACHABLE("Unexpected align value"); 1457 break; 1458 } 1459 } 1460 } 1461 1462 void nsGenericHTMLElement::MapTableVAlignAttributeInto( 1463 MappedDeclarationsBuilder& aBuilder) { 1464 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::valign); 1465 if (value && value->Type() == nsAttrValue::eEnum) { 1466 switch (TableCellAlignment(value->GetEnumValue())) { 1467 case TableCellAlignment::Top: 1468 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, 1469 StyleVerticalAlignKeyword::Top); 1470 break; 1471 case TableCellAlignment::Middle: 1472 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, 1473 StyleVerticalAlignKeyword::Middle); 1474 break; 1475 case TableCellAlignment::Bottom: 1476 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, 1477 StyleVerticalAlignKeyword::Bottom); 1478 break; 1479 case TableCellAlignment::Baseline: 1480 aBuilder.SetKeywordValue(eCSSProperty_vertical_align, 1481 StyleVerticalAlignKeyword::Baseline); 1482 break; 1483 } 1484 } 1485 } 1486 1487 void nsGenericHTMLElement::MapTableHAlignAttributeInto( 1488 MappedDeclarationsBuilder& aBuilder) { 1489 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align); 1490 if (value && value->Type() == nsAttrValue::eEnum) { 1491 switch (HTMLAlignValue(value->GetEnumValue())) { 1492 case HTMLAlignValue::Center: 1493 aBuilder.SetAutoValueIfUnset(eCSSProperty_margin_left); 1494 aBuilder.SetAutoValueIfUnset(eCSSProperty_margin_right); 1495 break; 1496 case HTMLAlignValue::Left: 1497 aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Left); 1498 break; 1499 case HTMLAlignValue::Right: 1500 aBuilder.SetKeywordValue(eCSSProperty_float, StyleFloat::Right); 1501 break; 1502 default: 1503 MOZ_ASSERT_UNREACHABLE("Unexpected align value"); 1504 break; 1505 } 1506 } 1507 } 1508 1509 void nsGenericHTMLElement::MapTableCellHAlignAttributeInto( 1510 MappedDeclarationsBuilder& aBuilder) { 1511 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::align); 1512 if (value && value->Type() == nsAttrValue::eEnum) { 1513 switch (HTMLAlignValue(value->GetEnumValue())) { 1514 case HTMLAlignValue::Left: 1515 aBuilder.SetKeywordValue(eCSSProperty_text_align, 1516 StyleTextAlign::MozLeft); 1517 break; 1518 case HTMLAlignValue::Right: 1519 aBuilder.SetKeywordValue(eCSSProperty_text_align, 1520 StyleTextAlign::MozRight); 1521 break; 1522 case HTMLAlignValue::Center: 1523 case HTMLAlignValue::Middle: 1524 aBuilder.SetKeywordValue(eCSSProperty_text_align, 1525 StyleTextAlign::MozCenter); 1526 break; 1527 case HTMLAlignValue::AbsMiddle: 1528 aBuilder.SetKeywordValue(eCSSProperty_text_align, 1529 StyleTextAlign::Center); 1530 break; 1531 case HTMLAlignValue::Justify: 1532 aBuilder.SetKeywordValue(eCSSProperty_text_align, 1533 StyleTextAlign::Justify); 1534 break; 1535 default: 1536 MOZ_ASSERT_UNREACHABLE("Unexpected align value"); 1537 break; 1538 } 1539 } 1540 } 1541 1542 void nsGenericHTMLElement::MapDimensionAttributeInto( 1543 MappedDeclarationsBuilder& aBuilder, NonCustomCSSPropertyId aProp, 1544 const nsAttrValue& aValue) { 1545 MOZ_ASSERT(!aBuilder.PropertyIsSet(aProp), 1546 "Why mapping the same property twice?"); 1547 if (aValue.Type() == nsAttrValue::eInteger) { 1548 return aBuilder.SetPixelValue(aProp, aValue.GetIntegerValue()); 1549 } 1550 if (aValue.Type() == nsAttrValue::ePercent) { 1551 return aBuilder.SetPercentValue(aProp, aValue.GetPercentValue()); 1552 } 1553 if (aValue.Type() == nsAttrValue::eDoubleValue) { 1554 return aBuilder.SetPixelValue(aProp, aValue.GetDoubleValue()); 1555 } 1556 } 1557 1558 void nsGenericHTMLElement::MapImageMarginAttributeInto( 1559 MappedDeclarationsBuilder& aBuilder) { 1560 // hspace: value 1561 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::hspace)) { 1562 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_left, *value); 1563 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_right, *value); 1564 } 1565 1566 // vspace: value 1567 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::vspace)) { 1568 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_top, *value); 1569 MapDimensionAttributeInto(aBuilder, eCSSProperty_margin_bottom, *value); 1570 } 1571 } 1572 1573 void nsGenericHTMLElement::MapWidthAttributeInto( 1574 MappedDeclarationsBuilder& aBuilder) { 1575 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::width)) { 1576 MapDimensionAttributeInto(aBuilder, eCSSProperty_width, *value); 1577 } 1578 } 1579 1580 void nsGenericHTMLElement::MapHeightAttributeInto( 1581 MappedDeclarationsBuilder& aBuilder) { 1582 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::height)) { 1583 MapDimensionAttributeInto(aBuilder, eCSSProperty_height, *value); 1584 } 1585 } 1586 1587 void nsGenericHTMLElement::DoMapAspectRatio( 1588 const nsAttrValue& aWidth, const nsAttrValue& aHeight, 1589 MappedDeclarationsBuilder& aBuilder) { 1590 Maybe<double> w; 1591 if (aWidth.Type() == nsAttrValue::eInteger) { 1592 w.emplace(aWidth.GetIntegerValue()); 1593 } else if (aWidth.Type() == nsAttrValue::eDoubleValue) { 1594 w.emplace(aWidth.GetDoubleValue()); 1595 } 1596 1597 Maybe<double> h; 1598 if (aHeight.Type() == nsAttrValue::eInteger) { 1599 h.emplace(aHeight.GetIntegerValue()); 1600 } else if (aHeight.Type() == nsAttrValue::eDoubleValue) { 1601 h.emplace(aHeight.GetDoubleValue()); 1602 } 1603 1604 if (w && h) { 1605 aBuilder.SetAspectRatio(*w, *h); 1606 } 1607 } 1608 1609 void nsGenericHTMLElement::MapImageSizeAttributesInto( 1610 MappedDeclarationsBuilder& aBuilder, MapAspectRatio aMapAspectRatio) { 1611 auto* width = aBuilder.GetAttr(nsGkAtoms::width); 1612 auto* height = aBuilder.GetAttr(nsGkAtoms::height); 1613 if (width) { 1614 MapDimensionAttributeInto(aBuilder, eCSSProperty_width, *width); 1615 } 1616 if (height) { 1617 MapDimensionAttributeInto(aBuilder, eCSSProperty_height, *height); 1618 } 1619 if (aMapAspectRatio == MapAspectRatio::Yes && width && height) { 1620 DoMapAspectRatio(*width, *height, aBuilder); 1621 } 1622 } 1623 1624 void nsGenericHTMLElement::MapAspectRatioInto( 1625 MappedDeclarationsBuilder& aBuilder) { 1626 auto* width = aBuilder.GetAttr(nsGkAtoms::width); 1627 auto* height = aBuilder.GetAttr(nsGkAtoms::height); 1628 if (width && height) { 1629 DoMapAspectRatio(*width, *height, aBuilder); 1630 } 1631 } 1632 1633 void nsGenericHTMLElement::MapImageBorderAttributeInto( 1634 MappedDeclarationsBuilder& aBuilder) { 1635 // border: pixels 1636 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::border); 1637 if (!value) return; 1638 1639 nscoord val = 0; 1640 if (value->Type() == nsAttrValue::eInteger) val = value->GetIntegerValue(); 1641 1642 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_top_width, (float)val); 1643 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_right_width, (float)val); 1644 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_bottom_width, (float)val); 1645 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_left_width, (float)val); 1646 1647 aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_top_style, 1648 StyleBorderStyle::Solid); 1649 aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_right_style, 1650 StyleBorderStyle::Solid); 1651 aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_bottom_style, 1652 StyleBorderStyle::Solid); 1653 aBuilder.SetKeywordValueIfUnset(eCSSProperty_border_left_style, 1654 StyleBorderStyle::Solid); 1655 1656 aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_top_color); 1657 aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_right_color); 1658 aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_bottom_color); 1659 aBuilder.SetCurrentColorIfUnset(eCSSProperty_border_left_color); 1660 } 1661 1662 void nsGenericHTMLElement::MapBackgroundInto( 1663 MappedDeclarationsBuilder& aBuilder) { 1664 if (!aBuilder.PropertyIsSet(eCSSProperty_background_image)) { 1665 // background 1666 if (const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::background)) { 1667 aBuilder.SetBackgroundImage(*value); 1668 } 1669 } 1670 } 1671 1672 void nsGenericHTMLElement::MapBGColorInto(MappedDeclarationsBuilder& aBuilder) { 1673 if (aBuilder.PropertyIsSet(eCSSProperty_background_color)) { 1674 return; 1675 } 1676 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::bgcolor); 1677 nscolor color; 1678 if (value && value->GetColorValue(color)) { 1679 aBuilder.SetColorValue(eCSSProperty_background_color, color); 1680 } 1681 } 1682 1683 void nsGenericHTMLElement::MapBackgroundAttributesInto( 1684 MappedDeclarationsBuilder& aBuilder) { 1685 MapBackgroundInto(aBuilder); 1686 MapBGColorInto(aBuilder); 1687 } 1688 1689 //---------------------------------------------------------------------- 1690 1691 int32_t nsGenericHTMLElement::GetIntAttr(nsAtom* aAttr, 1692 int32_t aDefault) const { 1693 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr); 1694 if (attrVal && attrVal->Type() == nsAttrValue::eInteger) { 1695 return attrVal->GetIntegerValue(); 1696 } 1697 return aDefault; 1698 } 1699 1700 nsresult nsGenericHTMLElement::SetIntAttr(nsAtom* aAttr, int32_t aValue) { 1701 nsAutoString value; 1702 value.AppendInt(aValue); 1703 1704 return SetAttr(kNameSpaceID_None, aAttr, value, true); 1705 } 1706 1707 uint32_t nsGenericHTMLElement::GetUnsignedIntAttr(nsAtom* aAttr, 1708 uint32_t aDefault) const { 1709 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr); 1710 if (!attrVal || attrVal->Type() != nsAttrValue::eInteger) { 1711 return aDefault; 1712 } 1713 1714 return attrVal->GetIntegerValue(); 1715 } 1716 1717 uint32_t nsGenericHTMLElement::GetDimensionAttrAsUnsignedInt( 1718 nsAtom* aAttr, uint32_t aDefault) const { 1719 const nsAttrValue* attrVal = mAttrs.GetAttr(aAttr); 1720 if (!attrVal) { 1721 return aDefault; 1722 } 1723 1724 if (attrVal->Type() == nsAttrValue::eInteger) { 1725 return attrVal->GetIntegerValue(); 1726 } 1727 1728 if (attrVal->Type() == nsAttrValue::ePercent) { 1729 // This is a nasty hack. When we parsed the value, we stored it as an 1730 // ePercent, not eInteger, because there was a '%' after it in the string. 1731 // But the spec says to basically re-parse the string as an integer. 1732 // Luckily, we can just return the value we have stored. But 1733 // GetPercentValue() divides it by 100, so we need to multiply it back. 1734 return uint32_t(attrVal->GetPercentValue() * 100.0f); 1735 } 1736 1737 if (attrVal->Type() == nsAttrValue::eDoubleValue) { 1738 return uint32_t(attrVal->GetDoubleValue()); 1739 } 1740 1741 // Unfortunately, the set of values that are valid dimensions is not a 1742 // superset of values that are valid unsigned ints. In particular "+100" is 1743 // not a valid dimension, but should parse as the unsigned int "100". So if 1744 // we got here and we don't have a valid dimension value, just try re-parsing 1745 // the string we have as an integer. 1746 nsAutoString val; 1747 attrVal->ToString(val); 1748 nsContentUtils::ParseHTMLIntegerResultFlags result; 1749 int32_t parsedInt = nsContentUtils::ParseHTMLInteger(val, &result); 1750 if ((result & nsContentUtils::eParseHTMLInteger_Error) || parsedInt < 0) { 1751 return aDefault; 1752 } 1753 1754 return parsedInt; 1755 } 1756 1757 void nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr, 1758 nsAString& aResult) const { 1759 nsCOMPtr<nsIURI> uri; 1760 const nsAttrValue* attr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri)); 1761 if (!attr) { 1762 aResult.Truncate(); 1763 return; 1764 } 1765 if (!uri) { 1766 // Just return the attr value 1767 attr->ToString(aResult); 1768 return; 1769 } 1770 nsAutoCString spec; 1771 uri->GetSpec(spec); 1772 CopyUTF8toUTF16(spec, aResult); 1773 } 1774 1775 void nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, nsAtom* aBaseAttr, 1776 nsACString& aResult) const { 1777 nsCOMPtr<nsIURI> uri; 1778 const nsAttrValue* attr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri)); 1779 if (!attr) { 1780 aResult.Truncate(); 1781 return; 1782 } 1783 if (!uri) { 1784 // Just return the attr value 1785 nsAutoString value; 1786 attr->ToString(value); 1787 CopyUTF16toUTF8(value, aResult); 1788 return; 1789 } 1790 uri->GetSpec(aResult); 1791 } 1792 1793 const nsAttrValue* nsGenericHTMLElement::GetURIAttr(nsAtom* aAttr, 1794 nsAtom* aBaseAttr, 1795 nsIURI** aURI) const { 1796 *aURI = nullptr; 1797 1798 const nsAttrValue* attr = mAttrs.GetAttr(aAttr); 1799 if (!attr) { 1800 return nullptr; 1801 } 1802 1803 nsCOMPtr<nsIURI> baseURI = GetBaseURI(); 1804 if (aBaseAttr) { 1805 nsAutoString baseAttrValue; 1806 if (GetAttr(aBaseAttr, baseAttrValue)) { 1807 nsCOMPtr<nsIURI> baseAttrURI; 1808 nsresult rv = nsContentUtils::NewURIWithDocumentCharset( 1809 getter_AddRefs(baseAttrURI), baseAttrValue, OwnerDoc(), baseURI); 1810 if (NS_FAILED(rv)) { 1811 return attr; 1812 } 1813 baseURI.swap(baseAttrURI); 1814 } 1815 } 1816 1817 // Don't care about return value. If it fails, we still want to 1818 // return true, and *aURI will be null. 1819 nsContentUtils::NewURIWithDocumentCharset( 1820 aURI, nsAttrValueOrString(attr).String(), OwnerDoc(), baseURI); 1821 return attr; 1822 } 1823 1824 bool nsGenericHTMLElement::IsContentEditable() const { 1825 for (const auto* element : InclusiveAncestorsOfType<nsGenericHTMLElement>()) { 1826 const ContentEditableState state = element->GetContentEditableState(); 1827 if (state != ContentEditableState::Inherit) { 1828 return IsEditableState(state); 1829 } 1830 } 1831 return false; 1832 } 1833 1834 bool nsGenericHTMLElement::IsLabelable() const { 1835 return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter); 1836 } 1837 1838 /* static */ 1839 bool nsGenericHTMLElement::MatchLabelsElement(Element* aElement, 1840 int32_t aNamespaceID, 1841 nsAtom* aAtom, void* aData) { 1842 HTMLLabelElement* element = HTMLLabelElement::FromNode(aElement); 1843 return element && element->GetControl() == aData; 1844 } 1845 1846 already_AddRefed<nsINodeList> nsGenericHTMLElement::Labels() { 1847 MOZ_ASSERT(IsLabelable(), 1848 "Labels() only allow labelable elements to use it."); 1849 nsExtendedDOMSlots* slots = ExtendedDOMSlots(); 1850 1851 if (!slots->mLabelsList) { 1852 slots->mLabelsList = 1853 new nsLabelsNodeList(SubtreeRoot(), MatchLabelsElement, nullptr, this); 1854 } 1855 1856 RefPtr<nsLabelsNodeList> labels = slots->mLabelsList; 1857 return labels.forget(); 1858 } 1859 1860 // static 1861 bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext* aCx, 1862 JSObject* aGlobal) { 1863 return TouchEvent::LegacyAPIEnabled(aCx, aGlobal); 1864 } 1865 1866 bool nsGenericHTMLElement::IsFormControlDefaultFocusable( 1867 IsFocusableFlags aFlags) const { 1868 if (!(aFlags & IsFocusableFlags::WithMouse)) { 1869 return true; 1870 } 1871 switch (StaticPrefs::accessibility_mouse_focuses_formcontrol()) { 1872 case 0: 1873 return false; 1874 case 1: 1875 return true; 1876 default: 1877 return !IsInChromeDocument(); 1878 } 1879 } 1880 1881 //---------------------------------------------------------------------- 1882 1883 nsGenericHTMLFormElement::nsGenericHTMLFormElement( 1884 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 1885 : nsGenericHTMLElement(std::move(aNodeInfo)) { 1886 // We should add the ElementState::ENABLED bit here as needed, but that 1887 // depends on our type, which is not initialized yet. So we have to do this 1888 // in subclasses. Same for a couple other bits. 1889 } 1890 1891 void nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm, 1892 bool aUnbindOrDelete) { 1893 MOZ_ASSERT(IsFormAssociatedElement()); 1894 1895 HTMLFormElement* form = GetFormInternal(); 1896 NS_ASSERTION((form != nullptr) == HasFlag(ADDED_TO_FORM), 1897 "Form control should have had flag set correctly"); 1898 1899 if (!form) { 1900 return; 1901 } 1902 1903 if (aRemoveFromForm) { 1904 nsAutoString nameVal, idVal; 1905 GetAttr(nsGkAtoms::name, nameVal); 1906 GetAttr(nsGkAtoms::id, idVal); 1907 1908 form->RemoveElement(this, true); 1909 1910 if (!nameVal.IsEmpty()) { 1911 form->RemoveElementFromTable(this, nameVal); 1912 } 1913 1914 if (!idVal.IsEmpty()) { 1915 form->RemoveElementFromTable(this, idVal); 1916 } 1917 } 1918 1919 UnsetFlags(ADDED_TO_FORM); 1920 SetFormInternal(nullptr, false); 1921 AfterClearForm(aUnbindOrDelete); 1922 } 1923 1924 nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext, 1925 nsINode& aParent) { 1926 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent); 1927 NS_ENSURE_SUCCESS(rv, rv); 1928 1929 if (IsFormAssociatedElement()) { 1930 // If @form is set, the element *has* to be in a composed document, 1931 // otherwise it wouldn't be possible to find an element with the 1932 // corresponding id. If @form isn't set, the element *has* to have a parent, 1933 // otherwise it wouldn't be possible to find a form ancestor. We should not 1934 // call UpdateFormOwner if none of these conditions are fulfilled. 1935 if (HasAttr(nsGkAtoms::form) ? IsInComposedDoc() : aParent.IsContent()) { 1936 UpdateFormOwner(true, nullptr); 1937 } 1938 } 1939 1940 // Set parent fieldset which should be used for the disabled state. 1941 UpdateFieldSet(false); 1942 return NS_OK; 1943 } 1944 1945 void nsGenericHTMLFormElement::UnbindFromTree(UnbindContext& aContext) { 1946 // Save state before doing anything else. 1947 SaveState(); 1948 1949 if (IsFormAssociatedElement()) { 1950 if (HTMLFormElement* form = GetFormInternal()) { 1951 // Might need to unset form 1952 if (aContext.IsUnbindRoot(this)) { 1953 // No more parent means no more form 1954 ClearForm(true, true); 1955 } else { 1956 // Recheck whether we should still have an form. 1957 if (HasAttr(nsGkAtoms::form) || !FindAncestorForm(form)) { 1958 ClearForm(true, true); 1959 } else { 1960 UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT); 1961 } 1962 } 1963 } 1964 1965 // We have to remove the form id observer if there was one. 1966 // We will re-add one later if needed (during bind to tree). 1967 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None, 1968 nsGkAtoms::form)) { 1969 RemoveFormIdObserver(); 1970 } 1971 } 1972 1973 nsGenericHTMLElement::UnbindFromTree(aContext); 1974 1975 // The element might not have a fieldset anymore. 1976 UpdateFieldSet(false); 1977 } 1978 1979 void nsGenericHTMLFormElement::BeforeSetAttr(int32_t aNameSpaceID, 1980 nsAtom* aName, 1981 const nsAttrValue* aValue, 1982 bool aNotify) { 1983 if (aNameSpaceID == kNameSpaceID_None && IsFormAssociatedElement()) { 1984 nsAutoString tmp; 1985 HTMLFormElement* form = GetFormInternal(); 1986 1987 // remove the control from the hashtable as needed 1988 1989 if (form && (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) { 1990 GetAttr(aName, tmp); 1991 1992 if (!tmp.IsEmpty()) { 1993 form->RemoveElementFromTable(this, tmp); 1994 } 1995 } 1996 1997 if (form && aName == nsGkAtoms::type) { 1998 GetAttr(nsGkAtoms::name, tmp); 1999 2000 if (!tmp.IsEmpty()) { 2001 form->RemoveElementFromTable(this, tmp); 2002 } 2003 2004 GetAttr(nsGkAtoms::id, tmp); 2005 2006 if (!tmp.IsEmpty()) { 2007 form->RemoveElementFromTable(this, tmp); 2008 } 2009 2010 form->RemoveElement(this, false); 2011 } 2012 2013 if (aName == nsGkAtoms::form) { 2014 // If @form isn't set or set to the empty string, there were no observer 2015 // so we don't have to remove it. 2016 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None, 2017 nsGkAtoms::form)) { 2018 // The current form id observer is no longer needed. 2019 // A new one may be added in AfterSetAttr. 2020 RemoveFormIdObserver(); 2021 } 2022 } 2023 } 2024 2025 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue, 2026 aNotify); 2027 } 2028 2029 void nsGenericHTMLFormElement::AfterSetAttr( 2030 int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, 2031 const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal, 2032 bool aNotify) { 2033 if (aNameSpaceID == kNameSpaceID_None && IsFormAssociatedElement()) { 2034 HTMLFormElement* form = GetFormInternal(); 2035 2036 // add the control to the hashtable as needed 2037 if (form && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) && 2038 aValue && !aValue->IsEmptyString()) { 2039 MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom, 2040 "Expected atom value for name/id"); 2041 form->AddElementToTable(this, 2042 nsDependentAtomString(aValue->GetAtomValue())); 2043 } 2044 2045 if (form && aName == nsGkAtoms::type) { 2046 nsAutoString tmp; 2047 2048 GetAttr(nsGkAtoms::name, tmp); 2049 2050 if (!tmp.IsEmpty()) { 2051 form->AddElementToTable(this, tmp); 2052 } 2053 2054 GetAttr(nsGkAtoms::id, tmp); 2055 2056 if (!tmp.IsEmpty()) { 2057 form->AddElementToTable(this, tmp); 2058 } 2059 2060 form->AddElement(this, false, aNotify); 2061 } 2062 2063 if (aName == nsGkAtoms::form) { 2064 // We need a new form id observer. 2065 DocumentOrShadowRoot* docOrShadow = 2066 GetUncomposedDocOrConnectedShadowRoot(); 2067 if (docOrShadow) { 2068 Element* formIdElement = nullptr; 2069 if (aValue && !aValue->IsEmptyString()) { 2070 formIdElement = AddFormIdObserver(); 2071 } 2072 2073 // Because we have a new @form value (or no more @form), we have to 2074 // update our form owner. 2075 UpdateFormOwner(false, formIdElement); 2076 } 2077 } 2078 } 2079 2080 return nsGenericHTMLElement::AfterSetAttr( 2081 aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); 2082 } 2083 2084 void nsGenericHTMLFormElement::ForgetFieldSet(nsIContent* aFieldset) { 2085 MOZ_DIAGNOSTIC_ASSERT(IsFormAssociatedElement()); 2086 if (GetFieldSetInternal() == aFieldset) { 2087 SetFieldSetInternal(nullptr); 2088 } 2089 } 2090 2091 Element* nsGenericHTMLFormElement::AddFormIdObserver() { 2092 MOZ_ASSERT(IsFormAssociatedElement()); 2093 2094 nsAutoString formId; 2095 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot(); 2096 GetAttr(nsGkAtoms::form, formId); 2097 NS_ASSERTION(!formId.IsEmpty(), 2098 "@form value should not be the empty string!"); 2099 RefPtr<nsAtom> atom = NS_Atomize(formId); 2100 2101 return docOrShadow->AddIDTargetObserver(atom, FormIdUpdated, this, false); 2102 } 2103 2104 void nsGenericHTMLFormElement::RemoveFormIdObserver() { 2105 MOZ_ASSERT(IsFormAssociatedElement()); 2106 2107 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot(); 2108 if (!docOrShadow) { 2109 return; 2110 } 2111 2112 nsAutoString formId; 2113 GetAttr(nsGkAtoms::form, formId); 2114 NS_ASSERTION(!formId.IsEmpty(), 2115 "@form value should not be the empty string!"); 2116 RefPtr<nsAtom> atom = NS_Atomize(formId); 2117 2118 docOrShadow->RemoveIDTargetObserver(atom, FormIdUpdated, this, false); 2119 } 2120 2121 /* static */ 2122 bool nsGenericHTMLFormElement::FormIdUpdated(Element* aOldElement, 2123 Element* aNewElement, 2124 void* aData) { 2125 nsGenericHTMLFormElement* element = 2126 static_cast<nsGenericHTMLFormElement*>(aData); 2127 2128 NS_ASSERTION(element->IsHTMLElement(), "aData should be an HTML element"); 2129 2130 element->UpdateFormOwner(false, aNewElement); 2131 2132 return true; 2133 } 2134 2135 bool nsGenericHTMLFormElement::IsElementDisabledForEvents(WidgetEvent* aEvent, 2136 nsIFrame* aFrame) { 2137 MOZ_ASSERT(aEvent); 2138 2139 // Allow dispatch of CustomEvent and untrusted Events. 2140 if (!aEvent->IsTrusted()) { 2141 return false; 2142 } 2143 2144 switch (aEvent->mMessage) { 2145 case eAnimationStart: 2146 case eAnimationEnd: 2147 case eAnimationIteration: 2148 case eAnimationCancel: 2149 case eFormChange: 2150 case eMouseMove: 2151 case eMouseOver: 2152 case eMouseOut: 2153 case eMouseEnter: 2154 case eMouseLeave: 2155 case ePointerMove: 2156 case ePointerOver: 2157 case ePointerOut: 2158 case ePointerEnter: 2159 case ePointerLeave: 2160 case eTransitionCancel: 2161 case eTransitionEnd: 2162 case eTransitionRun: 2163 case eTransitionStart: 2164 case eWheel: 2165 case eLegacyMouseLineOrPageScroll: 2166 case eLegacyMousePixelScroll: 2167 return false; 2168 case eFocus: 2169 case eBlur: 2170 case eFocusIn: 2171 case eFocusOut: 2172 case eKeyPress: 2173 case eKeyUp: 2174 case eKeyDown: 2175 if (StaticPrefs::dom_forms_always_allow_key_and_focus_events_enabled()) { 2176 return false; 2177 } 2178 [[fallthrough]]; 2179 case ePointerDown: 2180 case ePointerUp: 2181 case ePointerCancel: 2182 case ePointerGotCapture: 2183 case ePointerLostCapture: 2184 if (StaticPrefs::dom_forms_always_allow_pointer_events_enabled()) { 2185 return false; 2186 } 2187 [[fallthrough]]; 2188 default: 2189 break; 2190 } 2191 2192 if (aEvent->mSpecifiedEventType == nsGkAtoms::oninput) { 2193 return false; 2194 } 2195 2196 return IsDisabled(); 2197 } 2198 2199 void nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree, 2200 Element* aFormIdElement) { 2201 MOZ_ASSERT(IsFormAssociatedElement()); 2202 MOZ_ASSERT(!aBindToTree || !aFormIdElement, 2203 "aFormIdElement shouldn't be set if aBindToTree is true!"); 2204 2205 HTMLFormElement* form = GetFormInternal(); 2206 if (!aBindToTree) { 2207 ClearForm(true, false); 2208 form = nullptr; 2209 } 2210 2211 HTMLFormElement* oldForm = form; 2212 if (!form) { 2213 // If @form is set, we have to use that to find the form. 2214 nsAutoString formId; 2215 if (GetAttr(nsGkAtoms::form, formId)) { 2216 if (!formId.IsEmpty()) { 2217 Element* element = nullptr; 2218 2219 if (aBindToTree) { 2220 element = AddFormIdObserver(); 2221 } else { 2222 element = aFormIdElement; 2223 } 2224 2225 NS_ASSERTION(!IsInComposedDoc() || 2226 element == GetUncomposedDocOrConnectedShadowRoot() 2227 ->GetElementById(formId), 2228 "element should be equals to the current element " 2229 "associated with the id in @form!"); 2230 2231 if (element && element->IsHTMLElement(nsGkAtoms::form) && 2232 nsContentUtils::IsInSameAnonymousTree(this, element)) { 2233 form = static_cast<HTMLFormElement*>(element); 2234 SetFormInternal(form, aBindToTree); 2235 } 2236 } 2237 } else { 2238 // We now have a parent, so we may have picked up an ancestor form. Search 2239 // for it. Note that if form is already set we don't want to do this, 2240 // because that means someone (probably the content sink) has already set 2241 // it to the right value. Also note that even if being bound here didn't 2242 // change our parent, we still need to search, since our parent chain 2243 // probably changed _somewhere_. 2244 form = FindAncestorForm(); 2245 SetFormInternal(form, aBindToTree); 2246 } 2247 } 2248 2249 if (form && !HasFlag(ADDED_TO_FORM)) { 2250 // Now we need to add ourselves to the form 2251 nsAutoString nameVal, idVal; 2252 GetAttr(nsGkAtoms::name, nameVal); 2253 GetAttr(nsGkAtoms::id, idVal); 2254 2255 SetFlags(ADDED_TO_FORM); 2256 2257 // Notify only if we just found this form. 2258 form->AddElement(this, true, oldForm == nullptr); 2259 2260 if (!nameVal.IsEmpty()) { 2261 form->AddElementToTable(this, nameVal); 2262 } 2263 2264 if (!idVal.IsEmpty()) { 2265 form->AddElementToTable(this, idVal); 2266 } 2267 } 2268 } 2269 2270 void nsGenericHTMLFormElement::UpdateFieldSet(bool aNotify) { 2271 if (IsInNativeAnonymousSubtree() || !IsFormAssociatedElement()) { 2272 MOZ_ASSERT_IF(IsFormAssociatedElement(), !GetFieldSetInternal()); 2273 return; 2274 } 2275 2276 nsIContent* parent = nullptr; 2277 nsIContent* prev = nullptr; 2278 HTMLFieldSetElement* fieldset = GetFieldSetInternal(); 2279 2280 for (parent = GetParent(); parent; 2281 prev = parent, parent = parent->GetParent()) { 2282 HTMLFieldSetElement* parentFieldset = HTMLFieldSetElement::FromNode(parent); 2283 if (parentFieldset && (!prev || parentFieldset->GetFirstLegend() != prev)) { 2284 if (fieldset == parentFieldset) { 2285 // We already have the right fieldset; 2286 return; 2287 } 2288 2289 if (fieldset) { 2290 fieldset->RemoveElement(this); 2291 } 2292 SetFieldSetInternal(parentFieldset); 2293 parentFieldset->AddElement(this); 2294 2295 // The disabled state may have changed 2296 FieldSetDisabledChanged(aNotify); 2297 return; 2298 } 2299 } 2300 2301 // No fieldset found. 2302 if (fieldset) { 2303 fieldset->RemoveElement(this); 2304 SetFieldSetInternal(nullptr); 2305 // The disabled state may have changed 2306 FieldSetDisabledChanged(aNotify); 2307 } 2308 } 2309 2310 void nsGenericHTMLFormElement::UpdateDisabledState(bool aNotify) { 2311 if (!CanBeDisabled()) { 2312 return; 2313 } 2314 2315 HTMLFieldSetElement* fieldset = GetFieldSetInternal(); 2316 const bool isDisabled = 2317 HasAttr(nsGkAtoms::disabled) || (fieldset && fieldset->IsDisabled()); 2318 2319 const ElementState disabledStates = 2320 isDisabled ? ElementState::DISABLED : ElementState::ENABLED; 2321 2322 ElementState oldDisabledStates = State() & ElementState::DISABLED_STATES; 2323 ElementState changedStates = disabledStates ^ oldDisabledStates; 2324 2325 if (!changedStates.IsEmpty()) { 2326 ToggleStates(changedStates, aNotify); 2327 if (DoesReadWriteApply()) { 2328 // :disabled influences :read-only / :read-write. 2329 UpdateReadOnlyState(aNotify); 2330 } 2331 } 2332 } 2333 2334 bool nsGenericHTMLFormElement::IsReadOnlyInternal() const { 2335 if (DoesReadWriteApply()) { 2336 return IsDisabled() || GetBoolAttr(nsGkAtoms::readonly); 2337 } 2338 return nsGenericHTMLElement::IsReadOnlyInternal(); 2339 } 2340 2341 void nsGenericHTMLFormElement::FieldSetDisabledChanged(bool aNotify) { 2342 UpdateDisabledState(aNotify); 2343 } 2344 2345 void nsGenericHTMLFormElement::SaveSubtreeState() { 2346 SaveState(); 2347 2348 nsGenericHTMLElement::SaveSubtreeState(); 2349 } 2350 2351 //---------------------------------------------------------------------- 2352 2353 void nsGenericHTMLElement::Click(CallerType aCallerType) { 2354 if (HandlingClick()) { 2355 return; 2356 } 2357 2358 // There are two notions of disabled. 2359 // "disabled": 2360 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled 2361 // "actually disabled": 2362 // https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled 2363 // click() reads the former but IsDisabled() is for the latter. <fieldset> is 2364 // included only in the latter, so we exclude it here. 2365 // XXX(krosylight): What about <optgroup>? And should we add a separate method 2366 // for this? 2367 if (IsDisabled() && 2368 !(mNodeInfo->Equals(nsGkAtoms::fieldset) && 2369 StaticPrefs::dom_forms_fieldset_disable_only_descendants_enabled())) { 2370 return; 2371 } 2372 2373 // Strong in case the event kills it 2374 nsCOMPtr<Document> doc = GetComposedDoc(); 2375 2376 RefPtr<nsPresContext> context; 2377 if (doc) { 2378 PresShell* presShell = doc->GetPresShell(); 2379 if (!presShell) { 2380 // We need the nsPresContext for dispatching the click event. In some 2381 // rare cases we need to flush notifications to force creation of the 2382 // nsPresContext here (for example when a script calls button.click() 2383 // from script early during page load). We only flush the notifications 2384 // if the PresShell hasn't been created yet, to limit the performance 2385 // impact. 2386 doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames); 2387 presShell = doc->GetPresShell(); 2388 } 2389 if (presShell) { 2390 context = presShell->GetPresContext(); 2391 } 2392 } 2393 2394 SetHandlingClick(); 2395 2396 // Mark this event trusted if Click() is called from system code. 2397 WidgetPointerEvent event(aCallerType == CallerType::System, ePointerClick, 2398 nullptr); 2399 event.mFlags.mIsPositionless = true; 2400 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN; 2401 // pointerId definition in Pointer Events: 2402 // > The pointerId value of -1 MUST be reserved and used to indicate events 2403 // > that were generated by something other than a pointing device. 2404 event.pointerId = -1; 2405 2406 EventDispatcher::Dispatch(this, context, &event); 2407 2408 ClearHandlingClick(); 2409 } 2410 2411 bool nsGenericHTMLElement::IsHTMLFocusable(IsFocusableFlags aFlags, 2412 bool* aIsFocusable, 2413 int32_t* aTabIndex) { 2414 MOZ_ASSERT(aIsFocusable); 2415 MOZ_ASSERT(aTabIndex); 2416 if (ShadowRoot* root = GetShadowRoot()) { 2417 if (root->DelegatesFocus()) { 2418 *aIsFocusable = false; 2419 return true; 2420 } 2421 } 2422 2423 if (!IsInComposedDoc() || IsInDesignMode()) { 2424 // In designMode documents we only allow focusing the document. 2425 *aTabIndex = -1; 2426 *aIsFocusable = false; 2427 return true; 2428 } 2429 2430 *aTabIndex = TabIndex(); 2431 bool disabled = false; 2432 bool disallowOverridingFocusability = true; 2433 Maybe<int32_t> attrVal = GetTabIndexAttrValue(); 2434 if (IsEditingHost()) { 2435 // Editable roots should always be focusable. 2436 disallowOverridingFocusability = true; 2437 2438 // Ignore the disabled attribute in editable contentEditable/designMode 2439 // roots. 2440 if (attrVal.isNothing()) { 2441 // The default value for tabindex should be 0 for editable 2442 // contentEditable roots. 2443 *aTabIndex = 0; 2444 } 2445 } else { 2446 disallowOverridingFocusability = false; 2447 2448 // Just check for disabled attribute on form controls 2449 disabled = IsDisabled(); 2450 if (disabled) { 2451 *aTabIndex = -1; 2452 } 2453 } 2454 2455 // If a tabindex is specified at all, or the default tabindex is 0, we're 2456 // focusable. 2457 *aIsFocusable = (*aTabIndex >= 0 || (!disabled && attrVal.isSome())); 2458 return disallowOverridingFocusability; 2459 } 2460 2461 Result<bool, nsresult> nsGenericHTMLElement::PerformAccesskey( 2462 bool aKeyCausesActivation, bool aIsTrustedEvent) { 2463 RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc); 2464 if (!presContext) { 2465 return Err(NS_ERROR_UNEXPECTED); 2466 } 2467 2468 // It's hard to say what HTML4 wants us to do in all cases. 2469 bool focused = true; 2470 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 2471 fm->SetFocus(this, nsIFocusManager::FLAG_BYKEY); 2472 2473 // Return true if the element became the current focus within its window. 2474 nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow(); 2475 focused = window && window->GetFocusedElement() == this; 2476 } 2477 2478 if (aKeyCausesActivation) { 2479 // Click on it if the users prefs indicate to do so. 2480 AutoHandlingUserInputStatePusher userInputStatePusher(aIsTrustedEvent); 2481 AutoPopupStatePusher popupStatePusher( 2482 aIsTrustedEvent ? PopupBlocker::openAllowed : PopupBlocker::openAbused); 2483 DispatchSimulatedClick(this, aIsTrustedEvent, presContext); 2484 return focused; 2485 } 2486 2487 // If the accesskey won't cause the activation and the focus isn't changed, 2488 // either. Return error so EventStateManager would try to find next element 2489 // to handle the accesskey. 2490 return focused ? Result<bool, nsresult>{focused} : Err(NS_ERROR_ABORT); 2491 } 2492 2493 void nsGenericHTMLElement::HandleKeyboardActivation( 2494 EventChainPostVisitor& aVisitor) { 2495 MOZ_ASSERT(aVisitor.mEvent->HasKeyEventMessage()); 2496 MOZ_ASSERT(aVisitor.mEvent->IsTrusted()); 2497 2498 // If focused element is different from this element, it may be editable. 2499 // In that case, associated editor for the element should handle the keyboard 2500 // instead. Therefore, if this is not the focused element, we should not 2501 // handle the event here. Note that this element may be an editing host, 2502 // i.e., focused and editable. In the case, keyboard events should be 2503 // handled by the focused element instead of associated editor because 2504 // Chrome handles the case so. For compatibility with Chrome, we follow them. 2505 if (nsFocusManager::GetFocusedElementStatic() != this) { 2506 return; 2507 } 2508 2509 const auto message = aVisitor.mEvent->mMessage; 2510 const WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent(); 2511 if (nsEventStatus_eIgnore != aVisitor.mEventStatus) { 2512 if (message == eKeyUp && keyEvent->mKeyCode == NS_VK_SPACE) { 2513 // Unset the flag even if the event is default-prevented or something. 2514 UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD); 2515 } 2516 return; 2517 } 2518 2519 bool shouldActivate = false; 2520 switch (message) { 2521 case eKeyDown: 2522 if (keyEvent->ShouldWorkAsSpaceKey()) { 2523 SetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD); 2524 } 2525 return; 2526 case eKeyPress: 2527 shouldActivate = keyEvent->mKeyCode == NS_VK_RETURN; 2528 if (keyEvent->ShouldWorkAsSpaceKey()) { 2529 // Consume 'space' key to prevent scrolling the page down. 2530 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault; 2531 } 2532 break; 2533 case eKeyUp: 2534 shouldActivate = keyEvent->ShouldWorkAsSpaceKey() && 2535 HasFlag(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD); 2536 if (shouldActivate) { 2537 UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD); 2538 } 2539 break; 2540 default: 2541 MOZ_ASSERT_UNREACHABLE("why didn't we bail out earlier?"); 2542 break; 2543 } 2544 2545 if (!shouldActivate) { 2546 return; 2547 } 2548 2549 RefPtr<nsPresContext> presContext = aVisitor.mPresContext; 2550 DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(), presContext); 2551 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault; 2552 } 2553 2554 nsresult nsGenericHTMLElement::DispatchSimulatedClick( 2555 nsGenericHTMLElement* aElement, bool aIsTrusted, 2556 nsPresContext* aPresContext) { 2557 WidgetPointerEvent event(aIsTrusted, ePointerClick, nullptr); 2558 event.mFlags.mIsPositionless = true; 2559 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_KEYBOARD; 2560 // pointerId definition in Pointer Events: 2561 // > The pointerId value of -1 MUST be reserved and used to indicate events 2562 // > that were generated by something other than a pointing device. 2563 event.pointerId = -1; 2564 return EventDispatcher::Dispatch(aElement, aPresContext, &event); 2565 } 2566 2567 already_AddRefed<EditorBase> nsGenericHTMLElement::GetAssociatedEditor() { 2568 // If contenteditable is ever implemented, it might need to do something 2569 // different here? 2570 2571 RefPtr<TextEditor> textEditor = GetTextEditorInternal(); 2572 return textEditor.forget(); 2573 } 2574 2575 // static 2576 void nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent* content) { 2577 /* Sync this node */ 2578 nsGenericHTMLElement* element = FromNode(content); 2579 if (element) { 2580 if (RefPtr<EditorBase> editorBase = element->GetAssociatedEditor()) { 2581 editorBase->SyncRealTimeSpell(); 2582 } 2583 } 2584 2585 /* Sync all children */ 2586 for (nsIContent* child = content->GetFirstChild(); child; 2587 child = child->GetNextSibling()) { 2588 SyncEditorsOnSubtree(child); 2589 } 2590 } 2591 2592 static void MakeContentDescendantsEditable(nsIContent* aContent) { 2593 // If aContent is not an element, we just need to update its 2594 // internal editable state and don't need to notify anyone about 2595 // that. For elements, we need to send a ElementStateChanged 2596 // notification. 2597 if (!aContent->IsElement()) { 2598 aContent->UpdateEditableState(false); 2599 return; 2600 } 2601 2602 Element* element = aContent->AsElement(); 2603 2604 element->UpdateEditableState(true); 2605 2606 for (nsIContent* child = aContent->GetFirstChild(); child; 2607 child = child->GetNextSibling()) { 2608 if (!child->IsElement() || 2609 !child->AsElement()->HasAttr(nsGkAtoms::contenteditable)) { 2610 MakeContentDescendantsEditable(child); 2611 } 2612 } 2613 } 2614 2615 void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) { 2616 Document* document = GetComposedDoc(); 2617 if (!document) { 2618 return; 2619 } 2620 2621 Document::EditingState previousEditingState = Document::EditingState::eOff; 2622 if (aChange != 0) { 2623 document->ChangeContentEditableCount(this, aChange); 2624 previousEditingState = document->GetEditingState(); 2625 } 2626 2627 // MakeContentDescendantsEditable is going to call ElementStateChanged for 2628 // this element and all descendants if editable state has changed. 2629 // We might as well wrap it all in one script blocker. 2630 nsAutoScriptBlocker scriptBlocker; 2631 MakeContentDescendantsEditable(this); 2632 2633 // If the document already had contenteditable and JS adds new 2634 // contenteditable, that might cause changing editing host to current editing 2635 // host's ancestor. In such case, HTMLEditor needs to know that 2636 // synchronously to update selection limitter. 2637 // Additionally, elements in shadow DOM is not editable in the normal cases, 2638 // but if its content has `contenteditable`, only in it can be ediable. 2639 // So we don't need to notify HTMLEditor of this change only when we're not 2640 // in shadow DOM and the composed document is in design mode. 2641 if (IsInDesignMode() && !IsInShadowTree() && aChange > 0 && 2642 previousEditingState == Document::EditingState::eContentEditable) { 2643 if (const RefPtr<HTMLEditor> htmlEditor = 2644 nsContentUtils::GetHTMLEditor(document->GetPresContext())) { 2645 htmlEditor->NotifyEditingHostMaybeChanged(); 2646 } 2647 } 2648 } 2649 2650 //---------------------------------------------------------------------- 2651 2652 nsGenericHTMLFormControlElement::nsGenericHTMLFormControlElement( 2653 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, FormControlType aType) 2654 : nsGenericHTMLFormElement(std::move(aNodeInfo)), 2655 nsIFormControl(aType), 2656 mForm(nullptr), 2657 mFieldSet(nullptr) {} 2658 2659 nsGenericHTMLFormControlElement::~nsGenericHTMLFormControlElement() { 2660 if (mFieldSet) { 2661 mFieldSet->RemoveElement(this); 2662 } 2663 2664 // Check that this element doesn't know anything about its form at this point. 2665 NS_ASSERTION(!mForm, "mForm should be null at this point!"); 2666 } 2667 2668 NS_IMPL_ISUPPORTS_INHERITED(nsGenericHTMLFormControlElement, 2669 nsGenericHTMLFormElement, nsIFormControl) 2670 2671 nsINode* nsGenericHTMLFormControlElement::GetScopeChainParent() const { 2672 return mForm ? mForm : nsGenericHTMLElement::GetScopeChainParent(); 2673 } 2674 2675 nsIContent::IMEState nsGenericHTMLFormControlElement::GetDesiredIMEState() { 2676 TextEditor* textEditor = GetTextEditorInternal(); 2677 if (!textEditor) { 2678 return nsGenericHTMLFormElement::GetDesiredIMEState(); 2679 } 2680 Result<IMEState, nsresult> stateOrError = textEditor->GetPreferredIMEState(); 2681 if (MOZ_UNLIKELY(stateOrError.isErr())) { 2682 return nsGenericHTMLFormElement::GetDesiredIMEState(); 2683 } 2684 return stateOrError.unwrap(); 2685 } 2686 2687 void nsGenericHTMLFormControlElement::GetAutocapitalize( 2688 nsAString& aValue) const { 2689 if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None, 2690 nsGkAtoms::autocapitalize)) { 2691 nsGenericHTMLFormElement::GetAutocapitalize(aValue); 2692 return; 2693 } 2694 2695 if (mForm && IsAutocapitalizeOrAutocorrectInheriting()) { 2696 mForm->GetAutocapitalize(aValue); 2697 } 2698 } 2699 2700 // https://html.spec.whatwg.org/#dom-autocorrect 2701 bool nsGenericHTMLFormControlElement::Autocorrect() const { 2702 auto controlType = ControlType(); 2703 2704 switch (controlType) { 2705 case FormControlType::InputEmail: 2706 case FormControlType::InputPassword: 2707 case FormControlType::InputUrl: 2708 return false; 2709 default: 2710 break; 2711 } 2712 2713 if (HasAttr(kNameSpaceID_None, nsGkAtoms::autocorrect)) { 2714 return nsGenericHTMLElement::Autocorrect(); 2715 } 2716 2717 if (mForm && IsAutocapitalizeOrAutocorrectInheriting()) { 2718 return mForm->Autocorrect(); 2719 } 2720 2721 return true; 2722 } 2723 2724 bool nsGenericHTMLFormControlElement::IsHTMLFocusable(IsFocusableFlags aFlags, 2725 bool* aIsFocusable, 2726 int32_t* aTabIndex) { 2727 if (nsGenericHTMLFormElement::IsHTMLFocusable(aFlags, aIsFocusable, 2728 aTabIndex)) { 2729 return true; 2730 } 2731 2732 *aIsFocusable = *aIsFocusable && IsFormControlDefaultFocusable(aFlags); 2733 return false; 2734 } 2735 2736 HTMLFieldSetElement* nsGenericHTMLFormControlElement::GetFieldSet() { 2737 return GetFieldSetInternal(); 2738 } 2739 2740 void nsGenericHTMLFormControlElement::SetForm(HTMLFormElement* aForm) { 2741 MOZ_ASSERT(aForm, "Don't pass null here"); 2742 NS_ASSERTION(!mForm, 2743 "We don't support switching from one non-null form to another."); 2744 2745 SetFormInternal(aForm, false); 2746 } 2747 2748 void nsGenericHTMLFormControlElement::ClearForm(bool aRemoveFromForm, 2749 bool aUnbindOrDelete) { 2750 nsGenericHTMLFormElement::ClearForm(aRemoveFromForm, aUnbindOrDelete); 2751 } 2752 2753 bool nsGenericHTMLFormControlElement::IsLabelable() const { 2754 auto type = ControlType(); 2755 return (IsInputElement(type) && type != FormControlType::InputHidden) || 2756 IsButtonElement(type) || type == FormControlType::Output || 2757 type == FormControlType::Select || type == FormControlType::Textarea; 2758 } 2759 2760 bool nsGenericHTMLFormControlElement::CanBeDisabled() const { 2761 auto type = ControlType(); 2762 // It's easier to test the types that _cannot_ be disabled 2763 return type != FormControlType::Object && type != FormControlType::Output; 2764 } 2765 2766 bool nsGenericHTMLFormControlElement::DoesReadWriteApply() const { 2767 auto type = ControlType(); 2768 if (!IsInputElement(type) && type != FormControlType::Textarea) { 2769 return false; 2770 } 2771 2772 switch (type) { 2773 case FormControlType::InputHidden: 2774 case FormControlType::InputButton: 2775 case FormControlType::InputImage: 2776 case FormControlType::InputReset: 2777 case FormControlType::InputSubmit: 2778 case FormControlType::InputRadio: 2779 case FormControlType::InputFile: 2780 case FormControlType::InputCheckbox: 2781 case FormControlType::InputRange: 2782 case FormControlType::InputColor: 2783 return false; 2784 #ifdef DEBUG 2785 case FormControlType::Textarea: 2786 case FormControlType::InputText: 2787 case FormControlType::InputPassword: 2788 case FormControlType::InputSearch: 2789 case FormControlType::InputTel: 2790 case FormControlType::InputEmail: 2791 case FormControlType::InputUrl: 2792 case FormControlType::InputNumber: 2793 case FormControlType::InputDate: 2794 case FormControlType::InputTime: 2795 case FormControlType::InputMonth: 2796 case FormControlType::InputWeek: 2797 case FormControlType::InputDatetimeLocal: 2798 return true; 2799 default: 2800 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesReadWriteApply()"); 2801 return true; 2802 #else // DEBUG 2803 default: 2804 return true; 2805 #endif // DEBUG 2806 } 2807 } 2808 2809 void nsGenericHTMLFormControlElement::SetFormInternal(HTMLFormElement* aForm, 2810 bool aBindToTree) { 2811 if (aForm) { 2812 BeforeSetForm(aForm, aBindToTree); 2813 } 2814 2815 // keep a *weak* ref to the form here 2816 mForm = aForm; 2817 } 2818 2819 HTMLFormElement* nsGenericHTMLFormControlElement::GetFormInternal() const { 2820 return mForm; 2821 } 2822 2823 HTMLFieldSetElement* nsGenericHTMLFormControlElement::GetFieldSetInternal() 2824 const { 2825 return mFieldSet; 2826 } 2827 2828 void nsGenericHTMLFormControlElement::SetFieldSetInternal( 2829 HTMLFieldSetElement* aFieldset) { 2830 mFieldSet = aFieldset; 2831 } 2832 2833 void nsGenericHTMLFormControlElement::UpdateRequiredState(bool aIsRequired, 2834 bool aNotify) { 2835 #ifdef DEBUG 2836 auto type = ControlType(); 2837 #endif 2838 MOZ_ASSERT(IsInputElement(type) || type == FormControlType::Select || 2839 type == FormControlType::Textarea, 2840 "This should be called only on types that @required applies"); 2841 2842 #ifdef DEBUG 2843 if (HTMLInputElement* input = HTMLInputElement::FromNode(this)) { 2844 MOZ_ASSERT( 2845 input->DoesRequiredApply(), 2846 "This should be called only on input types that @required applies"); 2847 } 2848 #endif 2849 2850 ElementState requiredStates; 2851 if (aIsRequired) { 2852 requiredStates |= ElementState::REQUIRED; 2853 } else { 2854 requiredStates |= ElementState::OPTIONAL_; 2855 } 2856 2857 ElementState oldRequiredStates = State() & ElementState::REQUIRED_STATES; 2858 ElementState changedStates = requiredStates ^ oldRequiredStates; 2859 2860 if (!changedStates.IsEmpty()) { 2861 ToggleStates(changedStates, aNotify); 2862 } 2863 } 2864 2865 bool nsGenericHTMLFormControlElement::IsAutocapitalizeOrAutocorrectInheriting() 2866 const { 2867 auto type = ControlType(); 2868 return IsInputElement(type) || IsButtonElement(type) || 2869 type == FormControlType::Fieldset || type == FormControlType::Output || 2870 type == FormControlType::Select || type == FormControlType::Textarea; 2871 } 2872 2873 nsresult nsGenericHTMLFormControlElement::SubmitDirnameDir( 2874 FormData* aFormData) { 2875 // Submit dirname=dir if element has non-empty dirname attribute 2876 if (HasAttr(nsGkAtoms::dirname)) { 2877 nsAutoString dirname; 2878 GetAttr(nsGkAtoms::dirname, dirname); 2879 if (!dirname.IsEmpty()) { 2880 const Directionality dir = GetDirectionality(); 2881 MOZ_ASSERT(dir == Directionality::Ltr || dir == Directionality::Rtl, 2882 "The directionality of an element is either ltr or rtl"); 2883 return aFormData->AddNameValuePair( 2884 dirname, dir == Directionality::Ltr ? u"ltr"_ns : u"rtl"_ns); 2885 } 2886 } 2887 return NS_OK; 2888 } 2889 2890 void nsGenericHTMLFormControlElement::GetFormAutofillState( 2891 nsAString& aState) const { 2892 if (State().HasState(ElementState::AUTOFILL_PREVIEW)) { 2893 aState.AssignLiteral("preview"); 2894 } else if (State().HasState(ElementState::AUTOFILL)) { 2895 aState.AssignLiteral("autofill"); 2896 } else { 2897 aState.Truncate(); 2898 } 2899 } 2900 2901 void nsGenericHTMLFormControlElement::SetFormAutofillState( 2902 const nsAString& aState) { 2903 if (aState.EqualsLiteral("autofill")) { 2904 RemoveStates(ElementState::AUTOFILL_PREVIEW); 2905 AddStates(ElementState::AUTOFILL); 2906 } else if (aState.EqualsLiteral("preview")) { 2907 AddStates(ElementState::AUTOFILL | ElementState::AUTOFILL_PREVIEW); 2908 } else { 2909 RemoveStates(ElementState::AUTOFILL | ElementState::AUTOFILL_PREVIEW); 2910 } 2911 } 2912 2913 //---------------------------------------------------------------------- 2914 2915 static constexpr nsAttrValue::EnumTableEntry kPopoverTargetActionTable[] = { 2916 {"toggle", PopoverTargetAction::Toggle}, 2917 {"show", PopoverTargetAction::Show}, 2918 {"hide", PopoverTargetAction::Hide}, 2919 }; 2920 2921 static constexpr const nsAttrValue::EnumTableEntry* 2922 kPopoverTargetActionDefault = &kPopoverTargetActionTable[0]; 2923 2924 nsGenericHTMLFormControlElementWithState:: 2925 nsGenericHTMLFormControlElementWithState( 2926 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, 2927 FromParser aFromParser, FormControlType aType) 2928 : nsGenericHTMLFormControlElement(std::move(aNodeInfo), aType), 2929 mControlNumber(!!(aFromParser & FROM_PARSER_NETWORK) 2930 ? OwnerDoc()->GetNextControlNumber() 2931 : -1) { 2932 mStateKey.SetIsVoid(true); 2933 } 2934 2935 bool nsGenericHTMLFormControlElementWithState::ParseAttribute( 2936 int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue, 2937 nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) { 2938 if (aNamespaceID == kNameSpaceID_None) { 2939 if (aAttribute == nsGkAtoms::popovertargetaction) { 2940 return aResult.ParseEnumValue(aValue, kPopoverTargetActionTable, false, 2941 kPopoverTargetActionDefault); 2942 } 2943 if (aAttribute == nsGkAtoms::popovertarget) { 2944 aResult.ParseAtom(aValue); 2945 return true; 2946 } 2947 } 2948 2949 return nsGenericHTMLFormControlElement::ParseAttribute( 2950 aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); 2951 } 2952 2953 mozilla::dom::Element* 2954 nsGenericHTMLFormControlElementWithState::GetPopoverTargetElement() const { 2955 return GetAttrAssociatedElement(nsGkAtoms::popovertarget); 2956 } 2957 2958 void nsGenericHTMLFormControlElementWithState::SetPopoverTargetElement( 2959 mozilla::dom::Element* aElement) { 2960 ExplicitlySetAttrElement(nsGkAtoms::popovertarget, aElement); 2961 } 2962 2963 // https://html.spec.whatwg.org/multipage/#popover-target-attribute-activation-behavior 2964 void nsGenericHTMLFormControlElementWithState::HandlePopoverTargetAction( 2965 mozilla::dom::Element* aEventTarget) { 2966 // 1. Let popover be node's popover target element. 2967 RefPtr<nsGenericHTMLElement> popover = GetEffectivePopoverTargetElement(); 2968 2969 // 2. If popover is null, then return. 2970 if (!popover) { 2971 return; 2972 } 2973 2974 // 3. If eventTarget is a shadow-including inclusive descendant of popover and 2975 // popover is a shadow-including descendant of node, then return. 2976 if (aEventTarget && 2977 aEventTarget->IsShadowIncludingInclusiveDescendantOf(popover) && 2978 popover->IsShadowIncludingDescendantOf(this)) { 2979 return; 2980 } 2981 2982 // 4. If node's popovertargetaction attribute is in the show state and 2983 // popover's popover visibility state is showing, then return. 2984 // 5. If node's popovertargetaction attribute is in the hide state and 2985 // popover's popover visibility state is hidden, then return. 2986 // 6. If popover's popover visibility state is showing, then run the hide 2987 // popover algorithm given popover, true, true, false, and node. 2988 // 7. Otherwise, if popover's popover visibility state is hidden and the 2989 // result of running check popover validity given popover, false, false, and 2990 // null is true, then run show popover given popover, false, and node. 2991 auto action = PopoverTargetAction::Toggle; 2992 if (const nsAttrValue* value = 2993 GetParsedAttr(nsGkAtoms::popovertargetaction)) { 2994 MOZ_ASSERT(value->Type() == nsAttrValue::eEnum); 2995 action = static_cast<PopoverTargetAction>(value->GetEnumValue()); 2996 } 2997 2998 bool canHide = action == PopoverTargetAction::Hide || 2999 action == PopoverTargetAction::Toggle; 3000 bool shouldHide = canHide && popover->IsPopoverOpen(); 3001 bool canShow = action == PopoverTargetAction::Show || 3002 action == PopoverTargetAction::Toggle; 3003 bool shouldShow = canShow && !popover->IsPopoverOpen(); 3004 3005 if (shouldHide) { 3006 popover->HidePopoverInternal(true, true, this, IgnoreErrors()); 3007 } else if (shouldShow) { 3008 popover->ShowPopoverInternal(this, IgnoreErrors()); 3009 } 3010 } 3011 3012 bool nsGenericHTMLElement::IsValidCommandAction(Command aCommand) const { 3013 return Element::IsValidCommandAction(aCommand) || 3014 aCommand == Command::ShowPopover || 3015 aCommand == Command::TogglePopover || aCommand == Command::HidePopover; 3016 } 3017 3018 MOZ_CAN_RUN_SCRIPT bool nsGenericHTMLElement::HandleCommandInternal( 3019 Element* aSource, Command aCommand, ErrorResult& aRv) { 3020 if (Element::HandleCommandInternal(aSource, aCommand, aRv)) { 3021 return true; 3022 } 3023 3024 // If the element is a `popover` then we may want to handle the 3025 // command... 3026 auto popoverState = GetPopoverAttributeState(); 3027 if (popoverState == PopoverAttributeState::None) { 3028 return false; 3029 } 3030 3031 const bool canShow = 3032 aCommand == Command::TogglePopover || aCommand == Command::ShowPopover; 3033 const bool canHide = 3034 aCommand == Command::TogglePopover || aCommand == Command::HidePopover; 3035 3036 if (canShow && !IsPopoverOpen()) { 3037 ShowPopoverInternal(aSource, aRv); 3038 return true; 3039 } 3040 3041 if (canHide && IsPopoverOpen()) { 3042 HidePopoverInternal(/* aFocusPreviousElement = */ true, 3043 /* aFireEvents = */ true, aSource, IgnoreErrors()); 3044 return true; 3045 } 3046 3047 return false; 3048 } 3049 3050 void nsGenericHTMLFormControlElementWithState::GenerateStateKey() { 3051 // Keep the key if already computed 3052 if (!mStateKey.IsVoid()) { 3053 return; 3054 } 3055 3056 Document* doc = GetUncomposedDoc(); 3057 if (!doc) { 3058 mStateKey.Truncate(); 3059 return; 3060 } 3061 3062 // Generate the state key 3063 nsContentUtils::GenerateStateKey(this, doc, mStateKey); 3064 3065 // If the state key is blank, this is anonymous content or for whatever 3066 // reason we are not supposed to save/restore state: keep it as such. 3067 if (!mStateKey.IsEmpty()) { 3068 // Add something unique to content so layout doesn't muck us up. 3069 mStateKey += "-C"; 3070 } 3071 } 3072 3073 PresState* nsGenericHTMLFormControlElementWithState::GetPrimaryPresState() { 3074 if (mStateKey.IsEmpty()) { 3075 return nullptr; 3076 } 3077 3078 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false); 3079 3080 if (!history) { 3081 return nullptr; 3082 } 3083 3084 // Get the pres state for this key, if it doesn't exist, create one. 3085 PresState* result = history->GetState(mStateKey); 3086 if (!result) { 3087 UniquePtr<PresState> newState = NewPresState(); 3088 result = newState.get(); 3089 history->AddState(mStateKey, std::move(newState)); 3090 } 3091 3092 return result; 3093 } 3094 3095 already_AddRefed<nsILayoutHistoryState> 3096 nsGenericHTMLFormElement::GetLayoutHistory(bool aRead) { 3097 nsCOMPtr<Document> doc = GetUncomposedDoc(); 3098 if (!doc) { 3099 return nullptr; 3100 } 3101 3102 // 3103 // Get the history 3104 // 3105 nsCOMPtr<nsILayoutHistoryState> history = doc->GetLayoutHistoryState(); 3106 if (!history) { 3107 return nullptr; 3108 } 3109 3110 if (aRead && !history->HasStates()) { 3111 return nullptr; 3112 } 3113 3114 return history.forget(); 3115 } 3116 3117 bool nsGenericHTMLFormControlElementWithState::RestoreFormControlState() { 3118 MOZ_ASSERT(!mStateKey.IsVoid(), 3119 "GenerateStateKey must already have been called"); 3120 3121 if (mStateKey.IsEmpty()) { 3122 return false; 3123 } 3124 3125 nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true); 3126 if (!history) { 3127 return false; 3128 } 3129 3130 // Get the pres state for this key 3131 PresState* state = history->GetState(mStateKey); 3132 if (state) { 3133 bool result = RestoreState(state); 3134 history->RemoveState(mStateKey); 3135 return result; 3136 } 3137 3138 return false; 3139 } 3140 3141 void nsGenericHTMLFormControlElementWithState::NodeInfoChanged( 3142 Document* aOldDoc) { 3143 nsGenericHTMLFormControlElement::NodeInfoChanged(aOldDoc); 3144 3145 // We need to regenerate the state key now we're in a new document. Clearing 3146 // mControlNumber means we stop considering this control to be parser 3147 // inserted, and we'll generate a state key based on its position in the 3148 // document rather than the order it was inserted into the document. 3149 mControlNumber = -1; 3150 mStateKey.SetIsVoid(true); 3151 } 3152 3153 void nsGenericHTMLFormControlElementWithState::GetFormAction(nsString& aValue) { 3154 auto type = ControlType(); 3155 if (!IsInputElement(type) && !IsButtonElement(type)) { 3156 return; 3157 } 3158 3159 if (!GetAttr(nsGkAtoms::formaction, aValue) || aValue.IsEmpty()) { 3160 Document* document = OwnerDoc(); 3161 nsIURI* docURI = document->GetDocumentURI(); 3162 if (docURI) { 3163 nsAutoCString spec; 3164 nsresult rv = docURI->GetSpec(spec); 3165 if (NS_FAILED(rv)) { 3166 return; 3167 } 3168 3169 CopyUTF8toUTF16(spec, aValue); 3170 } 3171 } else { 3172 GetURIAttr(nsGkAtoms::formaction, nullptr, aValue); 3173 } 3174 } 3175 3176 bool nsGenericHTMLElement::IsEventAttributeNameInternal(nsAtom* aName) { 3177 return nsContentUtils::IsEventAttributeName(aName, EventNameType_HTML); 3178 } 3179 3180 /** 3181 * Construct a URI from a string, as an element.src attribute 3182 * would be set to. Helper for the media elements. 3183 */ 3184 nsresult nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec, 3185 nsIURI** aURI) { 3186 NS_ENSURE_ARG_POINTER(aURI); 3187 3188 *aURI = nullptr; 3189 3190 nsCOMPtr<Document> doc = OwnerDoc(); 3191 3192 nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI, aURISpec, doc, 3193 GetBaseURI()); 3194 NS_ENSURE_SUCCESS(rv, rv); 3195 3196 bool equal; 3197 if (aURISpec.IsEmpty() && doc->GetDocumentURI() && 3198 NS_SUCCEEDED(doc->GetDocumentURI()->Equals(*aURI, &equal)) && equal) { 3199 // Assume an element can't point to a fragment of its embedding 3200 // document. Fail here instead of returning the recursive URI 3201 // and waiting for the subsequent load to fail. 3202 NS_RELEASE(*aURI); 3203 return NS_ERROR_DOM_INVALID_STATE_ERR; 3204 } 3205 3206 return NS_OK; 3207 } 3208 3209 void nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue, 3210 mozilla::ErrorResult& aError) { 3211 // innerText depends on layout. For example, white space processing is 3212 // something that happens during reflow and which must be reflected by 3213 // innerText. So for: 3214 // 3215 // <div style="white-space:normal"> A B C </div> 3216 // 3217 // innerText should give "A B C". 3218 // 3219 // The approach taken here to avoid the expense of reflow is to flush style 3220 // and then see whether it's necessary to flush layout afterwards. Flushing 3221 // layout can be skipped if we can detect that the element or its descendants 3222 // are not dirty. 3223 3224 // Obtain the composed doc to handle elements in Shadow DOM. 3225 Document* doc = GetComposedDoc(); 3226 if (doc) { 3227 doc->FlushPendingNotifications(FlushType::Style); 3228 } 3229 3230 // Elements with `display: content` will not have a frame. To handle Shadow 3231 // DOM, walk the flattened tree looking for parent frame. 3232 nsIFrame* frame = GetPrimaryFrame(); 3233 if (IsDisplayContents()) { 3234 for (Element* parent = GetFlattenedTreeParentElement(); parent; 3235 parent = parent->GetFlattenedTreeParentElement()) { 3236 frame = parent->GetPrimaryFrame(); 3237 if (frame) { 3238 break; 3239 } 3240 } 3241 } 3242 3243 // Check for dirty reflow roots in the subtree from targetFrame; this requires 3244 // a reflow flush. 3245 bool dirty = frame && frame->PresShell()->FrameIsAncestorOfDirtyRoot(frame); 3246 3247 // The way we do that is by checking whether the element has either of the two 3248 // dirty bits (NS_FRAME_IS_DIRTY or NS_FRAME_HAS_DIRTY_DESCENDANTS) or if any 3249 // ancestor has NS_FRAME_IS_DIRTY. We need to check for NS_FRAME_IS_DIRTY on 3250 // ancestors since that is something that implies NS_FRAME_IS_DIRTY on all 3251 // descendants. 3252 dirty |= frame && frame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 3253 while (!dirty && frame) { 3254 dirty |= frame->HasAnyStateBits(NS_FRAME_IS_DIRTY); 3255 frame = frame->GetInFlowParent(); 3256 } 3257 3258 // Flush layout if we determined a reflow is required. 3259 if (dirty && doc) { 3260 doc->FlushPendingNotifications(FlushType::Layout); 3261 } 3262 3263 if (!IsRendered()) { 3264 GetTextContentInternal(aValue, aError); 3265 } else { 3266 nsRange::GetInnerTextNoFlush(aValue, aError, this); 3267 } 3268 } 3269 3270 static already_AddRefed<nsINode> TextToNode(const nsAString& aString, 3271 nsNodeInfoManager* aNim) { 3272 nsString str; 3273 const char16_t* s = aString.BeginReading(); 3274 const char16_t* end = aString.EndReading(); 3275 RefPtr<DocumentFragment> fragment; 3276 while (true) { 3277 if (s != end && *s == '\r' && s + 1 != end && s[1] == '\n') { 3278 // a \r\n pair should only generate one <br>, so just skip the \r 3279 ++s; 3280 } 3281 if (s == end || *s == '\r' || *s == '\n') { 3282 if (!str.IsEmpty()) { 3283 RefPtr<nsTextNode> textContent = new (aNim) nsTextNode(aNim); 3284 textContent->SetText(str, true); 3285 if (!fragment) { 3286 if (s == end) { 3287 return textContent.forget(); 3288 } 3289 fragment = new (aNim) DocumentFragment(aNim); 3290 } 3291 fragment->AppendChildTo(textContent, true, IgnoreErrors()); 3292 } 3293 if (s == end) { 3294 break; 3295 } 3296 str.Truncate(); 3297 RefPtr<NodeInfo> ni = aNim->GetNodeInfo( 3298 nsGkAtoms::br, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); 3299 auto* nim = ni->NodeInfoManager(); 3300 RefPtr<HTMLBRElement> br = new (nim) HTMLBRElement(ni.forget()); 3301 if (!fragment) { 3302 if (s + 1 == end) { 3303 return br.forget(); 3304 } 3305 fragment = new (aNim) DocumentFragment(aNim); 3306 } 3307 fragment->AppendChildTo(br, true, IgnoreErrors()); 3308 } else { 3309 str.Append(*s); 3310 } 3311 ++s; 3312 } 3313 return fragment.forget(); 3314 } 3315 3316 void nsGenericHTMLElement::SetInnerTextInternal( 3317 const nsAString& aValue, MutationEffectOnScript aMutationEffectOnScript) { 3318 RefPtr<nsINode> node = TextToNode(aValue, NodeInfo()->NodeInfoManager()); 3319 ReplaceChildren(node, IgnoreErrors(), aMutationEffectOnScript); 3320 } 3321 3322 // https://html.spec.whatwg.org/#merge-with-the-next-text-node 3323 static void MergeWithNextTextNode(Text& aText, ErrorResult& aRv) { 3324 RefPtr<Text> nextSibling = Text::FromNodeOrNull(aText.GetNextSibling()); 3325 if (!nextSibling) { 3326 return; 3327 } 3328 nsAutoString data; 3329 nextSibling->GetData(data); 3330 aText.AppendDataInternal(data, MutationEffectOnScript::KeepTrustWorthiness, 3331 aRv); 3332 nextSibling->Remove(); 3333 } 3334 3335 // https://html.spec.whatwg.org/#dom-outertext 3336 void nsGenericHTMLElement::SetOuterText(const nsAString& aValue, 3337 ErrorResult& aRv) { 3338 nsCOMPtr<nsINode> parent = GetParentNode(); 3339 if (!parent) { 3340 return aRv.ThrowNoModificationAllowedError("Element has no parent"); 3341 } 3342 3343 RefPtr<nsINode> next = GetNextSibling(); 3344 RefPtr<nsINode> previous = GetPreviousSibling(); 3345 3346 nsNodeInfoManager* nim = NodeInfo()->NodeInfoManager(); 3347 RefPtr<nsINode> node = TextToNode(aValue, nim); 3348 if (!node) { 3349 // This doesn't match the spec, see 3350 // https://github.com/whatwg/html/issues/7508 3351 node = new (nim) nsTextNode(nim); 3352 } 3353 parent->ReplaceChildInternal( 3354 *node, *this, MutationEffectOnScript::DropTrustWorthiness, aRv); 3355 if (aRv.Failed()) { 3356 return; 3357 } 3358 3359 if (next) { 3360 if (RefPtr<Text> text = Text::FromNodeOrNull(next->GetPreviousSibling())) { 3361 MergeWithNextTextNode(*text, aRv); 3362 if (aRv.Failed()) { 3363 return; 3364 } 3365 } 3366 } 3367 if (auto* text = Text::FromNodeOrNull(previous)) { 3368 MergeWithNextTextNode(*text, aRv); 3369 } 3370 } 3371 3372 // This should be true when `:open` should match. 3373 bool nsGenericHTMLElement::PopoverOpen() const { 3374 if (PopoverData* popoverData = GetPopoverData()) { 3375 return popoverData->GetPopoverVisibilityState() == 3376 PopoverVisibilityState::Showing; 3377 } 3378 return false; 3379 } 3380 3381 // https://html.spec.whatwg.org/#check-popover-validity 3382 bool nsGenericHTMLElement::CheckPopoverValidity( 3383 PopoverVisibilityState aExpectedState, Document* aExpectedDocument, 3384 ErrorResult& aRv) { 3385 if (GetPopoverAttributeState() == PopoverAttributeState::None) { 3386 aRv.ThrowNotSupportedError("Element is in the no popover state"); 3387 return false; 3388 } 3389 3390 if (GetPopoverData()->GetPopoverVisibilityState() != aExpectedState) { 3391 return false; 3392 } 3393 3394 if (!IsInComposedDoc()) { 3395 aRv.ThrowInvalidStateError("Element is not connected"); 3396 return false; 3397 } 3398 3399 if (aExpectedDocument && aExpectedDocument != OwnerDoc()) { 3400 aRv.ThrowInvalidStateError("Element is moved to other document"); 3401 return false; 3402 } 3403 3404 if (auto* dialog = HTMLDialogElement::FromNode(this)) { 3405 if (dialog->IsInTopLayer()) { 3406 aRv.ThrowInvalidStateError("Element is a modal <dialog> element"); 3407 return false; 3408 } 3409 } 3410 3411 if (State().HasState(ElementState::FULLSCREEN)) { 3412 aRv.ThrowInvalidStateError("Element is fullscreen"); 3413 return false; 3414 } 3415 3416 return true; 3417 } 3418 3419 PopoverAttributeState nsGenericHTMLElement::GetPopoverAttributeState() const { 3420 return GetPopoverData() ? GetPopoverData()->GetPopoverAttributeState() 3421 : PopoverAttributeState::None; 3422 } 3423 3424 void nsGenericHTMLElement::PopoverPseudoStateUpdate(bool aOpen, bool aNotify) { 3425 SetStates(ElementState::POPOVER_OPEN, aOpen, aNotify); 3426 } 3427 3428 already_AddRefed<ToggleEvent> nsGenericHTMLElement::CreateToggleEvent( 3429 const nsAString& aEventType, const nsAString& aOldState, 3430 const nsAString& aNewState, Cancelable aCancelable, Element* aSource) { 3431 ToggleEventInit init; 3432 init.mBubbles = false; 3433 init.mOldState = aOldState; 3434 init.mNewState = aNewState; 3435 init.mCancelable = aCancelable == Cancelable::eYes; 3436 RefPtr<ToggleEvent> event = ToggleEvent::Constructor(this, aEventType, init); 3437 event->SetTrusted(true); 3438 event->SetTarget(this); 3439 event->SetSource(aSource); 3440 return event.forget(); 3441 } 3442 3443 bool nsGenericHTMLElement::FireToggleEvent(const nsAString& aOldState, 3444 const nsAString& aNewState, 3445 const nsAString& aType, 3446 Element* aSource) { 3447 const auto cancelable = aType == u"beforetoggle"_ns && aNewState == u"open"_ns 3448 ? Cancelable::eYes 3449 : Cancelable::eNo; 3450 RefPtr event = 3451 CreateToggleEvent(aType, aOldState, aNewState, cancelable, aSource); 3452 EventDispatcher::DispatchDOMEvent(this, nullptr, event, nullptr, nullptr); 3453 return event->DefaultPrevented(); 3454 } 3455 3456 // https://html.spec.whatwg.org/#queue-a-popover-toggle-event-task 3457 void nsGenericHTMLElement::QueuePopoverEventTask( 3458 PopoverVisibilityState aOldState, Element* aSource) { 3459 auto* data = GetPopoverData(); 3460 MOZ_ASSERT(data, "Should have popover data"); 3461 3462 if (auto* queuedToggleEventTask = data->GetToggleEventTask()) { 3463 aOldState = queuedToggleEventTask->GetOldState(); 3464 } 3465 3466 auto task = MakeRefPtr<PopoverToggleEventTask>( 3467 do_GetWeakReference(this), do_GetWeakReference(aSource), aOldState); 3468 data->SetToggleEventTask(task); 3469 OwnerDoc()->Dispatch(task.forget()); 3470 } 3471 3472 void nsGenericHTMLElement::RunPopoverToggleEventTask( 3473 PopoverToggleEventTask* aTask, PopoverVisibilityState aOldState, 3474 Element* aSource) { 3475 auto* data = GetPopoverData(); 3476 if (!data) { 3477 return; 3478 } 3479 3480 auto* popoverToggleEventTask = data->GetToggleEventTask(); 3481 if (!popoverToggleEventTask || aTask != popoverToggleEventTask) { 3482 return; 3483 } 3484 data->ClearToggleEventTask(); 3485 // Intentionally ignore the return value here as only on open event the 3486 // cancelable attribute is initialized to true for beforetoggle event. 3487 auto stringForState = [](PopoverVisibilityState state) { 3488 return state == PopoverVisibilityState::Hidden ? u"closed"_ns : u"open"_ns; 3489 }; 3490 FireToggleEvent(stringForState(aOldState), 3491 stringForState(data->GetPopoverVisibilityState()), 3492 u"toggle"_ns, aSource); 3493 } 3494 3495 // https://html.spec.whatwg.org/#dom-showpopover 3496 void nsGenericHTMLElement::ShowPopover(const ShowPopoverOptions& aOptions, 3497 ErrorResult& aRv) { 3498 Element* source = nullptr; 3499 if (aOptions.mSource.WasPassed()) { 3500 source = &aOptions.mSource.Value(); 3501 } 3502 return ShowPopoverInternal(MOZ_KnownLive(source), aRv); 3503 } 3504 3505 // https://html.spec.whatwg.org/#show-popover 3506 void nsGenericHTMLElement::ShowPopoverInternal(Element* aSource, 3507 ErrorResult& aRv) { 3508 // 1. If the result of running check popover validity given element, false, 3509 // throwExceptions, and null is false, then return. 3510 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, nullptr, aRv)) { 3511 return; 3512 } 3513 3514 // 2. Let document be element's node document. 3515 RefPtr<Document> document = OwnerDoc(); 3516 3517 // 3. Assert: element's popover trigger is null. 3518 MOZ_ASSERT(!GetPopoverData() || !GetPopoverData()->GetInvoker()); 3519 3520 // 4. Assert: element is not in document's top layer. 3521 MOZ_ASSERT(!OwnerDoc()->TopLayerContains(*this)); 3522 3523 // 5. Let nestedShow be element's popover showing or hiding. 3524 bool nestedShow = GetPopoverData()->IsShowingOrHiding(); 3525 3526 // 6. Let fireEvents be the boolean negation of nestedShow. 3527 bool fireEvents = !nestedShow; 3528 3529 // 7. Set element's popover showing or hiding to true. 3530 GetPopoverData()->SetIsShowingOrHiding(true); 3531 3532 // 8. Let cleanupShowingFlag be the following steps: 3533 auto cleanupShowingFlag = MakeScopeExit([&]() { 3534 // 8.1. If nestedShow is false, then set element's popover showing or hiding 3535 // to false. 3536 if (auto* popoverData = GetPopoverData()) { 3537 popoverData->SetIsShowingOrHiding(nestedShow); 3538 } 3539 }); 3540 3541 // 9. If the result of firing an event named beforetoggle, using ToggleEvent, 3542 // with the cancelable attribute initialized to true, the oldState attribute 3543 // initialized to "closed", the newState attribute initialized to "open", and 3544 // the source attribute initialized to source at element is false, then run 3545 // cleanupShowingFlag and return. 3546 if (FireToggleEvent(u"closed"_ns, u"open"_ns, u"beforetoggle"_ns, aSource)) { 3547 return; 3548 } 3549 3550 // 10. If the result of running check popover validity given element, false, 3551 // throwExceptions, and document is false, then run cleanupShowingFlag and 3552 // return. 3553 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) { 3554 return; 3555 } 3556 3557 // 11. Let shouldRestoreFocus be false. 3558 bool shouldRestoreFocus = false; 3559 3560 // 12. Let originalType be the current state of element's popover attribute. 3561 auto originalType = GetPopoverAttributeState(); 3562 3563 // 13. Let stackToAppendTo be null. 3564 PopoverAttributeState stackToAppendTo = PopoverAttributeState::None; 3565 3566 // 14. Let autoAncestor be the result of running the topmost popover ancestor 3567 // algorithm given element, document's showing auto popover list, source, and 3568 // true. 3569 // todo(keithamus) ^ popover hint 3570 3571 // 15. Let hintAncestor be the result of running the topmost popover ancestor 3572 // algorithm given element, document's showing hint popover list, source, and 3573 // true. 3574 // todo(keithamus): ^ popover hint 3575 3576 nsWeakPtr originallyFocusedElement; 3577 3578 // 16. If originalType is the Auto state, then: 3579 if (originalType == PopoverAttributeState::Auto) { 3580 // 16.1. Run close entire popover list given document's showing hint popover 3581 // list, shouldRestoreFocus, and fireEvents. 3582 // todo(keithamus): ^ popover hint 3583 3584 // 16.2. Let ancestor be the result of running the topmost popover ancestor 3585 // algorithm given element, document's showing auto popover list, source, 3586 // and true. 3587 RefPtr<nsINode> ancestor = 3588 GetTopmostPopoverAncestor(PopoverAttributeState::Auto, aSource, true); 3589 3590 // 16.3. If ancestor is null, then set ancestor to document. 3591 if (!ancestor) { 3592 ancestor = document; 3593 } 3594 3595 // 16.4. Run hide all popovers until given ancestor, shouldRestoreFocus, and 3596 // fireEvents. 3597 document->HideAllPopoversUntil(*ancestor, false, fireEvents); 3598 3599 // 16.5. Set stackToAppendTo to "auto". 3600 stackToAppendTo = PopoverAttributeState::Auto; 3601 } 3602 3603 // 17. If originalType is the Hint state, then: 3604 // 17.1. If hintAncestor is not null, then: 3605 // 17.1.1. Run hide all popovers until given hintAncestor, shouldRestoreFocus, 3606 // and fireEvents. 3607 // 17.1.2. Set stackToAppendTo to "hint". 3608 // 17.2. Otherwise: 3609 // 17.2.1. Run close entire popover list given document's showing hint popover 3610 // list, shouldRestoreFocus, and fireEvents. 3611 // 17.2.2. If autoAncestor is not 3612 // null, then: 3613 // 17.2.2.1 Run hide all popovers until given autoAncestor, 3614 // shouldRestoreFocus, and fireEvents. 3615 // 17.2.2.2 Set stackToAppendTo to "auto". 3616 // 17.2.3. Otherwise, set stackToAppendTo to "hint". 3617 // todo(keithamus): ^ popover hint 3618 3619 // 18. If originalType is Auto or Hint, then: 3620 // todo(keithamus): ^ popover hint 3621 if (originalType == PopoverAttributeState::Auto) { 3622 // 18.1. Assert: stackToAppendTo is not null. 3623 MOZ_ASSERT(stackToAppendTo != PopoverAttributeState::None); 3624 3625 // 18.2. If originalType is not equal to the value of element's popover 3626 // attribute, then: 3627 if (originalType != GetPopoverAttributeState()) { 3628 // 18.2.1. If throwExceptions is true, then throw an 3629 // "InvalidStateError" DOMException. 3630 aRv.ThrowInvalidStateError( 3631 "The value of the popover attribute was changed while hiding the " 3632 "popover."); 3633 // 18.2.2. Return. 3634 return; 3635 } 3636 3637 // 18.3. If the result of running check popover validity given element, 3638 // false, throwExceptions, and document is false, then run 3639 // cleanupShowingFlag and return. 3640 if (!CheckPopoverValidity(PopoverVisibilityState::Hidden, document, aRv)) { 3641 return; 3642 } 3643 3644 // 18.4. If the result of running topmost 3645 // auto or hint popover on document is null, then set shouldRestoreFocus to 3646 // true. 3647 shouldRestoreFocus = 3648 !document->GetTopmostPopoverOf(PopoverAttributeState::Auto); 3649 3650 // 18.5. If stackToAppendTo is "auto": 3651 if (stackToAppendTo == PopoverAttributeState::Auto) { 3652 // 18.5.1. Assert: document's showing auto popover list does not contain 3653 // element. 3654 MOZ_ASSERT( 3655 !document->PopoverListOf(PopoverAttributeState::Auto).Contains(this)); 3656 3657 // 18.5.2. Set element's opened in popover mode to "auto". 3658 GetPopoverData()->SetOpenedInMode(PopoverAttributeState::Auto); 3659 } else { 3660 // 18.5.- Otherwise: 3661 // 18.5.-.1. Assert: stackToAppendTo is "hint". 3662 // 18.5.-.2. Assert: document's showing hint popover list does not contain 3663 // element. 3664 // 18.5.-.3. Set element's opened in popover mode to "hint". 3665 // todo(keithamus): ^ popover hint 3666 MOZ_ASSERT_UNREACHABLE("stackToAppendTo was not Auto!"); 3667 } 3668 // 18.6. Set element's popover close watcher to the result of establishing a 3669 // close watcher given element's relevant global object, with: 3670 // - cancelAction being to return true. 3671 // - closeAction being to hide a popover given element, true, true, 3672 // false, and null. 3673 // - getEnabledState being to return true. 3674 if (StaticPrefs::dom_closewatcher_enabled()) { 3675 GetPopoverData()->EnsureCloseWatcher(this); 3676 } 3677 } 3678 3679 // 20. Let originallyFocusedElement be document's focused area of the 3680 // document's DOM anchor. 3681 if (nsIContent* unretargetedFocus = 3682 document->GetUnretargetedFocusedContent()) { 3683 originallyFocusedElement = 3684 do_GetWeakReference(unretargetedFocus->AsElement()); 3685 } 3686 3687 // 21. Add an element to the top layer given element. 3688 document->AddPopoverToTopLayer(*this); 3689 3690 PopoverPseudoStateUpdate(true, true); 3691 3692 { 3693 auto* popoverData = GetPopoverData(); 3694 // 18.5.2. Set element's opened in popover mode to "auto". 3695 popoverData->SetOpenedInMode(GetPopoverAttributeState()); 3696 // 22. Set element's popover visibility state to showing. 3697 popoverData->SetPopoverVisibilityState(PopoverVisibilityState::Showing); 3698 // 23. Set element's popover trigger to source. 3699 popoverData->SetInvoker(aSource); 3700 if (aSource && aSource->IsHTMLElement()) { 3701 aSource->SetAssociatedPopover(*this); 3702 } 3703 } 3704 3705 // 25. Run the popover focusing steps given element. 3706 FocusPopover(); 3707 3708 // 26. If shouldRestoreFocus is true and element's popover attribute is not 3709 // in the No Popover state, then set element's previously focused element to 3710 // originallyFocusedElement. 3711 if (shouldRestoreFocus && 3712 GetPopoverAttributeState() != PopoverAttributeState::None) { 3713 GetPopoverData()->SetPreviouslyFocusedElement(originallyFocusedElement); 3714 } 3715 3716 // 27. Queue a popover toggle event task given element, "closed", "open", 3717 // and source. 3718 QueuePopoverEventTask(PopoverVisibilityState::Hidden, aSource); 3719 3720 // 28. Run cleanupShowingFlag. 3721 // XXX (see MakeScopeExit above). 3722 } 3723 3724 void nsGenericHTMLElement::HidePopoverWithoutRunningScript() { 3725 HidePopoverInternal(/* aFocusPreviousElement = */ false, 3726 /* aFireEvents = */ false, 3727 /* aSource = */ nullptr, IgnoreErrors()); 3728 } 3729 3730 // https://html.spec.whatwg.org/#dom-hidepopover 3731 void nsGenericHTMLElement::HidePopover(ErrorResult& aRv) { 3732 HidePopoverInternal(/* aFocusPreviousElement = */ true, 3733 /* aFireEvents = */ true, 3734 /* aSource = */ nullptr, aRv); 3735 } 3736 3737 void nsGenericHTMLElement::HidePopoverInternal(bool aFocusPreviousElement, 3738 bool aFireEvents, 3739 mozilla::dom::Element* aSource, 3740 ErrorResult& aRv) { 3741 OwnerDoc()->HidePopover(*this, aFocusPreviousElement, aFireEvents, aSource, 3742 aRv); 3743 } 3744 3745 void nsGenericHTMLElement::ForgetPreviouslyFocusedElementAfterHidingPopover() { 3746 auto* data = GetPopoverData(); 3747 MOZ_ASSERT(data, "Should have popover data"); 3748 data->SetPreviouslyFocusedElement(nullptr); 3749 } 3750 3751 void nsGenericHTMLElement::FocusPreviousElementAfterHidingPopover() { 3752 auto* data = GetPopoverData(); 3753 MOZ_ASSERT(data, "Should have popover data"); 3754 3755 RefPtr<Element> control = 3756 do_QueryReferent(data->GetPreviouslyFocusedElement().get()); 3757 data->SetPreviouslyFocusedElement(nullptr); 3758 3759 if (!control) { 3760 return; 3761 } 3762 3763 // Step 14.2 at 3764 // https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm 3765 // If focusPreviousElement is true and document's focused area of the 3766 // document's DOM anchor is a shadow-including inclusive descendant of 3767 // element, then run the focusing steps for previouslyFocusedElement; 3768 nsIContent* currentFocus = OwnerDoc()->GetUnretargetedFocusedContent(); 3769 if (currentFocus && 3770 currentFocus->IsShadowIncludingInclusiveDescendantOf(this)) { 3771 FocusOptions options; 3772 options.mPreventScroll = true; 3773 control->Focus(options, CallerType::NonSystem, IgnoreErrors()); 3774 } 3775 } 3776 3777 // https://html.spec.whatwg.org/multipage/popover.html#dom-togglepopover 3778 bool nsGenericHTMLElement::TogglePopover( 3779 const TogglePopoverOptionsOrBoolean& aOptions, ErrorResult& aRv) { 3780 std::optional<bool> force; 3781 // Kept alive by TogglePopoverOptions if non-null. 3782 Element* invoker = nullptr; 3783 3784 if (aOptions.IsBoolean()) { 3785 force = std::make_optional(aOptions.GetAsBoolean()); 3786 } else { 3787 const auto& options = aOptions.GetAsTogglePopoverOptions(); 3788 if (options.mForce.WasPassed()) { 3789 force = std::make_optional(options.mForce.Value()); 3790 } 3791 if (options.mSource.WasPassed()) { 3792 invoker = &options.mSource.Value(); 3793 } 3794 } 3795 3796 if (PopoverOpen() && !force.value_or(false)) { 3797 HidePopover(aRv); 3798 } else if (force.value_or(true)) { 3799 // Kept alive by TogglePopoverOptions. 3800 ShowPopoverInternal(MOZ_KnownLive(invoker), aRv); 3801 } else { 3802 CheckPopoverValidity(GetPopoverData() 3803 ? GetPopoverData()->GetPopoverVisibilityState() 3804 : PopoverVisibilityState::Showing, 3805 nullptr, aRv); 3806 } 3807 3808 return PopoverOpen(); 3809 } 3810 3811 // https://html.spec.whatwg.org/multipage/popover.html#popover-focusing-steps 3812 void nsGenericHTMLElement::FocusPopover() { 3813 if (auto* dialog = HTMLDialogElement::FromNode(this)) { 3814 return MOZ_KnownLive(dialog)->FocusDialog(); 3815 } 3816 3817 if (RefPtr<Document> doc = GetComposedDoc()) { 3818 doc->FlushPendingNotifications(FlushType::Frames); 3819 } 3820 3821 RefPtr<Element> control = GetBoolAttr(nsGkAtoms::autofocus) 3822 ? this 3823 : GetAutofocusDelegate(IsFocusableFlags(0)); 3824 if (!control) { 3825 return; 3826 } 3827 FocusCandidate(control, false /* aClearUpFocus */); 3828 } 3829 3830 void nsGenericHTMLElement::FocusCandidate(Element* aControl, 3831 bool aClearUpFocus) { 3832 // 1) Run the focusing steps given control. 3833 IgnoredErrorResult rv; 3834 if (RefPtr<Element> elementToFocus = nsFocusManager::GetTheFocusableArea( 3835 aControl, nsFocusManager::ProgrammaticFocusFlags(FocusOptions()))) { 3836 elementToFocus->Focus(FocusOptions(), CallerType::NonSystem, rv); 3837 if (rv.Failed()) { 3838 return; 3839 } 3840 } else if (aClearUpFocus) { 3841 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 3842 // Clear the focus which ends up making the body gets focused 3843 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = OwnerDoc()->GetWindow(); 3844 fm->ClearFocus(outerWindow); 3845 } 3846 } 3847 3848 // 2) Let topDocument be the active document of control's node document's 3849 // browsing context's top-level browsing context. 3850 // 3) If control's node document's origin is not the same as the origin of 3851 // topDocument, then return. 3852 BrowsingContext* bc = aControl->OwnerDoc()->GetBrowsingContext(); 3853 if (bc && bc->IsInProcess() && bc->SameOriginWithTop()) { 3854 if (nsCOMPtr<nsIDocShell> docShell = bc->Top()->GetDocShell()) { 3855 if (Document* topDocument = docShell->GetExtantDocument()) { 3856 // 4) Empty topDocument's autofocus candidates. 3857 // 5) Set topDocument's autofocus processed flag to true. 3858 topDocument->SetAutoFocusFired(); 3859 } 3860 } 3861 } 3862 } 3863 3864 already_AddRefed<ElementInternals> nsGenericHTMLElement::AttachInternals( 3865 ErrorResult& aRv) { 3866 // ElementInternals is only available on autonomous custom element, so 3867 // throws an error by default. The spec steps are implemented in HTMLElement 3868 // because ElementInternals needs to hold a pointer to HTMLElement in order 3869 // to forward form operation to it. 3870 aRv.ThrowNotSupportedError(nsPrintfCString( 3871 "Cannot attach ElementInternals to a customized built-in or non-custom " 3872 "element " 3873 "'%s'", 3874 NS_ConvertUTF16toUTF8(NodeInfo()->NameAtom()->GetUTF16String()).get())); 3875 return nullptr; 3876 } 3877 3878 ElementInternals* nsGenericHTMLElement::GetInternals() const { 3879 if (CustomElementData* data = GetCustomElementData()) { 3880 return data->GetElementInternals(); 3881 } 3882 return nullptr; 3883 } 3884 3885 bool nsGenericHTMLElement::IsFormAssociatedCustomElement() const { 3886 CustomElementData* data = GetCustomElementData(); 3887 return data && data->IsFormAssociated(); 3888 } 3889 3890 void nsGenericHTMLElement::GetAutocapitalize(nsAString& aValue) const { 3891 GetEnumAttr(nsGkAtoms::autocapitalize, nullptr, kDefaultAutocapitalize->tag, 3892 aValue); 3893 } 3894 3895 bool nsGenericHTMLElement::Translate() const { 3896 if (const nsAttrValue* attr = mAttrs.GetAttr(nsGkAtoms::translate)) { 3897 if (attr->IsEmptyString() || attr->Equals(nsGkAtoms::yes, eIgnoreCase)) { 3898 return true; 3899 } 3900 if (attr->Equals(nsGkAtoms::no, eIgnoreCase)) { 3901 return false; 3902 } 3903 } 3904 return nsGenericHTMLElementBase::Translate(); 3905 } 3906 3907 void nsGenericHTMLElement::GetPopover(nsString& aPopover) const { 3908 GetHTMLEnumAttr(nsGkAtoms::popover, aPopover); 3909 if (aPopover.IsEmpty() && !DOMStringIsNull(aPopover)) { 3910 aPopover.Assign(NS_ConvertUTF8toUTF16(kPopoverAttributeValueAuto)); 3911 } 3912 } 3913 3914 /****************************************************************************** 3915 * nsIFormControl 3916 *****************************************************************************/ 3917 3918 // static 3919 nsIFormControl* nsIFormControl::FromEventTarget( 3920 mozilla::dom::EventTarget* aTarget) { 3921 MOZ_ASSERT(aTarget); 3922 return aTarget->IsNode() ? aTarget->AsNode()->GetAsFormControl() : nullptr; 3923 } 3924 3925 // static 3926 nsIFormControl* nsIFormControl::FromEventTargetOrNull( 3927 mozilla::dom::EventTarget* aTarget) { 3928 return aTarget && aTarget->IsNode() ? aTarget->AsNode()->GetAsFormControl() 3929 : nullptr; 3930 } 3931 3932 // static 3933 const nsIFormControl* nsIFormControl::FromEventTarget( 3934 const mozilla::dom::EventTarget* aTarget) { 3935 MOZ_ASSERT(aTarget); 3936 return aTarget->IsNode() ? aTarget->AsNode()->GetAsFormControl() : nullptr; 3937 } 3938 3939 // static 3940 const nsIFormControl* nsIFormControl::FromEventTargetOrNull( 3941 const mozilla::dom::EventTarget* aTarget) { 3942 return aTarget && aTarget->IsNode() ? aTarget->AsNode()->GetAsFormControl() 3943 : nullptr; 3944 } 3945 3946 // static 3947 nsIFormControl* nsIFormControl::FromNode(nsINode* aNode) { 3948 MOZ_ASSERT(aNode); 3949 return aNode->GetAsFormControl(); 3950 } 3951 3952 // static 3953 nsIFormControl* nsIFormControl::FromNodeOrNull(nsINode* aNode) { 3954 return aNode ? aNode->GetAsFormControl() : nullptr; 3955 } 3956 3957 // static 3958 const nsIFormControl* nsIFormControl::FromNode(const nsINode* aNode) { 3959 MOZ_ASSERT(aNode); 3960 return aNode->GetAsFormControl(); 3961 } 3962 3963 // static 3964 const nsIFormControl* nsIFormControl::FromNodeOrNull(const nsINode* aNode) { 3965 return aNode ? aNode->GetAsFormControl() : nullptr; 3966 }