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/. */
7 #include "ContentEventHandler.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/ContentIterator.h"
12 #include "mozilla/IMEStateManager.h"
13 #include "mozilla/IntegerRange.h"
14 #include "mozilla/Maybe.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/RangeBoundary.h"
17 #include "mozilla/RangeUtils.h"
18 #include "mozilla/TextComposition.h"
19 #include "mozilla/TextEditor.h"
20 #include "mozilla/TextEvents.h"
21 #include "mozilla/dom/Element.h"
22 #include "mozilla/dom/HTMLBRElement.h"
23 #include "mozilla/dom/HTMLUnknownElement.h"
24 #include "mozilla/dom/Selection.h"
25 #include "mozilla/dom/Text.h"
28 #include "nsContentUtils.h"
29 #include "nsCopySupport.h"
30 #include "nsElementTable.h"
31 #include "nsFocusManager.h"
32 #include "nsFontMetrics.h"
33 #include "nsFrameSelection.h"
35 #include "nsLayoutUtils.h"
36 #include "nsPresContext.h"
37 #include "nsQueryObject.h"
39 #include "nsTextFragment.h"
40 #include "nsTextFrame.h"
42 #include "mozilla/ViewportUtils.h"
46 // Work around conflicting define in rpcndr.h
49 #endif // defined(small)
51 #if defined(XP_WIN) && !defined(NIGHTLY_BUILD)
52 # define TRANSLATE_NEW_LINES
58 using namespace widget
;
60 /******************************************************************/
61 /* ContentEventHandler::RawRange */
62 /******************************************************************/
64 void ContentEventHandler::RawRange::AssertStartIsBeforeOrEqualToEnd() {
66 *nsContentUtils::ComparePoints(
68 *mStart
.Offset(NodePosition::OffsetFilter::kValidOrInvalidOffsets
),
70 *mEnd
.Offset(NodePosition::OffsetFilter::kValidOrInvalidOffsets
)) <=
74 nsresult
ContentEventHandler::RawRange::SetStart(
75 const RawRangeBoundary
& aStart
) {
76 nsINode
* newRoot
= RangeUtils::ComputeRootNode(aStart
.Container());
78 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
;
81 if (!aStart
.IsSetAndValid()) {
82 return NS_ERROR_DOM_INDEX_SIZE_ERR
;
85 // Collapse if not positioned yet, or if positioned in another document.
86 if (!IsPositioned() || newRoot
!= mRoot
) {
88 mStart
.CopyFrom(aStart
, RangeBoundaryIsMutationObserved::Yes
);
89 mEnd
.CopyFrom(aStart
, RangeBoundaryIsMutationObserved::Yes
);
93 mStart
.CopyFrom(aStart
, RangeBoundaryIsMutationObserved::Yes
);
94 AssertStartIsBeforeOrEqualToEnd();
98 nsresult
ContentEventHandler::RawRange::SetEnd(const RawRangeBoundary
& aEnd
) {
99 nsINode
* newRoot
= RangeUtils::ComputeRootNode(aEnd
.Container());
101 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
;
104 if (!aEnd
.IsSetAndValid()) {
105 return NS_ERROR_DOM_INDEX_SIZE_ERR
;
108 // Collapse if not positioned yet, or if positioned in another document.
109 if (!IsPositioned() || newRoot
!= mRoot
) {
111 mStart
.CopyFrom(aEnd
, RangeBoundaryIsMutationObserved::Yes
);
112 mEnd
.CopyFrom(aEnd
, RangeBoundaryIsMutationObserved::Yes
);
116 mEnd
.CopyFrom(aEnd
, RangeBoundaryIsMutationObserved::Yes
);
117 AssertStartIsBeforeOrEqualToEnd();
121 nsresult
ContentEventHandler::RawRange::SetEndAfter(nsINode
* aEndContainer
) {
122 return SetEnd(RangeUtils::GetRawRangeBoundaryAfter(aEndContainer
));
125 void ContentEventHandler::RawRange::SetStartAndEnd(const nsRange
* aRange
) {
126 DebugOnly
<nsresult
> rv
=
127 SetStartAndEnd(aRange
->StartRef().AsRaw(), aRange
->EndRef().AsRaw());
128 MOZ_ASSERT(!aRange
->IsPositioned() || NS_SUCCEEDED(rv
));
131 nsresult
ContentEventHandler::RawRange::SetStartAndEnd(
132 const RawRangeBoundary
& aStart
, const RawRangeBoundary
& aEnd
) {
133 nsINode
* newStartRoot
= RangeUtils::ComputeRootNode(aStart
.Container());
135 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
;
137 if (!aStart
.IsSetAndValid()) {
138 return NS_ERROR_DOM_INDEX_SIZE_ERR
;
141 if (aStart
.Container() == aEnd
.Container()) {
142 if (!aEnd
.IsSetAndValid()) {
143 return NS_ERROR_DOM_INDEX_SIZE_ERR
;
145 MOZ_ASSERT(*aStart
.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets
) <=
146 *aEnd
.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets
));
147 mRoot
= newStartRoot
;
148 mStart
.CopyFrom(aStart
, RangeBoundaryIsMutationObserved::Yes
);
149 mEnd
.CopyFrom(aEnd
, RangeBoundaryIsMutationObserved::Yes
);
153 nsINode
* newEndRoot
= RangeUtils::ComputeRootNode(aEnd
.Container());
155 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
;
157 if (!aEnd
.IsSetAndValid()) {
158 return NS_ERROR_DOM_INDEX_SIZE_ERR
;
161 // If they have different root, this should be collapsed at the end point.
162 if (newStartRoot
!= newEndRoot
) {
164 mStart
.CopyFrom(aEnd
, RangeBoundaryIsMutationObserved::Yes
);
165 mEnd
.CopyFrom(aEnd
, RangeBoundaryIsMutationObserved::Yes
);
169 // Otherwise, set the range as specified.
170 mRoot
= newStartRoot
;
171 mStart
.CopyFrom(aStart
, RangeBoundaryIsMutationObserved::Yes
);
172 mEnd
.CopyFrom(aEnd
, RangeBoundaryIsMutationObserved::Yes
);
173 AssertStartIsBeforeOrEqualToEnd();
177 nsresult
ContentEventHandler::RawRange::SelectNodeContents(
178 const nsINode
* aNodeToSelectContents
) {
179 nsINode
* const newRoot
=
180 RangeUtils::ComputeRootNode(const_cast<nsINode
*>(aNodeToSelectContents
));
182 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
;
185 mStart
= RangeBoundary(const_cast<nsINode
*>(aNodeToSelectContents
), nullptr);
186 mEnd
= RangeBoundary(const_cast<nsINode
*>(aNodeToSelectContents
),
187 aNodeToSelectContents
->GetLastChild());
191 /******************************************************************/
192 /* ContentEventHandler */
193 /******************************************************************/
197 // ContentEventHandler *creates* ranges as following rules:
198 // 1. Start of range:
199 // 1.1. Cases: [textNode or text[Node or textNode[
200 // When text node is start of a range, start node is the text node and
201 // start offset is any number between 0 and the length of the text.
202 // 1.2. Case: [<element>:
203 // When start of an element node is start of a range, start node is
204 // parent of the element and start offset is the element's index in the
206 // 1.3. Case: <element/>[
207 // When after an empty element node is start of a range, start node is
208 // parent of the element and start offset is the element's index in the
210 // 1.4. Case: <element>[
211 // When start of a non-empty element is start of a range, start node is
212 // the element and start offset is 0.
213 // 1.5. Case: <root>[
214 // When start of a range is 0 and there are no nodes causing text,
215 // start node is the root node and start offset is 0.
216 // 1.6. Case: [</root>
217 // When start of a range is out of bounds, start node is the root node
218 // and start offset is number of the children.
220 // 2.1. Cases: ]textNode or text]Node or textNode]
221 // When a text node is end of a range, end node is the text node and
222 // end offset is any number between 0 and the length of the text.
223 // 2.2. Case: ]<element>
224 // When before an element node (meaning before the open tag of the
225 // element) is end of a range, end node is previous node causing text.
226 // Note that this case shouldn't be handled directly. If rule 2.1 and
227 // 2.3 are handled correctly, the loop with ContentIterator shouldn't
228 // reach the element node since the loop should've finished already at
229 // handling the last node which caused some text.
230 // 2.3. Case: <element>]
231 // When a line break is caused before a non-empty element node and it's
232 // end of a range, end node is the element and end offset is 0.
233 // (i.e., including open tag of the element)
234 // 2.4. Cases: <element/>]
235 // When after an empty element node is end of a range, end node is
236 // parent of the element node and end offset is the element's index in
237 // the parent + 1. (i.e., including close tag of the element or empty
239 // 2.5. Case: ]</root>
240 // When end of a range is out of bounds, end node is the root node and
241 // end offset is number of the children.
243 // ContentEventHandler *treats* ranges as following additional rules:
244 // 1. When the start node is an element node which doesn't have children,
245 // it includes a line break caused before itself (i.e., includes its open
246 // tag). For example, if start position is { <br>, 0 }, the line break
247 // caused by <br> should be included into the flatten text.
248 // 2. When the end node is an element node which doesn't have children,
249 // it includes the end (i.e., includes its close tag except empty element).
250 // Although, currently, any close tags don't cause line break, this also
251 // includes its open tag. For example, if end position is { <br>, 0 }, the
252 // line break caused by the <br> should be included into the flatten text.
254 ContentEventHandler::ContentEventHandler(nsPresContext
* aPresContext
)
255 : mDocument(aPresContext
->Document()) {}
257 nsresult
ContentEventHandler::InitBasic(bool aRequireFlush
) {
258 NS_ENSURE_TRUE(mDocument
, NS_ERROR_NOT_AVAILABLE
);
260 // If text frame which has overflowing selection underline is dirty,
261 // we need to flush the pending reflow here.
262 mDocument
->FlushPendingNotifications(FlushType::Layout
);
267 nsresult
ContentEventHandler::InitRootContent(
268 const Selection
& aNormalSelection
) {
269 // Root content should be computed with normal selection because normal
270 // selection is typically has at least one range but the other selections
271 // not so. If there is a range, computing its root is easy, but if
272 // there are no ranges, we need to use ancestor limit instead.
273 MOZ_ASSERT(aNormalSelection
.Type() == SelectionType::eNormal
);
275 if (!aNormalSelection
.RangeCount()) {
276 // If there is no selection range, we should compute the selection root
277 // from ancestor limiter or root content of the document.
279 Element::FromNodeOrNull(aNormalSelection
.GetAncestorLimiter());
281 mRootElement
= mDocument
->GetRootElement();
282 if (NS_WARN_IF(!mRootElement
)) {
283 return NS_ERROR_NOT_AVAILABLE
;
289 RefPtr
<const nsRange
> range(aNormalSelection
.GetRangeAt(0));
290 if (NS_WARN_IF(!range
)) {
291 return NS_ERROR_UNEXPECTED
;
294 // If there is a selection, we should retrieve the selection root from
295 // the range since when the window is inactivated, the ancestor limiter
296 // of selection was cleared by blur event handler of EditorBase but the
297 // selection range still keeps storing the nodes. If the active element of
298 // the deactive window is <input> or <textarea>, we can compute the
299 // selection root from them.
300 nsCOMPtr
<nsINode
> startNode
= range
->GetStartContainer();
301 nsINode
* endNode
= range
->GetEndContainer();
302 if (NS_WARN_IF(!startNode
) || NS_WARN_IF(!endNode
)) {
303 return NS_ERROR_FAILURE
;
306 // See bug 537041 comment 5, the range could have removed node.
307 if (NS_WARN_IF(startNode
->GetComposedDoc() != mDocument
)) {
308 return NS_ERROR_FAILURE
;
311 NS_ASSERTION(startNode
->GetComposedDoc() == endNode
->GetComposedDoc(),
312 "firstNormalSelectionRange crosses the document boundary");
314 RefPtr
<PresShell
> presShell
= mDocument
->GetPresShell();
316 Element::FromNodeOrNull(startNode
->GetSelectionRootContent(presShell
));
317 if (NS_WARN_IF(!mRootElement
)) {
318 return NS_ERROR_FAILURE
;
324 nsresult
ContentEventHandler::InitCommon(EventMessage aEventMessage
,
325 SelectionType aSelectionType
,
326 bool aRequireFlush
) {
327 if (mSelection
&& mSelection
->Type() == aSelectionType
) {
331 mSelection
= nullptr;
332 mRootElement
= nullptr;
333 mFirstSelectedRawRange
.Clear();
335 nsresult rv
= InitBasic(aRequireFlush
);
336 NS_ENSURE_SUCCESS(rv
, rv
);
338 RefPtr
<nsFrameSelection
> frameSel
;
339 if (PresShell
* presShell
= mDocument
->GetPresShell()) {
340 frameSel
= presShell
->GetLastFocusedFrameSelection();
342 if (NS_WARN_IF(!frameSel
)) {
343 return NS_ERROR_NOT_AVAILABLE
;
346 mSelection
= frameSel
->GetSelection(aSelectionType
);
347 if (NS_WARN_IF(!mSelection
)) {
348 return NS_ERROR_NOT_AVAILABLE
;
351 RefPtr
<Selection
> normalSelection
;
352 if (mSelection
->Type() == SelectionType::eNormal
) {
353 normalSelection
= mSelection
;
355 normalSelection
= frameSel
->GetSelection(SelectionType::eNormal
);
356 if (NS_WARN_IF(!normalSelection
)) {
357 return NS_ERROR_NOT_AVAILABLE
;
361 rv
= InitRootContent(*normalSelection
);
362 if (NS_WARN_IF(NS_FAILED(rv
))) {
366 if (mSelection
->RangeCount()) {
367 mFirstSelectedRawRange
.SetStartAndEnd(mSelection
->GetRangeAt(0));
371 // Even if there are no selection ranges, it's usual case if aSelectionType
372 // is a special selection or we're handling eQuerySelectedText.
373 if (aSelectionType
!= SelectionType::eNormal
||
374 aEventMessage
== eQuerySelectedText
) {
375 MOZ_ASSERT(!mFirstSelectedRawRange
.IsPositioned());
379 // But otherwise, we need to assume that there is a selection range at the
380 // beginning of the root content if aSelectionType is eNormal.
381 rv
= mFirstSelectedRawRange
.CollapseTo(RawRangeBoundary(mRootElement
, 0u));
382 if (NS_WARN_IF(NS_FAILED(rv
))) {
383 return NS_ERROR_UNEXPECTED
;
388 nsresult
ContentEventHandler::Init(WidgetQueryContentEvent
* aEvent
) {
389 NS_ASSERTION(aEvent
, "aEvent must not be null");
390 MOZ_ASSERT(aEvent
->mMessage
== eQuerySelectedText
||
391 aEvent
->mInput
.mSelectionType
== SelectionType::eNormal
);
393 if (NS_WARN_IF(!aEvent
->mInput
.IsValidOffset()) ||
394 NS_WARN_IF(!aEvent
->mInput
.IsValidEventMessage(aEvent
->mMessage
))) {
395 return NS_ERROR_FAILURE
;
398 // Note that we should ignore WidgetQueryContentEvent::Input::mSelectionType
399 // if the event isn't eQuerySelectedText.
400 SelectionType selectionType
= aEvent
->mMessage
== eQuerySelectedText
401 ? aEvent
->mInput
.mSelectionType
402 : SelectionType::eNormal
;
403 if (NS_WARN_IF(selectionType
== SelectionType::eNone
)) {
404 return NS_ERROR_FAILURE
;
408 InitCommon(aEvent
->mMessage
, selectionType
, aEvent
->NeedsToFlushLayout());
409 NS_ENSURE_SUCCESS(rv
, rv
);
411 // Be aware, WidgetQueryContentEvent::mInput::mOffset should be made absolute
412 // offset before sending it to ContentEventHandler because querying selection
413 // every time may be expensive. So, if the caller caches selection, it
414 // should initialize the event with the cached value.
415 if (aEvent
->mInput
.mRelativeToInsertionPoint
) {
416 MOZ_ASSERT(selectionType
== SelectionType::eNormal
);
417 RefPtr
<TextComposition
> composition
=
418 IMEStateManager::GetTextCompositionFor(aEvent
->mWidget
);
420 uint32_t compositionStart
= composition
->NativeOffsetOfStartComposition();
421 if (NS_WARN_IF(!aEvent
->mInput
.MakeOffsetAbsolute(compositionStart
))) {
422 return NS_ERROR_FAILURE
;
425 LineBreakType lineBreakType
= GetLineBreakType(aEvent
);
426 uint32_t selectionStart
= 0;
427 rv
= GetStartOffset(mFirstSelectedRawRange
, &selectionStart
,
429 if (NS_WARN_IF(NS_FAILED(rv
))) {
430 return NS_ERROR_FAILURE
;
432 if (NS_WARN_IF(!aEvent
->mInput
.MakeOffsetAbsolute(selectionStart
))) {
433 return NS_ERROR_FAILURE
;
438 // Ideally, we should emplace only when we return succeeded event.
439 // However, we need to emplace here since it's hard to store the various
440 // result. Intead, `HandleQueryContentEvent()` will reset `mReply` if
441 // corresponding handler returns error.
442 aEvent
->EmplaceReply();
444 aEvent
->mReply
->mContentsRoot
= mRootElement
.get();
445 aEvent
->mReply
->mIsEditableContent
=
446 mRootElement
&& mRootElement
->IsEditable();
449 nsIFrame
* frame
= nsCaret::GetGeometry(mSelection
, &r
);
451 frame
= mRootElement
->GetPrimaryFrame();
452 if (NS_WARN_IF(!frame
)) {
453 return NS_ERROR_FAILURE
;
456 aEvent
->mReply
->mFocusedWidget
= frame
->GetNearestWidget();
461 nsresult
ContentEventHandler::Init(WidgetSelectionEvent
* aEvent
) {
462 NS_ASSERTION(aEvent
, "aEvent must not be null");
464 nsresult rv
= InitCommon(aEvent
->mMessage
);
465 NS_ENSURE_SUCCESS(rv
, rv
);
467 aEvent
->mSucceeded
= false;
472 nsIContent
* ContentEventHandler::GetFocusedContent() {
473 nsCOMPtr
<nsPIDOMWindowOuter
> window
= mDocument
->GetWindow();
474 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
475 return nsFocusManager::GetFocusedDescendant(
476 window
, nsFocusManager::eIncludeAllDescendants
,
477 getter_AddRefs(focusedWindow
));
480 nsresult
ContentEventHandler::QueryContentRect(
481 nsIContent
* aContent
, WidgetQueryContentEvent
* aEvent
) {
482 MOZ_ASSERT(aContent
, "aContent must not be null");
484 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
485 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
487 // get rect for first frame
488 nsRect
resultRect(nsPoint(0, 0), frame
->GetRect().Size());
489 nsresult rv
= ConvertToRootRelativeOffset(frame
, resultRect
);
490 NS_ENSURE_SUCCESS(rv
, rv
);
492 nsPresContext
* presContext
= frame
->PresContext();
494 // account for any additional frames
495 while ((frame
= frame
->GetNextContinuation())) {
496 nsRect
frameRect(nsPoint(0, 0), frame
->GetRect().Size());
497 rv
= ConvertToRootRelativeOffset(frame
, frameRect
);
498 NS_ENSURE_SUCCESS(rv
, rv
);
499 resultRect
.UnionRect(resultRect
, frameRect
);
502 aEvent
->mReply
->mRect
= LayoutDeviceIntRect::FromAppUnitsToOutside(
503 resultRect
, presContext
->AppUnitsPerDevPixel());
504 // Returning empty rect may cause native IME confused, let's make sure to
505 // return non-empty rect.
506 EnsureNonEmptyRect(aEvent
->mReply
->mRect
);
511 // Editor places a padding <br> element under its root content if the editor
512 // doesn't have any text. This happens even for single line editors.
513 // When we get text content and when we change the selection,
514 // we don't want to include the padding <br> elements at the end.
515 static bool IsContentBR(const nsIContent
& aContent
) {
516 const HTMLBRElement
* brElement
= HTMLBRElement::FromNode(aContent
);
517 return brElement
&& !brElement
->IsPaddingForEmptyLastLine() &&
518 !brElement
->IsPaddingForEmptyEditor();
521 static bool IsPaddingBR(const nsIContent
& aContent
) {
522 return aContent
.IsHTMLElement(nsGkAtoms::br
) && !IsContentBR(aContent
);
525 static void ConvertToNativeNewlines(nsString
& aString
) {
526 #if defined(TRANSLATE_NEW_LINES)
527 aString
.ReplaceSubstring(u
"\n"_ns
, u
"\r\n"_ns
);
531 static void AppendString(nsString
& aString
, const Text
& aTextNode
) {
532 const uint32_t oldXPLength
= aString
.Length();
533 aTextNode
.TextFragment().AppendTo(aString
);
534 if (aTextNode
.HasFlag(NS_MAYBE_MASKED
)) {
535 TextEditor::MaskString(aString
, aTextNode
, oldXPLength
, 0);
539 static void AppendSubString(nsString
& aString
, const Text
& aTextNode
,
540 uint32_t aXPOffset
, uint32_t aXPLength
) {
541 const uint32_t oldXPLength
= aString
.Length();
542 aTextNode
.TextFragment().AppendTo(aString
, aXPOffset
, aXPLength
);
543 if (aTextNode
.HasFlag(NS_MAYBE_MASKED
)) {
544 TextEditor::MaskString(aString
, aTextNode
, oldXPLength
, aXPOffset
);
548 #if defined(TRANSLATE_NEW_LINES)
549 template <typename StringType
>
550 static uint32_t CountNewlinesInXPLength(const StringType
& aString
) {
552 const auto* end
= aString
.EndReading();
553 for (const auto* iter
= aString
.BeginReading(); iter
< end
; ++iter
) {
561 static uint32_t CountNewlinesInXPLength(const Text
& aTextNode
,
562 uint32_t aXPLength
) {
563 const nsTextFragment
& textFragment
= aTextNode
.TextFragment();
564 // For automated tests, we should abort on debug build.
565 MOZ_ASSERT(aXPLength
== UINT32_MAX
|| aXPLength
<= textFragment
.GetLength(),
566 "aXPLength is out-of-bounds");
567 const uint32_t length
= std::min(aXPLength
, textFragment
.GetLength());
571 if (textFragment
.Is2b()) {
572 nsDependentSubstring
str(textFragment
.Get2b(), length
);
573 return CountNewlinesInXPLength(str
);
575 nsDependentCSubstring
str(textFragment
.Get1b(), length
);
576 return CountNewlinesInXPLength(str
);
579 template <typename StringType
>
580 static uint32_t CountNewlinesInNativeLength(const StringType
& aString
,
581 uint32_t aNativeLength
) {
583 (aNativeLength
== UINT32_MAX
|| aNativeLength
<= aString
.Length() * 2),
584 "aNativeLength is unexpected value");
586 uint32_t nativeOffset
= 0;
587 const auto* end
= aString
.EndReading();
588 for (const auto* iter
= aString
.BeginReading();
589 iter
< end
&& nativeOffset
< aNativeLength
; ++iter
, ++nativeOffset
) {
598 static uint32_t CountNewlinesInNativeLength(const Text
& aTextNode
,
599 uint32_t aNativeLength
) {
600 const nsTextFragment
& textFragment
= aTextNode
.TextFragment();
601 const uint32_t xpLength
= textFragment
.GetLength();
605 if (textFragment
.Is2b()) {
606 nsDependentSubstring
str(textFragment
.Get2b(), xpLength
);
607 return CountNewlinesInNativeLength(str
, aNativeLength
);
609 nsDependentCSubstring
str(textFragment
.Get1b(), xpLength
);
610 return CountNewlinesInNativeLength(str
, aNativeLength
);
615 uint32_t ContentEventHandler::GetNativeTextLength(const Text
& aTextNode
,
616 uint32_t aStartOffset
,
617 uint32_t aEndOffset
) {
618 MOZ_ASSERT(aEndOffset
>= aStartOffset
,
619 "aEndOffset must be equals or larger than aStartOffset");
620 if (aStartOffset
== aEndOffset
) {
623 return GetTextLength(aTextNode
, LINE_BREAK_TYPE_NATIVE
, aEndOffset
) -
624 GetTextLength(aTextNode
, LINE_BREAK_TYPE_NATIVE
, aStartOffset
);
628 uint32_t ContentEventHandler::GetNativeTextLength(const Text
& aTextNode
,
629 uint32_t aMaxLength
) {
630 return GetTextLength(aTextNode
, LINE_BREAK_TYPE_NATIVE
, aMaxLength
);
634 uint32_t ContentEventHandler::GetBRLength(LineBreakType aLineBreakType
) {
635 #if defined(TRANSLATE_NEW_LINES)
637 return (aLineBreakType
== LINE_BREAK_TYPE_NATIVE
) ? 2 : 1;
644 uint32_t ContentEventHandler::GetTextLength(const Text
& aTextNode
,
645 LineBreakType aLineBreakType
,
646 uint32_t aMaxLength
) {
647 const uint32_t textLengthDifference
=
648 #if defined(TRANSLATE_NEW_LINES)
649 // On Windows, the length of a native newline ("\r\n") is twice the length
650 // of the XP newline ("\n"), so XP length is equal to the length of the
651 // native offset plus the number of newlines encountered in the string.
652 (aLineBreakType
== LINE_BREAK_TYPE_NATIVE
)
653 ? CountNewlinesInXPLength(aTextNode
, aMaxLength
)
656 // On other platforms, the native and XP newlines are the same.
660 const uint32_t length
=
661 std::min(aTextNode
.TextFragment().GetLength(), aMaxLength
);
662 return length
+ textLengthDifference
;
665 static uint32_t ConvertToXPOffset(const Text
& aTextNode
,
666 uint32_t aNativeOffset
) {
667 #if defined(TRANSLATE_NEW_LINES)
668 // On Windows, the length of a native newline ("\r\n") is twice the length of
669 // the XP newline ("\n"), so XP offset is equal to the length of the native
670 // offset minus the number of newlines encountered in the string.
671 return aNativeOffset
- CountNewlinesInNativeLength(aTextNode
, aNativeOffset
);
673 // On other platforms, the native and XP newlines are the same.
674 return aNativeOffset
;
679 uint32_t ContentEventHandler::GetNativeTextLength(const nsAString
& aText
) {
680 const uint32_t textLengthDifference
=
681 #if defined(TRANSLATE_NEW_LINES)
682 // On Windows, the length of a native newline ("\r\n") is twice the length
683 // of the XP newline ("\n"), so XP length is equal to the length of the
684 // native offset plus the number of newlines encountered in the string.
685 CountNewlinesInXPLength(aText
);
687 // On other platforms, the native and XP newlines are the same.
690 return aText
.Length() + textLengthDifference
;
694 bool ContentEventHandler::ShouldBreakLineBefore(const nsIContent
& aContent
,
695 const Element
* aRootElement
) {
696 // We don't need to append linebreak at the start of the root element.
697 if (&aContent
== aRootElement
) {
701 // If it's not an HTML element (including other markup language's elements),
702 // we shouldn't insert like break before that for now. Becoming this is a
703 // problem must be edge case. E.g., when ContentEventHandler is used with
704 // MathML or SVG elements.
705 if (!aContent
.IsHTMLElement()) {
709 // If the element is <br>, we need to check if the <br> is caused by web
710 // content. Otherwise, i.e., it's caused by internal reason of Gecko,
711 // it shouldn't be exposed as a line break to flatten text.
712 if (aContent
.IsHTMLElement(nsGkAtoms::br
)) {
713 return IsContentBR(aContent
);
716 // Note that ideally, we should refer the style of the primary frame of
717 // aContent for deciding if it's an inline. However, it's difficult
718 // IMEContentObserver to notify IME of text change caused by style change.
719 // Therefore, currently, we should check only from the tag for now.
720 if (aContent
.IsAnyOfHTMLElements(
721 nsGkAtoms::a
, nsGkAtoms::abbr
, nsGkAtoms::acronym
, nsGkAtoms::b
,
722 nsGkAtoms::bdi
, nsGkAtoms::bdo
, nsGkAtoms::big
, nsGkAtoms::cite
,
723 nsGkAtoms::code
, nsGkAtoms::data
, nsGkAtoms::del
, nsGkAtoms::dfn
,
724 nsGkAtoms::em
, nsGkAtoms::font
, nsGkAtoms::i
, nsGkAtoms::ins
,
725 nsGkAtoms::kbd
, nsGkAtoms::mark
, nsGkAtoms::s
, nsGkAtoms::samp
,
726 nsGkAtoms::small
, nsGkAtoms::span
, nsGkAtoms::strike
,
727 nsGkAtoms::strong
, nsGkAtoms::sub
, nsGkAtoms::sup
, nsGkAtoms::time
,
728 nsGkAtoms::tt
, nsGkAtoms::u
, nsGkAtoms::var
)) {
732 // If the element is unknown element, we shouldn't insert line breaks before
733 // it since unknown elements should be ignored.
734 RefPtr
<HTMLUnknownElement
> unknownHTMLElement
=
735 do_QueryObject(const_cast<nsIContent
*>(&aContent
));
736 return !unknownHTMLElement
;
739 nsresult
ContentEventHandler::GenerateFlatTextContent(
740 const Element
* aElement
, nsString
& aString
, LineBreakType aLineBreakType
) {
741 MOZ_ASSERT(aString
.IsEmpty());
744 nsresult rv
= rawRange
.SelectNodeContents(aElement
);
745 if (NS_WARN_IF(NS_FAILED(rv
))) {
748 return GenerateFlatTextContent(rawRange
, aString
, aLineBreakType
);
751 nsresult
ContentEventHandler::GenerateFlatTextContent(
752 const RawRange
& aRawRange
, nsString
& aString
,
753 LineBreakType aLineBreakType
) {
754 MOZ_ASSERT(aString
.IsEmpty());
756 if (aRawRange
.Collapsed()) {
760 nsINode
* startNode
= aRawRange
.GetStartContainer();
761 nsINode
* endNode
= aRawRange
.GetEndContainer();
762 if (NS_WARN_IF(!startNode
) || NS_WARN_IF(!endNode
)) {
763 return NS_ERROR_FAILURE
;
766 if (startNode
== endNode
&& startNode
->IsText()) {
767 AppendSubString(aString
, *startNode
->AsText(), aRawRange
.StartOffset(),
768 aRawRange
.EndOffset() - aRawRange
.StartOffset());
769 ConvertToNativeNewlines(aString
);
773 PreContentIterator preOrderIter
;
775 preOrderIter
.Init(aRawRange
.Start().AsRaw(), aRawRange
.End().AsRaw());
776 if (NS_WARN_IF(NS_FAILED(rv
))) {
779 for (; !preOrderIter
.IsDone(); preOrderIter
.Next()) {
780 nsINode
* node
= preOrderIter
.GetCurrentNode();
781 if (NS_WARN_IF(!node
)) {
784 if (!node
->IsContent()) {
788 if (const Text
* textNode
= Text::FromNode(node
)) {
789 if (textNode
== startNode
) {
790 AppendSubString(aString
, *textNode
, aRawRange
.StartOffset(),
791 textNode
->TextLength() - aRawRange
.StartOffset());
792 } else if (textNode
== endNode
) {
793 AppendSubString(aString
, *textNode
, 0, aRawRange
.EndOffset());
795 AppendString(aString
, *textNode
);
797 } else if (ShouldBreakLineBefore(*node
->AsContent(), mRootElement
)) {
798 aString
.Append(char16_t('\n'));
801 if (aLineBreakType
== LINE_BREAK_TYPE_NATIVE
) {
802 ConvertToNativeNewlines(aString
);
807 static FontRange
* AppendFontRange(nsTArray
<FontRange
>& aFontRanges
,
808 uint32_t aBaseOffset
) {
809 FontRange
* fontRange
= aFontRanges
.AppendElement();
810 fontRange
->mStartOffset
= aBaseOffset
;
815 uint32_t ContentEventHandler::GetTextLengthInRange(
816 const Text
& aTextNode
, uint32_t aXPStartOffset
, uint32_t aXPEndOffset
,
817 LineBreakType aLineBreakType
) {
818 return aLineBreakType
== LINE_BREAK_TYPE_NATIVE
819 ? GetNativeTextLength(aTextNode
, aXPStartOffset
, aXPEndOffset
)
820 : aXPEndOffset
- aXPStartOffset
;
824 void ContentEventHandler::AppendFontRanges(FontRangeArray
& aFontRanges
,
825 const Text
& aTextNode
,
826 uint32_t aBaseOffset
,
827 uint32_t aXPStartOffset
,
828 uint32_t aXPEndOffset
,
829 LineBreakType aLineBreakType
) {
830 nsIFrame
* frame
= aTextNode
.GetPrimaryFrame();
832 // It is a non-rendered content, create an empty range for it.
833 AppendFontRange(aFontRanges
, aBaseOffset
);
837 uint32_t baseOffset
= aBaseOffset
;
840 nsTextFrame
* text
= do_QueryFrame(frame
);
841 MOZ_ASSERT(text
, "Not a text frame");
844 auto* curr
= static_cast<nsTextFrame
*>(frame
);
846 uint32_t frameXPStart
= std::max(
847 static_cast<uint32_t>(curr
->GetContentOffset()), aXPStartOffset
);
848 uint32_t frameXPEnd
=
849 std::min(static_cast<uint32_t>(curr
->GetContentEnd()), aXPEndOffset
);
850 if (frameXPStart
>= frameXPEnd
) {
851 curr
= curr
->GetNextContinuation();
855 gfxSkipCharsIterator iter
= curr
->EnsureTextRun(nsTextFrame::eInflated
);
856 gfxTextRun
* textRun
= curr
->GetTextRun(nsTextFrame::eInflated
);
858 nsTextFrame
* next
= nullptr;
859 if (frameXPEnd
< aXPEndOffset
) {
860 next
= curr
->GetNextContinuation();
861 while (next
&& next
->GetTextRun(nsTextFrame::eInflated
) == textRun
) {
862 frameXPEnd
= std::min(static_cast<uint32_t>(next
->GetContentEnd()),
865 frameXPEnd
< aXPEndOffset
? next
->GetNextContinuation() : nullptr;
869 gfxTextRun::Range
skipRange(iter
.ConvertOriginalToSkipped(frameXPStart
),
870 iter
.ConvertOriginalToSkipped(frameXPEnd
));
871 uint32_t lastXPEndOffset
= frameXPStart
;
872 for (gfxTextRun::GlyphRunIterator
runIter(textRun
, skipRange
);
873 !runIter
.AtEnd(); runIter
.NextRun()) {
874 gfxFont
* font
= runIter
.GlyphRun()->mFont
.get();
875 uint32_t startXPOffset
=
876 iter
.ConvertSkippedToOriginal(runIter
.StringStart());
877 // It is possible that the first glyph run has exceeded the frame,
878 // because the whole frame is filled by skipped chars.
879 if (startXPOffset
>= frameXPEnd
) {
883 if (startXPOffset
> lastXPEndOffset
) {
884 // Create range for skipped leading chars.
885 AppendFontRange(aFontRanges
, baseOffset
);
886 baseOffset
+= GetTextLengthInRange(aTextNode
, lastXPEndOffset
,
887 startXPOffset
, aLineBreakType
);
890 FontRange
* fontRange
= AppendFontRange(aFontRanges
, baseOffset
);
891 fontRange
->mFontName
.Append(NS_ConvertUTF8toUTF16(font
->GetName()));
893 ParentLayerToScreenScale2D cumulativeResolution
=
894 ParentLayerToParentLayerScale(
895 frame
->PresShell()->GetCumulativeResolution()) *
896 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
899 std::max(cumulativeResolution
.xScale
, cumulativeResolution
.yScale
);
901 fontRange
->mFontSize
= font
->GetAdjustedSize() * scale
;
903 // The converted original offset may exceed the range,
904 // hence we need to clamp it.
905 uint32_t endXPOffset
= iter
.ConvertSkippedToOriginal(runIter
.StringEnd());
906 endXPOffset
= std::min(frameXPEnd
, endXPOffset
);
907 baseOffset
+= GetTextLengthInRange(aTextNode
, startXPOffset
, endXPOffset
,
909 lastXPEndOffset
= endXPOffset
;
911 if (lastXPEndOffset
< frameXPEnd
) {
912 // Create range for skipped trailing chars. It also handles case
913 // that the whole frame contains only skipped chars.
914 AppendFontRange(aFontRanges
, baseOffset
);
915 baseOffset
+= GetTextLengthInRange(aTextNode
, lastXPEndOffset
, frameXPEnd
,
923 nsresult
ContentEventHandler::GenerateFlatFontRanges(
924 const RawRange
& aRawRange
, FontRangeArray
& aFontRanges
, uint32_t& aLength
,
925 LineBreakType aLineBreakType
) {
926 MOZ_ASSERT(aFontRanges
.IsEmpty(), "aRanges must be empty array");
928 if (aRawRange
.Collapsed()) {
932 nsINode
* startNode
= aRawRange
.GetStartContainer();
933 nsINode
* endNode
= aRawRange
.GetEndContainer();
934 if (NS_WARN_IF(!startNode
) || NS_WARN_IF(!endNode
)) {
935 return NS_ERROR_FAILURE
;
938 // baseOffset is the flattened offset of each content node.
939 uint32_t baseOffset
= 0;
940 PreContentIterator preOrderIter
;
942 preOrderIter
.Init(aRawRange
.Start().AsRaw(), aRawRange
.End().AsRaw());
943 if (NS_WARN_IF(NS_FAILED(rv
))) {
946 for (; !preOrderIter
.IsDone(); preOrderIter
.Next()) {
947 nsINode
* node
= preOrderIter
.GetCurrentNode();
948 if (NS_WARN_IF(!node
)) {
951 if (!node
->IsContent()) {
954 nsIContent
* content
= node
->AsContent();
956 if (const Text
* textNode
= Text::FromNode(content
)) {
957 const uint32_t startOffset
=
958 textNode
!= startNode
? 0 : aRawRange
.StartOffset();
959 const uint32_t endOffset
=
960 textNode
!= endNode
? textNode
->TextLength() : aRawRange
.EndOffset();
961 AppendFontRanges(aFontRanges
, *textNode
, baseOffset
, startOffset
,
962 endOffset
, aLineBreakType
);
963 baseOffset
+= GetTextLengthInRange(*textNode
, startOffset
, endOffset
,
965 } else if (ShouldBreakLineBefore(*content
, mRootElement
)) {
966 if (aFontRanges
.IsEmpty()) {
967 MOZ_ASSERT(baseOffset
== 0);
968 FontRange
* fontRange
= AppendFontRange(aFontRanges
, baseOffset
);
969 if (nsIFrame
* frame
= content
->GetPrimaryFrame()) {
970 const nsFont
& font
= frame
->GetParent()->StyleFont()->mFont
;
971 const StyleFontFamilyList
& fontList
= font
.family
.families
;
972 MOZ_ASSERT(!fontList
.list
.IsEmpty(), "Empty font family?");
973 const StyleSingleFontFamily
* fontName
=
974 fontList
.list
.IsEmpty() ? nullptr : &fontList
.list
.AsSpan()[0];
977 fontName
->AppendToString(name
, false);
979 AppendUTF8toUTF16(name
, fontRange
->mFontName
);
981 ParentLayerToScreenScale2D cumulativeResolution
=
982 ParentLayerToParentLayerScale(
983 frame
->PresShell()->GetCumulativeResolution()) *
985 GetTransformToAncestorScaleCrossProcessForFrameMetrics(frame
);
987 float scale
= std::max(cumulativeResolution
.xScale
,
988 cumulativeResolution
.yScale
);
990 fontRange
->mFontSize
= frame
->PresContext()->CSSPixelsToDevPixels(
991 font
.size
.ToCSSPixels() * scale
);
994 baseOffset
+= GetBRLength(aLineBreakType
);
998 aLength
= baseOffset
;
1002 nsresult
ContentEventHandler::ExpandToClusterBoundary(
1003 Text
& aTextNode
, bool aForward
, uint32_t* aXPOffset
) const {
1004 // XXX This method assumes that the frame boundaries must be cluster
1005 // boundaries. It's false, but no problem now, maybe.
1006 if (*aXPOffset
== 0 || *aXPOffset
== aTextNode
.TextLength()) {
1010 NS_ASSERTION(*aXPOffset
<= aTextNode
.TextLength(), "offset is out of range.");
1012 MOZ_DIAGNOSTIC_ASSERT(mDocument
->GetPresShell());
1013 int32_t offsetInFrame
;
1014 CaretAssociationHint hint
=
1015 aForward
? CARET_ASSOCIATE_BEFORE
: CARET_ASSOCIATE_AFTER
;
1016 nsIFrame
* frame
= nsFrameSelection::GetFrameForNodeOffset(
1017 &aTextNode
, int32_t(*aXPOffset
), hint
, &offsetInFrame
);
1019 auto [startOffset
, endOffset
] = frame
->GetOffsets();
1020 if (*aXPOffset
== static_cast<uint32_t>(startOffset
) ||
1021 *aXPOffset
== static_cast<uint32_t>(endOffset
)) {
1024 if (!frame
->IsTextFrame()) {
1025 return NS_ERROR_FAILURE
;
1027 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1028 int32_t newOffsetInFrame
= *aXPOffset
- startOffset
;
1029 newOffsetInFrame
+= aForward
? -1 : 1;
1030 // PeekOffsetCharacter() should respect cluster but ignore user-select
1031 // style. If it returns "FOUND", we should use the result. Otherwise,
1032 // we shouldn't use the result because the offset was moved to reversed
1034 nsTextFrame::PeekOffsetCharacterOptions options
;
1035 options
.mRespectClusters
= true;
1036 options
.mIgnoreUserStyleAll
= true;
1037 if (textFrame
->PeekOffsetCharacter(aForward
, &newOffsetInFrame
, options
) ==
1039 *aXPOffset
= startOffset
+ newOffsetInFrame
;
1044 // If the frame isn't available, we only can check surrogate pair...
1045 if (aTextNode
.TextFragment().IsLowSurrogateFollowingHighSurrogateAt(
1047 *aXPOffset
+= aForward
? 1 : -1;
1052 nsresult
ContentEventHandler::SetRawRangeFromFlatTextOffset(
1053 RawRange
* aRawRange
, uint32_t aOffset
, uint32_t aLength
,
1054 LineBreakType aLineBreakType
, bool aExpandToClusterBoundaries
,
1055 uint32_t* aNewOffset
, Text
** aLastTextNode
) {
1057 *aNewOffset
= aOffset
;
1059 if (aLastTextNode
) {
1060 *aLastTextNode
= nullptr;
1063 // Special case like <br contenteditable>
1064 if (!mRootElement
->HasChildren()) {
1065 nsresult rv
= aRawRange
->CollapseTo(RawRangeBoundary(mRootElement
, 0u));
1066 if (NS_WARN_IF(NS_FAILED(rv
))) {
1071 PreContentIterator preOrderIter
;
1072 nsresult rv
= preOrderIter
.Init(mRootElement
);
1073 if (NS_WARN_IF(NS_FAILED(rv
))) {
1077 uint32_t offset
= 0;
1078 uint32_t endOffset
= aOffset
+ aLength
;
1079 bool startSet
= false;
1080 for (; !preOrderIter
.IsDone(); preOrderIter
.Next()) {
1081 nsINode
* node
= preOrderIter
.GetCurrentNode();
1082 if (NS_WARN_IF(!node
)) {
1085 // FYI: mRootElement shouldn't cause any text. So, we can skip it simply.
1086 if (node
== mRootElement
|| !node
->IsContent()) {
1089 nsIContent
* const content
= node
->AsContent();
1090 Text
* const contentAsText
= Text::FromNode(content
);
1092 if (aLastTextNode
&& contentAsText
) {
1093 NS_IF_RELEASE(*aLastTextNode
);
1094 NS_ADDREF(*aLastTextNode
= contentAsText
);
1097 uint32_t textLength
= contentAsText
1098 ? GetTextLength(*contentAsText
, aLineBreakType
)
1099 : (ShouldBreakLineBefore(*content
, mRootElement
)
1100 ? GetBRLength(aLineBreakType
)
1106 // When the start offset is in between accumulated offset and the last
1107 // offset of the node, the node is the start node of the range.
1108 if (!startSet
&& aOffset
<= offset
+ textLength
) {
1109 nsINode
* startNode
= nullptr;
1110 Maybe
<uint32_t> startNodeOffset
;
1111 if (contentAsText
) {
1112 // Rule #1.1: [textNode or text[Node or textNode[
1113 uint32_t xpOffset
= aOffset
- offset
;
1114 if (aLineBreakType
== LINE_BREAK_TYPE_NATIVE
) {
1115 xpOffset
= ConvertToXPOffset(*contentAsText
, xpOffset
);
1118 if (aExpandToClusterBoundaries
) {
1119 const uint32_t oldXPOffset
= xpOffset
;
1121 ExpandToClusterBoundary(*contentAsText
, false, &xpOffset
);
1122 if (NS_WARN_IF(NS_FAILED(rv
))) {
1126 // This is correct since a cluster shouldn't include line break.
1127 *aNewOffset
-= (oldXPOffset
- xpOffset
);
1130 startNode
= contentAsText
;
1131 startNodeOffset
= Some(xpOffset
);
1132 } else if (aOffset
< offset
+ textLength
) {
1133 // Rule #1.2 [<element>
1134 startNode
= content
->GetParent();
1135 if (NS_WARN_IF(!startNode
)) {
1136 return NS_ERROR_FAILURE
;
1138 startNodeOffset
= startNode
->ComputeIndexOf(content
);
1139 if (MOZ_UNLIKELY(NS_WARN_IF(startNodeOffset
.isNothing()))) {
1140 // The content is being removed from the parent!
1141 return NS_ERROR_FAILURE
;
1143 } else if (!content
->HasChildren()) {
1144 // Rule #1.3: <element/>[
1145 startNode
= content
->GetParent();
1146 if (NS_WARN_IF(!startNode
)) {
1147 return NS_ERROR_FAILURE
;
1149 startNodeOffset
= startNode
->ComputeIndexOf(content
);
1150 if (MOZ_UNLIKELY(NS_WARN_IF(startNodeOffset
.isNothing()))) {
1151 // The content is being removed from the parent!
1152 return NS_ERROR_FAILURE
;
1154 MOZ_ASSERT(*startNodeOffset
!= UINT32_MAX
);
1155 ++(*startNodeOffset
);
1157 // Rule #1.4: <element>[
1158 startNode
= content
;
1159 startNodeOffset
= Some(0);
1161 NS_ASSERTION(startNode
, "startNode must not be nullptr");
1162 MOZ_ASSERT(startNodeOffset
.isSome(),
1163 "startNodeOffset must not be Nothing");
1164 rv
= aRawRange
->SetStart(startNode
, *startNodeOffset
);
1165 if (NS_WARN_IF(NS_FAILED(rv
))) {
1171 rv
= aRawRange
->SetEnd(startNode
, *startNodeOffset
);
1172 if (NS_WARN_IF(NS_FAILED(rv
))) {
1179 // When the end offset is in the content, the node is the end node of the
1181 if (endOffset
<= offset
+ textLength
) {
1182 MOZ_ASSERT(startSet
, "The start of the range should've been set already");
1183 if (contentAsText
) {
1184 // Rule #2.1: ]textNode or text]Node or textNode]
1185 uint32_t xpOffset
= endOffset
- offset
;
1186 if (aLineBreakType
== LINE_BREAK_TYPE_NATIVE
) {
1187 const uint32_t xpOffsetCurrent
=
1188 ConvertToXPOffset(*contentAsText
, xpOffset
);
1189 if (xpOffset
&& GetBRLength(aLineBreakType
) > 1) {
1190 MOZ_ASSERT(GetBRLength(aLineBreakType
) == 2);
1191 const uint32_t xpOffsetPre
=
1192 ConvertToXPOffset(*contentAsText
, xpOffset
- 1);
1193 // If previous character's XP offset is same as current character's,
1194 // it means that the end offset is between \r and \n. So, the
1195 // range end should be after the \n.
1196 if (xpOffsetPre
== xpOffsetCurrent
) {
1197 xpOffset
= xpOffsetCurrent
+ 1;
1199 xpOffset
= xpOffsetCurrent
;
1203 if (aExpandToClusterBoundaries
) {
1205 ExpandToClusterBoundary(*contentAsText
, true, &xpOffset
);
1206 if (NS_WARN_IF(NS_FAILED(rv
))) {
1210 NS_ASSERTION(xpOffset
<= INT32_MAX
, "The end node offset is too large");
1211 nsresult rv
= aRawRange
->SetEnd(contentAsText
, xpOffset
);
1212 if (NS_WARN_IF(NS_FAILED(rv
))) {
1218 if (endOffset
== offset
) {
1219 // Rule #2.2: ]<element>
1220 // NOTE: Please don't crash on release builds because it must be
1221 // overreaction but we shouldn't allow this bug when some
1222 // automated tests find this.
1224 "This case should've already been handled at "
1225 "the last node which caused some text");
1226 return NS_ERROR_FAILURE
;
1229 if (content
->HasChildren() &&
1230 ShouldBreakLineBefore(*content
, mRootElement
)) {
1231 // Rule #2.3: </element>]
1232 rv
= aRawRange
->SetEnd(content
, 0);
1233 if (NS_WARN_IF(NS_FAILED(rv
))) {
1239 // Rule #2.4: <element/>]
1240 nsINode
* endNode
= content
->GetParent();
1241 if (NS_WARN_IF(!endNode
)) {
1242 return NS_ERROR_FAILURE
;
1244 const Maybe
<uint32_t> indexInParent
= endNode
->ComputeIndexOf(content
);
1245 if (MOZ_UNLIKELY(NS_WARN_IF(indexInParent
.isNothing()))) {
1246 // The content is being removed from the parent!
1247 return NS_ERROR_FAILURE
;
1249 MOZ_ASSERT(*indexInParent
!= UINT32_MAX
);
1250 rv
= aRawRange
->SetEnd(endNode
, *indexInParent
+ 1);
1251 if (NS_WARN_IF(NS_FAILED(rv
))) {
1257 offset
+= textLength
;
1262 // Rule #1.5: <root>[</root>
1263 // When there are no nodes causing text, the start of the DOM range
1264 // should be start of the root node since clicking on such editor (e.g.,
1265 // <div contenteditable><span></span></div>) sets caret to the start of
1266 // the editor (i.e., before <span> in the example).
1267 rv
= aRawRange
->SetStart(mRootElement
, 0);
1268 if (NS_WARN_IF(NS_FAILED(rv
))) {
1272 rv
= aRawRange
->SetEnd(mRootElement
, 0);
1273 if (NS_WARN_IF(NS_FAILED(rv
))) {
1279 // Rule #1.5: [</root>
1280 rv
= aRawRange
->SetStart(mRootElement
, mRootElement
->GetChildCount());
1281 if (NS_WARN_IF(NS_FAILED(rv
))) {
1286 *aNewOffset
= offset
;
1289 // Rule #2.5: ]</root>
1290 rv
= aRawRange
->SetEnd(mRootElement
, mRootElement
->GetChildCount());
1291 if (NS_WARN_IF(NS_FAILED(rv
))) {
1298 LineBreakType
ContentEventHandler::GetLineBreakType(
1299 WidgetQueryContentEvent
* aEvent
) {
1300 return GetLineBreakType(aEvent
->mUseNativeLineBreak
);
1304 LineBreakType
ContentEventHandler::GetLineBreakType(
1305 WidgetSelectionEvent
* aEvent
) {
1306 return GetLineBreakType(aEvent
->mUseNativeLineBreak
);
1310 LineBreakType
ContentEventHandler::GetLineBreakType(bool aUseNativeLineBreak
) {
1311 return aUseNativeLineBreak
? LINE_BREAK_TYPE_NATIVE
: LINE_BREAK_TYPE_XP
;
1314 nsresult
ContentEventHandler::HandleQueryContentEvent(
1315 WidgetQueryContentEvent
* aEvent
) {
1316 nsresult rv
= NS_ERROR_NOT_IMPLEMENTED
;
1317 switch (aEvent
->mMessage
) {
1318 case eQuerySelectedText
:
1319 rv
= OnQuerySelectedText(aEvent
);
1321 case eQueryTextContent
:
1322 rv
= OnQueryTextContent(aEvent
);
1324 case eQueryCaretRect
:
1325 rv
= OnQueryCaretRect(aEvent
);
1327 case eQueryTextRect
:
1328 rv
= OnQueryTextRect(aEvent
);
1330 case eQueryTextRectArray
:
1331 rv
= OnQueryTextRectArray(aEvent
);
1333 case eQueryEditorRect
:
1334 rv
= OnQueryEditorRect(aEvent
);
1336 case eQueryContentState
:
1337 rv
= OnQueryContentState(aEvent
);
1339 case eQuerySelectionAsTransferable
:
1340 rv
= OnQuerySelectionAsTransferable(aEvent
);
1342 case eQueryCharacterAtPoint
:
1343 rv
= OnQueryCharacterAtPoint(aEvent
);
1345 case eQueryDOMWidgetHittest
:
1346 rv
= OnQueryDOMWidgetHittest(aEvent
);
1351 if (NS_FAILED(rv
)) {
1352 aEvent
->mReply
.reset(); // Mark the query failed.
1356 MOZ_ASSERT(aEvent
->Succeeded());
1360 // Similar to nsFrameSelection::GetFrameForNodeOffset,
1361 // but this is more flexible for OnQueryTextRect to use
1362 static Result
<nsIFrame
*, nsresult
> GetFrameForTextRect(const nsINode
* aNode
,
1363 int32_t aNodeOffset
,
1365 const nsIContent
* content
= nsIContent::FromNodeOrNull(aNode
);
1366 if (NS_WARN_IF(!content
)) {
1367 return Err(NS_ERROR_UNEXPECTED
);
1369 nsIFrame
* frame
= content
->GetPrimaryFrame();
1370 // The node may be invisible, e.g., `display: none`, invisible text node
1371 // around block elements, etc. Therefore, don't warn when we don't find
1376 int32_t childNodeOffset
= 0;
1377 nsIFrame
* returnFrame
= nullptr;
1378 nsresult rv
= frame
->GetChildFrameContainingOffset(
1379 aNodeOffset
, aHint
, &childNodeOffset
, &returnFrame
);
1380 if (NS_FAILED(rv
)) {
1386 nsresult
ContentEventHandler::OnQuerySelectedText(
1387 WidgetQueryContentEvent
* aEvent
) {
1388 nsresult rv
= Init(aEvent
);
1389 if (NS_FAILED(rv
)) {
1393 MOZ_ASSERT(aEvent
->mReply
->mOffsetAndData
.isNothing());
1395 if (!mFirstSelectedRawRange
.IsPositioned()) {
1396 MOZ_ASSERT(aEvent
->mReply
->mOffsetAndData
.isNothing());
1397 MOZ_ASSERT_IF(mSelection
, !mSelection
->RangeCount());
1398 // This is special case that `mReply` is emplaced, but mOffsetAndData is
1399 // not emplaced but treated as succeeded because of no selection ranges
1404 nsINode
* const startNode
= mFirstSelectedRawRange
.GetStartContainer();
1405 nsINode
* const endNode
= mFirstSelectedRawRange
.GetEndContainer();
1407 // Make sure the selection is within the root content range.
1408 if (!startNode
->IsInclusiveDescendantOf(mRootElement
) ||
1409 !endNode
->IsInclusiveDescendantOf(mRootElement
)) {
1410 return NS_ERROR_NOT_AVAILABLE
;
1413 LineBreakType lineBreakType
= GetLineBreakType(aEvent
);
1414 uint32_t startOffset
= 0;
1415 if (NS_WARN_IF(NS_FAILED(GetStartOffset(mFirstSelectedRawRange
, &startOffset
,
1417 return NS_ERROR_FAILURE
;
1420 const RangeBoundary
& anchorRef
= mSelection
->RangeCount() > 0
1421 ? mSelection
->AnchorRef()
1422 : mFirstSelectedRawRange
.Start();
1423 const RangeBoundary
& focusRef
= mSelection
->RangeCount() > 0
1424 ? mSelection
->FocusRef()
1425 : mFirstSelectedRawRange
.End();
1426 if (NS_WARN_IF(!anchorRef
.IsSet()) || NS_WARN_IF(!focusRef
.IsSet())) {
1427 return NS_ERROR_FAILURE
;
1430 if (mSelection
->RangeCount()) {
1431 // If there is only one selection range, the anchor/focus node and offset
1432 // are the information of the range. Therefore, we have the direction
1434 if (mSelection
->RangeCount() == 1) {
1435 // The selection's points should always be comparable, independent of the
1436 // selection (see nsISelectionController.idl).
1437 Maybe
<int32_t> compare
=
1438 nsContentUtils::ComparePoints(anchorRef
, focusRef
);
1439 if (compare
.isNothing()) {
1440 return NS_ERROR_FAILURE
;
1443 aEvent
->mReply
->mReversed
= compare
.value() > 0;
1445 // However, if there are 2 or more selection ranges, we have no information
1448 aEvent
->mReply
->mReversed
= false;
1451 nsString selectedString
;
1452 if (!mFirstSelectedRawRange
.Collapsed() &&
1453 NS_WARN_IF(NS_FAILED(GenerateFlatTextContent(
1454 mFirstSelectedRawRange
, selectedString
, lineBreakType
)))) {
1455 return NS_ERROR_FAILURE
;
1457 aEvent
->mReply
->mOffsetAndData
.emplace(startOffset
, selectedString
,
1458 OffsetAndDataFor::SelectedString
);
1460 NS_ASSERTION(anchorRef
== focusRef
,
1461 "When mSelection doesn't have selection, "
1462 "mFirstSelectedRawRange must be collapsed");
1464 aEvent
->mReply
->mReversed
= false;
1465 aEvent
->mReply
->mOffsetAndData
.emplace(startOffset
, EmptyString(),
1466 OffsetAndDataFor::SelectedString
);
1469 Result
<nsIFrame
*, nsresult
> frameForTextRectOrError
= GetFrameForTextRect(
1470 focusRef
.Container(),
1471 focusRef
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
).valueOr(0),
1473 if (NS_WARN_IF(frameForTextRectOrError
.isErr()) ||
1474 !frameForTextRectOrError
.inspect()) {
1475 aEvent
->mReply
->mWritingMode
= WritingMode();
1477 aEvent
->mReply
->mWritingMode
=
1478 frameForTextRectOrError
.inspect()->GetWritingMode();
1481 MOZ_ASSERT(aEvent
->Succeeded());
1485 nsresult
ContentEventHandler::OnQueryTextContent(
1486 WidgetQueryContentEvent
* aEvent
) {
1487 nsresult rv
= Init(aEvent
);
1488 if (NS_FAILED(rv
)) {
1492 MOZ_ASSERT(aEvent
->mReply
->mOffsetAndData
.isNothing());
1494 LineBreakType lineBreakType
= GetLineBreakType(aEvent
);
1497 uint32_t startOffset
= 0;
1498 if (NS_WARN_IF(NS_FAILED(SetRawRangeFromFlatTextOffset(
1499 &rawRange
, aEvent
->mInput
.mOffset
, aEvent
->mInput
.mLength
,
1500 lineBreakType
, false, &startOffset
)))) {
1501 return NS_ERROR_FAILURE
;
1504 nsString textInRange
;
1505 if (NS_WARN_IF(NS_FAILED(
1506 GenerateFlatTextContent(rawRange
, textInRange
, lineBreakType
)))) {
1507 return NS_ERROR_FAILURE
;
1510 aEvent
->mReply
->mOffsetAndData
.emplace(startOffset
, textInRange
,
1511 OffsetAndDataFor::EditorString
);
1513 if (aEvent
->mWithFontRanges
) {
1514 uint32_t fontRangeLength
;
1515 if (NS_WARN_IF(NS_FAILED(
1516 GenerateFlatFontRanges(rawRange
, aEvent
->mReply
->mFontRanges
,
1517 fontRangeLength
, lineBreakType
)))) {
1518 return NS_ERROR_FAILURE
;
1521 MOZ_ASSERT(fontRangeLength
== aEvent
->mReply
->DataLength(),
1522 "Font ranges doesn't match the string");
1525 MOZ_ASSERT(aEvent
->Succeeded());
1529 void ContentEventHandler::EnsureNonEmptyRect(nsRect
& aRect
) const {
1530 // See the comment in ContentEventHandler.h why this doesn't set them to
1531 // one device pixel.
1532 aRect
.height
= std::max(1, aRect
.height
);
1533 aRect
.width
= std::max(1, aRect
.width
);
1536 void ContentEventHandler::EnsureNonEmptyRect(LayoutDeviceIntRect
& aRect
) const {
1537 aRect
.height
= std::max(1, aRect
.height
);
1538 aRect
.width
= std::max(1, aRect
.width
);
1541 ContentEventHandler::FrameAndNodeOffset
1542 ContentEventHandler::GetFirstFrameInRangeForTextRect(
1543 const RawRange
& aRawRange
) {
1544 NodePosition nodePosition
;
1545 PreContentIterator preOrderIter
;
1547 preOrderIter
.Init(aRawRange
.Start().AsRaw(), aRawRange
.End().AsRaw());
1548 if (NS_WARN_IF(NS_FAILED(rv
))) {
1549 return FrameAndNodeOffset();
1551 for (; !preOrderIter
.IsDone(); preOrderIter
.Next()) {
1552 nsINode
* node
= preOrderIter
.GetCurrentNode();
1553 if (NS_WARN_IF(!node
)) {
1557 auto* content
= nsIContent::FromNode(node
);
1558 if (MOZ_UNLIKELY(!content
)) {
1562 // If the node is invisible (e.g., the node is or is in an invisible node or
1563 // it's a white-space only text node around a block boundary), we should
1565 if (!content
->GetPrimaryFrame()) {
1569 if (auto* textNode
= Text::FromNode(content
)) {
1570 // If the range starts at the end of a text node, we need to find
1571 // next node which causes text.
1572 const uint32_t offsetInNode
= textNode
== aRawRange
.GetStartContainer()
1573 ? aRawRange
.StartOffset()
1575 if (offsetInNode
< textNode
->TextDataLength()) {
1576 nodePosition
= {textNode
, offsetInNode
};
1582 // If the element node causes a line break before it, it's the first
1583 // node causing text.
1584 if (ShouldBreakLineBefore(*content
, mRootElement
) ||
1585 IsPaddingBR(*content
)) {
1586 nodePosition
= {content
, 0u};
1590 if (!nodePosition
.IsSetAndValid()) {
1591 return FrameAndNodeOffset();
1594 Result
<nsIFrame
*, nsresult
> firstFrameOrError
= GetFrameForTextRect(
1595 nodePosition
.Container(),
1596 *nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
), true);
1597 if (NS_WARN_IF(firstFrameOrError
.isErr()) || !firstFrameOrError
.inspect()) {
1598 return FrameAndNodeOffset();
1600 return FrameAndNodeOffset(
1601 firstFrameOrError
.inspect(),
1602 *nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
));
1605 ContentEventHandler::FrameAndNodeOffset
1606 ContentEventHandler::GetLastFrameInRangeForTextRect(const RawRange
& aRawRange
) {
1607 NodePosition nodePosition
;
1608 PreContentIterator preOrderIter
;
1610 preOrderIter
.Init(aRawRange
.Start().AsRaw(), aRawRange
.End().AsRaw());
1611 if (NS_WARN_IF(NS_FAILED(rv
))) {
1612 return FrameAndNodeOffset();
1615 const RangeBoundary
& endPoint
= aRawRange
.End();
1616 MOZ_ASSERT(endPoint
.IsSetAndValid());
1617 // If the end point is start of a text node or specified by its parent and
1618 // index, the node shouldn't be included into the range. For example,
1619 // with this case, |<p>abc[<br>]def</p>|, the range ends at 3rd children of
1620 // <p> (see the range creation rules, "2.4. Cases: <element/>]"). This causes
1621 // following frames:
1628 // So, if this method includes the 2nd text frame's rect to its result, the
1629 // caller will return too tall rect which includes 2 lines in this case isn't
1630 // expected by native IME (e.g., popup of IME will be positioned at bottom
1631 // of "d" instead of right-bottom of "c"). Therefore, this method shouldn't
1632 // include the last frame when its content isn't really in aRawRange.
1633 nsINode
* nextNodeOfRangeEnd
= nullptr;
1634 if (endPoint
.Container()->IsText()) {
1635 // Don't set nextNodeOfRangeEnd to the start node of aRawRange because if
1636 // the container of the end is same as start node of the range, the text
1637 // node shouldn't be next of range end even if the offset is 0. This
1638 // could occur with empty text node.
1639 if (endPoint
.IsStartOfContainer() &&
1640 aRawRange
.GetStartContainer() != endPoint
.Container()) {
1641 nextNodeOfRangeEnd
= endPoint
.Container();
1643 } else if (endPoint
.IsSetAndValid()) {
1644 nextNodeOfRangeEnd
= endPoint
.GetChildAtOffset();
1647 for (preOrderIter
.Last(); !preOrderIter
.IsDone(); preOrderIter
.Prev()) {
1648 nsINode
* node
= preOrderIter
.GetCurrentNode();
1649 if (NS_WARN_IF(!node
)) {
1653 if (node
== nextNodeOfRangeEnd
) {
1657 auto* content
= nsIContent::FromNode(node
);
1658 if (MOZ_UNLIKELY(!content
)) {
1662 // If the node is invisible (e.g., the node is or is in an invisible node or
1663 // it's a white-space only text node around a block boundary), we should
1665 if (!content
->GetPrimaryFrame()) {
1669 if (auto* textNode
= Text::FromNode(node
)) {
1670 nodePosition
= {textNode
, textNode
== aRawRange
.GetEndContainer()
1671 ? aRawRange
.EndOffset()
1672 : textNode
->TextDataLength()};
1674 // If the text node is empty or the last node of the range but the index
1675 // is 0, we should store current position but continue looking for
1676 // previous node (If there are no nodes before it, we should use current
1677 // node position for returning its frame).
1678 if (*nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
) ==
1685 if (ShouldBreakLineBefore(*content
, mRootElement
) ||
1686 IsPaddingBR(*content
)) {
1687 nodePosition
= {content
, 0u};
1692 if (!nodePosition
.IsSet()) {
1693 return FrameAndNodeOffset();
1696 Result
<nsIFrame
*, nsresult
> lastFrameOrError
= GetFrameForTextRect(
1697 nodePosition
.Container(),
1698 *nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
), true);
1699 if (NS_WARN_IF(lastFrameOrError
.isErr()) || !lastFrameOrError
.inspect()) {
1700 return FrameAndNodeOffset();
1703 // If the last frame is a text frame, we need to check if the range actually
1704 // includes at least one character in the range. Therefore, if it's not a
1705 // text frame, we need to do nothing anymore.
1706 if (!lastFrameOrError
.inspect()->IsTextFrame()) {
1707 return FrameAndNodeOffset(
1708 lastFrameOrError
.inspect(),
1709 *nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
));
1712 int32_t start
= lastFrameOrError
.inspect()->GetOffsets().first
;
1714 // If the start offset in the node is same as the computed offset in the
1715 // node and it's not 0, the frame shouldn't be added to the text rect. So,
1716 // this should return previous text frame and its last offset if there is
1717 // at least one text frame.
1718 if (*nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
) &&
1719 *nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
) ==
1720 static_cast<uint32_t>(start
)) {
1721 const uint32_t newNodePositionOffset
=
1722 *nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
);
1723 MOZ_ASSERT(newNodePositionOffset
!= 0);
1724 nodePosition
= {nodePosition
.Container(), newNodePositionOffset
- 1u};
1725 lastFrameOrError
= GetFrameForTextRect(
1726 nodePosition
.Container(),
1727 *nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
), true);
1728 if (NS_WARN_IF(lastFrameOrError
.isErr()) || !lastFrameOrError
.inspect()) {
1729 return FrameAndNodeOffset();
1733 return FrameAndNodeOffset(
1734 lastFrameOrError
.inspect(),
1735 *nodePosition
.Offset(NodePosition::OffsetFilter::kValidOffsets
));
1738 ContentEventHandler::FrameRelativeRect
1739 ContentEventHandler::GetLineBreakerRectBefore(nsIFrame
* aFrame
) {
1740 // Note that this method should be called only with an element's frame whose
1741 // open tag causes a line break or moz-<br> for computing empty last line's
1743 MOZ_ASSERT(aFrame
->GetContent());
1744 MOZ_ASSERT(ShouldBreakLineBefore(*aFrame
->GetContent(), mRootElement
) ||
1745 IsPaddingBR(*aFrame
->GetContent()));
1747 nsIFrame
* frameForFontMetrics
= aFrame
;
1749 // If it's not a <br> frame, this method computes the line breaker's rect
1750 // outside the frame. Therefore, we need to compute with parent frame's
1751 // font metrics in such case.
1752 if (!aFrame
->IsBrFrame() && aFrame
->GetParent()) {
1753 frameForFontMetrics
= aFrame
->GetParent();
1756 // Note that <br> element's rect is decided with line-height but we need
1757 // a rect only with font height. Additionally, <br> frame's width and
1758 // height are 0 in quirks mode if it's not an empty line. So, we cannot
1759 // use frame rect information even if it's a <br> frame.
1761 RefPtr
<nsFontMetrics
> fontMetrics
=
1762 nsLayoutUtils::GetInflatedFontMetricsForFrame(frameForFontMetrics
);
1763 if (NS_WARN_IF(!fontMetrics
)) {
1764 return FrameRelativeRect();
1767 const WritingMode kWritingMode
= frameForFontMetrics
->GetWritingMode();
1769 auto caretBlockAxisMetrics
=
1770 aFrame
->GetCaretBlockAxisMetrics(kWritingMode
, *fontMetrics
);
1771 nscoord inlineOffset
= 0;
1773 // If aFrame isn't a <br> frame, caret should be at outside of it because
1774 // the line break is before its open tag. For example, case of
1775 // |<div><p>some text</p></div>|, caret is before <p> element and in <div>
1776 // element, the caret should be left of top-left corner of <p> element like:
1778 // +-<div>------------------- <div>'s border box
1779 // | I +-<p>----------------- <p>'s border box
1785 // However, this is a hack for unusual scenario. This hack shouldn't be
1786 // used as far as possible.
1787 if (!aFrame
->IsBrFrame()) {
1788 if (kWritingMode
.IsVertical() && !kWritingMode
.IsLineInverted()) {
1789 // above of top-right corner of aFrame.
1790 caretBlockAxisMetrics
.mOffset
=
1791 aFrame
->GetRect().XMost() - caretBlockAxisMetrics
.mExtent
;
1793 // above (For vertical) or left (For horizontal) of top-left corner of
1795 caretBlockAxisMetrics
.mOffset
= 0;
1797 inlineOffset
= -aFrame
->PresContext()->AppUnitsPerDevPixel();
1799 FrameRelativeRect
result(aFrame
);
1800 if (kWritingMode
.IsVertical()) {
1801 result
.mRect
.x
= caretBlockAxisMetrics
.mOffset
;
1802 result
.mRect
.y
= inlineOffset
;
1803 result
.mRect
.width
= caretBlockAxisMetrics
.mExtent
;
1805 result
.mRect
.x
= inlineOffset
;
1806 result
.mRect
.y
= caretBlockAxisMetrics
.mOffset
;
1807 result
.mRect
.height
= caretBlockAxisMetrics
.mExtent
;
1812 ContentEventHandler::FrameRelativeRect
1813 ContentEventHandler::GuessLineBreakerRectAfter(const Text
& aTextNode
) {
1814 FrameRelativeRect result
;
1815 const int32_t length
= static_cast<int32_t>(aTextNode
.TextLength());
1816 if (NS_WARN_IF(length
< 0)) {
1819 // Get the last nsTextFrame which is caused by aTextNode. Note that
1820 // a text node can cause multiple text frames, e.g., the text is too long
1821 // and wrapped by its parent block or the text has line breakers and its
1822 // white-space property respects the line breakers (e.g., |pre|).
1823 Result
<nsIFrame
*, nsresult
> lastTextFrameOrError
=
1824 GetFrameForTextRect(&aTextNode
, length
, true);
1825 if (NS_WARN_IF(lastTextFrameOrError
.isErr()) ||
1826 !lastTextFrameOrError
.inspect()) {
1829 const nsRect kLastTextFrameRect
= lastTextFrameOrError
.inspect()->GetRect();
1830 if (lastTextFrameOrError
.inspect()->GetWritingMode().IsVertical()) {
1831 // Below of the last text frame.
1832 result
.mRect
.SetRect(0, kLastTextFrameRect
.height
, kLastTextFrameRect
.width
,
1835 // Right of the last text frame (not bidi-aware).
1836 result
.mRect
.SetRect(kLastTextFrameRect
.width
, 0, 0,
1837 kLastTextFrameRect
.height
);
1839 result
.mBaseFrame
= lastTextFrameOrError
.unwrap();
1843 ContentEventHandler::FrameRelativeRect
1844 ContentEventHandler::GuessFirstCaretRectIn(nsIFrame
* aFrame
) {
1845 const WritingMode kWritingMode
= aFrame
->GetWritingMode();
1846 nsPresContext
* presContext
= aFrame
->PresContext();
1848 // Computes the font height, but if it's not available, we should use
1849 // default font size of Firefox. The default font size in default settings
1851 RefPtr
<nsFontMetrics
> fontMetrics
=
1852 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame
);
1853 const nscoord kMaxHeight
= fontMetrics
1854 ? fontMetrics
->MaxHeight()
1855 : 16 * presContext
->AppUnitsPerDevPixel();
1858 const nsRect kContentRect
= aFrame
->GetContentRect() - aFrame
->GetPosition();
1859 caretRect
.y
= kContentRect
.y
;
1860 if (!kWritingMode
.IsVertical()) {
1861 if (kWritingMode
.IsBidiLTR()) {
1862 caretRect
.x
= kContentRect
.x
;
1864 // Move 1px left for the space of caret itself.
1865 const nscoord kOnePixel
= presContext
->AppUnitsPerDevPixel();
1866 caretRect
.x
= kContentRect
.XMost() - kOnePixel
;
1868 caretRect
.height
= kMaxHeight
;
1869 // However, don't add kOnePixel here because it may cause 2px width at
1870 // aligning the edge to device pixels.
1871 caretRect
.width
= 1;
1873 if (kWritingMode
.IsVerticalLR()) {
1874 caretRect
.x
= kContentRect
.x
;
1876 caretRect
.x
= kContentRect
.XMost() - kMaxHeight
;
1878 caretRect
.width
= kMaxHeight
;
1879 // Don't add app units for a device pixel because it may cause 2px height
1880 // at aligning the edge to device pixels.
1881 caretRect
.height
= 1;
1883 return FrameRelativeRect(caretRect
, aFrame
);
1887 LayoutDeviceIntRect
ContentEventHandler::GetCaretRectBefore(
1888 const LayoutDeviceIntRect
& aCharRect
, const WritingMode
& aWritingMode
) {
1889 LayoutDeviceIntRect
caretRectBefore(aCharRect
);
1890 if (aWritingMode
.IsVertical()) {
1891 caretRectBefore
.height
= 1;
1893 // TODO: Make here bidi-aware.
1894 caretRectBefore
.width
= 1;
1896 return caretRectBefore
;
1900 nsRect
ContentEventHandler::GetCaretRectBefore(
1901 const nsRect
& aCharRect
, const WritingMode
& aWritingMode
) {
1902 nsRect
caretRectBefore(aCharRect
);
1903 if (aWritingMode
.IsVertical()) {
1904 // For making the height 1 device pixel after aligning the rect edges to
1905 // device pixels, don't add one device pixel in app units here.
1906 caretRectBefore
.height
= 1;
1908 // TODO: Make here bidi-aware.
1909 // For making the width 1 device pixel after aligning the rect edges to
1910 // device pixels, don't add one device pixel in app units here.
1911 caretRectBefore
.width
= 1;
1913 return caretRectBefore
;
1917 LayoutDeviceIntRect
ContentEventHandler::GetCaretRectAfter(
1918 const LayoutDeviceIntRect
& aCharRect
, const WritingMode
& aWritingMode
) {
1919 LayoutDeviceIntRect
caretRectAfter(aCharRect
);
1920 if (aWritingMode
.IsVertical()) {
1921 caretRectAfter
.y
= aCharRect
.YMost() + 1;
1922 caretRectAfter
.height
= 1;
1924 // TODO: Make here bidi-aware.
1925 caretRectAfter
.x
= aCharRect
.XMost() + 1;
1926 caretRectAfter
.width
= 1;
1928 return caretRectAfter
;
1932 nsRect
ContentEventHandler::GetCaretRectAfter(nsPresContext
& aPresContext
,
1933 const nsRect
& aCharRect
,
1934 const WritingMode
& aWritingMode
) {
1935 nsRect
caretRectAfter(aCharRect
);
1936 const nscoord onePixel
= aPresContext
.AppUnitsPerDevPixel();
1937 if (aWritingMode
.IsVertical()) {
1938 caretRectAfter
.y
= aCharRect
.YMost() + onePixel
;
1939 // For making the height 1 device pixel after aligning the rect edges to
1940 // device pixels, don't add one device pixel in app units here.
1941 caretRectAfter
.height
= 1;
1943 // TODO: Make here bidi-aware.
1944 caretRectAfter
.x
= aCharRect
.XMost() + onePixel
;
1945 // For making the width 1 device pixel after aligning the rect edges to
1946 // device pixels, don't add one device pixel in app units here.
1947 caretRectAfter
.width
= 1;
1949 return caretRectAfter
;
1952 nsresult
ContentEventHandler::OnQueryTextRectArray(
1953 WidgetQueryContentEvent
* aEvent
) {
1954 nsresult rv
= Init(aEvent
);
1955 if (NS_WARN_IF(NS_FAILED(rv
))) {
1959 MOZ_ASSERT(aEvent
->mReply
->mOffsetAndData
.isNothing());
1961 LineBreakType lineBreakType
= GetLineBreakType(aEvent
);
1962 const uint32_t kBRLength
= GetBRLength(lineBreakType
);
1964 WritingMode lastVisibleFrameWritingMode
;
1965 LayoutDeviceIntRect rect
;
1966 uint32_t offset
= aEvent
->mInput
.mOffset
;
1967 const uint32_t kEndOffset
= aEvent
->mInput
.EndOffset();
1968 bool wasLineBreaker
= false;
1969 // lastCharRect stores the last charRect value (see below for the detail of
1971 nsRect lastCharRect
;
1972 // lastFrame is base frame of lastCharRect.
1973 // TODO: We should look for this if the first text is not visible. However,
1974 // users cannot put caret invisible text and users cannot type in it
1975 // at least only with user's operations. Therefore, we don't need to
1976 // fix this immediately.
1977 nsIFrame
* lastFrame
= nullptr;
1978 nsAutoString flattenedAllText
;
1979 flattenedAllText
.SetIsVoid(true);
1980 while (offset
< kEndOffset
) {
1981 RefPtr
<Text
> lastTextNode
;
1984 SetRawRangeFromFlatTextOffset(&rawRange
, offset
, 1, lineBreakType
, true,
1985 nullptr, getter_AddRefs(lastTextNode
));
1986 if (NS_WARN_IF(NS_FAILED(rv
))) {
1990 // TODO: When we crossed parent block boundary now, we should fill pending
1991 // character rects with caret rect after the last visible character
1994 // If the range is collapsed, offset has already reached the end of the
1996 if (rawRange
.Collapsed()) {
2000 // Get the first frame which causes some text after the offset.
2001 FrameAndNodeOffset firstFrame
= GetFirstFrameInRangeForTextRect(rawRange
);
2003 // If GetFirstFrameInRangeForTextRect() does not return valid frame, that
2004 // means that the offset reached the end of contents or there is no visible
2005 // frame in the range generating flattened text.
2006 if (!firstFrame
.IsValid()) {
2007 if (flattenedAllText
.IsVoid()) {
2008 flattenedAllText
.SetIsVoid(false);
2009 if (NS_WARN_IF(NS_FAILED(GenerateFlatTextContent(
2010 mRootElement
, flattenedAllText
, lineBreakType
)))) {
2011 NS_WARNING("ContentEventHandler::GenerateFlatTextContent() failed");
2012 return NS_ERROR_FAILURE
;
2015 // If we've reached end of the root, append caret rect at the end of
2017 if (offset
>= flattenedAllText
.Length()) {
2020 // Otherwise, we're in an invisible node. If the node is followed by a
2021 // block boundary causing a line break, we can use the boundary.
2022 // Otherwise, if the node follows a block boundary of a parent block, we
2023 // can use caret rect at previous visible frame causing flattened text.
2024 const uint32_t remainingLengthInCurrentRange
= [&]() {
2026 if (rawRange
.GetStartContainer() == lastTextNode
) {
2027 if (rawRange
.StartOffset() < lastTextNode
->TextDataLength()) {
2028 return lastTextNode
->TextDataLength() - rawRange
.StartOffset();
2032 // Must be there are not nodes which may cause generating text.
2033 // Therefore, we can skip all nodes before the last found text node
2034 // and all text in the last text node.
2035 return lastTextNode
->TextDataLength();
2037 if (rawRange
.GetStartContainer() &&
2038 rawRange
.GetStartContainer()->IsContent() &&
2039 ShouldBreakLineBefore(*rawRange
.GetStartContainer()->AsContent(),
2041 if (kBRLength
!= 1u && offset
- aEvent
->mInput
.mOffset
< kBRLength
) {
2042 // Don't return kBRLength if start position is less than the length
2043 // of a line-break because the offset may be between CRLF on
2044 // Windows. In the case, we will be again here and gets same
2045 // result and we need to pay the penalty only once. Therefore, we
2046 // can keep going without complicated check.
2053 offset
+= std::max(1u, remainingLengthInCurrentRange
);
2057 nsIContent
* firstContent
= firstFrame
.mFrame
->GetContent();
2058 if (NS_WARN_IF(!firstContent
)) {
2059 return NS_ERROR_FAILURE
;
2062 bool startsBetweenLineBreaker
= false;
2064 lastVisibleFrameWritingMode
= firstFrame
->GetWritingMode();
2066 nsIFrame
* baseFrame
= firstFrame
;
2067 // charRect should have each character rect or line breaker rect relative
2068 // to the base frame.
2069 AutoTArray
<nsRect
, 16> charRects
;
2071 // If the first frame is a text frame, the result should be computed with
2073 if (firstFrame
->IsTextFrame()) {
2074 rv
= firstFrame
->GetCharacterRectsInRange(firstFrame
.mOffsetInNode
,
2075 kEndOffset
- offset
, charRects
);
2076 if (NS_WARN_IF(NS_FAILED(rv
)) || NS_WARN_IF(charRects
.IsEmpty())) {
2079 // Assign the characters whose rects are computed by the call of
2080 // nsTextFrame::GetCharacterRectsInRange().
2081 AppendSubString(chars
, *firstContent
->AsText(), firstFrame
.mOffsetInNode
,
2082 charRects
.Length());
2083 if (NS_WARN_IF(chars
.Length() != charRects
.Length())) {
2084 return NS_ERROR_UNEXPECTED
;
2086 if (kBRLength
> 1 && chars
[0] == '\n' &&
2087 offset
== aEvent
->mInput
.mOffset
&& offset
) {
2088 // If start of range starting from previous offset of query range is
2089 // same as the start of query range, the query range starts from
2090 // between a line breaker (i.e., the range starts between "\r" and
2092 RawRange rawRangeToPrevOffset
;
2093 nsresult rv
= SetRawRangeFromFlatTextOffset(&rawRangeToPrevOffset
,
2094 aEvent
->mInput
.mOffset
- 1,
2095 1, lineBreakType
, true);
2096 if (NS_WARN_IF(NS_FAILED(rv
))) {
2099 startsBetweenLineBreaker
=
2100 rawRange
.GetStartContainer() ==
2101 rawRangeToPrevOffset
.GetStartContainer() &&
2102 rawRange
.StartOffset() == rawRangeToPrevOffset
.StartOffset();
2105 // Other contents should cause a line breaker rect before it.
2106 // Note that moz-<br> element does not cause any text, however,
2107 // it represents empty line at the last of current block. Therefore,
2108 // we need to compute its rect too.
2109 else if (ShouldBreakLineBefore(*firstContent
, mRootElement
) ||
2110 IsPaddingBR(*firstContent
)) {
2112 // If the frame is not a <br> frame, we need to compute the caret rect
2113 // with last character's rect before firstContent if there is.
2114 // For example, if caret is after "c" of |<p>abc</p><p>def</p>|, IME may
2115 // query a line breaker's rect after "c". Then, if we compute it only
2116 // with the 2nd <p>'s block frame, the result will be:
2117 // +-<p>--------------------------------+
2119 // +------------------------------------+
2121 // I+-<p>--------------------------------+
2123 // +------------------------------------+
2124 // However, users expect popup windows of IME should be positioned at
2125 // right-bottom of "c" like this:
2126 // +-<p>--------------------------------+
2128 // +------------------------------------+
2130 // +-<p>--------------------------------+
2132 // +------------------------------------+
2133 // Therefore, if the first frame isn't a <br> frame and there is a text
2134 // node before the first node in the queried range, we should compute the
2135 // first rect with the previous character's rect.
2136 // If we already compute a character's rect in the queried range, we can
2137 // compute it with the cached last character's rect. (However, don't
2138 // use this path if it's a <br> frame because trusting <br> frame's rect
2139 // is better than guessing the rect from the previous character.)
2140 if (!firstFrame
->IsBrFrame() && !aEvent
->mReply
->mRectArray
.IsEmpty()) {
2141 baseFrame
= lastFrame
;
2142 brRect
= lastCharRect
;
2143 if (!wasLineBreaker
) {
2144 brRect
= GetCaretRectAfter(*baseFrame
->PresContext(), brRect
,
2145 lastVisibleFrameWritingMode
);
2148 // If it's not a <br> frame and it's the first character rect at the
2149 // queried range, we need the previous character rect of the start of
2150 // the queried range if there is a visible text node.
2151 else if (!firstFrame
->IsBrFrame() && lastTextNode
&&
2152 lastTextNode
->GetPrimaryFrame()) {
2153 FrameRelativeRect brRectRelativeToLastTextFrame
=
2154 GuessLineBreakerRectAfter(*lastTextNode
);
2155 if (NS_WARN_IF(!brRectRelativeToLastTextFrame
.IsValid())) {
2156 return NS_ERROR_FAILURE
;
2158 // Look for the last text frame for lastTextNode.
2159 nsIFrame
* primaryFrame
= lastTextNode
->GetPrimaryFrame();
2160 if (NS_WARN_IF(!primaryFrame
)) {
2161 return NS_ERROR_FAILURE
;
2163 baseFrame
= primaryFrame
->LastContinuation();
2164 if (NS_WARN_IF(!baseFrame
)) {
2165 return NS_ERROR_FAILURE
;
2167 brRect
= brRectRelativeToLastTextFrame
.RectRelativeTo(baseFrame
);
2169 // Otherwise, we need to compute the line breaker's rect only with the
2170 // first frame's rect. But this may be unexpected. For example,
2171 // |<div contenteditable>[<p>]abc</p></div>|. In this case, caret is
2172 // before "a", therefore, users expect the rect left of "a". However,
2173 // we don't have enough information about the next character here and
2174 // this isn't usual case (e.g., IME typically tries to query the rect
2175 // of "a" or caret rect for computing its popup position). Therefore,
2176 // we shouldn't do more complicated hack here unless we'll get some bug
2177 // reports actually.
2179 FrameRelativeRect relativeBRRect
= GetLineBreakerRectBefore(firstFrame
);
2180 brRect
= relativeBRRect
.RectRelativeTo(firstFrame
);
2182 charRects
.AppendElement(brRect
);
2183 chars
.AssignLiteral("\n");
2184 if (kBRLength
> 1 && offset
== aEvent
->mInput
.mOffset
&& offset
) {
2185 // If the first frame for the previous offset of the query range and
2186 // the first frame for the start of query range are same, that means
2187 // the start offset is between the first line breaker (i.e., the range
2188 // starts between "\r" and "\n").
2189 nsresult rv
= SetRawRangeFromFlatTextOffset(
2190 &rawRange
, aEvent
->mInput
.mOffset
- 1, 1, lineBreakType
, true);
2191 if (NS_WARN_IF(NS_FAILED(rv
))) {
2192 return NS_ERROR_UNEXPECTED
;
2194 FrameAndNodeOffset frameForPrevious
=
2195 GetFirstFrameInRangeForTextRect(rawRange
);
2196 startsBetweenLineBreaker
= frameForPrevious
.mFrame
== firstFrame
.mFrame
;
2200 "The frame is neither a text frame nor a frame whose content "
2201 "causes a line break");
2202 return NS_ERROR_FAILURE
;
2205 for (size_t i
= 0; i
< charRects
.Length() && offset
< kEndOffset
; i
++) {
2206 nsRect charRect
= charRects
[i
];
2207 // Store lastCharRect before applying CSS transform because it may be
2208 // used for computing a line breaker rect. Then, the computed line
2209 // breaker rect will be applied CSS transform again. Therefore,
2210 // the value of lastCharRect should be raw rect value relative to the
2212 lastCharRect
= charRect
;
2213 lastFrame
= baseFrame
;
2214 rv
= ConvertToRootRelativeOffset(baseFrame
, charRect
);
2215 if (NS_WARN_IF(NS_FAILED(rv
))) {
2219 nsPresContext
* presContext
= baseFrame
->PresContext();
2220 rect
= LayoutDeviceIntRect::FromAppUnitsToOutside(
2221 charRect
, presContext
->AppUnitsPerDevPixel());
2222 if (nsPresContext
* rootContext
=
2223 presContext
->GetInProcessRootContentDocumentPresContext()) {
2224 rect
= RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2225 rect
, rootContext
->PresShell()));
2227 // Returning empty rect may cause native IME confused, let's make sure to
2228 // return non-empty rect.
2229 EnsureNonEmptyRect(rect
);
2231 // If we found some invisible characters followed by current visible
2232 // character, make their rects same as caret rect before the first visible
2233 // character because IME may want to put their UI next to the rect of the
2234 // invisible character for next input.
2235 // Note that chars do not contain the invisible characters.
2236 if (i
== 0u && MOZ_LIKELY(offset
> aEvent
->mInput
.mOffset
)) {
2237 const uint32_t offsetInRange
=
2238 offset
- CheckedInt
<uint32_t>(aEvent
->mInput
.mOffset
).value();
2239 if (offsetInRange
> aEvent
->mReply
->mRectArray
.Length()) {
2240 LayoutDeviceIntRect caretRectBefore
=
2241 GetCaretRectBefore(rect
, lastVisibleFrameWritingMode
);
2242 for ([[maybe_unused
]] uint32_t index
: IntegerRange
<uint32_t>(
2243 offsetInRange
- aEvent
->mReply
->mRectArray
.Length())) {
2244 aEvent
->mReply
->mRectArray
.AppendElement(caretRectBefore
);
2246 MOZ_ASSERT(aEvent
->mReply
->mRectArray
.Length() == offsetInRange
);
2250 aEvent
->mReply
->mRectArray
.AppendElement(rect
);
2253 // If it's not a line breaker or the line breaker length is same as
2254 // XP line breaker's, we need to do nothing for current character.
2255 wasLineBreaker
= chars
[i
] == '\n';
2256 if (!wasLineBreaker
|| kBRLength
== 1) {
2260 MOZ_ASSERT(kBRLength
== 2);
2262 // If it's already reached the end of query range, we don't need to do
2264 if (offset
== kEndOffset
) {
2268 // If the query range starts from between a line breaker, i.e., it starts
2269 // between "\r" and "\n", the appended rect was for the "\n". Therefore,
2270 // we don't need to append same rect anymore for current "\r\n".
2271 if (startsBetweenLineBreaker
) {
2275 // The appended rect was for "\r" of "\r\n". Therefore, we need to
2276 // append same rect for "\n" too because querying rect of "\r" and "\n"
2277 // should return same rect. E.g., IME may query previous character's
2278 // rect of first character of a line.
2279 aEvent
->mReply
->mRectArray
.AppendElement(rect
);
2284 // If we've not handled some invisible character rects, fill them as caret
2285 // rect after the last visible character.
2286 if (!aEvent
->mReply
->mRectArray
.IsEmpty()) {
2287 const uint32_t offsetInRange
=
2288 offset
- CheckedInt
<uint32_t>(aEvent
->mInput
.mOffset
).value();
2289 if (offsetInRange
> aEvent
->mReply
->mRectArray
.Length()) {
2290 LayoutDeviceIntRect caretRectAfter
=
2291 GetCaretRectAfter(aEvent
->mReply
->mRectArray
.LastElement(),
2292 lastVisibleFrameWritingMode
);
2293 for ([[maybe_unused
]] uint32_t index
: IntegerRange
<uint32_t>(
2294 offsetInRange
- aEvent
->mReply
->mRectArray
.Length())) {
2295 aEvent
->mReply
->mRectArray
.AppendElement(caretRectAfter
);
2297 MOZ_ASSERT(aEvent
->mReply
->mRectArray
.Length() == offsetInRange
);
2301 // If the query range is longer than actual content length, we should append
2302 // caret rect at the end of the content as the last character rect because
2303 // native IME may want to query character rect at the end of contents for
2304 // deciding the position of a popup window (e.g., suggest window for next
2305 // word). Note that when this method hasn't appended character rects, it
2306 // means that the offset is too large or the query range is collapsed.
2307 if (offset
< kEndOffset
|| aEvent
->mReply
->mRectArray
.IsEmpty()) {
2308 // If we've already retrieved some character rects before current offset,
2309 // we can guess the last rect from the last character's rect unless it's a
2310 // line breaker. (If it's a line breaker, the caret rect is in next line.)
2311 if (!aEvent
->mReply
->mRectArray
.IsEmpty() && !wasLineBreaker
) {
2312 rect
= GetCaretRectAfter(aEvent
->mReply
->mRectArray
.LastElement(),
2313 lastVisibleFrameWritingMode
);
2314 aEvent
->mReply
->mRectArray
.AppendElement(rect
);
2316 // Note that don't use eQueryCaretRect here because if caret is at the
2317 // end of the content, it returns actual caret rect instead of computing
2318 // the rect itself. It means that the result depends on caret position.
2319 // So, we shouldn't use it for consistency result in automated tests.
2320 WidgetQueryContentEvent
queryTextRectEvent(eQueryTextRect
, *aEvent
);
2321 WidgetQueryContentEvent::Options
options(*aEvent
);
2322 queryTextRectEvent
.InitForQueryTextRect(offset
, 1, options
);
2323 if (NS_WARN_IF(NS_FAILED(OnQueryTextRect(&queryTextRectEvent
))) ||
2324 NS_WARN_IF(queryTextRectEvent
.Failed())) {
2325 return NS_ERROR_FAILURE
;
2327 if (queryTextRectEvent
.mReply
->mWritingMode
.IsVertical()) {
2328 queryTextRectEvent
.mReply
->mRect
.height
= 1;
2330 queryTextRectEvent
.mReply
->mRect
.width
= 1;
2332 aEvent
->mReply
->mRectArray
.AppendElement(
2333 queryTextRectEvent
.mReply
->mRect
);
2337 MOZ_ASSERT(aEvent
->Succeeded());
2341 nsresult
ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent
* aEvent
) {
2342 // If mLength is 0 (this may be caused by bug of native IME), we should
2343 // redirect this event to OnQueryCaretRect().
2344 if (!aEvent
->mInput
.mLength
) {
2345 return OnQueryCaretRect(aEvent
);
2348 nsresult rv
= Init(aEvent
);
2349 if (NS_FAILED(rv
)) {
2353 MOZ_ASSERT(aEvent
->mReply
->mOffsetAndData
.isNothing());
2355 LineBreakType lineBreakType
= GetLineBreakType(aEvent
);
2357 RefPtr
<Text
> lastTextNode
;
2358 uint32_t startOffset
= 0;
2359 if (NS_WARN_IF(NS_FAILED(SetRawRangeFromFlatTextOffset(
2360 &rawRange
, aEvent
->mInput
.mOffset
, aEvent
->mInput
.mLength
,
2361 lineBreakType
, true, &startOffset
, getter_AddRefs(lastTextNode
))))) {
2362 return NS_ERROR_FAILURE
;
2365 if (NS_WARN_IF(NS_FAILED(
2366 GenerateFlatTextContent(rawRange
, string
, lineBreakType
)))) {
2367 return NS_ERROR_FAILURE
;
2369 aEvent
->mReply
->mOffsetAndData
.emplace(startOffset
, string
,
2370 OffsetAndDataFor::EditorString
);
2372 // used to iterate over all contents and their frames
2373 PostContentIterator postOrderIter
;
2374 rv
= postOrderIter
.Init(rawRange
.Start().AsRaw(), rawRange
.End().AsRaw());
2375 if (NS_WARN_IF(NS_FAILED(rv
))) {
2376 return NS_ERROR_FAILURE
;
2379 // Get the first frame which causes some text after the offset.
2380 FrameAndNodeOffset firstFrame
= GetFirstFrameInRangeForTextRect(rawRange
);
2382 // If GetFirstFrameInRangeForTextRect() does not return valid frame, that
2383 // means that there are no visible frames having text or the offset reached
2384 // the end of contents.
2385 if (!firstFrame
.IsValid()) {
2386 nsAutoString allText
;
2387 rv
= GenerateFlatTextContent(mRootElement
, allText
, lineBreakType
);
2388 // If the offset doesn't reach the end of contents but there is no frames
2389 // for the node, that means that current offset's node is hidden by CSS or
2390 // something. Ideally, we should handle it with the last visible text
2391 // node's last character's rect, but it's not usual cases in actual web
2392 // services. Therefore, currently, we should make this case fail.
2393 if (NS_WARN_IF(NS_FAILED(rv
)) ||
2394 static_cast<uint32_t>(aEvent
->mInput
.mOffset
) < allText
.Length()) {
2395 return NS_ERROR_FAILURE
;
2398 // Look for the last frame which should be included text rects.
2399 rv
= rawRange
.SelectNodeContents(mRootElement
);
2400 if (NS_WARN_IF(NS_FAILED(rv
))) {
2401 return NS_ERROR_UNEXPECTED
;
2404 FrameAndNodeOffset lastFrame
= GetLastFrameInRangeForTextRect(rawRange
);
2405 // If there is at least one frame which can be used for computing a rect
2406 // for a character or a line breaker, we should use it for guessing the
2407 // caret rect at the end of the contents.
2408 nsPresContext
* presContext
;
2410 presContext
= lastFrame
->PresContext();
2411 if (NS_WARN_IF(!lastFrame
->GetContent())) {
2412 return NS_ERROR_FAILURE
;
2414 FrameRelativeRect relativeRect
;
2415 // If there is a <br> frame at the end, it represents an empty line at
2416 // the end with moz-<br> or content <br> in a block level element.
2417 if (lastFrame
->IsBrFrame()) {
2418 relativeRect
= GetLineBreakerRectBefore(lastFrame
);
2420 // If there is a text frame at the end, use its information.
2421 else if (lastFrame
->IsTextFrame()) {
2422 const Text
* textNode
= Text::FromNode(lastFrame
->GetContent());
2423 MOZ_ASSERT(textNode
);
2425 relativeRect
= GuessLineBreakerRectAfter(*textNode
);
2428 // If there is an empty frame which is neither a text frame nor a <br>
2429 // frame at the end, guess caret rect in it.
2431 relativeRect
= GuessFirstCaretRectIn(lastFrame
);
2433 if (NS_WARN_IF(!relativeRect
.IsValid())) {
2434 return NS_ERROR_FAILURE
;
2436 rect
= relativeRect
.RectRelativeTo(lastFrame
);
2437 rv
= ConvertToRootRelativeOffset(lastFrame
, rect
);
2438 if (NS_WARN_IF(NS_FAILED(rv
))) {
2441 aEvent
->mReply
->mWritingMode
= lastFrame
->GetWritingMode();
2443 // Otherwise, if there are no contents in mRootElement, guess caret rect in
2444 // its frame (with its font height and content box).
2446 nsIFrame
* rootContentFrame
= mRootElement
->GetPrimaryFrame();
2447 if (NS_WARN_IF(!rootContentFrame
)) {
2448 return NS_ERROR_FAILURE
;
2450 presContext
= rootContentFrame
->PresContext();
2451 FrameRelativeRect relativeRect
= GuessFirstCaretRectIn(rootContentFrame
);
2452 if (NS_WARN_IF(!relativeRect
.IsValid())) {
2453 return NS_ERROR_FAILURE
;
2455 rect
= relativeRect
.RectRelativeTo(rootContentFrame
);
2456 rv
= ConvertToRootRelativeOffset(rootContentFrame
, rect
);
2457 if (NS_WARN_IF(NS_FAILED(rv
))) {
2460 aEvent
->mReply
->mWritingMode
= rootContentFrame
->GetWritingMode();
2462 aEvent
->mReply
->mRect
= LayoutDeviceIntRect::FromAppUnitsToOutside(
2463 rect
, presContext
->AppUnitsPerDevPixel());
2464 if (nsPresContext
* rootContext
=
2465 presContext
->GetInProcessRootContentDocumentPresContext()) {
2466 aEvent
->mReply
->mRect
=
2467 RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2468 aEvent
->mReply
->mRect
, rootContext
->PresShell()));
2470 EnsureNonEmptyRect(aEvent
->mReply
->mRect
);
2472 MOZ_ASSERT(aEvent
->Succeeded());
2476 nsRect rect
, frameRect
;
2479 // If the first frame is a text frame, the result should be computed with
2480 // the frame's rect but not including the rect before start point of the
2482 if (firstFrame
->IsTextFrame()) {
2483 rect
.SetRect(nsPoint(0, 0), firstFrame
->GetRect().Size());
2484 rv
= ConvertToRootRelativeOffset(firstFrame
, rect
);
2485 if (NS_WARN_IF(NS_FAILED(rv
))) {
2489 // Exclude the rect before start point of the queried range.
2490 firstFrame
->GetPointFromOffset(firstFrame
.mOffsetInNode
, &ptOffset
);
2491 if (firstFrame
->GetWritingMode().IsVertical()) {
2492 rect
.y
+= ptOffset
.y
;
2493 rect
.height
-= ptOffset
.y
;
2495 rect
.x
+= ptOffset
.x
;
2496 rect
.width
-= ptOffset
.x
;
2499 // If first frame causes a line breaker but it's not a <br> frame, we cannot
2500 // compute proper rect only with the frame because typically caret is at
2501 // right of the last character of it. For example, if caret is after "c" of
2502 // |<p>abc</p><p>def</p>|, IME may query a line breaker's rect after "c".
2503 // Then, if we compute it only with the 2nd <p>'s block frame, the result
2505 // +-<p>--------------------------------+
2507 // +------------------------------------+
2509 // I+-<p>--------------------------------+
2511 // +------------------------------------+
2512 // However, users expect popup windows of IME should be positioned at
2513 // right-bottom of "c" like this:
2514 // +-<p>--------------------------------+
2516 // +------------------------------------+
2518 // +-<p>--------------------------------+
2520 // +------------------------------------+
2521 // Therefore, if the first frame isn't a <br> frame and there is a visible
2522 // text node before the first node in the queried range, we should compute the
2523 // first rect with the previous character's rect.
2524 else if (!firstFrame
->IsBrFrame() && lastTextNode
&&
2525 lastTextNode
->GetPrimaryFrame()) {
2526 FrameRelativeRect brRectAfterLastChar
=
2527 GuessLineBreakerRectAfter(*lastTextNode
);
2528 if (NS_WARN_IF(!brRectAfterLastChar
.IsValid())) {
2529 return NS_ERROR_FAILURE
;
2531 rect
= brRectAfterLastChar
.mRect
;
2532 rv
= ConvertToRootRelativeOffset(brRectAfterLastChar
.mBaseFrame
, rect
);
2533 if (NS_WARN_IF(NS_FAILED(rv
))) {
2538 // Otherwise, we need to compute the line breaker's rect only with the
2539 // first frame's rect. But this may be unexpected. For example,
2540 // |<div contenteditable>[<p>]abc</p></div>|. In this case, caret is before
2541 // "a", therefore, users expect the rect left of "a". However, we don't
2542 // have enough information about the next character here and this isn't
2543 // usual case (e.g., IME typically tries to query the rect of "a" or caret
2544 // rect for computing its popup position). Therefore, we shouldn't do
2545 // more complicated hack here unless we'll get some bug reports actually.
2547 FrameRelativeRect relativeRect
= GetLineBreakerRectBefore(firstFrame
);
2548 if (NS_WARN_IF(!relativeRect
.IsValid())) {
2549 return NS_ERROR_FAILURE
;
2551 rect
= relativeRect
.RectRelativeTo(firstFrame
);
2552 rv
= ConvertToRootRelativeOffset(firstFrame
, rect
);
2553 if (NS_WARN_IF(NS_FAILED(rv
))) {
2558 // UnionRect() requires non-empty rect. So, let's make sure to get non-emtpy
2559 // rect from the first frame.
2560 EnsureNonEmptyRect(rect
);
2562 // Get the last frame which causes some text in the range.
2563 FrameAndNodeOffset lastFrame
= GetLastFrameInRangeForTextRect(rawRange
);
2564 if (NS_WARN_IF(!lastFrame
.IsValid())) {
2565 return NS_ERROR_FAILURE
;
2568 // iterate over all covered frames
2569 for (nsIFrame
* frame
= firstFrame
; frame
!= lastFrame
;) {
2570 frame
= frame
->GetNextContinuation();
2573 postOrderIter
.Next();
2574 nsINode
* node
= postOrderIter
.GetCurrentNode();
2578 if (!node
->IsContent()) {
2581 nsIFrame
* primaryFrame
= node
->AsContent()->GetPrimaryFrame();
2582 // The node may be hidden by CSS.
2583 if (!primaryFrame
) {
2586 // We should take only text frame's rect and br frame's rect. We can
2587 // always use frame rect of text frame and GetLineBreakerRectBefore()
2588 // can return exactly correct rect only for <br> frame for now. On the
2589 // other hand, GetLineBreakRectBefore() returns guessed caret rect for
2590 // the other frames. We shouldn't include such odd rect to the result.
2591 if (primaryFrame
->IsTextFrame() || primaryFrame
->IsBrFrame()) {
2592 frame
= primaryFrame
;
2594 } while (!frame
&& !postOrderIter
.IsDone());
2599 if (frame
->IsTextFrame()) {
2600 frameRect
.SetRect(nsPoint(0, 0), frame
->GetRect().Size());
2602 MOZ_ASSERT(frame
->IsBrFrame());
2603 FrameRelativeRect relativeRect
= GetLineBreakerRectBefore(frame
);
2604 if (NS_WARN_IF(!relativeRect
.IsValid())) {
2605 return NS_ERROR_FAILURE
;
2607 frameRect
= relativeRect
.RectRelativeTo(frame
);
2609 rv
= ConvertToRootRelativeOffset(frame
, frameRect
);
2610 if (NS_WARN_IF(NS_FAILED(rv
))) {
2613 // UnionRect() requires non-empty rect. So, let's make sure to get
2614 // non-emtpy rect from the frame.
2615 EnsureNonEmptyRect(frameRect
);
2616 if (frame
!= lastFrame
) {
2617 // not last frame, so just add rect to previous result
2618 rect
.UnionRect(rect
, frameRect
);
2622 // Get the ending frame rect.
2623 // FYI: If first frame and last frame are same, frameRect is already set
2624 // to the rect excluding the text before the query range.
2625 if (firstFrame
.mFrame
!= lastFrame
.mFrame
) {
2626 frameRect
.SetRect(nsPoint(0, 0), lastFrame
->GetRect().Size());
2627 rv
= ConvertToRootRelativeOffset(lastFrame
, frameRect
);
2628 if (NS_WARN_IF(NS_FAILED(rv
))) {
2633 // Shrink the last frame for cutting off the text after the query range.
2634 if (lastFrame
->IsTextFrame()) {
2635 lastFrame
->GetPointFromOffset(lastFrame
.mOffsetInNode
, &ptOffset
);
2636 if (lastFrame
->GetWritingMode().IsVertical()) {
2637 frameRect
.height
-= lastFrame
->GetRect().height
- ptOffset
.y
;
2639 frameRect
.width
-= lastFrame
->GetRect().width
- ptOffset
.x
;
2641 // UnionRect() requires non-empty rect. So, let's make sure to get
2642 // non-empty rect from the last frame.
2643 EnsureNonEmptyRect(frameRect
);
2645 if (firstFrame
.mFrame
== lastFrame
.mFrame
) {
2646 rect
.IntersectRect(rect
, frameRect
);
2648 rect
.UnionRect(rect
, frameRect
);
2652 nsPresContext
* presContext
= lastFrame
->PresContext();
2653 aEvent
->mReply
->mRect
= LayoutDeviceIntRect::FromAppUnitsToOutside(
2654 rect
, presContext
->AppUnitsPerDevPixel());
2655 if (nsPresContext
* rootContext
=
2656 presContext
->GetInProcessRootContentDocumentPresContext()) {
2657 aEvent
->mReply
->mRect
=
2658 RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2659 aEvent
->mReply
->mRect
, rootContext
->PresShell()));
2661 // Returning empty rect may cause native IME confused, let's make sure to
2662 // return non-empty rect.
2663 EnsureNonEmptyRect(aEvent
->mReply
->mRect
);
2664 aEvent
->mReply
->mWritingMode
= lastFrame
->GetWritingMode();
2666 MOZ_ASSERT(aEvent
->Succeeded());
2670 nsresult
ContentEventHandler::OnQueryEditorRect(
2671 WidgetQueryContentEvent
* aEvent
) {
2672 nsresult rv
= Init(aEvent
);
2673 if (NS_FAILED(rv
)) {
2677 if (NS_WARN_IF(NS_FAILED(QueryContentRect(mRootElement
, aEvent
)))) {
2678 return NS_ERROR_FAILURE
;
2681 MOZ_ASSERT(aEvent
->Succeeded());
2685 nsresult
ContentEventHandler::OnQueryCaretRect(
2686 WidgetQueryContentEvent
* aEvent
) {
2687 nsresult rv
= Init(aEvent
);
2688 if (NS_FAILED(rv
)) {
2692 // When the selection is collapsed and the queried offset is current caret
2693 // position, we should return the "real" caret rect.
2694 if (mSelection
->IsCollapsed()) {
2696 nsIFrame
* caretFrame
= nsCaret::GetGeometry(mSelection
, &caretRect
);
2699 rv
= GetStartOffset(mFirstSelectedRawRange
, &offset
,
2700 GetLineBreakType(aEvent
));
2701 NS_ENSURE_SUCCESS(rv
, rv
);
2702 if (offset
== aEvent
->mInput
.mOffset
) {
2703 rv
= ConvertToRootRelativeOffset(caretFrame
, caretRect
);
2704 NS_ENSURE_SUCCESS(rv
, rv
);
2705 nsPresContext
* presContext
= caretFrame
->PresContext();
2706 aEvent
->mReply
->mRect
= LayoutDeviceIntRect::FromAppUnitsToOutside(
2707 caretRect
, presContext
->AppUnitsPerDevPixel());
2708 if (nsPresContext
* rootContext
=
2709 presContext
->GetInProcessRootContentDocumentPresContext()) {
2710 aEvent
->mReply
->mRect
=
2711 RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2712 aEvent
->mReply
->mRect
, rootContext
->PresShell()));
2714 // Returning empty rect may cause native IME confused, let's make sure
2715 // to return non-empty rect.
2716 EnsureNonEmptyRect(aEvent
->mReply
->mRect
);
2717 aEvent
->mReply
->mWritingMode
= caretFrame
->GetWritingMode();
2718 aEvent
->mReply
->mOffsetAndData
.emplace(
2719 aEvent
->mInput
.mOffset
, EmptyString(),
2720 OffsetAndDataFor::SelectedString
);
2722 MOZ_ASSERT(aEvent
->Succeeded());
2728 // Otherwise, we should guess the caret rect from the character's rect.
2729 WidgetQueryContentEvent
queryTextRectEvent(eQueryTextRect
, *aEvent
);
2730 WidgetQueryContentEvent::Options
options(*aEvent
);
2731 queryTextRectEvent
.InitForQueryTextRect(aEvent
->mInput
.mOffset
, 1, options
);
2732 if (NS_WARN_IF(NS_FAILED(OnQueryTextRect(&queryTextRectEvent
))) ||
2733 NS_WARN_IF(queryTextRectEvent
.Failed())) {
2734 return NS_ERROR_FAILURE
;
2736 queryTextRectEvent
.mReply
->TruncateData();
2737 aEvent
->mReply
->mOffsetAndData
=
2738 std::move(queryTextRectEvent
.mReply
->mOffsetAndData
);
2739 aEvent
->mReply
->mWritingMode
=
2740 std::move(queryTextRectEvent
.mReply
->mWritingMode
);
2741 aEvent
->mReply
->mRect
= GetCaretRectBefore(queryTextRectEvent
.mReply
->mRect
,
2742 aEvent
->mReply
->mWritingMode
);
2744 MOZ_ASSERT(aEvent
->Succeeded());
2748 nsresult
ContentEventHandler::OnQueryContentState(
2749 WidgetQueryContentEvent
* aEvent
) {
2750 if (NS_FAILED(Init(aEvent
))) {
2751 return NS_ERROR_FAILURE
;
2753 MOZ_ASSERT(aEvent
->mReply
.isSome());
2754 MOZ_ASSERT(aEvent
->Succeeded());
2758 nsresult
ContentEventHandler::OnQuerySelectionAsTransferable(
2759 WidgetQueryContentEvent
* aEvent
) {
2760 nsresult rv
= Init(aEvent
);
2761 if (NS_FAILED(rv
)) {
2765 MOZ_ASSERT(aEvent
->mReply
.isSome());
2767 if (mSelection
->IsCollapsed()) {
2768 MOZ_ASSERT(!aEvent
->mReply
->mTransferable
);
2772 if (NS_WARN_IF(NS_FAILED(nsCopySupport::GetTransferableForSelection(
2773 mSelection
, mDocument
,
2774 getter_AddRefs(aEvent
->mReply
->mTransferable
))))) {
2775 return NS_ERROR_FAILURE
;
2778 MOZ_ASSERT(aEvent
->Succeeded());
2782 nsresult
ContentEventHandler::OnQueryCharacterAtPoint(
2783 WidgetQueryContentEvent
* aEvent
) {
2784 nsresult rv
= Init(aEvent
);
2785 if (NS_FAILED(rv
)) {
2789 MOZ_ASSERT(aEvent
->mReply
->mOffsetAndData
.isNothing());
2790 MOZ_ASSERT(aEvent
->mReply
->mTentativeCaretOffset
.isNothing());
2792 PresShell
* presShell
= mDocument
->GetPresShell();
2793 NS_ENSURE_TRUE(presShell
, NS_ERROR_FAILURE
);
2794 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
2795 NS_ENSURE_TRUE(rootFrame
, NS_ERROR_FAILURE
);
2796 nsIWidget
* rootWidget
= rootFrame
->GetNearestWidget();
2797 NS_ENSURE_TRUE(rootWidget
, NS_ERROR_FAILURE
);
2799 // The root frame's widget might be different, e.g., the event was fired on
2800 // a popup but the rootFrame is the document root.
2801 if (rootWidget
!= aEvent
->mWidget
) {
2802 MOZ_ASSERT(aEvent
->mWidget
, "The event must have the widget");
2803 nsView
* view
= nsView::GetViewFor(aEvent
->mWidget
);
2804 NS_ENSURE_TRUE(view
, NS_ERROR_FAILURE
);
2805 rootFrame
= view
->GetFrame();
2806 NS_ENSURE_TRUE(rootFrame
, NS_ERROR_FAILURE
);
2807 rootWidget
= rootFrame
->GetNearestWidget();
2808 NS_ENSURE_TRUE(rootWidget
, NS_ERROR_FAILURE
);
2811 WidgetQueryContentEvent
queryCharAtPointOnRootWidgetEvent(
2812 true, eQueryCharacterAtPoint
, rootWidget
);
2813 queryCharAtPointOnRootWidgetEvent
.mUseNativeLineBreak
=
2814 aEvent
->mUseNativeLineBreak
;
2815 queryCharAtPointOnRootWidgetEvent
.mRefPoint
= aEvent
->mRefPoint
;
2816 if (rootWidget
!= aEvent
->mWidget
) {
2817 queryCharAtPointOnRootWidgetEvent
.mRefPoint
+=
2818 aEvent
->mWidget
->WidgetToScreenOffset() -
2819 rootWidget
->WidgetToScreenOffset();
2821 nsPoint ptInRoot
= nsLayoutUtils::GetEventCoordinatesRelativeTo(
2822 &queryCharAtPointOnRootWidgetEvent
, RelativeTo
{rootFrame
});
2824 nsIFrame
* targetFrame
=
2825 nsLayoutUtils::GetFrameForPoint(RelativeTo
{rootFrame
}, ptInRoot
);
2826 if (!targetFrame
|| !targetFrame
->GetContent() ||
2827 !targetFrame
->GetContent()->IsInclusiveDescendantOf(mRootElement
)) {
2828 // There is no character at the point.
2829 MOZ_ASSERT(aEvent
->Succeeded());
2832 nsPoint ptInTarget
= ptInRoot
+ rootFrame
->GetOffsetToCrossDoc(targetFrame
);
2833 int32_t rootAPD
= rootFrame
->PresContext()->AppUnitsPerDevPixel();
2834 int32_t targetAPD
= targetFrame
->PresContext()->AppUnitsPerDevPixel();
2835 ptInTarget
= ptInTarget
.ScaleToOtherAppUnits(rootAPD
, targetAPD
);
2837 nsIFrame::ContentOffsets tentativeCaretOffsets
=
2838 targetFrame
->GetContentOffsetsFromPoint(ptInTarget
);
2839 if (!tentativeCaretOffsets
.content
||
2840 !tentativeCaretOffsets
.content
->IsInclusiveDescendantOf(mRootElement
)) {
2841 // There is no character nor tentative caret point at the point.
2842 MOZ_ASSERT(aEvent
->Succeeded());
2846 uint32_t tentativeCaretOffset
= 0;
2847 if (NS_WARN_IF(NS_FAILED(GetFlatTextLengthInRange(
2848 NodePosition(mRootElement
, 0u), NodePosition(tentativeCaretOffsets
),
2849 mRootElement
, &tentativeCaretOffset
, GetLineBreakType(aEvent
))))) {
2850 return NS_ERROR_FAILURE
;
2853 aEvent
->mReply
->mTentativeCaretOffset
.emplace(tentativeCaretOffset
);
2854 if (!targetFrame
->IsTextFrame()) {
2855 // There is no character at the point but there is tentative caret point.
2856 MOZ_ASSERT(aEvent
->Succeeded());
2860 nsTextFrame
* textframe
= static_cast<nsTextFrame
*>(targetFrame
);
2861 nsIFrame::ContentOffsets contentOffsets
=
2862 textframe
->GetCharacterOffsetAtFramePoint(ptInTarget
);
2863 NS_ENSURE_TRUE(contentOffsets
.content
, NS_ERROR_FAILURE
);
2864 uint32_t offset
= 0;
2865 if (NS_WARN_IF(NS_FAILED(GetFlatTextLengthInRange(
2866 NodePosition(mRootElement
, 0u), NodePosition(contentOffsets
),
2867 mRootElement
, &offset
, GetLineBreakType(aEvent
))))) {
2868 return NS_ERROR_FAILURE
;
2871 WidgetQueryContentEvent
queryTextRectEvent(true, eQueryTextRect
,
2873 WidgetQueryContentEvent::Options
options(*aEvent
);
2874 queryTextRectEvent
.InitForQueryTextRect(offset
, 1, options
);
2875 if (NS_WARN_IF(NS_FAILED(OnQueryTextRect(&queryTextRectEvent
))) ||
2876 NS_WARN_IF(queryTextRectEvent
.Failed())) {
2877 return NS_ERROR_FAILURE
;
2880 aEvent
->mReply
->mOffsetAndData
=
2881 std::move(queryTextRectEvent
.mReply
->mOffsetAndData
);
2882 aEvent
->mReply
->mRect
= queryTextRectEvent
.mReply
->mRect
;
2884 MOZ_ASSERT(aEvent
->Succeeded());
2888 nsresult
ContentEventHandler::OnQueryDOMWidgetHittest(
2889 WidgetQueryContentEvent
* aEvent
) {
2890 NS_ASSERTION(aEvent
, "aEvent must not be null");
2892 nsresult rv
= InitBasic();
2893 if (NS_FAILED(rv
)) {
2897 aEvent
->mReply
->mWidgetIsHit
= false;
2899 NS_ENSURE_TRUE(aEvent
->mWidget
, NS_ERROR_FAILURE
);
2901 PresShell
* presShell
= mDocument
->GetPresShell();
2902 NS_ENSURE_TRUE(presShell
, NS_ERROR_FAILURE
);
2903 nsIFrame
* docFrame
= presShell
->GetRootFrame();
2904 NS_ENSURE_TRUE(docFrame
, NS_ERROR_FAILURE
);
2906 LayoutDeviceIntPoint eventLoc
=
2907 aEvent
->mRefPoint
+ aEvent
->mWidget
->WidgetToScreenOffset();
2908 CSSIntRect docFrameRect
= docFrame
->GetScreenRect();
2909 CSSIntPoint
eventLocCSS(
2910 docFrame
->PresContext()->DevPixelsToIntCSSPixels(eventLoc
.x
) -
2912 docFrame
->PresContext()->DevPixelsToIntCSSPixels(eventLoc
.y
) -
2915 if (Element
* contentUnderMouse
= mDocument
->ElementFromPointHelper(
2916 eventLocCSS
.x
, eventLocCSS
.y
, false, false, ViewportType::Visual
)) {
2917 if (nsIFrame
* targetFrame
= contentUnderMouse
->GetPrimaryFrame()) {
2918 if (aEvent
->mWidget
== targetFrame
->GetNearestWidget()) {
2919 aEvent
->mReply
->mWidgetIsHit
= true;
2924 MOZ_ASSERT(aEvent
->Succeeded());
2929 nsresult
ContentEventHandler::GetFlatTextLengthInRange(
2930 const NodePosition
& aStartPosition
, const NodePosition
& aEndPosition
,
2931 const Element
* aRootElement
, uint32_t* aLength
,
2932 LineBreakType aLineBreakType
, bool aIsRemovingNode
/* = false */) {
2933 if (NS_WARN_IF(!aRootElement
) || NS_WARN_IF(!aStartPosition
.IsSet()) ||
2934 NS_WARN_IF(!aEndPosition
.IsSet()) || NS_WARN_IF(!aLength
)) {
2935 return NS_ERROR_INVALID_ARG
;
2938 if (aStartPosition
== aEndPosition
) {
2943 PreContentIterator preOrderIter
;
2945 // Working with ContentIterator, we may need to adjust the end position for
2946 // including it forcibly.
2947 NodePosition
endPosition(aEndPosition
);
2949 // This may be called for retrieving the text of removed nodes. Even in this
2950 // case, the node thinks it's still in the tree because UnbindFromTree() will
2951 // be called after here. However, the node was already removed from the
2952 // array of children of its parent. So, be careful to handle this case.
2953 if (aIsRemovingNode
) {
2954 DebugOnly
<nsIContent
*> parent
= aStartPosition
.Container()->GetParent();
2957 parent
->ComputeIndexOf(aStartPosition
.Container()).isNothing(),
2958 "At removing the node, the node shouldn't be in the array of children "
2960 MOZ_ASSERT(aStartPosition
.Container() == endPosition
.Container(),
2961 "At removing the node, start and end node should be same");
2962 MOZ_ASSERT(*aStartPosition
.Offset(
2963 NodePosition::OffsetFilter::kValidOrInvalidOffsets
) == 0,
2964 "When the node is being removed, the start offset should be 0");
2966 static_cast<uint32_t>(*endPosition
.Offset(
2967 NodePosition::OffsetFilter::kValidOrInvalidOffsets
)) ==
2968 endPosition
.Container()->GetChildCount(),
2969 "When the node is being removed, the end offset should be child count");
2970 nsresult rv
= preOrderIter
.Init(aStartPosition
.Container());
2971 if (NS_WARN_IF(NS_FAILED(rv
))) {
2975 RawRange prevRawRange
;
2976 nsresult rv
= prevRawRange
.SetStart(aStartPosition
.AsRaw());
2977 if (NS_WARN_IF(NS_FAILED(rv
))) {
2981 // When the end position is immediately after non-root element's open tag,
2982 // we need to include a line break caused by the open tag.
2983 if (endPosition
.Container() != aRootElement
&&
2984 endPosition
.IsImmediatelyAfterOpenTag()) {
2985 if (endPosition
.Container()->HasChildren()) {
2986 // When the end node has some children, move the end position to before
2987 // the open tag of its first child.
2988 nsINode
* firstChild
= endPosition
.Container()->GetFirstChild();
2989 if (NS_WARN_IF(!firstChild
)) {
2990 return NS_ERROR_FAILURE
;
2992 endPosition
= NodePositionBefore(firstChild
, 0u);
2994 // When the end node is empty, move the end position after the node.
2995 nsIContent
* parentContent
= endPosition
.Container()->GetParent();
2996 if (NS_WARN_IF(!parentContent
)) {
2997 return NS_ERROR_FAILURE
;
2999 Maybe
<uint32_t> indexInParent
=
3000 parentContent
->ComputeIndexOf(endPosition
.Container());
3001 if (MOZ_UNLIKELY(NS_WARN_IF(indexInParent
.isNothing()))) {
3002 return NS_ERROR_FAILURE
;
3004 MOZ_ASSERT(*indexInParent
!= UINT32_MAX
);
3005 endPosition
= NodePositionBefore(parentContent
, *indexInParent
+ 1u);
3009 if (endPosition
.IsSetAndValid()) {
3010 // Offset is within node's length; set end of range to that offset
3011 rv
= prevRawRange
.SetEnd(endPosition
.AsRaw());
3012 if (NS_WARN_IF(NS_FAILED(rv
))) {
3015 rv
= preOrderIter
.Init(prevRawRange
.Start().AsRaw(),
3016 prevRawRange
.End().AsRaw());
3017 if (NS_WARN_IF(NS_FAILED(rv
))) {
3020 } else if (endPosition
.Container() != aRootElement
) {
3021 // Offset is past node's length; set end of range to end of node
3022 rv
= prevRawRange
.SetEndAfter(endPosition
.Container());
3023 if (NS_WARN_IF(NS_FAILED(rv
))) {
3026 rv
= preOrderIter
.Init(prevRawRange
.Start().AsRaw(),
3027 prevRawRange
.End().AsRaw());
3028 if (NS_WARN_IF(NS_FAILED(rv
))) {
3032 // Offset is past the root node; set end of range to end of root node
3033 rv
= preOrderIter
.Init(const_cast<Element
*>(aRootElement
));
3034 if (NS_WARN_IF(NS_FAILED(rv
))) {
3041 for (; !preOrderIter
.IsDone(); preOrderIter
.Next()) {
3042 nsINode
* node
= preOrderIter
.GetCurrentNode();
3043 if (NS_WARN_IF(!node
)) {
3046 if (!node
->IsContent()) {
3049 nsIContent
* content
= node
->AsContent();
3051 if (const Text
* textNode
= Text::FromNode(content
)) {
3052 // Note: our range always starts from offset 0
3053 if (node
== endPosition
.Container()) {
3054 // NOTE: We should have an offset here, as endPosition.Container() is a
3055 // nsINode::eTEXT, which always has an offset.
3056 *aLength
+= GetTextLength(
3057 *textNode
, aLineBreakType
,
3058 *endPosition
.Offset(
3059 NodePosition::OffsetFilter::kValidOrInvalidOffsets
));
3061 *aLength
+= GetTextLength(*textNode
, aLineBreakType
);
3063 } else if (ShouldBreakLineBefore(*content
, aRootElement
)) {
3064 // If the start position is start of this node but doesn't include the
3065 // open tag, don't append the line break length.
3066 if (node
== aStartPosition
.Container() &&
3067 !aStartPosition
.IsBeforeOpenTag()) {
3070 // If the end position is before the open tag, don't append the line
3072 if (node
== endPosition
.Container() && endPosition
.IsBeforeOpenTag()) {
3075 *aLength
+= GetBRLength(aLineBreakType
);
3081 nsresult
ContentEventHandler::GetStartOffset(const RawRange
& aRawRange
,
3083 LineBreakType aLineBreakType
) {
3084 // To match the "no skip start" hack in ContentIterator::Init, when range
3085 // offset is 0 and the range node is not a container, we have to assume the
3086 // range _includes_ the node, which means the start offset should _not_
3087 // include the node.
3089 // For example, for this content: <br>abc, and range (<br>, 0)-("abc", 1), the
3090 // range includes the linebreak from <br>, so the start offset should _not_
3091 // include <br>, and the start offset should be 0.
3093 // However, for this content: <p/>abc, and range (<p>, 0)-("abc", 1), the
3094 // range does _not_ include the linebreak from <p> because <p> is a container,
3095 // so the start offset _should_ include <p>, and the start offset should be 1.
3097 nsINode
* startNode
= aRawRange
.GetStartContainer();
3098 bool startIsContainer
= true;
3099 if (startNode
->IsHTMLElement()) {
3100 nsAtom
* name
= startNode
->NodeInfo()->NameAtom();
3102 nsHTMLElement::IsContainer(nsHTMLTags::AtomTagToId(name
));
3104 const NodePosition
& startPos
=
3105 startIsContainer
? NodePosition(startNode
, aRawRange
.StartOffset())
3106 : NodePositionBefore(startNode
, aRawRange
.StartOffset());
3107 return GetFlatTextLengthInRange(NodePosition(mRootElement
, 0u), startPos
,
3108 mRootElement
, aOffset
, aLineBreakType
);
3111 nsresult
ContentEventHandler::AdjustCollapsedRangeMaybeIntoTextNode(
3112 RawRange
& aRawRange
) {
3113 MOZ_ASSERT(aRawRange
.Collapsed());
3115 if (!aRawRange
.Collapsed()) {
3116 return NS_ERROR_INVALID_ARG
;
3119 const RangeBoundary
& startPoint
= aRawRange
.Start();
3120 if (NS_WARN_IF(!startPoint
.IsSet())) {
3121 return NS_ERROR_INVALID_ARG
;
3124 // If the node does not have children like a text node, we don't need to
3125 // modify aRawRange.
3126 if (!startPoint
.Container()->HasChildren()) {
3130 // If the container is not a text node but it has a text node at the offset,
3131 // we should adjust the range into the text node.
3132 // NOTE: This is emulating similar situation of EditorBase.
3133 if (startPoint
.IsStartOfContainer()) {
3134 // If the range is the start of the container, adjusted the range to the
3135 // start of the first child.
3136 if (!startPoint
.Container()->GetFirstChild()->IsText()) {
3139 nsresult rv
= aRawRange
.CollapseTo(
3140 RawRangeBoundary(startPoint
.Container()->GetFirstChild(), 0u));
3141 if (NS_WARN_IF(NS_FAILED(rv
))) {
3147 if (!startPoint
.IsSetAndValid()) {
3151 // If start of the range is next to a child node, adjust the range to the
3152 // end of the previous child (i.e., startPoint.Ref()).
3153 if (!startPoint
.Ref()->IsText()) {
3156 nsresult rv
= aRawRange
.CollapseTo(
3157 RawRangeBoundary(startPoint
.Ref(), startPoint
.Ref()->Length()));
3158 if (NS_WARN_IF(NS_FAILED(rv
))) {
3164 nsresult
ContentEventHandler::ConvertToRootRelativeOffset(nsIFrame
* aFrame
,
3166 NS_ASSERTION(aFrame
, "aFrame must not be null");
3168 nsPresContext
* thisPC
= aFrame
->PresContext();
3169 nsPresContext
* rootPC
= thisPC
->GetRootPresContext();
3170 if (NS_WARN_IF(!rootPC
)) {
3171 return NS_ERROR_FAILURE
;
3173 nsIFrame
* rootFrame
= rootPC
->PresShell()->GetRootFrame();
3174 if (NS_WARN_IF(!rootFrame
)) {
3175 return NS_ERROR_FAILURE
;
3178 aRect
= nsLayoutUtils::TransformFrameRectToAncestor(aFrame
, aRect
, rootFrame
);
3180 // TransformFrameRectToAncestor returned the rect in the ancestor's appUnits,
3181 // but we want it in aFrame's units (in case of different full-zoom factors),
3183 aRect
= aRect
.ScaleToOtherAppUnitsRoundOut(rootPC
->AppUnitsPerDevPixel(),
3184 thisPC
->AppUnitsPerDevPixel());
3189 static void AdjustRangeForSelection(const Element
* aRootElement
,
3191 Maybe
<uint32_t>* aNodeOffset
) {
3192 nsINode
* node
= *aNode
;
3193 Maybe
<uint32_t> nodeOffset
= *aNodeOffset
;
3194 if (aRootElement
== node
|| NS_WARN_IF(!node
->GetParent()) ||
3199 // When the offset is at the end of the text node, set it to after the
3200 // text node, to make sure the caret is drawn on a new line when the last
3201 // character of the text node is '\n' in <textarea>.
3202 const uint32_t textLength
= node
->AsContent()->TextLength();
3203 MOZ_ASSERT(nodeOffset
.isNothing() || *nodeOffset
<= textLength
,
3204 "Offset is past length of text node");
3205 if (nodeOffset
.isNothing() || *nodeOffset
!= textLength
) {
3209 Element
* rootParentElement
= aRootElement
->GetParentElement();
3210 if (NS_WARN_IF(!rootParentElement
)) {
3213 // If the root node is not an anonymous div of <textarea>, we don't need to
3214 // do this hack. If you did this, ContentEventHandler couldn't distinguish
3215 // if the range includes open tag of the next node in some cases, e.g.,
3216 // textNode]<p></p> vs. textNode<p>]</p>
3217 if (!rootParentElement
->IsHTMLElement(nsGkAtoms::textarea
)) {
3221 // If the node is being removed from its parent, it holds the ex-parent,
3222 // but the parent have already removed the child from its child chain.
3223 // Therefore `ComputeIndexOf` may fail, but I don't want to make Beta/Nightly
3224 // crash at accessing `Maybe::operator*` so that here checks `isSome`, but
3225 // crashing only in debug builds may help to debug something complicated
3226 // situation, therefore, `MOZ_ASSERT` is put here.
3227 *aNode
= node
->GetParent();
3228 Maybe
<uint32_t> index
= (*aNode
)->ComputeIndexOf(node
);
3229 MOZ_ASSERT(index
.isSome());
3230 if (index
.isSome()) {
3231 MOZ_ASSERT(*index
!= UINT32_MAX
);
3232 *aNodeOffset
= Some(*index
+ 1u);
3234 *aNodeOffset
= Some(0u);
3238 nsresult
ContentEventHandler::OnSelectionEvent(WidgetSelectionEvent
* aEvent
) {
3239 aEvent
->mSucceeded
= false;
3241 // Get selection to manipulate
3242 // XXX why do we need to get them from ISM? This method should work fine
3244 nsresult rv
= IMEStateManager::GetFocusSelectionAndRootElement(
3245 getter_AddRefs(mSelection
), getter_AddRefs(mRootElement
));
3246 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
3247 NS_ENSURE_SUCCESS(rv
, rv
);
3250 NS_ENSURE_SUCCESS(rv
, rv
);
3253 // Get range from offset and length
3255 rv
= SetRawRangeFromFlatTextOffset(&rawRange
, aEvent
->mOffset
,
3256 aEvent
->mLength
, GetLineBreakType(aEvent
),
3257 aEvent
->mExpandToClusterBoundary
);
3258 NS_ENSURE_SUCCESS(rv
, rv
);
3260 nsINode
* startNode
= rawRange
.GetStartContainer();
3261 nsINode
* endNode
= rawRange
.GetEndContainer();
3262 Maybe
<uint32_t> startNodeOffset
= Some(rawRange
.StartOffset());
3263 Maybe
<uint32_t> endNodeOffset
= Some(rawRange
.EndOffset());
3264 AdjustRangeForSelection(mRootElement
, &startNode
, &startNodeOffset
);
3265 AdjustRangeForSelection(mRootElement
, &endNode
, &endNodeOffset
);
3266 if (NS_WARN_IF(!startNode
) || NS_WARN_IF(!endNode
) ||
3267 NS_WARN_IF(startNodeOffset
.isNothing()) ||
3268 NS_WARN_IF(endNodeOffset
.isNothing())) {
3269 return NS_ERROR_UNEXPECTED
;
3272 if (aEvent
->mReversed
) {
3273 nsCOMPtr
<nsINode
> startNodeStrong(startNode
);
3274 nsCOMPtr
<nsINode
> endNodeStrong(endNode
);
3276 MOZ_KnownLive(mSelection
)
3277 ->SetBaseAndExtentInLimiter(*endNodeStrong
, *endNodeOffset
,
3278 *startNodeStrong
, *startNodeOffset
, error
);
3279 if (NS_WARN_IF(error
.Failed())) {
3280 return error
.StealNSResult();
3283 nsCOMPtr
<nsINode
> startNodeStrong(startNode
);
3284 nsCOMPtr
<nsINode
> endNodeStrong(endNode
);
3286 MOZ_KnownLive(mSelection
)
3287 ->SetBaseAndExtentInLimiter(*startNodeStrong
, *startNodeOffset
,
3288 *endNodeStrong
, *endNodeOffset
, error
);
3289 if (NS_WARN_IF(error
.Failed())) {
3290 return error
.StealNSResult();
3294 // `ContentEventHandler` is a `MOZ_STACK_CLASS`, so `mSelection` is known to
3296 MOZ_KnownLive(mSelection
)
3297 ->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
3298 ScrollAxis(), ScrollAxis(), 0);
3299 aEvent
->mSucceeded
= true;
3303 nsRect
ContentEventHandler::FrameRelativeRect::RectRelativeTo(
3304 nsIFrame
* aDestFrame
) const {
3305 if (!mBaseFrame
|| NS_WARN_IF(!aDestFrame
)) {
3309 if (NS_WARN_IF(aDestFrame
->PresContext() != mBaseFrame
->PresContext())) {
3313 if (aDestFrame
== mBaseFrame
) {
3317 nsIFrame
* rootFrame
= mBaseFrame
->PresShell()->GetRootFrame();
3318 nsRect baseFrameRectInRootFrame
= nsLayoutUtils::TransformFrameRectToAncestor(
3319 mBaseFrame
, nsRect(), rootFrame
);
3320 nsRect destFrameRectInRootFrame
= nsLayoutUtils::TransformFrameRectToAncestor(
3321 aDestFrame
, nsRect(), rootFrame
);
3322 nsPoint difference
=
3323 destFrameRectInRootFrame
.TopLeft() - baseFrameRectInRootFrame
.TopLeft();
3324 return mRect
- difference
;
3327 } // namespace mozilla