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