/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 | } |