Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / events / ContentEventHandler.cpp
blob57351e1da910088290151d4e26d784cd1e191765
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"
26 #include "nsCaret.h"
27 #include "nsCOMPtr.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"
34 #include "nsIFrame.h"
35 #include "nsLayoutUtils.h"
36 #include "nsPresContext.h"
37 #include "nsQueryObject.h"
38 #include "nsRange.h"
39 #include "nsTextFragment.h"
40 #include "nsTextFrame.h"
41 #include "nsView.h"
42 #include "mozilla/ViewportUtils.h"
44 #include <algorithm>
46 // Work around conflicting define in rpcndr.h
47 #if defined(small)
48 # undef small
49 #endif // defined(small)
51 #if defined(XP_WIN) && !defined(NIGHTLY_BUILD)
52 # define TRANSLATE_NEW_LINES
53 #endif
55 namespace mozilla {
57 using namespace dom;
58 using namespace widget;
60 /******************************************************************/
61 /* ContentEventHandler::RawRange */
62 /******************************************************************/
64 void ContentEventHandler::RawRange::AssertStartIsBeforeOrEqualToEnd() {
65 MOZ_ASSERT(
66 *nsContentUtils::ComparePoints(
67 mStart.Container(),
68 *mStart.Offset(NodePosition::OffsetFilter::kValidOrInvalidOffsets),
69 mEnd.Container(),
70 *mEnd.Offset(NodePosition::OffsetFilter::kValidOrInvalidOffsets)) <=
71 0);
74 nsresult ContentEventHandler::RawRange::SetStart(
75 const RawRangeBoundary& aStart) {
76 nsINode* newRoot = RangeUtils::ComputeRootNode(aStart.Container());
77 if (!newRoot) {
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) {
87 mRoot = newRoot;
88 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
89 mEnd.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
90 return NS_OK;
93 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
94 AssertStartIsBeforeOrEqualToEnd();
95 return NS_OK;
98 nsresult ContentEventHandler::RawRange::SetEnd(const RawRangeBoundary& aEnd) {
99 nsINode* newRoot = RangeUtils::ComputeRootNode(aEnd.Container());
100 if (!newRoot) {
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) {
110 mRoot = newRoot;
111 mStart.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
112 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
113 return NS_OK;
116 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
117 AssertStartIsBeforeOrEqualToEnd();
118 return NS_OK;
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());
134 if (!newStartRoot) {
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);
150 return NS_OK;
153 nsINode* newEndRoot = RangeUtils::ComputeRootNode(aEnd.Container());
154 if (!newEndRoot) {
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) {
163 mRoot = newEndRoot;
164 mStart.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
165 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
166 return NS_OK;
169 // Otherwise, set the range as specified.
170 mRoot = newStartRoot;
171 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
172 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
173 AssertStartIsBeforeOrEqualToEnd();
174 return NS_OK;
177 nsresult ContentEventHandler::RawRange::SelectNodeContents(
178 const nsINode* aNodeToSelectContents) {
179 nsINode* const newRoot =
180 RangeUtils::ComputeRootNode(const_cast<nsINode*>(aNodeToSelectContents));
181 if (!newRoot) {
182 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
184 mRoot = newRoot;
185 mStart = RangeBoundary(const_cast<nsINode*>(aNodeToSelectContents), nullptr);
186 mEnd = RangeBoundary(const_cast<nsINode*>(aNodeToSelectContents),
187 aNodeToSelectContents->GetLastChild());
188 return NS_OK;
191 /******************************************************************/
192 /* ContentEventHandler */
193 /******************************************************************/
195 // NOTE
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
205 // parent.
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
209 // parent + 1.
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.
219 // 2. End of range:
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
238 // element)
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);
259 if (aRequireFlush) {
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);
264 return NS_OK;
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.
278 mRootElement =
279 Element::FromNodeOrNull(aNormalSelection.GetAncestorLimiter());
280 if (!mRootElement) {
281 mRootElement = mDocument->GetRootElement();
282 if (NS_WARN_IF(!mRootElement)) {
283 return NS_ERROR_NOT_AVAILABLE;
286 return NS_OK;
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();
315 mRootElement =
316 Element::FromNodeOrNull(startNode->GetSelectionRootContent(presShell));
317 if (NS_WARN_IF(!mRootElement)) {
318 return NS_ERROR_FAILURE;
321 return NS_OK;
324 nsresult ContentEventHandler::InitCommon(EventMessage aEventMessage,
325 SelectionType aSelectionType,
326 bool aRequireFlush) {
327 if (mSelection && mSelection->Type() == aSelectionType) {
328 return NS_OK;
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;
354 } else {
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))) {
363 return rv;
366 if (mSelection->RangeCount()) {
367 mFirstSelectedRawRange.SetStartAndEnd(mSelection->GetRangeAt(0));
368 return NS_OK;
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());
376 return NS_OK;
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;
385 return NS_OK;
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;
407 nsresult rv =
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);
419 if (composition) {
420 uint32_t compositionStart = composition->NativeOffsetOfStartComposition();
421 if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) {
422 return NS_ERROR_FAILURE;
424 } else {
425 LineBreakType lineBreakType = GetLineBreakType(aEvent);
426 uint32_t selectionStart = 0;
427 rv = GetStartOffset(mFirstSelectedRawRange, &selectionStart,
428 lineBreakType);
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();
448 nsRect r;
449 nsIFrame* frame = nsCaret::GetGeometry(mSelection, &r);
450 if (!frame) {
451 frame = mRootElement->GetPrimaryFrame();
452 if (NS_WARN_IF(!frame)) {
453 return NS_ERROR_FAILURE;
456 aEvent->mReply->mFocusedWidget = frame->GetNearestWidget();
458 return NS_OK;
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;
469 return NS_OK;
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);
508 return NS_OK;
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);
528 #endif
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) {
551 uint32_t count = 0;
552 const auto* end = aString.EndReading();
553 for (const auto* iter = aString.BeginReading(); iter < end; ++iter) {
554 if (*iter == '\n') {
555 count++;
558 return count;
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());
568 if (!length) {
569 return 0;
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) {
582 MOZ_ASSERT(
583 (aNativeLength == UINT32_MAX || aNativeLength <= aString.Length() * 2),
584 "aNativeLength is unexpected value");
585 uint32_t count = 0;
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) {
590 if (*iter == '\n') {
591 count++;
592 nativeOffset++;
595 return count;
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();
602 if (!xpLength) {
603 return 0;
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);
612 #endif
614 /* static */
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) {
621 return 0;
623 return GetTextLength(aTextNode, LINE_BREAK_TYPE_NATIVE, aEndOffset) -
624 GetTextLength(aTextNode, LINE_BREAK_TYPE_NATIVE, aStartOffset);
627 /* static */
628 uint32_t ContentEventHandler::GetNativeTextLength(const Text& aTextNode,
629 uint32_t aMaxLength) {
630 return GetTextLength(aTextNode, LINE_BREAK_TYPE_NATIVE, aMaxLength);
633 /* static inline */
634 uint32_t ContentEventHandler::GetBRLength(LineBreakType aLineBreakType) {
635 #if defined(TRANSLATE_NEW_LINES)
636 // Length of \r\n
637 return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1;
638 #else
639 return 1;
640 #endif
643 /* static */
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)
654 : 0;
655 #else
656 // On other platforms, the native and XP newlines are the same.
658 #endif
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);
672 #else
673 // On other platforms, the native and XP newlines are the same.
674 return aNativeOffset;
675 #endif
678 /* static */
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);
686 #else
687 // On other platforms, the native and XP newlines are the same.
689 #endif
690 return aText.Length() + textLengthDifference;
693 /* static */
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) {
698 return false;
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()) {
706 return false;
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)) {
729 return false;
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());
743 RawRange rawRange;
744 nsresult rv = rawRange.SelectNodeContents(aElement);
745 if (NS_WARN_IF(NS_FAILED(rv))) {
746 return 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()) {
757 return NS_OK;
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);
770 return NS_OK;
773 PreContentIterator preOrderIter;
774 nsresult rv =
775 preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
776 if (NS_WARN_IF(NS_FAILED(rv))) {
777 return rv;
779 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
780 nsINode* node = preOrderIter.GetCurrentNode();
781 if (NS_WARN_IF(!node)) {
782 break;
784 if (!node->IsContent()) {
785 continue;
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());
794 } else {
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);
804 return NS_OK;
807 static FontRange* AppendFontRange(nsTArray<FontRange>& aFontRanges,
808 uint32_t aBaseOffset) {
809 FontRange* fontRange = aFontRanges.AppendElement();
810 fontRange->mStartOffset = aBaseOffset;
811 return fontRange;
814 /* static */
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;
823 /* static */
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();
831 if (!frame) {
832 // It is a non-rendered content, create an empty range for it.
833 AppendFontRange(aFontRanges, aBaseOffset);
834 return;
837 uint32_t baseOffset = aBaseOffset;
838 #ifdef DEBUG
840 nsTextFrame* text = do_QueryFrame(frame);
841 MOZ_ASSERT(text, "Not a text frame");
843 #endif
844 auto* curr = static_cast<nsTextFrame*>(frame);
845 while (curr) {
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();
852 continue;
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()),
863 aXPEndOffset);
864 next =
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) {
880 break;
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(
897 frame);
898 float scale =
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,
908 aLineBreakType);
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,
916 aLineBreakType);
919 curr = next;
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()) {
929 return NS_OK;
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;
941 nsresult rv =
942 preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
943 if (NS_WARN_IF(NS_FAILED(rv))) {
944 return rv;
946 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
947 nsINode* node = preOrderIter.GetCurrentNode();
948 if (NS_WARN_IF(!node)) {
949 break;
951 if (!node->IsContent()) {
952 continue;
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,
964 aLineBreakType);
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];
975 nsAutoCString name;
976 if (fontName) {
977 fontName->AppendToString(name, false);
979 AppendUTF8toUTF16(name, fontRange->mFontName);
981 ParentLayerToScreenScale2D cumulativeResolution =
982 ParentLayerToParentLayerScale(
983 frame->PresShell()->GetCumulativeResolution()) *
984 nsLayoutUtils::
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;
999 return NS_OK;
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()) {
1007 return NS_OK;
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);
1018 if (frame) {
1019 auto [startOffset, endOffset] = frame->GetOffsets();
1020 if (*aXPOffset == static_cast<uint32_t>(startOffset) ||
1021 *aXPOffset == static_cast<uint32_t>(endOffset)) {
1022 return NS_OK;
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
1033 // direction.
1034 nsTextFrame::PeekOffsetCharacterOptions options;
1035 options.mRespectClusters = true;
1036 options.mIgnoreUserStyleAll = true;
1037 if (textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame, options) ==
1038 nsIFrame::FOUND) {
1039 *aXPOffset = startOffset + newOffsetInFrame;
1040 return NS_OK;
1044 // If the frame isn't available, we only can check surrogate pair...
1045 if (aTextNode.TextFragment().IsLowSurrogateFollowingHighSurrogateAt(
1046 *aXPOffset)) {
1047 *aXPOffset += aForward ? 1 : -1;
1049 return NS_OK;
1052 nsresult ContentEventHandler::SetRawRangeFromFlatTextOffset(
1053 RawRange* aRawRange, uint32_t aOffset, uint32_t aLength,
1054 LineBreakType aLineBreakType, bool aExpandToClusterBoundaries,
1055 uint32_t* aNewOffset, Text** aLastTextNode) {
1056 if (aNewOffset) {
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))) {
1067 return rv;
1071 PreContentIterator preOrderIter;
1072 nsresult rv = preOrderIter.Init(mRootElement);
1073 if (NS_WARN_IF(NS_FAILED(rv))) {
1074 return 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)) {
1083 break;
1085 // FYI: mRootElement shouldn't cause any text. So, we can skip it simply.
1086 if (node == mRootElement || !node->IsContent()) {
1087 continue;
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)
1101 : 0);
1102 if (!textLength) {
1103 continue;
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;
1120 nsresult rv =
1121 ExpandToClusterBoundary(*contentAsText, false, &xpOffset);
1122 if (NS_WARN_IF(NS_FAILED(rv))) {
1123 return rv;
1125 if (aNewOffset) {
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);
1156 } else {
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))) {
1166 return rv;
1168 startSet = true;
1170 if (!aLength) {
1171 rv = aRawRange->SetEnd(startNode, *startNodeOffset);
1172 if (NS_WARN_IF(NS_FAILED(rv))) {
1173 return rv;
1175 return NS_OK;
1179 // When the end offset is in the content, the node is the end node of the
1180 // range.
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;
1198 } else {
1199 xpOffset = xpOffsetCurrent;
1203 if (aExpandToClusterBoundaries) {
1204 nsresult rv =
1205 ExpandToClusterBoundary(*contentAsText, true, &xpOffset);
1206 if (NS_WARN_IF(NS_FAILED(rv))) {
1207 return 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))) {
1213 return rv;
1215 return NS_OK;
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.
1223 MOZ_ASSERT(false,
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))) {
1234 return rv;
1236 return NS_OK;
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))) {
1252 return rv;
1254 return NS_OK;
1257 offset += textLength;
1260 if (!startSet) {
1261 if (!offset) {
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))) {
1269 return rv;
1271 if (!aLength) {
1272 rv = aRawRange->SetEnd(mRootElement, 0);
1273 if (NS_WARN_IF(NS_FAILED(rv))) {
1274 return rv;
1276 return NS_OK;
1278 } else {
1279 // Rule #1.5: [</root>
1280 rv = aRawRange->SetStart(mRootElement, mRootElement->GetChildCount());
1281 if (NS_WARN_IF(NS_FAILED(rv))) {
1282 return rv;
1285 if (aNewOffset) {
1286 *aNewOffset = offset;
1289 // Rule #2.5: ]</root>
1290 rv = aRawRange->SetEnd(mRootElement, mRootElement->GetChildCount());
1291 if (NS_WARN_IF(NS_FAILED(rv))) {
1292 return rv;
1294 return NS_OK;
1297 /* static */
1298 LineBreakType ContentEventHandler::GetLineBreakType(
1299 WidgetQueryContentEvent* aEvent) {
1300 return GetLineBreakType(aEvent->mUseNativeLineBreak);
1303 /* static */
1304 LineBreakType ContentEventHandler::GetLineBreakType(
1305 WidgetSelectionEvent* aEvent) {
1306 return GetLineBreakType(aEvent->mUseNativeLineBreak);
1309 /* static */
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);
1320 break;
1321 case eQueryTextContent:
1322 rv = OnQueryTextContent(aEvent);
1323 break;
1324 case eQueryCaretRect:
1325 rv = OnQueryCaretRect(aEvent);
1326 break;
1327 case eQueryTextRect:
1328 rv = OnQueryTextRect(aEvent);
1329 break;
1330 case eQueryTextRectArray:
1331 rv = OnQueryTextRectArray(aEvent);
1332 break;
1333 case eQueryEditorRect:
1334 rv = OnQueryEditorRect(aEvent);
1335 break;
1336 case eQueryContentState:
1337 rv = OnQueryContentState(aEvent);
1338 break;
1339 case eQuerySelectionAsTransferable:
1340 rv = OnQuerySelectionAsTransferable(aEvent);
1341 break;
1342 case eQueryCharacterAtPoint:
1343 rv = OnQueryCharacterAtPoint(aEvent);
1344 break;
1345 case eQueryDOMWidgetHittest:
1346 rv = OnQueryDOMWidgetHittest(aEvent);
1347 break;
1348 default:
1349 break;
1351 if (NS_FAILED(rv)) {
1352 aEvent->mReply.reset(); // Mark the query failed.
1353 return rv;
1356 MOZ_ASSERT(aEvent->Succeeded());
1357 return NS_OK;
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,
1364 bool aHint) {
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
1372 // a primary frame.
1373 if (!frame) {
1374 return nullptr;
1376 int32_t childNodeOffset = 0;
1377 nsIFrame* returnFrame = nullptr;
1378 nsresult rv = frame->GetChildFrameContainingOffset(
1379 aNodeOffset, aHint, &childNodeOffset, &returnFrame);
1380 if (NS_FAILED(rv)) {
1381 return Err(rv);
1383 return returnFrame;
1386 nsresult ContentEventHandler::OnQuerySelectedText(
1387 WidgetQueryContentEvent* aEvent) {
1388 nsresult rv = Init(aEvent);
1389 if (NS_FAILED(rv)) {
1390 return 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
1400 // is a usual case.
1401 return NS_OK;
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,
1416 lineBreakType)))) {
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
1433 // information.
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
1446 // of that.
1447 else {
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);
1459 } else {
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),
1472 true);
1473 if (NS_WARN_IF(frameForTextRectOrError.isErr()) ||
1474 !frameForTextRectOrError.inspect()) {
1475 aEvent->mReply->mWritingMode = WritingMode();
1476 } else {
1477 aEvent->mReply->mWritingMode =
1478 frameForTextRectOrError.inspect()->GetWritingMode();
1481 MOZ_ASSERT(aEvent->Succeeded());
1482 return NS_OK;
1485 nsresult ContentEventHandler::OnQueryTextContent(
1486 WidgetQueryContentEvent* aEvent) {
1487 nsresult rv = Init(aEvent);
1488 if (NS_FAILED(rv)) {
1489 return rv;
1492 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
1494 LineBreakType lineBreakType = GetLineBreakType(aEvent);
1496 RawRange rawRange;
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());
1526 return NS_OK;
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;
1546 nsresult rv =
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)) {
1554 break;
1557 auto* content = nsIContent::FromNode(node);
1558 if (MOZ_UNLIKELY(!content)) {
1559 continue;
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
1564 // ignore it.
1565 if (!content->GetPrimaryFrame()) {
1566 continue;
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()
1574 : 0u;
1575 if (offsetInNode < textNode->TextDataLength()) {
1576 nodePosition = {textNode, offsetInNode};
1577 break;
1579 continue;
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;
1609 nsresult rv =
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:
1622 // +----+-----+
1623 // | abc|[<br>|
1624 // +----+-----+
1625 // +----+
1626 // |]def|
1627 // +----+
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)) {
1650 break;
1653 if (node == nextNodeOfRangeEnd) {
1654 continue;
1657 auto* content = nsIContent::FromNode(node);
1658 if (MOZ_UNLIKELY(!content)) {
1659 continue;
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
1664 // ignore it.
1665 if (!content->GetPrimaryFrame()) {
1666 continue;
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) ==
1679 0) {
1680 continue;
1682 break;
1685 if (ShouldBreakLineBefore(*content, mRootElement) ||
1686 IsPaddingBR(*content)) {
1687 nodePosition = {content, 0u};
1688 break;
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
1742 // rect.
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
1780 // | I |
1781 // | I |
1782 // | |
1783 // ^- caret
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;
1792 } else {
1793 // above (For vertical) or left (For horizontal) of top-left corner of
1794 // aFrame.
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;
1804 } else {
1805 result.mRect.x = inlineOffset;
1806 result.mRect.y = caretBlockAxisMetrics.mOffset;
1807 result.mRect.height = caretBlockAxisMetrics.mExtent;
1809 return result;
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)) {
1817 return result;
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()) {
1827 return result;
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,
1834 } else {
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();
1840 return result;
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
1850 // is 16px.
1851 RefPtr<nsFontMetrics> fontMetrics =
1852 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
1853 const nscoord kMaxHeight = fontMetrics
1854 ? fontMetrics->MaxHeight()
1855 : 16 * presContext->AppUnitsPerDevPixel();
1857 nsRect caretRect;
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;
1863 } else {
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;
1872 } else {
1873 if (kWritingMode.IsVerticalLR()) {
1874 caretRect.x = kContentRect.x;
1875 } else {
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);
1886 // static
1887 LayoutDeviceIntRect ContentEventHandler::GetCaretRectBefore(
1888 const LayoutDeviceIntRect& aCharRect, const WritingMode& aWritingMode) {
1889 LayoutDeviceIntRect caretRectBefore(aCharRect);
1890 if (aWritingMode.IsVertical()) {
1891 caretRectBefore.height = 1;
1892 } else {
1893 // TODO: Make here bidi-aware.
1894 caretRectBefore.width = 1;
1896 return caretRectBefore;
1899 // static
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;
1907 } else {
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;
1916 // static
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;
1923 } else {
1924 // TODO: Make here bidi-aware.
1925 caretRectAfter.x = aCharRect.XMost() + 1;
1926 caretRectAfter.width = 1;
1928 return caretRectAfter;
1931 // static
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;
1942 } else {
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))) {
1956 return 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
1970 // charRect).
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;
1982 RawRange rawRange;
1983 nsresult rv =
1984 SetRawRangeFromFlatTextOffset(&rawRange, offset, 1, lineBreakType, true,
1985 nullptr, getter_AddRefs(lastTextNode));
1986 if (NS_WARN_IF(NS_FAILED(rv))) {
1987 return 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
1992 // rect.
1994 // If the range is collapsed, offset has already reached the end of the
1995 // contents.
1996 if (rawRange.Collapsed()) {
1997 break;
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
2016 // the root later.
2017 if (offset >= flattenedAllText.Length()) {
2018 break;
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 = [&]() {
2025 if (lastTextNode) {
2026 if (rawRange.GetStartContainer() == lastTextNode) {
2027 if (rawRange.StartOffset() < lastTextNode->TextDataLength()) {
2028 return lastTextNode->TextDataLength() - rawRange.StartOffset();
2030 return 0u;
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(),
2040 mRootElement)) {
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.
2047 return 1u;
2049 return kBRLength;
2051 return 0u;
2052 }();
2053 offset += std::max(1u, remainingLengthInCurrentRange);
2054 continue;
2057 nsIContent* firstContent = firstFrame.mFrame->GetContent();
2058 if (NS_WARN_IF(!firstContent)) {
2059 return NS_ERROR_FAILURE;
2062 bool startsBetweenLineBreaker = false;
2063 nsAutoString chars;
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
2072 // the frame's API.
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())) {
2077 return rv;
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
2091 // "\n").
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))) {
2097 return 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)) {
2111 nsRect brRect;
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>--------------------------------+
2118 // |abc |
2119 // +------------------------------------+
2121 // I+-<p>--------------------------------+
2122 // |def |
2123 // +------------------------------------+
2124 // However, users expect popup windows of IME should be positioned at
2125 // right-bottom of "c" like this:
2126 // +-<p>--------------------------------+
2127 // |abcI |
2128 // +------------------------------------+
2130 // +-<p>--------------------------------+
2131 // |def |
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.
2178 else {
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;
2198 } else {
2199 NS_WARNING(
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
2211 // base frame.
2212 lastCharRect = charRect;
2213 lastFrame = baseFrame;
2214 rv = ConvertToRootRelativeOffset(baseFrame, charRect);
2215 if (NS_WARN_IF(NS_FAILED(rv))) {
2216 return 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);
2251 offset++;
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) {
2257 continue;
2260 MOZ_ASSERT(kBRLength == 2);
2262 // If it's already reached the end of query range, we don't need to do
2263 // anymore.
2264 if (offset == kEndOffset) {
2265 break;
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) {
2272 continue;
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);
2280 offset++;
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);
2315 } else {
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;
2329 } else {
2330 queryTextRectEvent.mReply->mRect.width = 1;
2332 aEvent->mReply->mRectArray.AppendElement(
2333 queryTextRectEvent.mReply->mRect);
2337 MOZ_ASSERT(aEvent->Succeeded());
2338 return NS_OK;
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)) {
2350 return rv;
2353 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
2355 LineBreakType lineBreakType = GetLineBreakType(aEvent);
2356 RawRange rawRange;
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;
2364 nsString string;
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;
2403 nsRect rect;
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;
2409 if (lastFrame) {
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);
2424 if (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.
2430 else {
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))) {
2439 return 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).
2445 else {
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))) {
2458 return 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());
2473 return NS_OK;
2476 nsRect rect, frameRect;
2477 nsPoint ptOffset;
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
2481 // queried range.
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))) {
2486 return rv;
2488 frameRect = rect;
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;
2494 } else {
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
2504 // will be:
2505 // +-<p>--------------------------------+
2506 // |abc |
2507 // +------------------------------------+
2509 // I+-<p>--------------------------------+
2510 // |def |
2511 // +------------------------------------+
2512 // However, users expect popup windows of IME should be positioned at
2513 // right-bottom of "c" like this:
2514 // +-<p>--------------------------------+
2515 // |abcI |
2516 // +------------------------------------+
2518 // +-<p>--------------------------------+
2519 // |def |
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))) {
2534 return rv;
2536 frameRect = rect;
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.
2546 else {
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))) {
2554 return rv;
2556 frameRect = rect;
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();
2571 if (!frame) {
2572 do {
2573 postOrderIter.Next();
2574 nsINode* node = postOrderIter.GetCurrentNode();
2575 if (!node) {
2576 break;
2578 if (!node->IsContent()) {
2579 continue;
2581 nsIFrame* primaryFrame = node->AsContent()->GetPrimaryFrame();
2582 // The node may be hidden by CSS.
2583 if (!primaryFrame) {
2584 continue;
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());
2595 if (!frame) {
2596 break;
2599 if (frame->IsTextFrame()) {
2600 frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size());
2601 } else {
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))) {
2611 return 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))) {
2629 return 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;
2638 } else {
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);
2647 } else {
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());
2667 return NS_OK;
2670 nsresult ContentEventHandler::OnQueryEditorRect(
2671 WidgetQueryContentEvent* aEvent) {
2672 nsresult rv = Init(aEvent);
2673 if (NS_FAILED(rv)) {
2674 return rv;
2677 if (NS_WARN_IF(NS_FAILED(QueryContentRect(mRootElement, aEvent)))) {
2678 return NS_ERROR_FAILURE;
2681 MOZ_ASSERT(aEvent->Succeeded());
2682 return NS_OK;
2685 nsresult ContentEventHandler::OnQueryCaretRect(
2686 WidgetQueryContentEvent* aEvent) {
2687 nsresult rv = Init(aEvent);
2688 if (NS_FAILED(rv)) {
2689 return 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()) {
2695 nsRect caretRect;
2696 nsIFrame* caretFrame = nsCaret::GetGeometry(mSelection, &caretRect);
2697 if (caretFrame) {
2698 uint32_t offset;
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());
2723 return NS_OK;
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());
2745 return NS_OK;
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());
2755 return NS_OK;
2758 nsresult ContentEventHandler::OnQuerySelectionAsTransferable(
2759 WidgetQueryContentEvent* aEvent) {
2760 nsresult rv = Init(aEvent);
2761 if (NS_FAILED(rv)) {
2762 return rv;
2765 MOZ_ASSERT(aEvent->mReply.isSome());
2767 if (mSelection->IsCollapsed()) {
2768 MOZ_ASSERT(!aEvent->mReply->mTransferable);
2769 return NS_OK;
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());
2779 return NS_OK;
2782 nsresult ContentEventHandler::OnQueryCharacterAtPoint(
2783 WidgetQueryContentEvent* aEvent) {
2784 nsresult rv = Init(aEvent);
2785 if (NS_FAILED(rv)) {
2786 return 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());
2830 return NS_OK;
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());
2843 return NS_OK;
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());
2857 return NS_OK;
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,
2872 aEvent->mWidget);
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());
2885 return NS_OK;
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)) {
2894 return 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) -
2911 docFrameRect.x,
2912 docFrame->PresContext()->DevPixelsToIntCSSPixels(eventLoc.y) -
2913 docFrameRect.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());
2925 return NS_OK;
2928 /* static */
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) {
2939 *aLength = 0;
2940 return NS_OK;
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();
2955 MOZ_ASSERT(
2956 parent &&
2957 parent->ComputeIndexOf(aStartPosition.Container()).isNothing(),
2958 "At removing the node, the node shouldn't be in the array of children "
2959 "of its parent");
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");
2965 MOZ_ASSERT(
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))) {
2972 return rv;
2974 } else {
2975 RawRange prevRawRange;
2976 nsresult rv = prevRawRange.SetStart(aStartPosition.AsRaw());
2977 if (NS_WARN_IF(NS_FAILED(rv))) {
2978 return 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);
2993 } else {
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))) {
3013 return rv;
3015 rv = preOrderIter.Init(prevRawRange.Start().AsRaw(),
3016 prevRawRange.End().AsRaw());
3017 if (NS_WARN_IF(NS_FAILED(rv))) {
3018 return 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))) {
3024 return rv;
3026 rv = preOrderIter.Init(prevRawRange.Start().AsRaw(),
3027 prevRawRange.End().AsRaw());
3028 if (NS_WARN_IF(NS_FAILED(rv))) {
3029 return rv;
3031 } else {
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))) {
3035 return rv;
3040 *aLength = 0;
3041 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
3042 nsINode* node = preOrderIter.GetCurrentNode();
3043 if (NS_WARN_IF(!node)) {
3044 break;
3046 if (!node->IsContent()) {
3047 continue;
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));
3060 } else {
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()) {
3068 continue;
3070 // If the end position is before the open tag, don't append the line
3071 // break length.
3072 if (node == endPosition.Container() && endPosition.IsBeforeOpenTag()) {
3073 continue;
3075 *aLength += GetBRLength(aLineBreakType);
3078 return NS_OK;
3081 nsresult ContentEventHandler::GetStartOffset(const RawRange& aRawRange,
3082 uint32_t* aOffset,
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();
3101 startIsContainer =
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()) {
3127 return NS_OK;
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()) {
3137 return NS_OK;
3139 nsresult rv = aRawRange.CollapseTo(
3140 RawRangeBoundary(startPoint.Container()->GetFirstChild(), 0u));
3141 if (NS_WARN_IF(NS_FAILED(rv))) {
3142 return rv;
3144 return NS_OK;
3147 if (!startPoint.IsSetAndValid()) {
3148 return NS_OK;
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()) {
3154 return NS_OK;
3156 nsresult rv = aRawRange.CollapseTo(
3157 RawRangeBoundary(startPoint.Ref(), startPoint.Ref()->Length()));
3158 if (NS_WARN_IF(NS_FAILED(rv))) {
3159 return rv;
3161 return NS_OK;
3164 nsresult ContentEventHandler::ConvertToRootRelativeOffset(nsIFrame* aFrame,
3165 nsRect& aRect) {
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),
3182 // so convert back.
3183 aRect = aRect.ScaleToOtherAppUnitsRoundOut(rootPC->AppUnitsPerDevPixel(),
3184 thisPC->AppUnitsPerDevPixel());
3186 return NS_OK;
3189 static void AdjustRangeForSelection(const Element* aRootElement,
3190 nsINode** aNode,
3191 Maybe<uint32_t>* aNodeOffset) {
3192 nsINode* node = *aNode;
3193 Maybe<uint32_t> nodeOffset = *aNodeOffset;
3194 if (aRootElement == node || NS_WARN_IF(!node->GetParent()) ||
3195 !node->IsText()) {
3196 return;
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) {
3206 return;
3209 Element* rootParentElement = aRootElement->GetParentElement();
3210 if (NS_WARN_IF(!rootParentElement)) {
3211 return;
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)) {
3218 return;
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);
3233 } else {
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
3243 // without ISM.
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);
3248 } else {
3249 rv = Init(aEvent);
3250 NS_ENSURE_SUCCESS(rv, rv);
3253 // Get range from offset and length
3254 RawRange rawRange;
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);
3275 ErrorResult error;
3276 MOZ_KnownLive(mSelection)
3277 ->SetBaseAndExtentInLimiter(*endNodeStrong, *endNodeOffset,
3278 *startNodeStrong, *startNodeOffset, error);
3279 if (NS_WARN_IF(error.Failed())) {
3280 return error.StealNSResult();
3282 } else {
3283 nsCOMPtr<nsINode> startNodeStrong(startNode);
3284 nsCOMPtr<nsINode> endNodeStrong(endNode);
3285 ErrorResult error;
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
3295 // be alive.
3296 MOZ_KnownLive(mSelection)
3297 ->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
3298 ScrollAxis(), ScrollAxis(), 0);
3299 aEvent->mSucceeded = true;
3300 return NS_OK;
3303 nsRect ContentEventHandler::FrameRelativeRect::RectRelativeTo(
3304 nsIFrame* aDestFrame) const {
3305 if (!mBaseFrame || NS_WARN_IF(!aDestFrame)) {
3306 return nsRect();
3309 if (NS_WARN_IF(aDestFrame->PresContext() != mBaseFrame->PresContext())) {
3310 return nsRect();
3313 if (aDestFrame == mBaseFrame) {
3314 return mRect;
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