Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/base/nsCaret.cpp
Line
Count
Source (jump to first uncovered line)
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 "gfxUtils.h"
14
#include "mozilla/gfx/2D.h"
15
#include "nsCOMPtr.h"
16
#include "nsFontMetrics.h"
17
#include "nsITimer.h"
18
#include "nsFrameSelection.h"
19
#include "nsIFrame.h"
20
#include "nsIScrollableFrame.h"
21
#include "nsIContent.h"
22
#include "nsIPresShell.h"
23
#include "nsLayoutUtils.h"
24
#include "nsPresContext.h"
25
#include "nsBlockFrame.h"
26
#include "nsISelectionController.h"
27
#include "nsTextFrame.h"
28
#include "nsXULPopupManager.h"
29
#include "nsMenuPopupFrame.h"
30
#include "nsTextFragment.h"
31
#include "mozilla/Preferences.h"
32
#include "mozilla/LookAndFeel.h"
33
#include "mozilla/dom/Selection.h"
34
#include "nsIBidiKeyboard.h"
35
#include "nsContentUtils.h"
36
37
using namespace mozilla;
38
using namespace mozilla::dom;
39
using namespace mozilla::gfx;
40
41
// The bidi indicator hangs off the caret to one side, to show which
42
// direction the typing is in. It needs to be at least 2x2 to avoid looking like
43
// an insignificant dot
44
static const int32_t kMinBidiIndicatorPixels = 2;
45
46
// The default caret blinking rate (in ms of blinking interval)
47
static const uint32_t kDefaultCaretBlinkRate = 500;
48
49
/**
50
 * Find the first frame in an in-order traversal of the frame subtree rooted
51
 * at aFrame which is either a text frame logically at the end of a line,
52
 * or which is aStopAtFrame. Return null if no such frame is found. We don't
53
 * descend into the children of non-eLineParticipant frames.
54
 */
55
static nsIFrame*
56
CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame)
57
0
{
58
0
  if (aFrame == aStopAtFrame ||
59
0
      ((aFrame->IsTextFrame() &&
60
0
       (static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine())))
61
0
    return aFrame;
62
0
  if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
63
0
    return nullptr;
64
0
65
0
  for (nsIFrame* f : aFrame->PrincipalChildList())
66
0
  {
67
0
    nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame);
68
0
    if (r)
69
0
      return r;
70
0
  }
71
0
  return nullptr;
72
0
}
73
74
static nsLineBox*
75
FindContainingLine(nsIFrame* aFrame)
76
0
{
77
0
  while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
78
0
  {
79
0
    nsIFrame* parent = aFrame->GetParent();
80
0
    nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent);
81
0
    if (blockParent)
82
0
    {
83
0
      bool isValid;
84
0
      nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
85
0
      return isValid ? iter.GetLine().get() : nullptr;
86
0
    }
87
0
    aFrame = parent;
88
0
  }
89
0
  return nullptr;
90
0
}
91
92
static void
93
AdjustCaretFrameForLineEnd(nsIFrame** aFrame, int32_t* aOffset)
94
0
{
95
0
  nsLineBox* line = FindContainingLine(*aFrame);
96
0
  if (!line)
97
0
    return;
98
0
  int32_t count = line->GetChildCount();
99
0
  for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling())
100
0
  {
101
0
    nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
102
0
    if (r == *aFrame)
103
0
      return;
104
0
    if (r)
105
0
    {
106
0
      *aFrame = r;
107
0
      NS_ASSERTION(r->IsTextFrame(), "Expected text frame");
108
0
      *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
109
0
      return;
110
0
    }
111
0
  }
112
0
}
113
114
static bool
115
IsBidiUI()
116
0
{
117
0
  return Preferences::GetBool("bidi.browser.ui");
118
0
}
119
120
nsCaret::nsCaret()
121
: mOverrideOffset(0)
122
, mBlinkCount(-1)
123
, mBlinkRate(0)
124
, mHideCount(0)
125
, mIsBlinkOn(false)
126
, mVisible(false)
127
, mReadOnly(false)
128
, mShowDuringSelection(false)
129
, mIgnoreUserModify(true)
130
0
{
131
0
}
132
133
nsCaret::~nsCaret()
134
0
{
135
0
  StopBlinking();
136
0
}
137
138
nsresult nsCaret::Init(nsIPresShell *inPresShell)
139
0
{
140
0
  NS_ENSURE_ARG(inPresShell);
141
0
142
0
  mPresShell = do_GetWeakReference(inPresShell);    // the presshell owns us, so no addref
143
0
  NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
144
0
145
0
  mShowDuringSelection =
146
0
    LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection,
147
0
                        mShowDuringSelection ? 1 : 0) != 0;
148
0
149
0
  // get the selection from the pres shell, and set ourselves up as a selection
150
0
  // listener
151
0
152
0
  nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
153
0
  if (!selCon) {
154
0
    return NS_ERROR_FAILURE;
155
0
  }
156
0
157
0
  RefPtr<Selection> selection =
158
0
    selCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
159
0
  if (!selection) {
160
0
    return NS_ERROR_FAILURE;
161
0
  }
162
0
163
0
  selection->AddSelectionListener(this);
164
0
  mDomSelectionWeak = selection;
165
0
166
0
  return NS_OK;
167
0
}
168
169
static bool
170
DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset)
171
0
{
172
0
  nsIContent* content = aFrame->GetContent();
173
0
  const nsTextFragment* frag = content->GetText();
174
0
  if (!frag)
175
0
    return false;
176
0
  if (aOffset < 0 || uint32_t(aOffset) >= frag->GetLength())
177
0
    return false;
178
0
  char16_t ch = frag->CharAt(aOffset);
179
0
  return 0x2e80 <= ch && ch <= 0xd7ff;
180
0
}
181
182
nsCaret::Metrics
183
nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, nscoord aCaretHeight)
184
0
{
185
0
  // Compute nominal sizes in appunits
186
0
  nscoord caretWidth =
187
0
    (aCaretHeight * LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio, 0.0f)) +
188
0
    nsPresContext::CSSPixelsToAppUnits(
189
0
        LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth, 1));
190
0
191
0
  if (DrawCJKCaret(aFrame, aOffset)) {
192
0
    caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
193
0
  }
194
0
  nscoord bidiIndicatorSize = nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
195
0
  bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize);
196
0
197
0
  // Round them to device pixels. Always round down, except that anything
198
0
  // between 0 and 1 goes up to 1 so we don't let the caret disappear.
199
0
  int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
200
0
  Metrics result;
201
0
  result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
202
0
  result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
203
0
  return result;
204
0
}
205
206
void nsCaret::Terminate()
207
0
{
208
0
  // this doesn't erase the caret if it's drawn. Should it? We might not have
209
0
  // a good drawing environment during teardown.
210
0
211
0
  StopBlinking();
212
0
  mBlinkTimer = nullptr;
213
0
214
0
  // unregiser ourselves as a selection listener
215
0
  if (mDomSelectionWeak) {
216
0
    mDomSelectionWeak->RemoveSelectionListener(this);
217
0
  }
218
0
  mDomSelectionWeak = nullptr;
219
0
  mPresShell = nullptr;
220
0
221
0
  mOverrideContent = nullptr;
222
0
}
223
224
NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener)
225
226
Selection* nsCaret::GetSelection()
227
0
{
228
0
  return mDomSelectionWeak;
229
0
}
230
231
void nsCaret::SetSelection(Selection *aDOMSel)
232
0
{
233
0
  MOZ_ASSERT(aDOMSel);
234
0
  mDomSelectionWeak = aDOMSel;
235
0
  ResetBlinking();
236
0
  SchedulePaint(aDOMSel);
237
0
}
238
239
void nsCaret::SetVisible(bool inMakeVisible)
240
0
{
241
0
  mVisible = inMakeVisible;
242
0
  mIgnoreUserModify = mVisible;
243
0
  ResetBlinking();
244
0
  SchedulePaint();
245
0
}
246
247
void nsCaret::AddForceHide()
248
0
{
249
0
  MOZ_ASSERT(mHideCount < UINT32_MAX);
250
0
  if (++mHideCount > 1) {
251
0
    return;
252
0
  }
253
0
  ResetBlinking();
254
0
  SchedulePaint();
255
0
}
256
257
void nsCaret::RemoveForceHide()
258
0
{
259
0
  if (!mHideCount || --mHideCount) {
260
0
    return;
261
0
  }
262
0
  ResetBlinking();
263
0
  SchedulePaint();
264
0
}
265
266
void nsCaret::SetCaretReadOnly(bool inMakeReadonly)
267
0
{
268
0
  mReadOnly = inMakeReadonly;
269
0
  ResetBlinking();
270
0
  SchedulePaint();
271
0
}
272
273
/* static */ nsRect
274
nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
275
                             int32_t   aFrameOffset,
276
                             nscoord*  aBidiIndicatorSize)
277
0
{
278
0
  nsPoint framePos(0, 0);
279
0
  nsRect rect;
280
0
  nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
281
0
  if (NS_FAILED(rv)) {
282
0
    if (aBidiIndicatorSize) {
283
0
      *aBidiIndicatorSize = 0;
284
0
    }
285
0
    return rect;
286
0
  }
287
0
288
0
  nsIFrame* frame = aFrame->GetContentInsertionFrame();
289
0
  if (!frame) {
290
0
    frame = aFrame;
291
0
  }
292
0
  NS_ASSERTION(!(frame->GetStateBits() & NS_FRAME_IN_REFLOW),
293
0
               "We should not be in the middle of reflow");
294
0
  nscoord baseline = frame->GetCaretBaseline();
295
0
  nscoord ascent = 0, descent = 0;
296
0
  RefPtr<nsFontMetrics> fm =
297
0
    nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
298
0
  NS_ASSERTION(fm, "We should be able to get the font metrics");
299
0
  if (fm) {
300
0
    ascent = fm->MaxAscent();
301
0
    descent = fm->MaxDescent();
302
0
  }
303
0
  nscoord height = ascent + descent;
304
0
  WritingMode wm = aFrame->GetWritingMode();
305
0
  bool vertical = wm.IsVertical();
306
0
  if (vertical) {
307
0
    if (wm.IsLineInverted()) {
308
0
      framePos.x = baseline - descent;
309
0
    } else {
310
0
      framePos.x = baseline - ascent;
311
0
    }
312
0
  } else {
313
0
    framePos.y = baseline - ascent;
314
0
  }
315
0
  Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height);
316
0
317
0
  nsTextFrame* textFrame = do_QueryFrame(aFrame);
318
0
  if (textFrame) {
319
0
    gfxTextRun* textRun =
320
0
      textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated);
321
0
    if (textRun) {
322
0
      // For "upstream" text where the textrun direction is reversed from the
323
0
      // frame's inline-dir we want the caret to be painted before rather than
324
0
      // after its nominal inline position, so we offset by its width.
325
0
      bool textRunDirIsReverseOfFrame =
326
0
        wm.IsInlineReversed() != textRun->IsInlineReversed();
327
0
      // However, in sideways-lr mode we invert this behavior because this is
328
0
      // the one writing mode where bidi-LTR corresponds to inline-reversed
329
0
      // already, which reverses the desired caret placement behavior.
330
0
      // Note that the following condition is equivalent to:
331
0
      //   if ( (!textRun->IsSidewaysLeft() && textRunDirIsReverseOfFrame) ||
332
0
      //        (textRun->IsSidewaysLeft()  && !textRunDirIsReverseOfFrame) )
333
0
      if (textRunDirIsReverseOfFrame != textRun->IsSidewaysLeft()) {
334
0
        int dir = wm.IsBidiLTR() ? -1 : 1;
335
0
        if (vertical) {
336
0
          framePos.y += dir * caretMetrics.mCaretWidth;
337
0
        } else {
338
0
          framePos.x += dir * caretMetrics.mCaretWidth;
339
0
        }
340
0
      }
341
0
    }
342
0
  }
343
0
344
0
  rect = nsRect(framePos, vertical ? nsSize(height, caretMetrics.mCaretWidth) :
345
0
                                     nsSize(caretMetrics.mCaretWidth, height));
346
0
347
0
  // Clamp the inline-position to be within our scroll frame. If we don't, then
348
0
  // it clips us, and we don't appear at all. See bug 335560.
349
0
  nsIFrame* scrollFrame =
350
0
    nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::Scroll);
351
0
  if (scrollFrame) {
352
0
    // First, use the scrollFrame to get at the scrollable view that we're in.
353
0
    nsIScrollableFrame *sf = do_QueryFrame(scrollFrame);
354
0
    nsIFrame *scrolled = sf->GetScrolledFrame();
355
0
    nsRect caretInScroll = rect + aFrame->GetOffsetTo(scrolled);
356
0
357
0
    // Now see if the caret extends beyond the view's bounds. If it does,
358
0
    // then snap it back, put it as close to the edge as it can.
359
0
    if (vertical) {
360
0
      nscoord overflow = caretInScroll.YMost() -
361
0
        scrolled->GetVisualOverflowRectRelativeToSelf().height;
362
0
      if (overflow > 0) {
363
0
        rect.y -= overflow;
364
0
      }
365
0
    } else {
366
0
      nscoord overflow = caretInScroll.XMost() -
367
0
        scrolled->GetVisualOverflowRectRelativeToSelf().width;
368
0
      if (overflow > 0) {
369
0
        rect.x -= overflow;
370
0
      }
371
0
    }
372
0
  }
373
0
374
0
  if (aBidiIndicatorSize) {
375
0
    *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
376
0
  }
377
0
  return rect;
378
0
}
379
380
nsIFrame*
381
nsCaret::GetFrameAndOffset(Selection* aSelection,
382
                           nsINode* aOverrideNode, int32_t aOverrideOffset,
383
                           int32_t* aFrameOffset)
384
0
{
385
0
  nsINode* focusNode;
386
0
  int32_t focusOffset;
387
0
388
0
  if (aOverrideNode) {
389
0
    focusNode = aOverrideNode;
390
0
    focusOffset = aOverrideOffset;
391
0
  } else if (aSelection) {
392
0
    focusNode = aSelection->GetFocusNode();
393
0
    focusOffset = aSelection->FocusOffset();
394
0
  } else {
395
0
    return nullptr;
396
0
  }
397
0
398
0
  if (!focusNode || !focusNode->IsContent()) {
399
0
    return nullptr;
400
0
  }
401
0
402
0
  nsIContent* contentNode = focusNode->AsContent();
403
0
  nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
404
0
  nsBidiLevel bidiLevel = frameSelection->GetCaretBidiLevel();
405
0
  nsIFrame* frame;
406
0
  nsresult rv = nsCaret::GetCaretFrameForNodeOffset(
407
0
      frameSelection, contentNode, focusOffset,
408
0
      frameSelection->GetHint(), bidiLevel, &frame, aFrameOffset);
409
0
  if (NS_FAILED(rv) || !frame) {
410
0
    return nullptr;
411
0
  }
412
0
413
0
  return frame;
414
0
}
415
416
/* static */ nsIFrame*
417
nsCaret::GetGeometry(Selection* aSelection, nsRect* aRect)
418
0
{
419
0
  int32_t frameOffset;
420
0
  nsIFrame* frame = GetFrameAndOffset(aSelection, nullptr, 0, &frameOffset);
421
0
  if (frame) {
422
0
    *aRect = GetGeometryForFrame(frame, frameOffset, nullptr);
423
0
  }
424
0
  return frame;
425
0
}
426
427
void
428
nsCaret::SchedulePaint(Selection* aSelection)
429
0
{
430
0
  Selection* selection;
431
0
  if (aSelection) {
432
0
    selection = aSelection;
433
0
  } else {
434
0
    selection = GetSelection();
435
0
  }
436
0
  nsINode* focusNode;
437
0
  if (mOverrideContent) {
438
0
    focusNode = mOverrideContent;
439
0
  } else if (selection) {
440
0
    focusNode = selection->GetFocusNode();
441
0
  } else {
442
0
    return;
443
0
  }
444
0
  if (!focusNode || !focusNode->IsContent()) {
445
0
    return;
446
0
  }
447
0
  nsIFrame* f = focusNode->AsContent()->GetPrimaryFrame();
448
0
  if (!f) {
449
0
    return;
450
0
  }
451
0
  // This may not be the correct continuation frame, but that's OK since we're
452
0
  // just scheduling a paint of the window (or popup).
453
0
  f->SchedulePaint();
454
0
}
455
456
void nsCaret::SetVisibilityDuringSelection(bool aVisibility)
457
0
{
458
0
  mShowDuringSelection = aVisibility;
459
0
  SchedulePaint();
460
0
}
461
462
void
463
nsCaret::SetCaretPosition(nsINode* aNode, int32_t aOffset)
464
0
{
465
0
  mOverrideContent = aNode;
466
0
  mOverrideOffset = aOffset;
467
0
468
0
  ResetBlinking();
469
0
  SchedulePaint();
470
0
}
471
472
void
473
nsCaret::CheckSelectionLanguageChange()
474
0
{
475
0
  if (!IsBidiUI()) {
476
0
    return;
477
0
  }
478
0
479
0
  bool isKeyboardRTL = false;
480
0
  nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
481
0
  if (bidiKeyboard) {
482
0
    bidiKeyboard->IsLangRTL(&isKeyboardRTL);
483
0
  }
484
0
  // Call SelectionLanguageChange on every paint. Mostly it will be a noop
485
0
  // but it should be fast anyway. This guarantees we never paint the caret
486
0
  // at the wrong place.
487
0
  Selection* selection = GetSelection();
488
0
  if (selection) {
489
0
    selection->SelectionLanguageChange(isKeyboardRTL);
490
0
  }
491
0
}
492
493
nsIFrame*
494
nsCaret::GetPaintGeometry(nsRect* aRect)
495
0
{
496
0
  // Return null if we should not be visible.
497
0
  if (!IsVisible() || !mIsBlinkOn) {
498
0
    return nullptr;
499
0
  }
500
0
501
0
  // Update selection language direction now so the new direction will be
502
0
  // taken into account when computing the caret position below.
503
0
  CheckSelectionLanguageChange();
504
0
505
0
  int32_t frameOffset;
506
0
  nsIFrame* frame = GetFrameAndOffset(GetSelection(),
507
0
      mOverrideContent, mOverrideOffset, &frameOffset);
508
0
  if (!frame) {
509
0
    return nullptr;
510
0
  }
511
0
512
0
  // now we have a frame, check whether it's appropriate to show the caret here
513
0
  const nsStyleUI* ui = frame->StyleUI();
514
0
  if ((!mIgnoreUserModify && ui->mUserModify == StyleUserModify::ReadOnly) ||
515
0
      frame->IsContentDisabled()) {
516
0
    return nullptr;
517
0
  }
518
0
519
0
  // If the offset falls outside of the frame, then don't paint the caret.
520
0
  int32_t startOffset, endOffset;
521
0
  if (frame->IsTextFrame() &&
522
0
      (NS_FAILED(frame->GetOffsets(startOffset, endOffset)) ||
523
0
       startOffset > frameOffset || endOffset < frameOffset)) {
524
0
    return nullptr;
525
0
  }
526
0
527
0
  nsRect caretRect;
528
0
  nsRect hookRect;
529
0
  ComputeCaretRects(frame, frameOffset, &caretRect, &hookRect);
530
0
531
0
  aRect->UnionRect(caretRect, hookRect);
532
0
  return frame;
533
0
}
534
535
nsIFrame*
536
0
nsCaret::GetFrame(int32_t* aContentOffset) {
537
0
  return GetFrameAndOffset(GetSelection(),
538
0
                           mOverrideContent,
539
0
                           mOverrideOffset,
540
0
                           aContentOffset);
541
0
}
542
543
void nsCaret::PaintCaret(DrawTarget& aDrawTarget,
544
                         nsIFrame* aForFrame,
545
                         const nsPoint &aOffset)
546
0
{
547
0
  int32_t contentOffset;
548
0
  nsIFrame* frame = GetFrame(&contentOffset);
549
0
  if (!frame) {
550
0
    return;
551
0
  }
552
0
  NS_ASSERTION(frame == aForFrame, "We're referring different frame");
553
0
554
0
  int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
555
0
556
0
  nsRect caretRect;
557
0
  nsRect hookRect;
558
0
  ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect);
559
0
560
0
  Rect devPxCaretRect =
561
0
    NSRectToSnappedRect(caretRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
562
0
  Rect devPxHookRect =
563
0
    NSRectToSnappedRect(hookRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
564
0
  ColorPattern color(ToDeviceColor(frame->GetCaretColorAt(contentOffset)));
565
0
566
0
  aDrawTarget.FillRect(devPxCaretRect, color);
567
0
  if (!hookRect.IsEmpty()) {
568
0
    aDrawTarget.FillRect(devPxHookRect, color);
569
0
  }
570
0
}
571
572
NS_IMETHODIMP
573
nsCaret::NotifySelectionChanged(nsIDocument *, Selection* aDomSel,
574
                                int16_t aReason)
575
0
{
576
0
  // Note that aDomSel, per the comment below may not be the same as our
577
0
  // selection, but that's OK since if that is the case, it wouldn't have
578
0
  // mattered what IsVisible() returns here, so we just opt for checking
579
0
  // the selection later down below.
580
0
  if ((aReason & nsISelectionListener::MOUSEUP_REASON) || !IsVisible(aDomSel))//this wont do
581
0
    return NS_OK;
582
0
583
0
  // The same caret is shared amongst the document and any text widgets it
584
0
  // may contain. This means that the caret could get notifications from
585
0
  // multiple selections.
586
0
  //
587
0
  // If this notification is for a selection that is not the one the
588
0
  // the caret is currently interested in (mDomSelectionWeak), then there
589
0
  // is nothing to do!
590
0
591
0
  if (mDomSelectionWeak != aDomSel)
592
0
    return NS_OK;
593
0
594
0
  ResetBlinking();
595
0
  SchedulePaint(aDomSel);
596
0
597
0
  return NS_OK;
598
0
}
599
600
void nsCaret::ResetBlinking()
601
0
{
602
0
  mIsBlinkOn = true;
603
0
604
0
  if (mReadOnly || !mVisible || mHideCount) {
605
0
    StopBlinking();
606
0
    return;
607
0
  }
608
0
609
0
  uint32_t blinkRate = static_cast<uint32_t>(
610
0
    LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime,
611
0
                        kDefaultCaretBlinkRate));
612
0
  if (mBlinkRate == blinkRate) {
613
0
    // If the rate hasn't changed, then there is nothing to do.
614
0
    return;
615
0
  }
616
0
  mBlinkRate = blinkRate;
617
0
618
0
  if (mBlinkTimer) {
619
0
    mBlinkTimer->Cancel();
620
0
  } else {
621
0
    nsIEventTarget* target = nullptr;
622
0
    if (nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell)) {
623
0
      if (nsCOMPtr<nsIDocument> doc = presShell->GetDocument()) {
624
0
        target = doc->EventTargetFor(TaskCategory::Other);
625
0
      }
626
0
    }
627
0
628
0
    mBlinkTimer = NS_NewTimer(target);
629
0
    if (!mBlinkTimer) {
630
0
      return;
631
0
    }
632
0
  }
633
0
634
0
  if (blinkRate > 0) {
635
0
    mBlinkCount = Preferences::GetInt("ui.caretBlinkCount", -1);
636
0
    mBlinkTimer->InitWithNamedFuncCallback(CaretBlinkCallback, this, blinkRate,
637
0
                                           nsITimer::TYPE_REPEATING_SLACK,
638
0
                                           "nsCaret::CaretBlinkCallback_timer");
639
0
  }
640
0
}
641
642
void nsCaret::StopBlinking()
643
0
{
644
0
  if (mBlinkTimer)
645
0
  {
646
0
    mBlinkTimer->Cancel();
647
0
    mBlinkRate = 0;
648
0
  }
649
0
}
650
651
nsresult
652
nsCaret::GetCaretFrameForNodeOffset(nsFrameSelection*    aFrameSelection,
653
                                    nsIContent*          aContentNode,
654
                                    int32_t              aOffset,
655
                                    CaretAssociationHint aFrameHint,
656
                                    nsBidiLevel          aBidiLevel,
657
                                    nsIFrame**           aReturnFrame,
658
                                    int32_t*             aReturnOffset)
659
0
{
660
0
  if (!aFrameSelection)
661
0
    return NS_ERROR_FAILURE;
662
0
  nsIPresShell* presShell = aFrameSelection->GetShell();
663
0
  if (!presShell)
664
0
    return NS_ERROR_FAILURE;
665
0
666
0
  if (!aContentNode || !aContentNode->IsInComposedDoc() ||
667
0
      presShell->GetDocument() != aContentNode->GetComposedDoc())
668
0
    return NS_ERROR_FAILURE;
669
0
670
0
  nsIFrame* theFrame = nullptr;
671
0
  int32_t   theFrameOffset = 0;
672
0
673
0
  theFrame = aFrameSelection->GetFrameForNodeOffset(
674
0
      aContentNode, aOffset, aFrameHint, &theFrameOffset);
675
0
  if (!theFrame)
676
0
    return NS_ERROR_FAILURE;
677
0
678
0
  // if theFrame is after a text frame that's logically at the end of the line
679
0
  // (e.g. if theFrame is a <br> frame), then put the caret at the end of
680
0
  // that text frame instead. This way, the caret will be positioned as if
681
0
  // trailing whitespace was not trimmed.
682
0
  AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset);
683
0
684
0
  // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
685
0
  //
686
0
  // Direction Style from visibility->mDirection
687
0
  // ------------------
688
0
  // NS_STYLE_DIRECTION_LTR : LTR or Default
689
0
  // NS_STYLE_DIRECTION_RTL
690
0
  if (theFrame->PresContext()->BidiEnabled())
691
0
  {
692
0
    // If there has been a reflow, take the caret Bidi level to be the level of the current frame
693
0
    if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
694
0
      aBidiLevel = theFrame->GetEmbeddingLevel();
695
0
    }
696
0
697
0
    int32_t start;
698
0
    int32_t end;
699
0
    nsIFrame* frameBefore;
700
0
    nsIFrame* frameAfter;
701
0
    nsBidiLevel levelBefore; // Bidi level of the character before the caret
702
0
    nsBidiLevel levelAfter;  // Bidi level of the character after the caret
703
0
704
0
    theFrame->GetOffsets(start, end);
705
0
    if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset)
706
0
    {
707
0
      nsPrevNextBidiLevels levels = aFrameSelection->
708
0
        GetPrevNextBidiLevels(aContentNode, aOffset, false);
709
0
710
0
      /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
711
0
      if (levels.mFrameBefore || levels.mFrameAfter)
712
0
      {
713
0
        frameBefore = levels.mFrameBefore;
714
0
        frameAfter = levels.mFrameAfter;
715
0
        levelBefore = levels.mLevelBefore;
716
0
        levelAfter = levels.mLevelAfter;
717
0
718
0
        if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore))
719
0
        {
720
0
          aBidiLevel = std::max(aBidiLevel, std::min(levelBefore, levelAfter));                                  // rule c3
721
0
          aBidiLevel = std::min(aBidiLevel, std::max(levelBefore, levelAfter));                                  // rule c4
722
0
          if (aBidiLevel == levelBefore                                                                      // rule c1
723
0
              || (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
724
0
                  IS_SAME_DIRECTION(aBidiLevel, levelBefore))   // rule c5
725
0
              || (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
726
0
                  IS_SAME_DIRECTION(aBidiLevel, levelBefore)))  // rule c9
727
0
          {
728
0
            if (theFrame != frameBefore)
729
0
            {
730
0
              if (frameBefore) // if there is a frameBefore, move into it
731
0
              {
732
0
                theFrame = frameBefore;
733
0
                theFrame->GetOffsets(start, end);
734
0
                theFrameOffset = end;
735
0
              }
736
0
              else
737
0
              {
738
0
                // if there is no frameBefore, we must be at the beginning of the line
739
0
                // so we stay with the current frame.
740
0
                // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
741
0
                // real frame for the caret to be in. We have to find the visually first frame on the line.
742
0
                nsBidiLevel baseLevel = frameAfter->GetBaseLevel();
743
0
                if (baseLevel != levelAfter)
744
0
                {
745
0
                  nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0,
746
0
                                         nsPoint(0, 0), false, true, false,
747
0
                                         true, false);
748
0
                  if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
749
0
                    theFrame = pos.mResultFrame;
750
0
                    theFrameOffset = pos.mContentOffset;
751
0
                  }
752
0
                }
753
0
              }
754
0
            }
755
0
          }
756
0
          else if (aBidiLevel == levelAfter                                                                     // rule c2
757
0
                   || (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
758
0
                       IS_SAME_DIRECTION(aBidiLevel, levelAfter))   // rule c6
759
0
                   || (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
760
0
                       IS_SAME_DIRECTION(aBidiLevel, levelAfter)))  // rule c10
761
0
          {
762
0
            if (theFrame != frameAfter)
763
0
            {
764
0
              if (frameAfter)
765
0
              {
766
0
                // if there is a frameAfter, move into it
767
0
                theFrame = frameAfter;
768
0
                theFrame->GetOffsets(start, end);
769
0
                theFrameOffset = start;
770
0
              }
771
0
              else
772
0
              {
773
0
                // if there is no frameAfter, we must be at the end of the line
774
0
                // so we stay with the current frame.
775
0
                // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
776
0
                // real frame for the caret to be in. We have to find the visually last frame on the line.
777
0
                nsBidiLevel baseLevel = frameBefore->GetBaseLevel();
778
0
                if (baseLevel != levelBefore)
779
0
                {
780
0
                  nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0,
781
0
                                         nsPoint(0, 0), false, true, false,
782
0
                                         true, false);
783
0
                  if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
784
0
                    theFrame = pos.mResultFrame;
785
0
                    theFrameOffset = pos.mContentOffset;
786
0
                  }
787
0
                }
788
0
              }
789
0
            }
790
0
          }
791
0
          else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter  // rule c7/8
792
0
                   && IS_SAME_DIRECTION(levelBefore, levelAfter)        // before and after have the same parity
793
0
                   && !IS_SAME_DIRECTION(aBidiLevel, levelAfter))       // caret has different parity
794
0
          {
795
0
            if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameAfter, eDirNext, aBidiLevel, &theFrame)))
796
0
            {
797
0
              theFrame->GetOffsets(start, end);
798
0
              levelAfter = theFrame->GetEmbeddingLevel();
799
0
              if (IS_LEVEL_RTL(aBidiLevel)) // c8: caret to the right of the rightmost character
800
0
                theFrameOffset = IS_LEVEL_RTL(levelAfter) ? start : end;
801
0
              else               // c7: caret to the left of the leftmost character
802
0
                theFrameOffset = IS_LEVEL_RTL(levelAfter) ? end : start;
803
0
            }
804
0
          }
805
0
          else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter  // rule c11/12
806
0
                   && IS_SAME_DIRECTION(levelBefore, levelAfter)        // before and after have the same parity
807
0
                   && !IS_SAME_DIRECTION(aBidiLevel, levelAfter))       // caret has different parity
808
0
          {
809
0
            if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(frameBefore, eDirPrevious, aBidiLevel, &theFrame)))
810
0
            {
811
0
              theFrame->GetOffsets(start, end);
812
0
              levelBefore = theFrame->GetEmbeddingLevel();
813
0
              if (IS_LEVEL_RTL(aBidiLevel)) // c12: caret to the left of the leftmost character
814
0
                theFrameOffset = IS_LEVEL_RTL(levelBefore) ? end : start;
815
0
              else               // c11: caret to the right of the rightmost character
816
0
                theFrameOffset = IS_LEVEL_RTL(levelBefore) ? start : end;
817
0
            }
818
0
          }
819
0
        }
820
0
      }
821
0
    }
822
0
  }
823
0
824
0
  *aReturnFrame = theFrame;
825
0
  *aReturnOffset = theFrameOffset;
826
0
  return NS_OK;
827
0
}
828
829
size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
830
0
{
831
0
  size_t total = aMallocSizeOf(this);
832
0
  if (mPresShell) {
833
0
    // We only want the size of the nsWeakReference object, not the PresShell
834
0
    // (since we don't own the PresShell).
835
0
    total += mPresShell->SizeOfOnlyThis(aMallocSizeOf);
836
0
  }
837
0
  if (mBlinkTimer) {
838
0
    total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
839
0
  }
840
0
  return total;
841
0
}
842
843
bool nsCaret::IsMenuPopupHidingCaret()
844
0
{
845
0
#ifdef MOZ_XUL
846
0
  // Check if there are open popups.
847
0
  nsXULPopupManager *popMgr = nsXULPopupManager::GetInstance();
848
0
  nsTArray<nsIFrame*> popups;
849
0
  popMgr->GetVisiblePopups(popups);
850
0
851
0
  if (popups.Length() == 0)
852
0
    return false; // No popups, so caret can't be hidden by them.
853
0
854
0
  // Get the selection focus content, that's where the caret would
855
0
  // go if it was drawn.
856
0
  if (!mDomSelectionWeak) {
857
0
    return true; // No selection/caret to draw.
858
0
  }
859
0
  nsCOMPtr<nsIContent> caretContent =
860
0
    nsIContent::FromNodeOrNull(mDomSelectionWeak->GetFocusNode());
861
0
  if (!caretContent)
862
0
    return true; // No selection/caret to draw.
863
0
864
0
  // If there's a menu popup open before the popup with
865
0
  // the caret, don't show the caret.
866
0
  for (uint32_t i=0; i<popups.Length(); i++) {
867
0
    nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
868
0
    nsIContent* popupContent = popupFrame->GetContent();
869
0
870
0
    if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) {
871
0
      // The caret is in this popup. There were no menu popups before this
872
0
      // popup, so don't hide the caret.
873
0
      return false;
874
0
    }
875
0
876
0
    if (popupFrame->PopupType() == ePopupTypeMenu && !popupFrame->IsContextMenu()) {
877
0
      // This is an open menu popup. It does not contain the caret (else we'd
878
0
      // have returned above). Even if the caret is in a subsequent popup,
879
0
      // or another document/frame, it should be hidden.
880
0
      return true;
881
0
    }
882
0
  }
883
0
#endif
884
0
885
0
  // There are no open menu popups, no need to hide the caret.
886
0
  return false;
887
0
}
888
889
void
890
nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
891
                           nsRect* aCaretRect, nsRect* aHookRect)
892
0
{
893
0
  NS_ASSERTION(aFrame, "Should have a frame here");
894
0
895
0
  WritingMode wm = aFrame->GetWritingMode();
896
0
  bool isVertical = wm.IsVertical();
897
0
898
0
  nscoord bidiIndicatorSize;
899
0
  *aCaretRect = GetGeometryForFrame(aFrame, aFrameOffset, &bidiIndicatorSize);
900
0
901
0
  // on RTL frames the right edge of mCaretRect must be equal to framePos
902
0
  const nsStyleVisibility* vis = aFrame->StyleVisibility();
903
0
  if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
904
0
    if (isVertical) {
905
0
      aCaretRect->y -= aCaretRect->height;
906
0
    } else {
907
0
      aCaretRect->x -= aCaretRect->width;
908
0
    }
909
0
  }
910
0
911
0
  // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
912
0
  aHookRect->SetEmpty();
913
0
  if (!IsBidiUI()) {
914
0
    return;
915
0
  }
916
0
917
0
  bool isCaretRTL;
918
0
  nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
919
0
  // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
920
0
  // keyboard direction, or the user has no right-to-left keyboard
921
0
  // installed, so we never draw the hook.
922
0
  if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) {
923
0
    // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
924
0
    // The height of the hook rectangle is the same as the width of the caret
925
0
    // rectangle.
926
0
    if (isVertical) {
927
0
      bool isSidewaysLR = wm.IsVerticalLR() && !wm.IsLineInverted();
928
0
      if (isSidewaysLR) {
929
0
        aHookRect->SetRect(aCaretRect->x + bidiIndicatorSize,
930
0
                           aCaretRect->y + (!isCaretRTL ? bidiIndicatorSize * -1 :
931
0
                                                          aCaretRect->height),
932
0
                           aCaretRect->height,
933
0
                           bidiIndicatorSize);
934
0
      } else {
935
0
        aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize,
936
0
                           aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1 :
937
0
                                                         aCaretRect->height),
938
0
                           aCaretRect->height,
939
0
                           bidiIndicatorSize);
940
0
      }
941
0
    } else {
942
0
      aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1 :
943
0
                                                       aCaretRect->width),
944
0
                         aCaretRect->y + bidiIndicatorSize,
945
0
                         bidiIndicatorSize,
946
0
                         aCaretRect->width);
947
0
    }
948
0
  }
949
0
}
950
951
/* static */
952
void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure)
953
0
{
954
0
  nsCaret* theCaret = reinterpret_cast<nsCaret*>(aClosure);
955
0
  if (!theCaret) {
956
0
    return;
957
0
  }
958
0
  theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn;
959
0
  theCaret->SchedulePaint();
960
0
961
0
  // mBlinkCount of -1 means blink count is not enabled.
962
0
  if (theCaret->mBlinkCount == -1) {
963
0
    return;
964
0
  }
965
0
966
0
  // Track the blink count, but only at end of a blink cycle.
967
0
  if (!theCaret->mIsBlinkOn) {
968
0
    // If we exceeded the blink count, stop the timer.
969
0
    if (--theCaret->mBlinkCount <= 0) {
970
0
      theCaret->StopBlinking();
971
0
    }
972
0
  }
973
0
}
974
975
void
976
nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify)
977
0
{
978
0
  mIgnoreUserModify = aIgnoreUserModify;
979
0
  SchedulePaint();
980
0
}