nsTextControlFrame.cpp (43265B)
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 "nsTextControlFrame.h" 8 9 #include <algorithm> 10 11 #include "gfxContext.h" 12 #include "mozilla/EventStateManager.h" 13 #include "mozilla/PresShell.h" 14 #include "mozilla/PresState.h" 15 #include "mozilla/ScrollContainerFrame.h" 16 #include "mozilla/StaticPrefs_layout.h" 17 #include "mozilla/TextEditor.h" 18 #include "mozilla/Try.h" 19 #include "mozilla/dom/HTMLInputElement.h" 20 #include "mozilla/dom/HTMLTextAreaElement.h" 21 #include "mozilla/dom/ScriptSettings.h" 22 #include "mozilla/dom/Selection.h" 23 #include "mozilla/dom/Text.h" 24 #include "nsAttrValueInlines.h" 25 #include "nsCOMPtr.h" 26 #include "nsCSSPseudoElements.h" 27 #include "nsCaret.h" 28 #include "nsContentUtils.h" 29 #include "nsDisplayList.h" 30 #include "nsFocusManager.h" 31 #include "nsFontMetrics.h" 32 #include "nsFrameSelection.h" 33 #include "nsGenericHTMLElement.h" 34 #include "nsGkAtoms.h" 35 #include "nsIContent.h" 36 #include "nsIEditor.h" 37 #include "nsILayoutHistoryState.h" 38 #include "nsINode.h" 39 #include "nsLayoutUtils.h" 40 #include "nsNameSpaceManager.h" 41 #include "nsPIDOMWindow.h" //needed for notify selection changed to update the menus ect. 42 #include "nsPresContext.h" 43 #include "nsQueryObject.h" 44 #include "nsRange.h" //for selection setting helper func 45 #include "nsTextNode.h" 46 47 using namespace mozilla; 48 using namespace mozilla::dom; 49 50 nsIFrame* NS_NewTextControlFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 51 return new (aPresShell) 52 nsTextControlFrame(aStyle, aPresShell->GetPresContext()); 53 } 54 55 NS_IMPL_FRAMEARENA_HELPERS(nsTextControlFrame) 56 57 NS_QUERYFRAME_HEAD(nsTextControlFrame) 58 NS_QUERYFRAME_ENTRY(nsTextControlFrame) 59 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) 60 NS_QUERYFRAME_ENTRY(nsIStatefulFrame) 61 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 62 63 #ifdef ACCESSIBILITY 64 a11y::AccType nsTextControlFrame::AccessibleType() { 65 return a11y::eHTMLTextFieldType; 66 } 67 #endif 68 69 #ifdef DEBUG 70 class EditorInitializerEntryTracker { 71 public: 72 explicit EditorInitializerEntryTracker(nsTextControlFrame& frame) 73 : mFrame(frame), mFirstEntry(false) { 74 if (!mFrame.mInEditorInitialization) { 75 mFrame.mInEditorInitialization = true; 76 mFirstEntry = true; 77 } 78 } 79 ~EditorInitializerEntryTracker() { 80 if (mFirstEntry) { 81 mFrame.mInEditorInitialization = false; 82 } 83 } 84 bool EnteredMoreThanOnce() const { return !mFirstEntry; } 85 86 private: 87 nsTextControlFrame& mFrame; 88 bool mFirstEntry; 89 }; 90 #endif 91 92 class nsTextControlFrame::nsAnonDivObserver final 93 : public nsStubMutationObserver { 94 public: 95 explicit nsAnonDivObserver(nsTextControlFrame& aFrame) : mFrame(aFrame) {} 96 NS_DECL_ISUPPORTS 97 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED 98 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED 99 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED 100 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED 101 102 private: 103 ~nsAnonDivObserver() = default; 104 nsTextControlFrame& mFrame; 105 }; 106 107 nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle, 108 nsPresContext* aPresContext, 109 nsIFrame::ClassID aClassID) 110 : nsContainerFrame(aStyle, aPresContext, aClassID) {} 111 112 nsTextControlFrame::~nsTextControlFrame() = default; 113 114 ScrollContainerFrame* nsTextControlFrame::GetScrollTargetFrame() const { 115 if (!mRootNode) { 116 return nullptr; 117 } 118 return do_QueryFrame(mRootNode->GetPrimaryFrame()); 119 } 120 121 void nsTextControlFrame::Destroy(DestroyContext& aContext) { 122 RemoveProperty(TextControlInitializer()); 123 124 // Unbind the text editor state object from the frame. The editor will live 125 // on, but things like controllers will be released. 126 RefPtr textControlElement = ControlElement(); 127 if (mMutationObserver) { 128 textControlElement->UnbindFromFrame(this); 129 mRootNode->RemoveMutationObserver(mMutationObserver); 130 mMutationObserver = nullptr; 131 } 132 133 // If there is a drag session, user may be dragging selection in removing 134 // text node in the text control. If so, we should set source node to the 135 // text control because another text node may be recreated soon if the text 136 // control is just reframed. 137 if (nsCOMPtr<nsIDragSession> dragSession = 138 nsContentUtils::GetDragSession(PresContext())) { 139 if (dragSession->IsDraggingTextInTextControl() && mRootNode && 140 mRootNode->GetFirstChild()) { 141 nsCOMPtr<nsINode> sourceNode; 142 if (NS_SUCCEEDED( 143 dragSession->GetSourceNode(getter_AddRefs(sourceNode))) && 144 mRootNode->Contains(sourceNode)) { 145 MOZ_ASSERT(sourceNode->IsText()); 146 dragSession->UpdateSource(textControlElement, nullptr); 147 } 148 } 149 } 150 // Otherwise, EventStateManager may track gesture to start drag with native 151 // anonymous nodes in the text control element. 152 else if (textControlElement->GetPresContext(Element::eForComposedDoc)) { 153 textControlElement->GetPresContext(Element::eForComposedDoc) 154 ->EventStateManager() 155 ->TextControlRootWillBeRemoved(*textControlElement); 156 } 157 158 // If we're a subclass like nsNumberControlFrame, then it owns the root of the 159 // anonymous subtree where mRootNode is. 160 aContext.AddAnonymousContent(mRootNode.forget()); 161 aContext.AddAnonymousContent(mPlaceholderDiv.forget()); 162 aContext.AddAnonymousContent(mPreviewDiv.forget()); 163 aContext.AddAnonymousContent(mButton.forget()); 164 165 nsContainerFrame::Destroy(aContext); 166 } 167 168 LogicalSize nsTextControlFrame::CalcIntrinsicSize(gfxContext* aRenderingContext, 169 WritingMode aWM) const { 170 LogicalSize intrinsicSize(aWM); 171 const float inflation = nsLayoutUtils::FontSizeInflationFor(this); 172 RefPtr<nsFontMetrics> fontMet = 173 nsLayoutUtils::GetFontMetricsForFrame(this, inflation); 174 const nscoord lineHeight = ReflowInput::CalcLineHeight( 175 *Style(), PresContext(), GetContent(), NS_UNCONSTRAINEDSIZE, inflation); 176 // Use the larger of the font's "average" char width or the width of the 177 // zero glyph (if present) as the basis for resolving the size attribute. 178 const nscoord charWidth = 179 std::max(fontMet->ZeroOrAveCharWidth(), fontMet->AveCharWidth()); 180 const nscoord charMaxAdvance = fontMet->MaxAdvance(); 181 182 // Initialize based on the width in characters. 183 const Maybe<int32_t> maybeCols = GetCols(); 184 const int32_t cols = maybeCols.valueOr(TextControlElement::DEFAULT_COLS); 185 intrinsicSize.ISize(aWM) = cols * charWidth; 186 187 // If we do not have what appears to be a fixed-width font, add a "slop" 188 // amount based on the max advance of the font (clamped to twice charWidth, 189 // because some fonts have a few extremely-wide outliers that would result 190 // in excessive width here; e.g. the triple-emdash ligature in SFNS Text), 191 // minus 4px. This helps avoid input fields becoming unusably narrow with 192 // small size values. 193 if (charMaxAdvance - charWidth > AppUnitsPerCSSPixel()) { 194 nscoord internalPadding = 195 std::max(0, std::min(charMaxAdvance, charWidth * 2) - 196 nsPresContext::CSSPixelsToAppUnits(4)); 197 internalPadding = RoundToMultiple(internalPadding, AppUnitsPerCSSPixel()); 198 intrinsicSize.ISize(aWM) += internalPadding; 199 } 200 201 // Increment width with cols * letter-spacing. 202 { 203 const auto& letterSpacing = StyleText()->mLetterSpacing; 204 if (!letterSpacing.IsDefinitelyZero()) { 205 intrinsicSize.ISize(aWM) += 206 cols * letterSpacing.Resolve(fontMet->EmHeight()); 207 } 208 } 209 210 // Set the height equal to total number of rows (times the height of each 211 // line, of course) 212 intrinsicSize.BSize(aWM) = lineHeight * GetRows(); 213 214 // Add in the size of the scrollbars for textarea 215 if (IsTextArea()) { 216 ScrollContainerFrame* scrollContainerFrame = GetScrollTargetFrame(); 217 NS_ASSERTION(scrollContainerFrame, "Child must be scrollable"); 218 if (scrollContainerFrame) { 219 LogicalMargin scrollbarSizes( 220 aWM, scrollContainerFrame->GetDesiredScrollbarSizes()); 221 intrinsicSize.ISize(aWM) += scrollbarSizes.IStartEnd(aWM); 222 223 // We only include scrollbar-thickness in our BSize if the scrollbar on 224 // that side is explicitly forced-to-be-present. 225 const bool includeScrollbarBSize = [&] { 226 if (!StaticPrefs:: 227 layout_forms_textarea_sizing_excludes_auto_scrollbar_enabled()) { 228 return true; 229 } 230 auto overflow = aWM.IsVertical() ? StyleDisplay()->mOverflowY 231 : StyleDisplay()->mOverflowX; 232 return overflow == StyleOverflow::Scroll; 233 }(); 234 if (includeScrollbarBSize) { 235 intrinsicSize.BSize(aWM) += scrollbarSizes.BStartEnd(aWM); 236 } 237 } 238 } 239 240 // Add the inline size of the button if our char size is explicit, so as to 241 // make sure to make enough space for it. 242 if (maybeCols.isSome() && mButton && mButton->GetPrimaryFrame()) { 243 const IntrinsicSizeInput input(aRenderingContext, Nothing(), Nothing()); 244 intrinsicSize.ISize(aWM) += mButton->GetPrimaryFrame()->GetMinISize(input); 245 } 246 247 return intrinsicSize; 248 } 249 250 nsresult nsTextControlFrame::EnsureEditorInitialized() { 251 // This method initializes our editor, if needed. 252 253 // This code used to be called from CreateAnonymousContent(), but 254 // when the editor set the initial string, it would trigger a 255 // PresShell listener which called FlushPendingNotifications() 256 // during frame construction. This was causing other form controls 257 // to display wrong values. Additionally, calling this every time 258 // a text frame control is instantiated means that we're effectively 259 // instantiating the editor for all text fields, even if they 260 // never get used. So, now this method is being called lazily only 261 // when we actually need an editor. 262 263 if (mEditorHasBeenInitialized) { 264 return NS_OK; 265 } 266 267 Document* doc = mContent->GetComposedDoc(); 268 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); 269 270 AutoWeakFrame weakFrame(this); 271 272 // Flush out content on our document. Have to do this, because script 273 // blockers don't prevent the sink flushing out content and notifying in the 274 // process, which can destroy frames. 275 doc->FlushPendingNotifications(FlushType::ContentAndNotify); 276 NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_ERROR_FAILURE); 277 278 // Make sure that editor init doesn't do things that would kill us off 279 // (especially off the script blockers it'll create for its DOM mutations). 280 { 281 RefPtr<TextControlElement> textControlElement = ControlElement(); 282 283 // Hide selection changes during the initialization, as webpages should not 284 // be aware of these initializations 285 AutoHideSelectionChanges hideSelectionChanges( 286 textControlElement->GetIndependentFrameSelection()); 287 288 nsAutoScriptBlocker scriptBlocker; 289 290 // Time to mess with our security context... See comments in GetValue() 291 // for why this is needed. 292 mozilla::dom::AutoNoJSAPI nojsapi; 293 294 #ifdef DEBUG 295 // Make sure we are not being called again until we're finished. 296 // If reentrancy happens, just pretend that we don't have an editor. 297 const EditorInitializerEntryTracker tracker(*this); 298 NS_ASSERTION(!tracker.EnteredMoreThanOnce(), 299 "EnsureEditorInitialized has been called while a previous " 300 "call was in progress"); 301 #endif 302 303 // Create an editor for the frame, if one doesn't already exist 304 nsresult rv = textControlElement->CreateEditor(); 305 NS_ENSURE_SUCCESS(rv, rv); 306 NS_ENSURE_STATE(weakFrame.IsAlive()); 307 308 // Set mEditorHasBeenInitialized so that subsequent calls will use the 309 // editor. 310 mEditorHasBeenInitialized = true; 311 312 if (weakFrame.IsAlive()) { 313 uint32_t position = 0; 314 315 // Set the selection to the end of the text field (bug 1287655), 316 // but only if the contents has changed (bug 1337392). 317 if (textControlElement->ValueChanged()) { 318 nsAutoString val; 319 textControlElement->GetTextEditorValue(val); 320 position = val.Length(); 321 } 322 323 SetSelectionEndPoints(position, position, SelectionDirection::None); 324 } 325 } 326 NS_ENSURE_STATE(weakFrame.IsAlive()); 327 return NS_OK; 328 } 329 330 already_AddRefed<Element> nsTextControlFrame::MakeAnonElement( 331 PseudoStyleType aPseudoType, Element* aParent, nsAtom* aTag) const { 332 MOZ_ASSERT(aPseudoType != PseudoStyleType::NotPseudo); 333 Document* doc = PresContext()->Document(); 334 RefPtr<Element> element = doc->CreateHTMLElement(aTag); 335 element->SetPseudoElementType(aPseudoType); 336 if (aPseudoType == PseudoStyleType::mozTextControlEditingRoot) { 337 // Make our root node editable 338 element->SetFlags(NODE_IS_EDITABLE); 339 } 340 341 if (aPseudoType == PseudoStyleType::mozNumberSpinDown || 342 aPseudoType == PseudoStyleType::mozNumberSpinUp) { 343 element->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_hidden, u"true"_ns, 344 false); 345 } 346 347 if (aParent) { 348 aParent->AppendChildTo(element, false, IgnoreErrors()); 349 } 350 351 return element.forget(); 352 } 353 354 already_AddRefed<Element> nsTextControlFrame::MakeAnonDivWithTextNode( 355 PseudoStyleType aPseudoType) const { 356 RefPtr<Element> div = MakeAnonElement(aPseudoType); 357 358 // Create the text node for the anonymous <div> element. 359 nsNodeInfoManager* nim = div->OwnerDoc()->NodeInfoManager(); 360 RefPtr<nsTextNode> textNode = new (nim) nsTextNode(nim); 361 // If the anonymous div element is not for the placeholder, we should 362 // mark the text node as "maybe modified frequently" for avoiding ASCII 363 // range checks at every input. 364 if (aPseudoType != PseudoStyleType::placeholder) { 365 textNode->MarkAsMaybeModifiedFrequently(); 366 // Additionally, this is a password field, the text node needs to be 367 // marked as "maybe masked" unless it's in placeholder. 368 if (IsPasswordTextControl()) { 369 textNode->MarkAsMaybeMasked(); 370 } 371 } 372 div->AppendChildTo(textNode, false, IgnoreErrors()); 373 return div.forget(); 374 } 375 376 nsresult nsTextControlFrame::CreateAnonymousContent( 377 nsTArray<ContentInfo>& aElements) { 378 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); 379 MOZ_ASSERT(mContent, "We should have a content!"); 380 381 RefPtr<TextControlElement> textControlElement = ControlElement(); 382 mRootNode = MakeAnonElement(PseudoStyleType::mozTextControlEditingRoot); 383 if (NS_WARN_IF(!mRootNode)) { 384 return NS_ERROR_FAILURE; 385 } 386 387 mMutationObserver = new nsAnonDivObserver(*this); 388 mRootNode->AddMutationObserver(mMutationObserver); 389 390 // Bind the frame to its text control. 391 // 392 // This can realistically fail in paginated mode, where we may replicate 393 // fixed-positioned elements and the replicated frame will not get the chance 394 // to get an editor. 395 nsresult rv = textControlElement->BindToFrame(this); 396 if (NS_WARN_IF(NS_FAILED(rv))) { 397 mRootNode->RemoveMutationObserver(mMutationObserver); 398 mMutationObserver = nullptr; 399 mRootNode = nullptr; 400 return rv; 401 } 402 403 CreatePlaceholderIfNeeded(); 404 if (mPlaceholderDiv) { 405 aElements.AppendElement(mPlaceholderDiv); 406 } 407 CreatePreviewIfNeeded(); 408 if (mPreviewDiv) { 409 aElements.AppendElement(mPreviewDiv); 410 } 411 412 // NOTE(emilio): We want the root node always after the placeholder so that 413 // background on the placeholder doesn't obscure the caret. 414 aElements.AppendElement(mRootNode); 415 416 rv = UpdateValueDisplay(false); 417 NS_ENSURE_SUCCESS(rv, rv); 418 419 if ((StaticPrefs::layout_forms_reveal_password_button_enabled() || 420 PresContext()->Document()->ChromeRulesEnabled()) && 421 IsPasswordTextControl() && 422 StyleDisplay()->EffectiveAppearance() != StyleAppearance::Textfield) { 423 mButton = 424 MakeAnonElement(PseudoStyleType::mozReveal, nullptr, nsGkAtoms::button); 425 mButton->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_hidden, u"true"_ns, 426 false); 427 mButton->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, u"-1"_ns, false); 428 aElements.AppendElement(mButton); 429 } 430 431 InitializeEagerlyIfNeeded(); 432 return NS_OK; 433 } 434 435 bool nsTextControlFrame::ShouldInitializeEagerly() const { 436 // Input elements which have a cached selection should get eager 437 // editor initialization. 438 TextControlElement* textControlElement = ControlElement(); 439 if (textControlElement->HasCachedSelection()) { 440 return true; 441 } 442 443 // So do input text controls with spellcheck=true 444 if (auto* htmlElement = nsGenericHTMLElement::FromNode(mContent)) { 445 if (htmlElement->Spellcheck()) { 446 return true; 447 } 448 } 449 450 return false; 451 } 452 453 void nsTextControlFrame::InitializeEagerlyIfNeeded() { 454 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), 455 "Someone forgot a script blocker?"); 456 if (!ShouldInitializeEagerly()) { 457 return; 458 } 459 460 EditorInitializer* initializer = new EditorInitializer(this); 461 SetProperty(TextControlInitializer(), initializer); 462 nsContentUtils::AddScriptRunner(initializer); 463 } 464 465 void nsTextControlFrame::CreatePlaceholderIfNeeded() { 466 MOZ_ASSERT(!mPlaceholderDiv); 467 468 // Do we need a placeholder node? 469 nsAutoString placeholder; 470 if (!mContent->AsElement()->GetAttr(nsGkAtoms::placeholder, placeholder)) { 471 return; 472 } 473 474 mPlaceholderDiv = MakeAnonDivWithTextNode(PseudoStyleType::placeholder); 475 UpdatePlaceholderText(placeholder, false); 476 } 477 478 void nsTextControlFrame::PlaceholderChanged(const nsAttrValue* aOld, 479 const nsAttrValue* aNew) { 480 if (!aOld || !aNew) { 481 return; // This should be handled by GetAttributeChangeHint. 482 } 483 484 // If we've changed the attribute but we still haven't reframed, there's 485 // nothing to do either. 486 if (!mPlaceholderDiv) { 487 return; 488 } 489 490 nsAutoString placeholder; 491 aNew->ToString(placeholder); 492 UpdatePlaceholderText(placeholder, true); 493 } 494 495 void nsTextControlFrame::UpdatePlaceholderText(nsString& aPlaceholder, 496 bool aNotify) { 497 MOZ_DIAGNOSTIC_ASSERT(mPlaceholderDiv); 498 MOZ_DIAGNOSTIC_ASSERT(mPlaceholderDiv->GetFirstChild()); 499 500 if (IsTextArea()) { // <textarea>s preserve newlines... 501 nsContentUtils::PlatformToDOMLineBreaks(aPlaceholder); 502 } else { // ...<input>s don't 503 nsContentUtils::RemoveNewlines(aPlaceholder); 504 } 505 506 mPlaceholderDiv->GetFirstChild()->AsText()->SetText(aPlaceholder, aNotify); 507 } 508 509 void nsTextControlFrame::CreatePreviewIfNeeded() { 510 if (!ControlElement()->IsPreviewEnabled()) { 511 return; 512 } 513 mPreviewDiv = MakeAnonDivWithTextNode(PseudoStyleType::mozTextControlPreview); 514 } 515 516 void nsTextControlFrame::AppendAnonymousContentTo( 517 nsTArray<nsIContent*>& aElements, uint32_t aFilter) { 518 if (mPlaceholderDiv && !(aFilter & nsIContent::eSkipPlaceholderContent)) { 519 aElements.AppendElement(mPlaceholderDiv); 520 } 521 522 if (mPreviewDiv) { 523 aElements.AppendElement(mPreviewDiv); 524 } 525 526 if (mButton) { 527 aElements.AppendElement(mButton); 528 } 529 530 aElements.AppendElement(mRootNode); 531 } 532 533 nscoord nsTextControlFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 534 IntrinsicISizeType aType) { 535 // Our min inline size is just our preferred inline-size if we have auto 536 // inline size. 537 WritingMode wm = GetWritingMode(); 538 return CalcIntrinsicSize(aInput.mContext, wm).ISize(wm); 539 } 540 541 Maybe<nscoord> nsTextControlFrame::ComputeBaseline( 542 const nsIFrame* aFrame, const ReflowInput& aReflowInput, 543 bool aForSingleLineControl) { 544 // If we're layout-contained, we have no baseline. 545 if (aReflowInput.mStyleDisplay->IsContainLayout()) { 546 return Nothing(); 547 } 548 WritingMode wm = aReflowInput.GetWritingMode(); 549 550 nscoord lineHeight = aReflowInput.ComputedBSize(); 551 if (!aForSingleLineControl || lineHeight == NS_UNCONSTRAINEDSIZE) { 552 lineHeight = aReflowInput.ApplyMinMaxBSize(aReflowInput.GetLineHeight()); 553 } 554 RefPtr<nsFontMetrics> fontMet = 555 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame); 556 return Some(nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight, 557 wm.IsLineInverted()) + 558 aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm)); 559 } 560 561 void nsTextControlFrame::Reflow(nsPresContext* aPresContext, 562 ReflowOutput& aDesiredSize, 563 const ReflowInput& aReflowInput, 564 nsReflowStatus& aStatus) { 565 MarkInReflow(); 566 DO_GLOBAL_REFLOW_COUNT("nsTextControlFrame"); 567 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 568 569 // set values of reflow's out parameters 570 WritingMode wm = aReflowInput.GetWritingMode(); 571 const auto contentBoxSize = aReflowInput.ComputedSizeWithBSizeFallback([&] { 572 return CalcIntrinsicSize(aReflowInput.mRenderingContext, wm).BSize(wm); 573 }); 574 aDesiredSize.SetSize( 575 wm, 576 contentBoxSize + aReflowInput.ComputedLogicalBorderPadding(wm).Size(wm)); 577 578 { 579 // Calculate the baseline and store it in mFirstBaseline. 580 auto baseline = 581 ComputeBaseline(this, aReflowInput, IsSingleLineTextControl()); 582 mFirstBaseline = baseline.valueOr(NS_INTRINSIC_ISIZE_UNKNOWN); 583 if (baseline) { 584 aDesiredSize.SetBlockStartAscent(*baseline); 585 } 586 } 587 588 // overflow handling 589 aDesiredSize.SetOverflowAreasToDesiredBounds(); 590 591 nsIFrame* buttonBox = [&]() -> nsIFrame* { 592 nsIFrame* last = mFrames.LastChild(); 593 if (!last || !IsButtonBox(last)) { 594 return nullptr; 595 } 596 return last; 597 }(); 598 599 // Reflow the button box first, so that we can use its size for the other 600 // frames. 601 nscoord buttonBoxISize = 0; 602 if (buttonBox) { 603 ReflowTextControlChild(buttonBox, aPresContext, aReflowInput, aStatus, 604 aDesiredSize, contentBoxSize, buttonBoxISize); 605 } 606 607 // perform reflow on all kids 608 nsIFrame* kid = mFrames.FirstChild(); 609 while (kid) { 610 if (kid != buttonBox) { 611 MOZ_ASSERT(!IsButtonBox(kid), 612 "Should only have one button box, and should be last"); 613 ReflowTextControlChild(kid, aPresContext, aReflowInput, aStatus, 614 aDesiredSize, contentBoxSize, buttonBoxISize); 615 } 616 kid = kid->GetNextSibling(); 617 } 618 619 // take into account css properties that affect overflow handling 620 FinishAndStoreOverflow(&aDesiredSize); 621 622 aStatus.Reset(); // This type of frame can't be split. 623 } 624 625 void nsTextControlFrame::ReflowTextControlChild( 626 nsIFrame* aKid, nsPresContext* aPresContext, 627 const ReflowInput& aReflowInput, nsReflowStatus& aStatus, 628 ReflowOutput& aParentDesiredSize, const LogicalSize& aParentContentBoxSize, 629 nscoord& aButtonBoxISize) { 630 const WritingMode outerWM = aReflowInput.GetWritingMode(); 631 // compute available size and frame offsets for child 632 const WritingMode wm = aKid->GetWritingMode(); 633 const auto parentPadding = aReflowInput.ComputedLogicalPadding(wm); 634 const LogicalSize contentBoxSize = 635 aParentContentBoxSize.ConvertTo(wm, outerWM); 636 const LogicalSize paddingBoxSize = contentBoxSize + parentPadding.Size(wm); 637 const LogicalSize borderBoxSize = 638 paddingBoxSize + aReflowInput.ComputedLogicalBorder(wm).Size(wm); 639 const bool singleLine = IsSingleLineTextControl(); 640 const bool isButtonBox = IsButtonBox(aKid); 641 LogicalSize availSize = 642 !isButtonBox && singleLine ? contentBoxSize : paddingBoxSize; 643 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; 644 ReflowInput kidReflowInput(aPresContext, aReflowInput, aKid, availSize, 645 Nothing(), ReflowInput::InitFlag::CallerWillInit); 646 647 // Override padding with our computed padding in case we got it from theming 648 // or percentage, if we're not the button box. 649 auto overridePadding = isButtonBox ? Nothing() : Some(parentPadding); 650 if (!isButtonBox && singleLine) { 651 // Button box respects inline-end-padding, so we don't need to. 652 // inline-padding is not propagated to the scroller for single-line text 653 // controls. 654 overridePadding->IStart(outerWM) = overridePadding->IEnd(outerWM) = 0; 655 } 656 657 // We want to let our button box fill the frame in the block axis, up to the 658 // edge of the control's border. So, we use the control's padding-box as the 659 // containing block size for our button box. 660 auto overrideCBSize = isButtonBox ? Some(paddingBoxSize) : Nothing(); 661 kidReflowInput.Init(aPresContext, overrideCBSize, Nothing(), overridePadding); 662 663 LogicalPoint position(wm); 664 if (!isButtonBox) { 665 MOZ_ASSERT(wm == outerWM, 666 "Shouldn't have to care about orthogonal " 667 "writing-modes and such inside the control, " 668 "except for the number spin-box which forces " 669 "horizontal-tb"); 670 671 const auto& border = aReflowInput.ComputedLogicalBorder(wm); 672 673 // Offset the frame by the size of the parent's border. Note that we don't 674 // have to account for the parent's padding here, because this child 675 // actually "inherits" that padding and manages it on behalf of the parent. 676 position.B(wm) = border.BStart(wm); 677 position.I(wm) = border.IStart(wm); 678 if (singleLine) { 679 position.I(wm) += parentPadding.IStart(wm); 680 } 681 682 // Set computed width and computed height for the child (the button box is 683 // the only exception, which has an auto size). 684 kidReflowInput.SetComputedISize( 685 std::max(0, aReflowInput.ComputedISize() - aButtonBoxISize)); 686 kidReflowInput.SetComputedBSize(contentBoxSize.BSize(wm)); 687 } 688 689 // reflow the child 690 ReflowOutput desiredSize(aReflowInput); 691 const nsSize containerSize = borderBoxSize.GetPhysicalSize(wm); 692 ReflowChild(aKid, aPresContext, desiredSize, kidReflowInput, wm, position, 693 containerSize, ReflowChildFlags::Default, aStatus); 694 695 if (isButtonBox) { 696 const auto& bp = aReflowInput.ComputedLogicalBorderPadding(outerWM); 697 auto size = desiredSize.Size(outerWM); 698 // Center button in the block axis of our content box. We do this 699 // computation in terms of outerWM for simplicity. 700 LogicalRect buttonRect(outerWM); 701 buttonRect.BSize(outerWM) = size.BSize(outerWM); 702 buttonRect.ISize(outerWM) = size.ISize(outerWM); 703 buttonRect.BStart(outerWM) = 704 bp.BStart(outerWM) + 705 (aParentContentBoxSize.BSize(outerWM) - size.BSize(outerWM)) / 2; 706 // Align to the inline-end of the content box. 707 buttonRect.IStart(outerWM) = 708 bp.IStart(outerWM) + aReflowInput.ComputedISize() - size.ISize(outerWM); 709 buttonRect = buttonRect.ConvertTo(wm, outerWM, containerSize); 710 position = buttonRect.Origin(wm); 711 aButtonBoxISize = size.ISize(outerWM); 712 } 713 714 // place the child 715 FinishReflowChild(aKid, aPresContext, desiredSize, &kidReflowInput, wm, 716 position, containerSize, ReflowChildFlags::Default); 717 718 // consider the overflow 719 aParentDesiredSize.mOverflowAreas.UnionWith(desiredSize.mOverflowAreas); 720 } 721 722 // IMPLEMENTING NS_IFORMCONTROLFRAME 723 void nsTextControlFrame::OnFocus() { 724 nsISelectionController* selCon = GetSelectionController(); 725 if (!selCon) { 726 return; 727 } 728 729 RefPtr<Selection> ourSel = 730 selCon->GetSelection(nsISelectionController::SELECTION_NORMAL); 731 if (!ourSel) { 732 return; 733 } 734 735 mozilla::PresShell* presShell = PresShell(); 736 RefPtr<nsCaret> caret = presShell->GetCaret(); 737 if (!caret) { 738 return; 739 } 740 741 // Tell the caret to use our selection 742 caret->SetSelection(ourSel); 743 744 // mutual-exclusion: the selection is either controlled by the 745 // document or by the text input/area. Clear any selection in the 746 // document since the focus is now on our independent selection. 747 748 RefPtr<Selection> docSel = 749 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL); 750 if (!docSel) { 751 return; 752 } 753 754 if (!docSel->IsCollapsed()) { 755 docSel->RemoveAllRanges(IgnoreErrors()); 756 } 757 758 // If the focus moved to a text control during text selection by pointer 759 // device, stop extending the selection. 760 if (RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection()) { 761 frameSelection->SetDragState(false); 762 } 763 } 764 765 already_AddRefed<TextEditor> nsTextControlFrame::GetTextEditor() { 766 if (NS_WARN_IF(NS_FAILED(EnsureEditorInitialized()))) { 767 return nullptr; 768 } 769 RefPtr el = ControlElement(); 770 return do_AddRef(el->GetTextEditor()); 771 } 772 773 nsresult nsTextControlFrame::SetSelectionInternal( 774 nsINode* aStartNode, uint32_t aStartOffset, nsINode* aEndNode, 775 uint32_t aEndOffset, SelectionDirection aDirection) { 776 // Get the selection, clear it and add the new range to it! 777 nsISelectionController* selCon = GetSelectionController(); 778 NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE); 779 780 RefPtr<Selection> selection = 781 selCon->GetSelection(nsISelectionController::SELECTION_NORMAL); 782 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); 783 784 nsDirection direction; 785 if (aDirection == SelectionDirection::None) { 786 // Preserve the direction 787 direction = selection->GetDirection(); 788 } else { 789 direction = 790 aDirection == SelectionDirection::Backward ? eDirPrevious : eDirNext; 791 } 792 793 MOZ_TRY(selection->SetStartAndEndInLimiter(*aStartNode, aStartOffset, 794 *aEndNode, aEndOffset, direction, 795 nsISelectionListener::JS_REASON)); 796 return NS_OK; 797 } 798 799 void nsTextControlFrame::ScrollSelectionIntoViewAsync( 800 ScrollAncestors aScrollAncestors) { 801 nsCOMPtr<nsISelectionController> selCon = GetSelectionController(); 802 if (!selCon) { 803 return; 804 } 805 806 // Scroll the selection into view (see bug 231389). 807 const auto flags = aScrollAncestors == ScrollAncestors::Yes 808 ? ScrollFlags::None 809 : ScrollFlags::ScrollFirstAncestorOnly; 810 selCon->ScrollSelectionIntoView( 811 SelectionType::eNormal, nsISelectionController::SELECTION_FOCUS_REGION, 812 ScrollAxis(), ScrollAxis(), flags); 813 } 814 815 nsresult nsTextControlFrame::SelectAll() { 816 nsresult rv = EnsureEditorInitialized(); 817 if (NS_WARN_IF(NS_FAILED(rv))) { 818 return rv; 819 } 820 821 RefPtr<nsINode> rootNode = mRootNode; 822 NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); 823 824 RefPtr<Text> text = Text::FromNodeOrNull(rootNode->GetFirstChild()); 825 MOZ_ASSERT(text); 826 827 uint32_t length = text->Length(); 828 829 rv = SetSelectionInternal(text, 0, text, length, SelectionDirection::None); 830 NS_ENSURE_SUCCESS(rv, rv); 831 832 ScrollSelectionIntoViewAsync(); 833 return NS_OK; 834 } 835 836 nsresult nsTextControlFrame::SetSelectionEndPoints( 837 uint32_t aSelStart, uint32_t aSelEnd, SelectionDirection aDirection) { 838 NS_ASSERTION(aSelStart <= aSelEnd, "Invalid selection offsets!"); 839 840 if (aSelStart > aSelEnd) { 841 return NS_ERROR_FAILURE; 842 } 843 844 nsCOMPtr<nsINode> startNode, endNode; 845 uint32_t startOffset, endOffset; 846 847 // Calculate the selection start point. 848 849 nsresult rv = 850 OffsetToDOMPoint(aSelStart, getter_AddRefs(startNode), &startOffset); 851 852 NS_ENSURE_SUCCESS(rv, rv); 853 854 if (aSelStart == aSelEnd) { 855 // Collapsed selection, so start and end are the same! 856 endNode = startNode; 857 endOffset = startOffset; 858 } else { 859 // Selection isn't collapsed so we have to calculate 860 // the end point too. 861 862 rv = OffsetToDOMPoint(aSelEnd, getter_AddRefs(endNode), &endOffset); 863 864 NS_ENSURE_SUCCESS(rv, rv); 865 } 866 867 return SetSelectionInternal(startNode, startOffset, endNode, endOffset, 868 aDirection); 869 } 870 871 NS_IMETHODIMP 872 nsTextControlFrame::SetSelectionRange(uint32_t aSelStart, uint32_t aSelEnd, 873 SelectionDirection aDirection) { 874 nsresult rv = EnsureEditorInitialized(); 875 NS_ENSURE_SUCCESS(rv, rv); 876 877 if (aSelStart > aSelEnd) { 878 // Simulate what we'd see SetSelectionStart() was called, followed 879 // by a SetSelectionEnd(). 880 881 aSelStart = aSelEnd; 882 } 883 884 return SetSelectionEndPoints(aSelStart, aSelEnd, aDirection); 885 } 886 887 nsresult nsTextControlFrame::OffsetToDOMPoint(uint32_t aOffset, 888 nsINode** aResult, 889 uint32_t* aPosition) { 890 NS_ENSURE_ARG_POINTER(aResult && aPosition); 891 892 *aResult = nullptr; 893 *aPosition = 0; 894 895 nsresult rv = EnsureEditorInitialized(); 896 if (NS_WARN_IF(NS_FAILED(rv))) { 897 return rv; 898 } 899 900 RefPtr<Element> rootNode = mRootNode; 901 NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); 902 903 nsCOMPtr<nsINodeList> nodeList = rootNode->ChildNodes(); 904 uint32_t length = nodeList->Length(); 905 906 NS_ASSERTION(length <= 2, 907 "We should have one text node and one mozBR at most"); 908 909 nsCOMPtr<nsINode> firstNode = nodeList->Item(0); 910 Text* textNode = firstNode ? firstNode->GetAsText() : nullptr; 911 912 if (length == 0) { 913 rootNode.forget(aResult); 914 *aPosition = 0; 915 } else if (textNode) { 916 uint32_t textLength = textNode->Length(); 917 firstNode.forget(aResult); 918 *aPosition = std::min(aOffset, textLength); 919 } else { 920 rootNode.forget(aResult); 921 *aPosition = 0; 922 } 923 924 return NS_OK; 925 } 926 927 /////END INTERFACE IMPLEMENTATIONS 928 929 ////NSIFRAME 930 nsresult nsTextControlFrame::AttributeChanged(int32_t aNameSpaceID, 931 nsAtom* aAttribute, 932 AttrModType aModType) { 933 if (aAttribute == nsGkAtoms::value && !mEditorHasBeenInitialized) { 934 if (IsSingleLineTextControl()) { 935 UpdateValueDisplay(true); 936 } 937 return NS_OK; 938 } 939 940 if (aAttribute == nsGkAtoms::maxlength) { 941 if (RefPtr<TextEditor> textEditor = GetTextEditor()) { 942 textEditor->SetMaxTextLength(ControlElement()->UsedMaxLength()); 943 return NS_OK; 944 } 945 } 946 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); 947 } 948 949 void nsTextControlFrame::HandleReadonlyOrDisabledChange() { 950 RefPtr<TextControlElement> el = ControlElement(); 951 const RefPtr<TextEditor> editor = el->GetExtantTextEditor(); 952 if (!editor) { 953 return; 954 } 955 nsISelectionController* const selCon = el->GetSelectionController(); 956 if (!selCon) { 957 return; 958 } 959 if (el->IsDisabledOrReadOnly()) { 960 if (nsFocusManager::GetFocusedElementStatic() == el) { 961 selCon->SetCaretEnabled(false); 962 } 963 editor->AddFlags(nsIEditor::eEditorReadonlyMask); 964 } else { 965 if (nsFocusManager::GetFocusedElementStatic() == el) { 966 selCon->SetCaretEnabled(true); 967 } 968 editor->RemoveFlags(nsIEditor::eEditorReadonlyMask); 969 } 970 } 971 972 void nsTextControlFrame::ElementStateChanged(dom::ElementState aStates) { 973 if (aStates.HasAtLeastOneOfStates(dom::ElementState::READONLY | 974 dom::ElementState::DISABLED)) { 975 HandleReadonlyOrDisabledChange(); 976 } 977 if (aStates.HasState(dom::ElementState::FOCUS) && 978 mContent->AsElement()->State().HasState(dom::ElementState::FOCUS)) { 979 OnFocus(); 980 } 981 return nsContainerFrame::ElementStateChanged(aStates); 982 } 983 984 /// END NSIFRAME OVERLOADS 985 986 // NOTE(emilio): This is needed because the root->primary frame map is not set 987 // up by the time this is called. 988 static nsIFrame* FindRootNodeFrame(const nsFrameList& aChildList, 989 const nsIContent* aRoot) { 990 for (nsIFrame* f : aChildList) { 991 if (f->GetContent() == aRoot) { 992 return f; 993 } 994 if (nsIFrame* root = FindRootNodeFrame(f->PrincipalChildList(), aRoot)) { 995 return root; 996 } 997 } 998 return nullptr; 999 } 1000 1001 void nsTextControlFrame::SetInitialChildList(ChildListID aListID, 1002 nsFrameList&& aChildList) { 1003 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); 1004 if (aListID != FrameChildListID::Principal) { 1005 return; 1006 } 1007 1008 // Mark the scroll frame as being a reflow root. This will allow incremental 1009 // reflows to be initiated at the scroll frame, rather than descending from 1010 // the root frame of the frame hierarchy. 1011 if (nsIFrame* frame = FindRootNodeFrame(PrincipalChildList(), mRootNode)) { 1012 frame->AddStateBits(NS_FRAME_REFLOW_ROOT); 1013 1014 ControlElement()->InitializeKeyboardEventListeners(); 1015 1016 bool hasProperty; 1017 nsPoint contentScrollPos = TakeProperty(ContentScrollPos(), &hasProperty); 1018 if (hasProperty) { 1019 // If we have a scroll pos stored to be passed to our anonymous 1020 // div, do it here! 1021 nsIStatefulFrame* statefulFrame = do_QueryFrame(frame); 1022 NS_ASSERTION(statefulFrame, 1023 "unexpected type of frame for the anonymous div"); 1024 UniquePtr<PresState> fakePresState = NewPresState(); 1025 fakePresState->scrollState() = contentScrollPos; 1026 statefulFrame->RestoreState(fakePresState.get()); 1027 } 1028 } else { 1029 MOZ_ASSERT(!mRootNode || PrincipalChildList().IsEmpty()); 1030 } 1031 } 1032 1033 nsresult nsTextControlFrame::UpdateValueDisplay(bool aNotify) { 1034 MOZ_ASSERT(mRootNode, "Must have a div content\n"); 1035 MOZ_ASSERT(!mEditorHasBeenInitialized, 1036 "Do not call this after editor has been initialized"); 1037 1038 nsIContent* childContent = mRootNode->GetFirstChild(); 1039 Text* textContent; 1040 if (!childContent) { 1041 // Set up a textnode with our value 1042 RefPtr<nsTextNode> textNode = new (mContent->NodeInfo()->NodeInfoManager()) 1043 nsTextNode(mContent->NodeInfo()->NodeInfoManager()); 1044 textNode->MarkAsMaybeModifiedFrequently(); 1045 if (IsPasswordTextControl()) { 1046 textNode->MarkAsMaybeMasked(); 1047 } 1048 mRootNode->AppendChildTo(textNode, aNotify, IgnoreErrors()); 1049 textContent = textNode; 1050 } else { 1051 textContent = childContent->GetAsText(); 1052 } 1053 1054 NS_ENSURE_TRUE(textContent, NS_ERROR_UNEXPECTED); 1055 1056 // Get the current value of the textfield from the content. 1057 nsAutoString value; 1058 ControlElement()->GetTextEditorValue(value); 1059 return textContent->SetText(value, aNotify); 1060 } 1061 1062 NS_IMETHODIMP 1063 nsTextControlFrame::GetOwnedSelectionController( 1064 nsISelectionController** aSelCon) { 1065 NS_ENSURE_ARG_POINTER(aSelCon); 1066 NS_IF_ADDREF(*aSelCon = GetSelectionController()); 1067 return NS_OK; 1068 } 1069 1070 UniquePtr<PresState> nsTextControlFrame::SaveState() { 1071 if (nsIStatefulFrame* scrollStateFrame = GetScrollTargetFrame()) { 1072 return scrollStateFrame->SaveState(); 1073 } 1074 1075 return nullptr; 1076 } 1077 1078 NS_IMETHODIMP 1079 nsTextControlFrame::RestoreState(PresState* aState) { 1080 NS_ENSURE_ARG_POINTER(aState); 1081 1082 if (nsIStatefulFrame* scrollStateFrame = GetScrollTargetFrame()) { 1083 return scrollStateFrame->RestoreState(aState); 1084 } 1085 1086 // Most likely, we don't have our anonymous content constructed yet, which 1087 // would cause us to end up here. In this case, we'll just store the scroll 1088 // pos ourselves, and forward it to the scroll frame later when it's created. 1089 SetProperty(ContentScrollPos(), aState->scrollState()); 1090 return NS_OK; 1091 } 1092 1093 nsresult nsTextControlFrame::PeekOffset(PeekOffsetStruct* aPos) { 1094 return NS_ERROR_FAILURE; 1095 } 1096 1097 void nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 1098 const nsDisplayListSet& aLists) { 1099 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame"); 1100 1101 DisplayBorderBackgroundOutline(aBuilder, aLists); 1102 1103 if (HidesContent()) { 1104 return; 1105 } 1106 1107 // Redirect all lists to the Content list so that nothing can escape, ie 1108 // opacity creating stacking contexts that then get sorted with stacking 1109 // contexts external to us. 1110 nsDisplayList* content = aLists.Content(); 1111 nsDisplayListSet set(content, content, content, content, content, content); 1112 1113 for (auto* kid : mFrames) { 1114 BuildDisplayListForChild(aBuilder, kid, set); 1115 } 1116 } 1117 1118 NS_IMETHODIMP 1119 nsTextControlFrame::EditorInitializer::Run() { 1120 if (!mFrame) { 1121 return NS_OK; 1122 } 1123 1124 // Need to block script to avoid bug 669767. 1125 nsAutoScriptBlocker scriptBlocker; 1126 1127 RefPtr<mozilla::PresShell> presShell = mFrame->PresShell(); 1128 bool observes = presShell->ObservesNativeAnonMutationsForPrint(); 1129 presShell->ObserveNativeAnonMutationsForPrint(true); 1130 // This can cause the frame to be destroyed (and call Revoke()). 1131 mFrame->EnsureEditorInitialized(); 1132 presShell->ObserveNativeAnonMutationsForPrint(observes); 1133 1134 // The frame can *still* be destroyed even though we have a scriptblocker, 1135 // bug 682684. 1136 if (!mFrame) { 1137 return NS_ERROR_FAILURE; 1138 } 1139 1140 // If there is a drag session which is for dragging text in a text control 1141 // and its source node is the text control element, we're being reframed. 1142 // In this case we should restore the source node of the drag session to 1143 // new text node because it's required for dispatching `dragend` event. 1144 if (nsCOMPtr<nsIDragSession> dragSession = 1145 nsContentUtils::GetDragSession(mFrame->PresContext())) { 1146 if (dragSession->IsDraggingTextInTextControl()) { 1147 nsCOMPtr<nsINode> sourceNode; 1148 if (NS_SUCCEEDED( 1149 dragSession->GetSourceNode(getter_AddRefs(sourceNode))) && 1150 mFrame->GetContent() == sourceNode) { 1151 if (const TextEditor* const textEditor = 1152 mFrame->ControlElement()->GetExtantTextEditor()) { 1153 if (Element* anonymousDivElement = textEditor->GetRoot()) { 1154 if (anonymousDivElement && anonymousDivElement->GetFirstChild()) { 1155 MOZ_ASSERT(anonymousDivElement->GetFirstChild()->IsText()); 1156 dragSession->UpdateSource(anonymousDivElement->GetFirstChild(), 1157 textEditor->GetSelection()); 1158 } 1159 } 1160 } 1161 } 1162 } 1163 } 1164 // Otherwise, EventStateManager may be tracking gesture to start a drag. 1165 else { 1166 TextControlElement* textControlElement = mFrame->ControlElement(); 1167 if (nsPresContext* presContext = 1168 textControlElement->GetPresContext(Element::eForComposedDoc)) { 1169 if (const TextEditor* const textEditor = 1170 textControlElement->GetExtantTextEditor()) { 1171 if (Element* anonymousDivElement = textEditor->GetRoot()) { 1172 presContext->EventStateManager()->TextControlRootAdded( 1173 *anonymousDivElement, *textControlElement); 1174 } 1175 } 1176 } 1177 } 1178 1179 mFrame->FinishedInitializer(); 1180 return NS_OK; 1181 } 1182 1183 NS_IMPL_ISUPPORTS(nsTextControlFrame::nsAnonDivObserver, nsIMutationObserver) 1184 1185 void nsTextControlFrame::nsAnonDivObserver::CharacterDataChanged( 1186 nsIContent* aContent, const CharacterDataChangeInfo&) { 1187 mFrame.ClearCachedValue(); 1188 } 1189 1190 void nsTextControlFrame::nsAnonDivObserver::ContentAppended( 1191 nsIContent* aFirstNewContent, const ContentAppendInfo&) { 1192 mFrame.ClearCachedValue(); 1193 } 1194 1195 void nsTextControlFrame::nsAnonDivObserver::ContentInserted( 1196 nsIContent* aChild, const ContentInsertInfo&) { 1197 mFrame.ClearCachedValue(); 1198 } 1199 1200 void nsTextControlFrame::nsAnonDivObserver::ContentWillBeRemoved( 1201 nsIContent* aChild, const ContentRemoveInfo&) { 1202 mFrame.ClearCachedValue(); 1203 } 1204 1205 Maybe<nscoord> nsTextControlFrame::GetNaturalBaselineBOffset( 1206 mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup, 1207 BaselineExportContext aExportContext) const { 1208 if (!IsSingleLineTextControl()) { 1209 if (StyleDisplay()->IsContainLayout()) { 1210 return Nothing{}; 1211 } 1212 1213 if (aBaselineGroup == BaselineSharingGroup::First) { 1214 return Some(CSSMinMax(mFirstBaseline, 0, BSize(aWM))); 1215 } 1216 // This isn't great, but the content of the root NAC isn't guaranteed 1217 // to be loaded, so the best we can do is the edge of the border-box. 1218 if (aWM.IsCentralBaseline()) { 1219 return Some(BSize(aWM) / 2); 1220 } 1221 return Some(0); 1222 } 1223 NS_ASSERTION(!IsSubtreeDirty(), "frame must not be dirty"); 1224 return GetSingleLineTextControlBaseline(this, mFirstBaseline, aWM, 1225 aBaselineGroup); 1226 }