nsCaret.cpp (22456B)
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 /* the caret is the text cursor used, e.g., when editing */ 8 9 #include "nsCaret.h" 10 11 #include <algorithm> 12 13 #include "SelectionMovementUtils.h" 14 #include "gfxUtils.h" 15 #include "mozilla/CaretAssociationHint.h" 16 #include "mozilla/LookAndFeel.h" 17 #include "mozilla/Preferences.h" 18 #include "mozilla/PresShell.h" 19 #include "mozilla/ScrollContainerFrame.h" 20 #include "mozilla/StaticPrefs_bidi.h" 21 #include "mozilla/dom/CharacterDataBuffer.h" 22 #include "mozilla/dom/Selection.h" 23 #include "mozilla/gfx/2D.h" 24 #include "mozilla/intl/BidiEmbeddingLevel.h" 25 #include "nsBlockFrame.h" 26 #include "nsCOMPtr.h" 27 #include "nsContentUtils.h" 28 #include "nsFontMetrics.h" 29 #include "nsFrameSelection.h" 30 #include "nsIBidiKeyboard.h" 31 #include "nsIContent.h" 32 #include "nsIFrame.h" 33 #include "nsIFrameInlines.h" 34 #include "nsISelectionController.h" 35 #include "nsITimer.h" 36 #include "nsLayoutUtils.h" 37 #include "nsMenuPopupFrame.h" 38 #include "nsPresContext.h" 39 #include "nsTextFrame.h" 40 #include "nsXULPopupManager.h" 41 42 using namespace mozilla; 43 using namespace mozilla::dom; 44 using namespace mozilla::gfx; 45 46 using BidiEmbeddingLevel = mozilla::intl::BidiEmbeddingLevel; 47 48 // The bidi indicator hangs off the caret to one side, to show which 49 // direction the typing is in. It needs to be at least 2x2 to avoid looking 50 // like an insignificant dot 51 static const int32_t kMinBidiIndicatorPixels = 2; 52 53 nsCaret::nsCaret() = default; 54 55 nsCaret::~nsCaret() { StopBlinking(); } 56 57 nsresult nsCaret::Init(PresShell* aPresShell) { 58 NS_ENSURE_ARG(aPresShell); 59 60 RefPtr<Selection> selection = 61 aPresShell->GetSelection(nsISelectionController::SELECTION_NORMAL); 62 if (!selection) { 63 return NS_ERROR_FAILURE; 64 } 65 66 selection->AddSelectionListener(this); 67 mDomSelectionWeak = selection; 68 UpdateCaretPositionFromSelectionIfNeeded(); 69 70 return NS_OK; 71 } 72 73 static bool DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset) { 74 nsIContent* content = aFrame->GetContent(); 75 const CharacterDataBuffer* characterDataBuffer = 76 content->GetCharacterDataBuffer(); 77 if (!characterDataBuffer) { 78 return false; 79 } 80 if (aOffset < 0 || 81 static_cast<uint32_t>(aOffset) >= characterDataBuffer->GetLength()) { 82 return false; 83 } 84 const char16_t ch = 85 characterDataBuffer->CharAt(AssertedCast<uint32_t>(aOffset)); 86 return 0x2e80 <= ch && ch <= 0xd7ff; 87 } 88 89 nsCaret::Metrics nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, 90 nscoord aCaretHeight) { 91 // Compute nominal sizes in appunits 92 nscoord caretWidth = 93 (aCaretHeight * 94 LookAndFeel::GetFloat(LookAndFeel::FloatID::CaretAspectRatio, 0.0f)) + 95 nsPresContext::CSSPixelsToAppUnits( 96 LookAndFeel::GetInt(LookAndFeel::IntID::CaretWidth, 1)); 97 98 if (DrawCJKCaret(aFrame, aOffset)) { 99 caretWidth += nsPresContext::CSSPixelsToAppUnits(1); 100 } 101 nscoord bidiIndicatorSize = 102 nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels); 103 bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize); 104 105 // Round them to device pixels. Always round down, except that anything 106 // between 0 and 1 goes up to 1 so we don't let the caret disappear. 107 int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel(); 108 Metrics result; 109 result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp); 110 result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp); 111 return result; 112 } 113 114 void nsCaret::Terminate() { 115 // this doesn't erase the caret if it's drawn. Should it? We might not have 116 // a good drawing environment during teardown. 117 118 StopBlinking(); 119 mBlinkTimer = nullptr; 120 121 // unregiser ourselves as a selection listener 122 if (mDomSelectionWeak) { 123 mDomSelectionWeak->RemoveSelectionListener(this); 124 } 125 mDomSelectionWeak = nullptr; 126 mCaretPosition = {}; 127 } 128 129 NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener) 130 131 Selection* nsCaret::GetSelection() { return mDomSelectionWeak; } 132 133 void nsCaret::SetSelection(Selection* aDOMSel) { 134 MOZ_ASSERT(aDOMSel); 135 mDomSelectionWeak = aDOMSel; 136 UpdateCaretPositionFromSelectionIfNeeded(); 137 ResetBlinking(); 138 SchedulePaint(); 139 } 140 141 void nsCaret::SetVisible(bool aVisible) { 142 const bool wasVisible = mVisible; 143 mVisible = aVisible; 144 if (mVisible != wasVisible) { 145 CaretVisibilityMaybeChanged(); 146 } 147 } 148 149 bool nsCaret::IsVisible() const { return mVisible && !mHideCount; } 150 151 void nsCaret::CaretVisibilityMaybeChanged() { 152 ResetBlinking(); 153 SchedulePaint(); 154 if (IsVisible()) { 155 // We ignore caret position updates when the caret is not visible, so we 156 // update the caret position here if needed. 157 UpdateCaretPositionFromSelectionIfNeeded(); 158 } 159 } 160 161 void nsCaret::AddForceHide() { 162 MOZ_ASSERT(mHideCount < UINT32_MAX); 163 if (++mHideCount > 1) { 164 return; 165 } 166 CaretVisibilityMaybeChanged(); 167 } 168 169 void nsCaret::RemoveForceHide() { 170 if (!mHideCount || --mHideCount) { 171 return; 172 } 173 CaretVisibilityMaybeChanged(); 174 } 175 176 void nsCaret::SetCaretReadOnly(bool aReadOnly) { 177 mReadOnly = aReadOnly; 178 ResetBlinking(); 179 SchedulePaint(); 180 } 181 182 // Clamp the inline-position to be within our closest scroll frame and any 183 // ancestor clips if any. If we don't, then it clips us, and we don't appear at 184 // all. See bug 335560 and bug 1539720. 185 static nsPoint AdjustRectForClipping(const nsRect& aRect, nsIFrame* aFrame, 186 bool aVertical) { 187 nsRect rectRelativeToClip = aRect; 188 ScrollContainerFrame* sf = nullptr; 189 for (nsIFrame* current = aFrame; current; current = current->GetParent()) { 190 if ((sf = do_QueryFrame(current))) { 191 break; 192 } 193 if (current->IsTransformed()) { 194 // We don't account for transforms in rectRelativeToCurrent, so stop 195 // adjusting here. 196 break; 197 } 198 rectRelativeToClip += current->GetPosition(); 199 } 200 201 if (!sf) { 202 return {}; 203 } 204 205 nsRect clipRect = sf->GetScrollPortRect(); 206 nsPoint offset; 207 // Now see if the caret extends beyond the view's bounds. If it does, then 208 // snap it back, put it as close to the edge as it can. 209 if (aVertical) { 210 nscoord overflow = rectRelativeToClip.YMost() - clipRect.YMost(); 211 if (overflow > 0) { 212 offset.y -= overflow; 213 } else { 214 overflow = rectRelativeToClip.y - clipRect.y; 215 if (overflow < 0) { 216 offset.y -= overflow; 217 } 218 } 219 } else { 220 nscoord overflow = rectRelativeToClip.XMost() - clipRect.XMost(); 221 if (overflow > 0) { 222 offset.x -= overflow; 223 } else { 224 overflow = rectRelativeToClip.x - clipRect.x; 225 if (overflow < 0) { 226 offset.x -= overflow; 227 } 228 } 229 } 230 return offset; 231 } 232 233 /* static */ 234 nsRect nsCaret::GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset, 235 nscoord* aBidiIndicatorSize) { 236 nsPoint framePos(0, 0); 237 nsRect rect; 238 nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos); 239 if (NS_FAILED(rv)) { 240 if (aBidiIndicatorSize) { 241 *aBidiIndicatorSize = 0; 242 } 243 return rect; 244 } 245 246 nsIFrame* frame = aFrame->GetContentInsertionFrame(); 247 if (!frame) { 248 frame = aFrame; 249 } 250 NS_ASSERTION(!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW), 251 "We should not be in the middle of reflow"); 252 WritingMode wm = aFrame->GetWritingMode(); 253 RefPtr<nsFontMetrics> fm = 254 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame); 255 const auto caretBlockAxisMetrics = frame->GetCaretBlockAxisMetrics(wm, *fm); 256 const bool vertical = wm.IsVertical(); 257 Metrics caretMetrics = 258 ComputeMetrics(aFrame, aFrameOffset, caretBlockAxisMetrics.mExtent); 259 260 nscoord inlineOffset = 0; 261 if (nsTextFrame* textFrame = do_QueryFrame(aFrame)) { 262 if (gfxTextRun* textRun = textFrame->GetTextRun(nsTextFrame::eInflated)) { 263 // For "upstream" text where the textrun direction is reversed from the 264 // frame's inline-dir we want the caret to be painted before rather than 265 // after its nominal inline position, so we offset by its width. 266 const bool textRunDirIsReverseOfFrame = 267 wm.IsInlineReversed() != textRun->IsInlineReversed(); 268 // However, in sideways-lr mode we invert this behavior because this is 269 // the one writing mode where bidi-LTR corresponds to inline-reversed 270 // already, which reverses the desired caret placement behavior. 271 // Note that the following condition is equivalent to: 272 // if ( (!textRun->IsSidewaysLeft() && textRunDirIsReverseOfFrame) || 273 // (textRun->IsSidewaysLeft() && !textRunDirIsReverseOfFrame) ) 274 if (textRunDirIsReverseOfFrame != textRun->IsSidewaysLeft()) { 275 inlineOffset = wm.IsBidiLTR() ? -caretMetrics.mCaretWidth 276 : caretMetrics.mCaretWidth; 277 } 278 } 279 } 280 281 // on RTL frames the right edge of mCaretRect must be equal to framePos 282 if (aFrame->StyleVisibility()->mDirection == StyleDirection::Rtl) { 283 if (vertical) { 284 inlineOffset -= caretMetrics.mCaretWidth; 285 } else { 286 inlineOffset -= caretMetrics.mCaretWidth; 287 } 288 } 289 290 if (vertical) { 291 framePos.x = caretBlockAxisMetrics.mOffset; 292 framePos.y += inlineOffset; 293 } else { 294 framePos.x += inlineOffset; 295 framePos.y = caretBlockAxisMetrics.mOffset; 296 } 297 298 rect = nsRect(framePos, vertical ? nsSize(caretBlockAxisMetrics.mExtent, 299 caretMetrics.mCaretWidth) 300 : nsSize(caretMetrics.mCaretWidth, 301 caretBlockAxisMetrics.mExtent)); 302 303 rect.MoveBy(AdjustRectForClipping(rect, aFrame, vertical)); 304 if (aBidiIndicatorSize) { 305 *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize; 306 } 307 return rect; 308 } 309 310 auto nsCaret::CaretPositionFor(const Selection* aSelection) -> CaretPosition { 311 if (!aSelection) { 312 return {}; 313 } 314 const nsFrameSelection* frameSelection = aSelection->GetFrameSelection(); 315 if (!frameSelection) { 316 return {}; 317 } 318 nsINode* node = aSelection->GetFocusNode(); 319 if (!node) { 320 return {}; 321 } 322 return { 323 node, 324 int32_t(aSelection->FocusOffset()), 325 frameSelection->GetHint(), 326 frameSelection->GetCaretBidiLevel(), 327 }; 328 } 329 330 CaretFrameData nsCaret::GetFrameAndOffset(const CaretPosition& aPosition) { 331 nsINode* focusNode = aPosition.mContent; 332 int32_t focusOffset = aPosition.mOffset; 333 334 if (!focusNode || !focusNode->IsContent()) { 335 return {}; 336 } 337 338 nsIContent* contentNode = focusNode->AsContent(); 339 return SelectionMovementUtils::GetCaretFrameForNodeOffset( 340 nullptr, contentNode, focusOffset, aPosition.mHint, aPosition.mBidiLevel, 341 ForceEditableRegion::No); 342 } 343 344 /* static */ 345 nsIFrame* nsCaret::GetGeometry(const Selection* aSelection, nsRect* aRect) { 346 auto data = GetFrameAndOffset(CaretPositionFor(aSelection)); 347 if (data.mFrame) { 348 *aRect = 349 GetGeometryForFrame(data.mFrame, data.mOffsetInFrameContent, nullptr); 350 } 351 return data.mFrame; 352 } 353 354 [[nodiscard]] static nsIFrame* GetContainingBlockIfNeeded(nsIFrame* aFrame) { 355 if (aFrame->IsBlockOutside() || aFrame->IsBlockFrameOrSubclass()) { 356 return nullptr; 357 } 358 return aFrame->GetContainingBlock(); 359 } 360 361 void nsCaret::SchedulePaint() { 362 if (mLastPaintedFrame) { 363 mLastPaintedFrame->SchedulePaint(); 364 mLastPaintedFrame = nullptr; 365 } 366 auto data = GetFrameAndOffset(mCaretPosition); 367 if (!data.mFrame) { 368 return; 369 } 370 nsIFrame* frame = data.mFrame; 371 if (nsIFrame* cb = GetContainingBlockIfNeeded(frame)) { 372 frame = cb; 373 } 374 frame->SchedulePaint(); 375 } 376 377 void nsCaret::SetVisibilityDuringSelection(bool aVisibility) { 378 if (mShowDuringSelection == aVisibility) { 379 return; 380 } 381 mShowDuringSelection = aVisibility; 382 if (mHiddenDuringSelection && aVisibility) { 383 RemoveForceHide(); 384 mHiddenDuringSelection = false; 385 } 386 SchedulePaint(); 387 } 388 389 void nsCaret::UpdateCaretPositionFromSelectionIfNeeded() { 390 if (mFixedCaretPosition) { 391 return; 392 } 393 CaretPosition newPos = CaretPositionFor(GetSelection()); 394 if (newPos == mCaretPosition) { 395 return; 396 } 397 mCaretPosition = newPos; 398 SchedulePaint(); 399 } 400 401 void nsCaret::SetCaretPosition(nsINode* aNode, int32_t aOffset) { 402 // Schedule a paint with the old position to invalidate. 403 mFixedCaretPosition = !!aNode; 404 if (mFixedCaretPosition) { 405 mCaretPosition = {aNode, aOffset}; 406 SchedulePaint(); 407 } else { 408 UpdateCaretPositionFromSelectionIfNeeded(); 409 } 410 ResetBlinking(); 411 } 412 413 void nsCaret::CheckSelectionLanguageChange() { 414 if (!StaticPrefs::bidi_browser_ui()) { 415 return; 416 } 417 418 bool isKeyboardRTL = false; 419 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard(); 420 if (bidiKeyboard) { 421 bidiKeyboard->IsLangRTL(&isKeyboardRTL); 422 } 423 // Call SelectionLanguageChange on every paint. Mostly it will be a noop 424 // but it should be fast anyway. This guarantees we never paint the caret 425 // at the wrong place. 426 Selection* selection = GetSelection(); 427 if (selection) { 428 selection->SelectionLanguageChange(isKeyboardRTL); 429 } 430 } 431 432 // This ensures that the caret is not affected by clips on inlines and so forth. 433 [[nodiscard]] static nsIFrame* MapToContainingBlock(nsIFrame* aFrame, 434 nsRect* aCaretRect, 435 nsRect* aHookRect) { 436 nsIFrame* containingBlock = GetContainingBlockIfNeeded(aFrame); 437 if (!containingBlock) { 438 return aFrame; 439 } 440 441 if (aCaretRect) { 442 *aCaretRect = nsLayoutUtils::TransformFrameRectToAncestor( 443 aFrame, *aCaretRect, containingBlock); 444 } 445 if (aHookRect) { 446 *aHookRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, *aHookRect, 447 containingBlock); 448 } 449 return containingBlock; 450 } 451 452 nsIFrame* nsCaret::GetPaintGeometry(nsRect* aCaretRect, nsRect* aHookRect, 453 nscolor* aCaretColor) { 454 MOZ_ASSERT(!!aCaretRect == !!aHookRect); 455 456 // Return null if we should not be visible. 457 if (!IsVisible() || !mIsBlinkOn) { 458 return nullptr; 459 } 460 461 // Update selection language direction now so the new direction will be 462 // taken into account when computing the caret position below. 463 CheckSelectionLanguageChange(); 464 465 auto data = GetFrameAndOffset(mCaretPosition); 466 MOZ_ASSERT(!!data.mFrame == !!data.mUnadjustedFrame); 467 if (!data.mFrame) { 468 return nullptr; 469 } 470 471 nsIFrame* frame = data.mFrame; 472 nsIFrame* unadjustedFrame = data.mUnadjustedFrame; 473 int32_t frameOffset(data.mOffsetInFrameContent); 474 // Now we have a frame, check whether it's appropriate to show the caret here. 475 // Note we need to check the unadjusted frame, otherwise consider the 476 // following case: 477 // 478 // <div contenteditable><span contenteditable=false>Text </span><br> 479 // 480 // Where the selection is targeting the <br>. We want to display the caret, 481 // since the <br> we're focused at is editable, but we do want to paint it at 482 // the adjusted frame offset, so that we can see the collapsed whitespace. 483 if (unadjustedFrame->IsContentDisabled()) { 484 return nullptr; 485 } 486 487 // If the offset falls outside of the frame, then don't paint the caret. 488 if (frame->IsTextFrame()) { 489 auto [startOffset, endOffset] = frame->GetOffsets(); 490 if (startOffset > frameOffset || endOffset < frameOffset) { 491 return nullptr; 492 } 493 } 494 495 if (aCaretColor) { 496 *aCaretColor = frame->GetCaretColorAt(frameOffset); 497 } 498 499 if (aCaretRect || aHookRect) { 500 ComputeCaretRects(frame, frameOffset, aCaretRect, aHookRect); 501 } 502 return MapToContainingBlock(frame, aCaretRect, aHookRect); 503 } 504 505 nsIFrame* nsCaret::GetPaintGeometry() { 506 return GetPaintGeometry(nullptr, nullptr); 507 } 508 509 nsIFrame* nsCaret::GetPaintGeometry(nsRect* aRect) { 510 nsRect caretRect; 511 nsRect hookRect; 512 nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect); 513 aRect->UnionRect(caretRect, hookRect); 514 return frame; 515 } 516 517 void nsCaret::PaintCaret(DrawTarget& aDrawTarget, nsIFrame* aForFrame, 518 const nsPoint& aOffset) { 519 nsRect caretRect; 520 nsRect hookRect; 521 nscolor color; 522 nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect, &color); 523 MOZ_ASSERT(frame == aForFrame, "We're referring different frame"); 524 525 if (!frame) { 526 return; 527 } 528 529 int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); 530 Rect devPxCaretRect = NSRectToSnappedRect(caretRect + aOffset, 531 appUnitsPerDevPixel, aDrawTarget); 532 Rect devPxHookRect = 533 NSRectToSnappedRect(hookRect + aOffset, appUnitsPerDevPixel, aDrawTarget); 534 535 ColorPattern pattern(ToDeviceColor(color)); 536 aDrawTarget.FillRect(devPxCaretRect, pattern); 537 if (!hookRect.IsEmpty()) { 538 aDrawTarget.FillRect(devPxHookRect, pattern); 539 } 540 } 541 542 NS_IMETHODIMP 543 nsCaret::NotifySelectionChanged(Document*, Selection* aDomSel, int16_t aReason, 544 int32_t aAmount) { 545 // The same caret is shared amongst the document and any text widgets it 546 // may contain. This means that the caret could get notifications from 547 // multiple selections. 548 // 549 // If this notification is for a selection that is not the one the 550 // the caret is currently interested in (mDomSelectionWeak), or the caret 551 // position is fixed, then there is nothing to do! 552 if (mDomSelectionWeak != aDomSel) { 553 return NS_OK; 554 } 555 556 // Check if we need to hide / un-hide the caret due to the selection being 557 // collapsed. 558 if (!mShowDuringSelection && 559 !aDomSel->IsCollapsed() != mHiddenDuringSelection) { 560 if (mHiddenDuringSelection) { 561 RemoveForceHide(); 562 } else { 563 AddForceHide(); 564 } 565 mHiddenDuringSelection = !mHiddenDuringSelection; 566 } 567 568 // We don't bother computing the caret position when invisible. We'll do it if 569 // we become visible in CaretVisibilityMaybeChanged(). 570 if (IsVisible()) { 571 UpdateCaretPositionFromSelectionIfNeeded(); 572 ResetBlinking(); 573 } 574 575 return NS_OK; 576 } 577 578 void nsCaret::ResetBlinking() { 579 // How many milliseconds we allow the timer to get off-sync when resetting 580 // blinking too often. 50 is not likely to be user observable in practice, 581 // it's ~4 animation frames at 60fps. 582 static const auto kBlinkTimerSlack = TimeDuration::FromMilliseconds(50); 583 const bool wasBlinkOn = mIsBlinkOn; 584 mIsBlinkOn = true; 585 586 if (mReadOnly || !IsVisible()) { 587 StopBlinking(); 588 return; 589 } 590 591 const int32_t oldBlinkTime = mBlinkTime; 592 mBlinkTime = LookAndFeel::CaretBlinkTime(); 593 if (mBlinkTime <= 0) { 594 StopBlinking(); 595 return; 596 } 597 598 if (!wasBlinkOn) { 599 SchedulePaint(); 600 } 601 602 mBlinkCount = LookAndFeel::CaretBlinkCount(); 603 604 const auto now = TimeStamp::NowLoRes(); 605 const bool mustResetTimer = mBlinkTime != oldBlinkTime || 606 mLastBlinkTimerReset.IsNull() || 607 (now - mLastBlinkTimerReset) > kBlinkTimerSlack; 608 if (!mustResetTimer) { 609 return; 610 } 611 612 if (!mBlinkTimer) { 613 mBlinkTimer = NS_NewTimer(); 614 } 615 mLastBlinkTimerReset = now; 616 mBlinkTimer->InitWithNamedFuncCallback(CaretBlinkCallback, this, mBlinkTime, 617 nsITimer::TYPE_REPEATING_SLACK, 618 "CaretBlinkCallback"_ns); 619 } 620 621 void nsCaret::StopBlinking() { 622 if (mBlinkTimer) { 623 mBlinkTimer->Cancel(); 624 mLastBlinkTimerReset = TimeStamp(); 625 } 626 } 627 628 size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { 629 size_t total = aMallocSizeOf(this); 630 if (mBlinkTimer) { 631 total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf); 632 } 633 return total; 634 } 635 636 void nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset, 637 nsRect* aCaretRect, nsRect* aHookRect) { 638 MOZ_ASSERT(aCaretRect && aHookRect); 639 NS_ASSERTION(aFrame, "Should have a frame here"); 640 641 WritingMode wm = aFrame->GetWritingMode(); 642 bool isVertical = wm.IsVertical(); 643 644 nscoord bidiIndicatorSize; 645 *aCaretRect = GetGeometryForFrame(aFrame, aFrameOffset, &bidiIndicatorSize); 646 647 // Simon -- make a hook to draw to the left or right of the caret to show 648 // keyboard language direction 649 aHookRect->SetEmpty(); 650 if (!StaticPrefs::bidi_browser_ui()) { 651 return; 652 } 653 654 bool isCaretRTL; 655 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard(); 656 // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the 657 // keyboard direction, or the user has no right-to-left keyboard 658 // installed, so we never draw the hook. 659 if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) { 660 // If keyboard language is RTL, draw the hook on the left; if LTR, to the 661 // right The height of the hook rectangle is the same as the width of the 662 // caret rectangle. 663 if (isVertical) { 664 if (wm.IsSidewaysLR()) { 665 aHookRect->SetRect(aCaretRect->x + bidiIndicatorSize, 666 aCaretRect->y + (!isCaretRTL ? bidiIndicatorSize * -1 667 : aCaretRect->height), 668 aCaretRect->height, bidiIndicatorSize); 669 } else { 670 aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize, 671 aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1 672 : aCaretRect->height), 673 aCaretRect->height, bidiIndicatorSize); 674 } 675 } else { 676 aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1 677 : aCaretRect->width), 678 aCaretRect->y + bidiIndicatorSize, bidiIndicatorSize, 679 aCaretRect->width); 680 } 681 } 682 } 683 684 /* static */ 685 void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure) { 686 nsCaret* theCaret = static_cast<nsCaret*>(aClosure); 687 if (!theCaret) { 688 return; 689 } 690 theCaret->mLastBlinkTimerReset = TimeStamp(); 691 theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn; 692 theCaret->SchedulePaint(); 693 694 // mBlinkCount of -1 means blink count is not enabled. 695 if (theCaret->mBlinkCount == -1) { 696 return; 697 } 698 699 // Track the blink count, but only at end of a blink cycle. 700 if (theCaret->mIsBlinkOn) { 701 // If we exceeded the blink count, stop the timer. 702 if (--theCaret->mBlinkCount <= 0) { 703 theCaret->StopBlinking(); 704 } 705 } 706 }