Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / events / ContentEventHandler.cpp
blob78371b13e819e97a05a36103137a565dbddd4ebe
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/SelectionMovementUtils.h"
19 #include "mozilla/TextComposition.h"
20 #include "mozilla/TextEditor.h"
21 #include "mozilla/TextEvents.h"
22 #include "mozilla/dom/Element.h"
23 #include "mozilla/dom/HTMLBRElement.h"
24 #include "mozilla/dom/HTMLUnknownElement.h"
25 #include "mozilla/dom/Selection.h"
26 #include "mozilla/dom/Text.h"
27 #include "nsCaret.h"
28 #include "nsCOMPtr.h"
29 #include "nsContentUtils.h"
30 #include "nsCopySupport.h"
31 #include "nsElementTable.h"
32 #include "nsFocusManager.h"
33 #include "nsFontMetrics.h"
34 #include "nsFrameSelection.h"
35 #include "nsHTMLTags.h"
36 #include "nsIFrame.h"
37 #include "nsLayoutUtils.h"
38 #include "nsPresContext.h"
39 #include "nsQueryObject.h"
40 #include "nsRange.h"
41 #include "nsTextFragment.h"
42 #include "nsTextFrame.h"
43 #include "nsView.h"
44 #include "mozilla/ViewportUtils.h"
46 #include <algorithm>
48 // Work around conflicting define in rpcndr.h
49 #if defined(small)
50 # undef small
51 #endif // defined(small)
53 #if defined(XP_WIN) && 0
54 # define TRANSLATE_NEW_LINES
55 #endif
57 namespace mozilla {
59 using namespace dom;
60 using namespace widget;
62 /******************************************************************/
63 /* ContentEventHandler::SimpleRangeBase */
64 /******************************************************************/
65 template <>
66 ContentEventHandler::SimpleRangeBase<
67 RefPtr<nsINode>, RangeBoundary>::SimpleRangeBase() = default;
69 template <>
70 ContentEventHandler::SimpleRangeBase<nsINode*,
71 RawRangeBoundary>::SimpleRangeBase()
72 : mRoot(nullptr) {
73 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
74 mAssertNoGC.emplace();
75 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
78 template <>
79 template <typename OtherNodeType, typename OtherRangeBoundaryType>
80 ContentEventHandler::SimpleRangeBase<RefPtr<nsINode>, RangeBoundary>::
81 SimpleRangeBase(
82 const SimpleRangeBase<OtherNodeType, OtherRangeBoundaryType>& aOther)
83 : mRoot(aOther.GetRoot()),
84 mStart{aOther.Start().AsRaw()},
85 mEnd{aOther.End().AsRaw()}
86 // Don't use the copy constructor of mAssertNoGC
89 template <>
90 template <typename OtherNodeType, typename OtherRangeBoundaryType>
91 ContentEventHandler::SimpleRangeBase<nsINode*, RawRangeBoundary>::
92 SimpleRangeBase(
93 const SimpleRangeBase<OtherNodeType, OtherRangeBoundaryType>& aOther)
94 : mRoot(aOther.GetRoot()),
95 mStart{aOther.Start().AsRaw()},
96 mEnd{aOther.End().AsRaw()} {
97 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
98 mAssertNoGC.emplace();
99 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
102 template <>
103 ContentEventHandler::SimpleRangeBase<RefPtr<nsINode>, RangeBoundary>::
104 SimpleRangeBase(
105 SimpleRangeBase<RefPtr<nsINode>, RangeBoundary>&& aOther) noexcept
106 : mRoot(std::move(aOther.GetRoot())),
107 mStart(std::move(aOther.mStart)),
108 mEnd(std::move(aOther.mEnd)) {}
110 template <>
111 ContentEventHandler::SimpleRangeBase<nsINode*, RawRangeBoundary>::
112 SimpleRangeBase(
113 SimpleRangeBase<nsINode*, RawRangeBoundary>&& aOther) noexcept
114 : mRoot(std::move(aOther.GetRoot())),
115 mStart(std::move(aOther.mStart)),
116 mEnd(std::move(aOther.mEnd)) {
117 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
118 mAssertNoGC.emplace();
119 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
122 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
123 template <>
124 ContentEventHandler::SimpleRangeBase<
125 RefPtr<nsINode>, RangeBoundary>::~SimpleRangeBase() = default;
127 template <>
128 ContentEventHandler::SimpleRangeBase<nsINode*,
129 RawRangeBoundary>::~SimpleRangeBase() {
130 MOZ_DIAGNOSTIC_ASSERT(!mMutationGuard.Mutated(0));
132 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
134 template <typename NodeType, typename RangeBoundaryType>
135 void ContentEventHandler::SimpleRangeBase<
136 NodeType, RangeBoundaryType>::AssertStartIsBeforeOrEqualToEnd() {
137 MOZ_ASSERT(
138 *nsContentUtils::ComparePoints(
139 mStart.Container(),
140 *mStart.Offset(
141 RangeBoundaryType::OffsetFilter::kValidOrInvalidOffsets),
142 mEnd.Container(),
143 *mEnd.Offset(
144 RangeBoundaryType::OffsetFilter::kValidOrInvalidOffsets)) <= 0);
147 template <typename NodeType, typename RangeBoundaryType>
148 nsresult
149 ContentEventHandler::SimpleRangeBase<NodeType, RangeBoundaryType>::SetStart(
150 const RawRangeBoundary& aStart) {
151 nsINode* newRoot = RangeUtils::ComputeRootNode(aStart.Container());
152 if (!newRoot) {
153 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
156 if (!aStart.IsSetAndValid()) {
157 return NS_ERROR_DOM_INDEX_SIZE_ERR;
160 // Collapse if not positioned yet, or if positioned in another document.
161 if (!IsPositioned() || newRoot != mRoot) {
162 mRoot = newRoot;
163 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
164 mEnd.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
165 return NS_OK;
168 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
169 AssertStartIsBeforeOrEqualToEnd();
170 return NS_OK;
173 template <typename NodeType, typename RangeBoundaryType>
174 nsresult
175 ContentEventHandler::SimpleRangeBase<NodeType, RangeBoundaryType>::SetEnd(
176 const RawRangeBoundary& aEnd) {
177 nsINode* newRoot = RangeUtils::ComputeRootNode(aEnd.Container());
178 if (!newRoot) {
179 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
182 if (!aEnd.IsSetAndValid()) {
183 return NS_ERROR_DOM_INDEX_SIZE_ERR;
186 // Collapse if not positioned yet, or if positioned in another document.
187 if (!IsPositioned() || newRoot != mRoot) {
188 mRoot = newRoot;
189 mStart.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
190 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
191 return NS_OK;
194 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
195 AssertStartIsBeforeOrEqualToEnd();
196 return NS_OK;
199 template <typename NodeType, typename RangeBoundaryType>
200 nsresult
201 ContentEventHandler::SimpleRangeBase<NodeType, RangeBoundaryType>::SetEndAfter(
202 nsINode* aEndContainer) {
203 return SetEnd(RangeUtils::GetRawRangeBoundaryAfter(aEndContainer));
206 template <typename NodeType, typename RangeBoundaryType>
207 void ContentEventHandler::SimpleRangeBase<
208 NodeType, RangeBoundaryType>::SetStartAndEnd(const nsRange* aRange) {
209 DebugOnly<nsresult> rv =
210 SetStartAndEnd(aRange->StartRef().AsRaw(), aRange->EndRef().AsRaw());
211 MOZ_ASSERT(!aRange->IsPositioned() || NS_SUCCEEDED(rv));
214 template <typename NodeType, typename RangeBoundaryType>
215 nsresult ContentEventHandler::SimpleRangeBase<
216 NodeType, RangeBoundaryType>::SetStartAndEnd(const RawRangeBoundary& aStart,
217 const RawRangeBoundary& aEnd) {
218 nsINode* newStartRoot = RangeUtils::ComputeRootNode(aStart.Container());
219 if (!newStartRoot) {
220 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
222 if (!aStart.IsSetAndValid()) {
223 return NS_ERROR_DOM_INDEX_SIZE_ERR;
226 if (aStart.Container() == aEnd.Container()) {
227 if (!aEnd.IsSetAndValid()) {
228 return NS_ERROR_DOM_INDEX_SIZE_ERR;
230 MOZ_ASSERT(*aStart.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets) <=
231 *aEnd.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets));
232 mRoot = newStartRoot;
233 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
234 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
235 return NS_OK;
238 nsINode* newEndRoot = RangeUtils::ComputeRootNode(aEnd.Container());
239 if (!newEndRoot) {
240 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
242 if (!aEnd.IsSetAndValid()) {
243 return NS_ERROR_DOM_INDEX_SIZE_ERR;
246 // If they have different root, this should be collapsed at the end point.
247 if (newStartRoot != newEndRoot) {
248 mRoot = newEndRoot;
249 mStart.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
250 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
251 return NS_OK;
254 // Otherwise, set the range as specified.
255 mRoot = newStartRoot;
256 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
257 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
258 AssertStartIsBeforeOrEqualToEnd();
259 return NS_OK;
262 template <typename NodeType, typename RangeBoundaryType>
263 nsresult ContentEventHandler::SimpleRangeBase<NodeType, RangeBoundaryType>::
264 SelectNodeContents(const nsINode* aNodeToSelectContents) {
265 nsINode* const newRoot =
266 RangeUtils::ComputeRootNode(const_cast<nsINode*>(aNodeToSelectContents));
267 if (!newRoot) {
268 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
270 mRoot = newRoot;
271 mStart =
272 RangeBoundaryType(const_cast<nsINode*>(aNodeToSelectContents), nullptr);
273 mEnd = RangeBoundaryType(const_cast<nsINode*>(aNodeToSelectContents),
274 aNodeToSelectContents->GetLastChild());
275 return NS_OK;
278 /******************************************************************/
279 /* ContentEventHandler */
280 /******************************************************************/
282 // NOTE
284 // ContentEventHandler *creates* ranges as following rules:
285 // 1. Start of range:
286 // 1.1. Cases: [textNode or text[Node or textNode[
287 // When text node is start of a range, start node is the text node and
288 // start offset is any number between 0 and the length of the text.
289 // 1.2. Case: [<element>:
290 // When start of an element node is start of a range, start node is
291 // parent of the element and start offset is the element's index in the
292 // parent.
293 // 1.3. Case: <element/>[
294 // When after an empty element node is start of a range, start node is
295 // parent of the element and start offset is the element's index in the
296 // parent + 1.
297 // 1.4. Case: <element>[
298 // When start of a non-empty element is start of a range, start node is
299 // the element and start offset is 0.
300 // 1.5. Case: <root>[
301 // When start of a range is 0 and there are no nodes causing text,
302 // start node is the root node and start offset is 0.
303 // 1.6. Case: [</root>
304 // When start of a range is out of bounds, start node is the root node
305 // and start offset is number of the children.
306 // 2. End of range:
307 // 2.1. Cases: ]textNode or text]Node or textNode]
308 // When a text node is end of a range, end node is the text node and
309 // end offset is any number between 0 and the length of the text.
310 // 2.2. Case: ]<element>
311 // When before an element node (meaning before the open tag of the
312 // element) is end of a range, end node is previous node causing text.
313 // Note that this case shouldn't be handled directly. If rule 2.1 and
314 // 2.3 are handled correctly, the loop with ContentIterator shouldn't
315 // reach the element node since the loop should've finished already at
316 // handling the last node which caused some text.
317 // 2.3. Case: <element>]
318 // When a line break is caused before a non-empty element node and it's
319 // end of a range, end node is the element and end offset is 0.
320 // (i.e., including open tag of the element)
321 // 2.4. Cases: <element/>]
322 // When after an empty element node is end of a range, end node is
323 // parent of the element node and end offset is the element's index in
324 // the parent + 1. (i.e., including close tag of the element or empty
325 // element)
326 // 2.5. Case: ]</root>
327 // When end of a range is out of bounds, end node is the root node and
328 // end offset is number of the children.
330 // ContentEventHandler *treats* ranges as following additional rules:
331 // 1. When the start node is an element node which doesn't have children,
332 // it includes a line break caused before itself (i.e., includes its open
333 // tag). For example, if start position is { <br>, 0 }, the line break
334 // caused by <br> should be included into the flatten text.
335 // 2. When the end node is an element node which doesn't have children,
336 // it includes the end (i.e., includes its close tag except empty element).
337 // Although, currently, any close tags don't cause line break, this also
338 // includes its open tag. For example, if end position is { <br>, 0 }, the
339 // line break caused by the <br> should be included into the flatten text.
341 ContentEventHandler::ContentEventHandler(nsPresContext* aPresContext)
342 : mDocument(aPresContext->Document()) {}
344 nsresult ContentEventHandler::InitBasic(bool aRequireFlush) {
345 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
346 if (aRequireFlush) {
347 // If text frame which has overflowing selection underline is dirty,
348 // we need to flush the pending reflow here.
349 mDocument->FlushPendingNotifications(FlushType::Layout);
351 return NS_OK;
354 nsresult ContentEventHandler::InitRootContent(
355 const Selection& aNormalSelection) {
356 // Root content should be computed with normal selection because normal
357 // selection is typically has at least one range but the other selections
358 // not so. If there is a range, computing its root is easy, but if
359 // there are no ranges, we need to use ancestor limit instead.
360 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
362 if (!aNormalSelection.RangeCount()) {
363 // If there is no selection range, we should compute the selection root
364 // from ancestor limiter or root content of the document.
365 mRootElement =
366 Element::FromNodeOrNull(aNormalSelection.GetAncestorLimiter());
367 if (!mRootElement) {
368 mRootElement = mDocument->GetRootElement();
369 if (NS_WARN_IF(!mRootElement)) {
370 return NS_ERROR_NOT_AVAILABLE;
373 return NS_OK;
376 RefPtr<const nsRange> range(aNormalSelection.GetRangeAt(0));
377 if (NS_WARN_IF(!range)) {
378 return NS_ERROR_UNEXPECTED;
381 // If there is a selection, we should retrieve the selection root from
382 // the range since when the window is inactivated, the ancestor limiter
383 // of selection was cleared by blur event handler of EditorBase but the
384 // selection range still keeps storing the nodes. If the active element of
385 // the deactive window is <input> or <textarea>, we can compute the
386 // selection root from them.
387 nsCOMPtr<nsINode> startNode = range->GetStartContainer();
388 nsINode* endNode = range->GetEndContainer();
389 if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
390 return NS_ERROR_FAILURE;
393 // See bug 537041 comment 5, the range could have removed node.
394 if (NS_WARN_IF(startNode->GetComposedDoc() != mDocument)) {
395 return NS_ERROR_FAILURE;
398 NS_ASSERTION(startNode->GetComposedDoc() == endNode->GetComposedDoc(),
399 "firstNormalSelectionRange crosses the document boundary");
401 RefPtr<PresShell> presShell = mDocument->GetPresShell();
402 mRootElement =
403 Element::FromNodeOrNull(startNode->GetSelectionRootContent(presShell));
404 if (NS_WARN_IF(!mRootElement)) {
405 return NS_ERROR_FAILURE;
408 return NS_OK;
411 nsresult ContentEventHandler::InitCommon(EventMessage aEventMessage,
412 SelectionType aSelectionType,
413 bool aRequireFlush) {
414 if (mSelection && mSelection->Type() == aSelectionType) {
415 return NS_OK;
418 mSelection = nullptr;
419 mRootElement = nullptr;
420 mFirstSelectedSimpleRange.Clear();
422 nsresult rv = InitBasic(aRequireFlush);
423 NS_ENSURE_SUCCESS(rv, rv);
425 RefPtr<nsFrameSelection> frameSel;
426 if (PresShell* presShell = mDocument->GetPresShell()) {
427 frameSel = presShell->GetLastFocusedFrameSelection();
429 if (NS_WARN_IF(!frameSel)) {
430 return NS_ERROR_NOT_AVAILABLE;
433 mSelection = frameSel->GetSelection(aSelectionType);
434 if (NS_WARN_IF(!mSelection)) {
435 return NS_ERROR_NOT_AVAILABLE;
438 RefPtr<Selection> normalSelection;
439 if (mSelection->Type() == SelectionType::eNormal) {
440 normalSelection = mSelection;
441 } else {
442 normalSelection = frameSel->GetSelection(SelectionType::eNormal);
443 if (NS_WARN_IF(!normalSelection)) {
444 return NS_ERROR_NOT_AVAILABLE;
448 rv = InitRootContent(*normalSelection);
449 if (NS_WARN_IF(NS_FAILED(rv))) {
450 return rv;
453 if (mSelection->RangeCount()) {
454 mFirstSelectedSimpleRange.SetStartAndEnd(mSelection->GetRangeAt(0));
455 return NS_OK;
458 // Even if there are no selection ranges, it's usual case if aSelectionType
459 // is a special selection or we're handling eQuerySelectedText.
460 if (aSelectionType != SelectionType::eNormal ||
461 aEventMessage == eQuerySelectedText) {
462 MOZ_ASSERT(!mFirstSelectedSimpleRange.IsPositioned());
463 return NS_OK;
466 // But otherwise, we need to assume that there is a selection range at the
467 // beginning of the root content if aSelectionType is eNormal.
468 rv = mFirstSelectedSimpleRange.CollapseTo(RawRangeBoundary(mRootElement, 0u));
469 if (NS_WARN_IF(NS_FAILED(rv))) {
470 return NS_ERROR_UNEXPECTED;
472 return NS_OK;
475 nsresult ContentEventHandler::Init(WidgetQueryContentEvent* aEvent) {
476 NS_ASSERTION(aEvent, "aEvent must not be null");
477 MOZ_ASSERT(aEvent->mMessage == eQuerySelectedText ||
478 aEvent->mInput.mSelectionType == SelectionType::eNormal);
480 if (NS_WARN_IF(!aEvent->mInput.IsValidOffset()) ||
481 NS_WARN_IF(!aEvent->mInput.IsValidEventMessage(aEvent->mMessage))) {
482 return NS_ERROR_FAILURE;
485 // Note that we should ignore WidgetQueryContentEvent::Input::mSelectionType
486 // if the event isn't eQuerySelectedText.
487 SelectionType selectionType = aEvent->mMessage == eQuerySelectedText
488 ? aEvent->mInput.mSelectionType
489 : SelectionType::eNormal;
490 if (NS_WARN_IF(selectionType == SelectionType::eNone)) {
491 return NS_ERROR_FAILURE;
494 nsresult rv = InitCommon(aEvent->mMessage, selectionType,
495 aEvent->AllowFlushingPendingNotifications());
496 NS_ENSURE_SUCCESS(rv, rv);
498 // Be aware, WidgetQueryContentEvent::mInput::mOffset should be made absolute
499 // offset before sending it to ContentEventHandler because querying selection
500 // every time may be expensive. So, if the caller caches selection, it
501 // should initialize the event with the cached value.
502 if (aEvent->mInput.mRelativeToInsertionPoint) {
503 MOZ_ASSERT(selectionType == SelectionType::eNormal);
504 TextComposition* composition =
505 IMEStateManager::GetTextCompositionFor(aEvent->mWidget);
506 if (composition) {
507 uint32_t compositionStart = composition->NativeOffsetOfStartComposition();
508 if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) {
509 return NS_ERROR_FAILURE;
511 } else {
512 LineBreakType lineBreakType = GetLineBreakType(aEvent);
513 uint32_t selectionStart = 0;
514 rv = GetStartOffset(mFirstSelectedSimpleRange, &selectionStart,
515 lineBreakType);
516 if (NS_WARN_IF(NS_FAILED(rv))) {
517 return NS_ERROR_FAILURE;
519 if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(selectionStart))) {
520 return NS_ERROR_FAILURE;
525 // Ideally, we should emplace only when we return succeeded event.
526 // However, we need to emplace here since it's hard to store the various
527 // result. Intead, `HandleQueryContentEvent()` will reset `mReply` if
528 // corresponding handler returns error.
529 aEvent->EmplaceReply();
531 aEvent->mReply->mContentsRoot = mRootElement.get();
532 aEvent->mReply->mIsEditableContent =
533 mRootElement && mRootElement->IsEditable();
535 nsRect r;
536 nsIFrame* frame = nsCaret::GetGeometry(mSelection, &r);
537 if (!frame) {
538 frame = mRootElement->GetPrimaryFrame();
539 if (NS_WARN_IF(!frame)) {
540 return NS_ERROR_FAILURE;
543 aEvent->mReply->mFocusedWidget = frame->GetNearestWidget();
545 return NS_OK;
548 nsresult ContentEventHandler::Init(WidgetSelectionEvent* aEvent) {
549 NS_ASSERTION(aEvent, "aEvent must not be null");
551 nsresult rv = InitCommon(aEvent->mMessage);
552 NS_ENSURE_SUCCESS(rv, rv);
554 aEvent->mSucceeded = false;
556 return NS_OK;
559 nsIContent* ContentEventHandler::GetFocusedContent() {
560 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
561 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
562 return nsFocusManager::GetFocusedDescendant(
563 window, nsFocusManager::eIncludeAllDescendants,
564 getter_AddRefs(focusedWindow));
567 nsresult ContentEventHandler::QueryContentRect(
568 nsIContent* aContent, WidgetQueryContentEvent* aEvent) {
569 MOZ_ASSERT(aContent, "aContent must not be null");
571 nsIFrame* frame = aContent->GetPrimaryFrame();
572 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
574 // get rect for first frame
575 nsRect resultRect(nsPoint(0, 0), frame->GetRect().Size());
576 nsresult rv = ConvertToRootRelativeOffset(frame, resultRect);
577 NS_ENSURE_SUCCESS(rv, rv);
579 nsPresContext* presContext = frame->PresContext();
581 // account for any additional frames
582 while ((frame = frame->GetNextContinuation())) {
583 nsRect frameRect(nsPoint(0, 0), frame->GetRect().Size());
584 rv = ConvertToRootRelativeOffset(frame, frameRect);
585 NS_ENSURE_SUCCESS(rv, rv);
586 resultRect.UnionRect(resultRect, frameRect);
589 aEvent->mReply->mRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
590 resultRect, presContext->AppUnitsPerDevPixel());
591 // Returning empty rect may cause native IME confused, let's make sure to
592 // return non-empty rect.
593 EnsureNonEmptyRect(aEvent->mReply->mRect);
595 return NS_OK;
598 // Editor places a padding <br> element under its root content if the editor
599 // doesn't have any text. This happens even for single line editors.
600 // When we get text content and when we change the selection,
601 // we don't want to include the padding <br> elements at the end.
602 static bool IsContentBR(const nsIContent& aContent) {
603 const HTMLBRElement* brElement = HTMLBRElement::FromNode(aContent);
604 return brElement && !brElement->IsPaddingForEmptyLastLine() &&
605 !brElement->IsPaddingForEmptyEditor();
608 static bool IsPaddingBR(const nsIContent& aContent) {
609 return aContent.IsHTMLElement(nsGkAtoms::br) && !IsContentBR(aContent);
612 static void ConvertToNativeNewlines(nsString& aString) {
613 #if defined(TRANSLATE_NEW_LINES)
614 aString.ReplaceSubstring(u"\n"_ns, u"\r\n"_ns);
615 #endif
618 static void AppendString(nsString& aString, const Text& aTextNode) {
619 const uint32_t oldXPLength = aString.Length();
620 aTextNode.TextFragment().AppendTo(aString);
621 if (aTextNode.HasFlag(NS_MAYBE_MASKED)) {
622 TextEditor::MaskString(aString, aTextNode, oldXPLength, 0);
626 static void AppendSubString(nsString& aString, const Text& aTextNode,
627 uint32_t aXPOffset, uint32_t aXPLength) {
628 const uint32_t oldXPLength = aString.Length();
629 aTextNode.TextFragment().AppendTo(aString, aXPOffset, aXPLength);
630 if (aTextNode.HasFlag(NS_MAYBE_MASKED)) {
631 TextEditor::MaskString(aString, aTextNode, oldXPLength, aXPOffset);
635 #if defined(TRANSLATE_NEW_LINES)
636 template <typename StringType>
637 static uint32_t CountNewlinesInXPLength(const StringType& aString) {
638 uint32_t count = 0;
639 const auto* end = aString.EndReading();
640 for (const auto* iter = aString.BeginReading(); iter < end; ++iter) {
641 if (*iter == '\n') {
642 count++;
645 return count;
648 static uint32_t CountNewlinesInXPLength(const Text& aTextNode,
649 uint32_t aXPLength) {
650 const nsTextFragment& textFragment = aTextNode.TextFragment();
651 // For automated tests, we should abort on debug build.
652 MOZ_ASSERT(aXPLength == UINT32_MAX || aXPLength <= textFragment.GetLength(),
653 "aXPLength is out-of-bounds");
654 const uint32_t length = std::min(aXPLength, textFragment.GetLength());
655 if (!length) {
656 return 0;
658 if (textFragment.Is2b()) {
659 nsDependentSubstring str(textFragment.Get2b(), length);
660 return CountNewlinesInXPLength(str);
662 nsDependentCSubstring str(textFragment.Get1b(), length);
663 return CountNewlinesInXPLength(str);
666 template <typename StringType>
667 static uint32_t CountNewlinesInNativeLength(const StringType& aString,
668 uint32_t aNativeLength) {
669 MOZ_ASSERT(
670 (aNativeLength == UINT32_MAX || aNativeLength <= aString.Length() * 2),
671 "aNativeLength is unexpected value");
672 uint32_t count = 0;
673 uint32_t nativeOffset = 0;
674 const auto* end = aString.EndReading();
675 for (const auto* iter = aString.BeginReading();
676 iter < end && nativeOffset < aNativeLength; ++iter, ++nativeOffset) {
677 if (*iter == '\n') {
678 count++;
679 nativeOffset++;
682 return count;
685 static uint32_t CountNewlinesInNativeLength(const Text& aTextNode,
686 uint32_t aNativeLength) {
687 const nsTextFragment& textFragment = aTextNode.TextFragment();
688 const uint32_t xpLength = textFragment.GetLength();
689 if (!xpLength) {
690 return 0;
692 if (textFragment.Is2b()) {
693 nsDependentSubstring str(textFragment.Get2b(), xpLength);
694 return CountNewlinesInNativeLength(str, aNativeLength);
696 nsDependentCSubstring str(textFragment.Get1b(), xpLength);
697 return CountNewlinesInNativeLength(str, aNativeLength);
699 #endif
701 /* static */
702 uint32_t ContentEventHandler::GetNativeTextLength(const Text& aTextNode,
703 uint32_t aStartOffset,
704 uint32_t aEndOffset) {
705 MOZ_ASSERT(aEndOffset >= aStartOffset,
706 "aEndOffset must be equals or larger than aStartOffset");
707 if (aStartOffset == aEndOffset) {
708 return 0;
710 return GetTextLength(aTextNode, LINE_BREAK_TYPE_NATIVE, aEndOffset) -
711 GetTextLength(aTextNode, LINE_BREAK_TYPE_NATIVE, aStartOffset);
714 /* static */
715 uint32_t ContentEventHandler::GetNativeTextLength(const Text& aTextNode,
716 uint32_t aMaxLength) {
717 return GetTextLength(aTextNode, LINE_BREAK_TYPE_NATIVE, aMaxLength);
720 /* static inline */
721 uint32_t ContentEventHandler::GetBRLength(LineBreakType aLineBreakType) {
722 #if defined(TRANSLATE_NEW_LINES)
723 // Length of \r\n
724 return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1;
725 #else
726 return 1;
727 #endif
730 /* static */
731 uint32_t ContentEventHandler::GetTextLength(const Text& aTextNode,
732 LineBreakType aLineBreakType,
733 uint32_t aMaxLength) {
734 const uint32_t textLengthDifference =
735 #if defined(TRANSLATE_NEW_LINES)
736 // On Windows, the length of a native newline ("\r\n") is twice the length
737 // of the XP newline ("\n"), so XP length is equal to the length of the
738 // native offset plus the number of newlines encountered in the string.
739 (aLineBreakType == LINE_BREAK_TYPE_NATIVE)
740 ? CountNewlinesInXPLength(aTextNode, aMaxLength)
741 : 0;
742 #else
743 // On other platforms, the native and XP newlines are the same.
745 #endif
747 const uint32_t length =
748 std::min(aTextNode.TextFragment().GetLength(), aMaxLength);
749 return length + textLengthDifference;
752 static uint32_t ConvertToXPOffset(const Text& aTextNode,
753 uint32_t aNativeOffset) {
754 #if defined(TRANSLATE_NEW_LINES)
755 // On Windows, the length of a native newline ("\r\n") is twice the length of
756 // the XP newline ("\n"), so XP offset is equal to the length of the native
757 // offset minus the number of newlines encountered in the string.
758 return aNativeOffset - CountNewlinesInNativeLength(aTextNode, aNativeOffset);
759 #else
760 // On other platforms, the native and XP newlines are the same.
761 return aNativeOffset;
762 #endif
765 /* static */
766 uint32_t ContentEventHandler::GetNativeTextLength(const nsAString& aText) {
767 const uint32_t textLengthDifference =
768 #if defined(TRANSLATE_NEW_LINES)
769 // On Windows, the length of a native newline ("\r\n") is twice the length
770 // of the XP newline ("\n"), so XP length is equal to the length of the
771 // native offset plus the number of newlines encountered in the string.
772 CountNewlinesInXPLength(aText);
773 #else
774 // On other platforms, the native and XP newlines are the same.
776 #endif
777 return aText.Length() + textLengthDifference;
780 /* static */
781 bool ContentEventHandler::ShouldBreakLineBefore(const nsIContent& aContent,
782 const Element* aRootElement) {
783 // We don't need to append linebreak at the start of the root element.
784 if (&aContent == aRootElement) {
785 return false;
788 // If it's not an HTML element (including other markup language's elements),
789 // we shouldn't insert like break before that for now. Becoming this is a
790 // problem must be edge case. E.g., when ContentEventHandler is used with
791 // MathML or SVG elements.
792 if (!aContent.IsHTMLElement()) {
793 return false;
796 switch (
797 nsHTMLTags::CaseSensitiveAtomTagToId(aContent.NodeInfo()->NameAtom())) {
798 case eHTMLTag_br:
799 // If the element is <br>, we need to check if the <br> is caused by web
800 // content. Otherwise, i.e., it's caused by internal reason of Gecko,
801 // it shouldn't be exposed as a line break to flatten text.
802 return IsContentBR(aContent);
803 case eHTMLTag_a:
804 case eHTMLTag_abbr:
805 case eHTMLTag_acronym:
806 case eHTMLTag_b:
807 case eHTMLTag_bdi:
808 case eHTMLTag_bdo:
809 case eHTMLTag_big:
810 case eHTMLTag_cite:
811 case eHTMLTag_code:
812 case eHTMLTag_data:
813 case eHTMLTag_del:
814 case eHTMLTag_dfn:
815 case eHTMLTag_em:
816 case eHTMLTag_font:
817 case eHTMLTag_i:
818 case eHTMLTag_ins:
819 case eHTMLTag_kbd:
820 case eHTMLTag_mark:
821 case eHTMLTag_s:
822 case eHTMLTag_samp:
823 case eHTMLTag_small:
824 case eHTMLTag_span:
825 case eHTMLTag_strike:
826 case eHTMLTag_strong:
827 case eHTMLTag_sub:
828 case eHTMLTag_sup:
829 case eHTMLTag_time:
830 case eHTMLTag_tt:
831 case eHTMLTag_u:
832 case eHTMLTag_var:
833 // Note that ideally, we should refer the style of the primary frame of
834 // aContent for deciding if it's an inline. However, it's difficult
835 // IMEContentObserver to notify IME of text change caused by style change.
836 // Therefore, currently, we should check only from the tag for now.
837 return false;
838 case eHTMLTag_userdefined:
839 case eHTMLTag_unknown:
840 // If the element is unknown element, we shouldn't insert line breaks
841 // before it since unknown elements should be ignored.
842 return false;
843 default:
844 return true;
848 nsresult ContentEventHandler::GenerateFlatTextContent(
849 const Element* aElement, nsString& aString, LineBreakType aLineBreakType) {
850 MOZ_ASSERT(aString.IsEmpty());
852 UnsafeSimpleRange rawRange;
853 nsresult rv = rawRange.SelectNodeContents(aElement);
854 if (NS_WARN_IF(NS_FAILED(rv))) {
855 return rv;
857 return GenerateFlatTextContent(rawRange, aString, aLineBreakType);
860 template <typename NodeType, typename RangeBoundaryType>
861 nsresult ContentEventHandler::GenerateFlatTextContent(
862 const SimpleRangeBase<NodeType, RangeBoundaryType>& aSimpleRange,
863 nsString& aString, LineBreakType aLineBreakType) {
864 MOZ_ASSERT(aString.IsEmpty());
866 if (aSimpleRange.Collapsed()) {
867 return NS_OK;
870 nsINode* startNode = aSimpleRange.GetStartContainer();
871 nsINode* endNode = aSimpleRange.GetEndContainer();
872 if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
873 return NS_ERROR_FAILURE;
876 if (startNode == endNode && startNode->IsText()) {
877 AppendSubString(aString, *startNode->AsText(), aSimpleRange.StartOffset(),
878 aSimpleRange.EndOffset() - aSimpleRange.StartOffset());
879 ConvertToNativeNewlines(aString);
880 return NS_OK;
883 UnsafePreContentIterator preOrderIter;
884 nsresult rv = preOrderIter.Init(aSimpleRange.Start().AsRaw(),
885 aSimpleRange.End().AsRaw());
886 if (NS_WARN_IF(NS_FAILED(rv))) {
887 return rv;
889 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
890 nsINode* node = preOrderIter.GetCurrentNode();
891 if (NS_WARN_IF(!node)) {
892 break;
894 if (!node->IsContent()) {
895 continue;
898 if (const Text* textNode = Text::FromNode(node)) {
899 if (textNode == startNode) {
900 AppendSubString(aString, *textNode, aSimpleRange.StartOffset(),
901 textNode->TextLength() - aSimpleRange.StartOffset());
902 } else if (textNode == endNode) {
903 AppendSubString(aString, *textNode, 0, aSimpleRange.EndOffset());
904 } else {
905 AppendString(aString, *textNode);
907 } else if (ShouldBreakLineBefore(*node->AsContent(), mRootElement)) {
908 aString.Append(char16_t('\n'));
911 if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
912 ConvertToNativeNewlines(aString);
914 return NS_OK;
917 static FontRange* AppendFontRange(nsTArray<FontRange>& aFontRanges,
918 uint32_t aBaseOffset) {
919 FontRange* fontRange = aFontRanges.AppendElement();
920 fontRange->mStartOffset = aBaseOffset;
921 return fontRange;
924 /* static */
925 uint32_t ContentEventHandler::GetTextLengthInRange(
926 const Text& aTextNode, uint32_t aXPStartOffset, uint32_t aXPEndOffset,
927 LineBreakType aLineBreakType) {
928 return aLineBreakType == LINE_BREAK_TYPE_NATIVE
929 ? GetNativeTextLength(aTextNode, aXPStartOffset, aXPEndOffset)
930 : aXPEndOffset - aXPStartOffset;
933 /* static */
934 void ContentEventHandler::AppendFontRanges(FontRangeArray& aFontRanges,
935 const Text& aTextNode,
936 uint32_t aBaseOffset,
937 uint32_t aXPStartOffset,
938 uint32_t aXPEndOffset,
939 LineBreakType aLineBreakType) {
940 nsIFrame* frame = aTextNode.GetPrimaryFrame();
941 if (!frame) {
942 // It is a non-rendered content, create an empty range for it.
943 AppendFontRange(aFontRanges, aBaseOffset);
944 return;
947 uint32_t baseOffset = aBaseOffset;
948 #ifdef DEBUG
950 nsTextFrame* text = do_QueryFrame(frame);
951 MOZ_ASSERT(text, "Not a text frame");
953 #endif
954 auto* curr = static_cast<nsTextFrame*>(frame);
955 while (curr) {
956 uint32_t frameXPStart = std::max(
957 static_cast<uint32_t>(curr->GetContentOffset()), aXPStartOffset);
958 uint32_t frameXPEnd =
959 std::min(static_cast<uint32_t>(curr->GetContentEnd()), aXPEndOffset);
960 if (frameXPStart >= frameXPEnd) {
961 curr = curr->GetNextContinuation();
962 continue;
965 gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated);
966 gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated);
968 nsTextFrame* next = nullptr;
969 if (frameXPEnd < aXPEndOffset) {
970 next = curr->GetNextContinuation();
971 while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) {
972 frameXPEnd = std::min(static_cast<uint32_t>(next->GetContentEnd()),
973 aXPEndOffset);
974 next =
975 frameXPEnd < aXPEndOffset ? next->GetNextContinuation() : nullptr;
979 gfxTextRun::Range skipRange(iter.ConvertOriginalToSkipped(frameXPStart),
980 iter.ConvertOriginalToSkipped(frameXPEnd));
981 uint32_t lastXPEndOffset = frameXPStart;
982 for (gfxTextRun::GlyphRunIterator runIter(textRun, skipRange);
983 !runIter.AtEnd(); runIter.NextRun()) {
984 gfxFont* font = runIter.GlyphRun()->mFont.get();
985 uint32_t startXPOffset =
986 iter.ConvertSkippedToOriginal(runIter.StringStart());
987 // It is possible that the first glyph run has exceeded the frame,
988 // because the whole frame is filled by skipped chars.
989 if (startXPOffset >= frameXPEnd) {
990 break;
993 if (startXPOffset > lastXPEndOffset) {
994 // Create range for skipped leading chars.
995 AppendFontRange(aFontRanges, baseOffset);
996 baseOffset += GetTextLengthInRange(aTextNode, lastXPEndOffset,
997 startXPOffset, aLineBreakType);
1000 FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset);
1001 fontRange->mFontName.Append(NS_ConvertUTF8toUTF16(font->GetName()));
1003 ParentLayerToScreenScale2D cumulativeResolution =
1004 ParentLayerToParentLayerScale(
1005 frame->PresShell()->GetCumulativeResolution()) *
1006 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
1007 frame);
1008 float scale =
1009 std::max(cumulativeResolution.xScale, cumulativeResolution.yScale);
1011 fontRange->mFontSize = font->GetAdjustedSize() * scale;
1013 // The converted original offset may exceed the range,
1014 // hence we need to clamp it.
1015 uint32_t endXPOffset = iter.ConvertSkippedToOriginal(runIter.StringEnd());
1016 endXPOffset = std::min(frameXPEnd, endXPOffset);
1017 baseOffset += GetTextLengthInRange(aTextNode, startXPOffset, endXPOffset,
1018 aLineBreakType);
1019 lastXPEndOffset = endXPOffset;
1021 if (lastXPEndOffset < frameXPEnd) {
1022 // Create range for skipped trailing chars. It also handles case
1023 // that the whole frame contains only skipped chars.
1024 AppendFontRange(aFontRanges, baseOffset);
1025 baseOffset += GetTextLengthInRange(aTextNode, lastXPEndOffset, frameXPEnd,
1026 aLineBreakType);
1029 curr = next;
1033 nsresult ContentEventHandler::GenerateFlatFontRanges(
1034 const UnsafeSimpleRange& aSimpleRange, FontRangeArray& aFontRanges,
1035 uint32_t& aLength, LineBreakType aLineBreakType) {
1036 MOZ_ASSERT(aFontRanges.IsEmpty(), "aRanges must be empty array");
1038 if (aSimpleRange.Collapsed()) {
1039 return NS_OK;
1042 nsINode* startNode = aSimpleRange.GetStartContainer();
1043 nsINode* endNode = aSimpleRange.GetEndContainer();
1044 if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
1045 return NS_ERROR_FAILURE;
1048 // baseOffset is the flattened offset of each content node.
1049 uint32_t baseOffset = 0;
1050 UnsafePreContentIterator preOrderIter;
1051 nsresult rv = preOrderIter.Init(aSimpleRange.Start().AsRaw(),
1052 aSimpleRange.End().AsRaw());
1053 if (NS_WARN_IF(NS_FAILED(rv))) {
1054 return rv;
1056 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
1057 nsINode* node = preOrderIter.GetCurrentNode();
1058 if (NS_WARN_IF(!node)) {
1059 break;
1061 if (!node->IsContent()) {
1062 continue;
1064 nsIContent* content = node->AsContent();
1066 if (const Text* textNode = Text::FromNode(content)) {
1067 const uint32_t startOffset =
1068 textNode != startNode ? 0 : aSimpleRange.StartOffset();
1069 const uint32_t endOffset = textNode != endNode ? textNode->TextLength()
1070 : aSimpleRange.EndOffset();
1071 AppendFontRanges(aFontRanges, *textNode, baseOffset, startOffset,
1072 endOffset, aLineBreakType);
1073 baseOffset += GetTextLengthInRange(*textNode, startOffset, endOffset,
1074 aLineBreakType);
1075 } else if (ShouldBreakLineBefore(*content, mRootElement)) {
1076 if (aFontRanges.IsEmpty()) {
1077 MOZ_ASSERT(baseOffset == 0);
1078 FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset);
1079 if (nsIFrame* frame = content->GetPrimaryFrame()) {
1080 const nsFont& font = frame->GetParent()->StyleFont()->mFont;
1081 const StyleFontFamilyList& fontList = font.family.families;
1082 MOZ_ASSERT(!fontList.list.IsEmpty(), "Empty font family?");
1083 const StyleSingleFontFamily* fontName =
1084 fontList.list.IsEmpty() ? nullptr : &fontList.list.AsSpan()[0];
1085 nsAutoCString name;
1086 if (fontName) {
1087 fontName->AppendToString(name, false);
1089 AppendUTF8toUTF16(name, fontRange->mFontName);
1091 ParentLayerToScreenScale2D cumulativeResolution =
1092 ParentLayerToParentLayerScale(
1093 frame->PresShell()->GetCumulativeResolution()) *
1094 nsLayoutUtils::
1095 GetTransformToAncestorScaleCrossProcessForFrameMetrics(frame);
1097 float scale = std::max(cumulativeResolution.xScale,
1098 cumulativeResolution.yScale);
1100 fontRange->mFontSize = frame->PresContext()->CSSPixelsToDevPixels(
1101 font.size.ToCSSPixels() * scale);
1104 baseOffset += GetBRLength(aLineBreakType);
1108 aLength = baseOffset;
1109 return NS_OK;
1112 nsresult ContentEventHandler::ExpandToClusterBoundary(
1113 Text& aTextNode, bool aForward, uint32_t* aXPOffset) const {
1114 // XXX This method assumes that the frame boundaries must be cluster
1115 // boundaries. It's false, but no problem now, maybe.
1116 if (*aXPOffset == 0 || *aXPOffset == aTextNode.TextLength()) {
1117 return NS_OK;
1120 NS_ASSERTION(*aXPOffset <= aTextNode.TextLength(), "offset is out of range.");
1122 MOZ_DIAGNOSTIC_ASSERT(mDocument->GetPresShell());
1123 CaretAssociationHint hint =
1124 aForward ? CaretAssociationHint::Before : CaretAssociationHint::After;
1125 nsIFrame* frame = SelectionMovementUtils::GetFrameForNodeOffset(
1126 &aTextNode, int32_t(*aXPOffset), hint);
1127 if (frame) {
1128 auto [startOffset, endOffset] = frame->GetOffsets();
1129 if (*aXPOffset == static_cast<uint32_t>(startOffset) ||
1130 *aXPOffset == static_cast<uint32_t>(endOffset)) {
1131 return NS_OK;
1133 if (!frame->IsTextFrame()) {
1134 return NS_ERROR_FAILURE;
1136 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
1137 int32_t newOffsetInFrame = *aXPOffset - startOffset;
1138 newOffsetInFrame += aForward ? -1 : 1;
1139 // PeekOffsetCharacter() should respect cluster but ignore user-select
1140 // style. If it returns "FOUND", we should use the result. Otherwise,
1141 // we shouldn't use the result because the offset was moved to reversed
1142 // direction.
1143 nsTextFrame::PeekOffsetCharacterOptions options;
1144 options.mRespectClusters = true;
1145 options.mIgnoreUserStyleAll = true;
1146 if (textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame, options) ==
1147 nsIFrame::FOUND) {
1148 *aXPOffset = startOffset + newOffsetInFrame;
1149 return NS_OK;
1153 // If the frame isn't available, we only can check surrogate pair...
1154 if (aTextNode.TextFragment().IsLowSurrogateFollowingHighSurrogateAt(
1155 *aXPOffset)) {
1156 *aXPOffset += aForward ? 1 : -1;
1158 return NS_OK;
1161 template <typename RangeType, typename TextNodeType>
1162 Result<ContentEventHandler::DOMRangeAndAdjustedOffsetInFlattenedTextBase<
1163 RangeType, TextNodeType>,
1164 nsresult>
1165 ContentEventHandler::ConvertFlatTextOffsetToDOMRangeBase(
1166 uint32_t aOffset, uint32_t aLength, LineBreakType aLineBreakType,
1167 bool aExpandToClusterBoundaries) {
1168 DOMRangeAndAdjustedOffsetInFlattenedTextBase<RangeType, TextNodeType> result;
1169 result.mAdjustedOffset = aOffset;
1171 // Special case like <br contenteditable>
1172 if (!mRootElement->HasChildren()) {
1173 nsresult rv = result.mRange.CollapseTo(RawRangeBoundary(mRootElement, 0u));
1174 if (NS_WARN_IF(NS_FAILED(rv))) {
1175 return Err(rv);
1179 UnsafePreContentIterator preOrderIter;
1180 nsresult rv = preOrderIter.Init(mRootElement);
1181 if (NS_WARN_IF(NS_FAILED(rv))) {
1182 return Err(rv);
1185 uint32_t offset = 0;
1186 uint32_t endOffset = aOffset + aLength;
1187 bool startSet = false;
1188 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
1189 nsINode* node = preOrderIter.GetCurrentNode();
1190 if (NS_WARN_IF(!node)) {
1191 break;
1193 // FYI: mRootElement shouldn't cause any text. So, we can skip it simply.
1194 if (node == mRootElement || !node->IsContent()) {
1195 continue;
1197 nsIContent* const content = node->AsContent();
1198 Text* const contentAsText = Text::FromNode(content);
1200 if (contentAsText) {
1201 result.mLastTextNode = contentAsText;
1204 uint32_t textLength = contentAsText
1205 ? GetTextLength(*contentAsText, aLineBreakType)
1206 : (ShouldBreakLineBefore(*content, mRootElement)
1207 ? GetBRLength(aLineBreakType)
1208 : 0);
1209 if (!textLength) {
1210 continue;
1213 // When the start offset is in between accumulated offset and the last
1214 // offset of the node, the node is the start node of the range.
1215 if (!startSet && aOffset <= offset + textLength) {
1216 nsINode* startNode = nullptr;
1217 Maybe<uint32_t> startNodeOffset;
1218 if (contentAsText) {
1219 // Rule #1.1: [textNode or text[Node or textNode[
1220 uint32_t xpOffset = aOffset - offset;
1221 if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
1222 xpOffset = ConvertToXPOffset(*contentAsText, xpOffset);
1225 if (aExpandToClusterBoundaries) {
1226 const uint32_t oldXPOffset = xpOffset;
1227 nsresult rv =
1228 ExpandToClusterBoundary(*contentAsText, false, &xpOffset);
1229 if (NS_WARN_IF(NS_FAILED(rv))) {
1230 return Err(rv);
1232 // This is correct since a cluster shouldn't include line break.
1233 result.mAdjustedOffset -= (oldXPOffset - xpOffset);
1235 startNode = contentAsText;
1236 startNodeOffset = Some(xpOffset);
1237 } else if (aOffset < offset + textLength) {
1238 // Rule #1.2 [<element>
1239 startNode = content->GetParent();
1240 if (NS_WARN_IF(!startNode)) {
1241 return Err(NS_ERROR_FAILURE);
1243 startNodeOffset = startNode->ComputeIndexOf(content);
1244 if (NS_WARN_IF(startNodeOffset.isNothing())) {
1245 // The content is being removed from the parent!
1246 return Err(NS_ERROR_FAILURE);
1248 } else if (!content->HasChildren()) {
1249 // Rule #1.3: <element/>[
1250 startNode = content->GetParent();
1251 if (NS_WARN_IF(!startNode)) {
1252 return Err(NS_ERROR_FAILURE);
1254 startNodeOffset = startNode->ComputeIndexOf(content);
1255 if (NS_WARN_IF(startNodeOffset.isNothing())) {
1256 // The content is being removed from the parent!
1257 return Err(NS_ERROR_FAILURE);
1259 MOZ_ASSERT(*startNodeOffset != UINT32_MAX);
1260 ++(*startNodeOffset);
1261 } else {
1262 // Rule #1.4: <element>[
1263 startNode = content;
1264 startNodeOffset = Some(0);
1266 NS_ASSERTION(startNode, "startNode must not be nullptr");
1267 MOZ_ASSERT(startNodeOffset.isSome(),
1268 "startNodeOffset must not be Nothing");
1269 rv = result.mRange.SetStart(startNode, *startNodeOffset);
1270 if (NS_WARN_IF(NS_FAILED(rv))) {
1271 return Err(rv);
1273 startSet = true;
1275 if (!aLength) {
1276 rv = result.mRange.SetEnd(startNode, *startNodeOffset);
1277 if (NS_WARN_IF(NS_FAILED(rv))) {
1278 return Err(rv);
1280 return result;
1284 // When the end offset is in the content, the node is the end node of the
1285 // range.
1286 if (endOffset <= offset + textLength) {
1287 MOZ_ASSERT(startSet, "The start of the range should've been set already");
1288 if (contentAsText) {
1289 // Rule #2.1: ]textNode or text]Node or textNode]
1290 uint32_t xpOffset = endOffset - offset;
1291 if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
1292 const uint32_t xpOffsetCurrent =
1293 ConvertToXPOffset(*contentAsText, xpOffset);
1294 if (xpOffset && GetBRLength(aLineBreakType) > 1) {
1295 MOZ_ASSERT(GetBRLength(aLineBreakType) == 2);
1296 const uint32_t xpOffsetPre =
1297 ConvertToXPOffset(*contentAsText, xpOffset - 1);
1298 // If previous character's XP offset is same as current character's,
1299 // it means that the end offset is between \r and \n. So, the
1300 // range end should be after the \n.
1301 if (xpOffsetPre == xpOffsetCurrent) {
1302 xpOffset = xpOffsetCurrent + 1;
1303 } else {
1304 xpOffset = xpOffsetCurrent;
1308 if (aExpandToClusterBoundaries) {
1309 nsresult rv =
1310 ExpandToClusterBoundary(*contentAsText, true, &xpOffset);
1311 if (NS_WARN_IF(NS_FAILED(rv))) {
1312 return Err(rv);
1315 NS_ASSERTION(xpOffset <= INT32_MAX, "The end node offset is too large");
1316 nsresult rv = result.mRange.SetEnd(contentAsText, xpOffset);
1317 if (NS_WARN_IF(NS_FAILED(rv))) {
1318 return Err(rv);
1320 return result;
1323 if (endOffset == offset) {
1324 // Rule #2.2: ]<element>
1325 // NOTE: Please don't crash on release builds because it must be
1326 // overreaction but we shouldn't allow this bug when some
1327 // automated tests find this.
1328 MOZ_ASSERT(false,
1329 "This case should've already been handled at "
1330 "the last node which caused some text");
1331 return Err(NS_ERROR_FAILURE);
1334 if (content->HasChildren() &&
1335 ShouldBreakLineBefore(*content, mRootElement)) {
1336 // Rule #2.3: </element>]
1337 rv = result.mRange.SetEnd(content, 0);
1338 if (NS_WARN_IF(NS_FAILED(rv))) {
1339 return Err(rv);
1341 return result;
1344 // Rule #2.4: <element/>]
1345 nsINode* endNode = content->GetParent();
1346 if (NS_WARN_IF(!endNode)) {
1347 return Err(NS_ERROR_FAILURE);
1349 const Maybe<uint32_t> indexInParent = endNode->ComputeIndexOf(content);
1350 if (NS_WARN_IF(indexInParent.isNothing())) {
1351 // The content is being removed from the parent!
1352 return Err(NS_ERROR_FAILURE);
1354 MOZ_ASSERT(*indexInParent != UINT32_MAX);
1355 rv = result.mRange.SetEnd(endNode, *indexInParent + 1);
1356 if (NS_WARN_IF(NS_FAILED(rv))) {
1357 return Err(rv);
1359 return result;
1362 offset += textLength;
1365 if (!startSet) {
1366 if (!offset) {
1367 // Rule #1.5: <root>[</root>
1368 // When there are no nodes causing text, the start of the DOM range
1369 // should be start of the root node since clicking on such editor (e.g.,
1370 // <div contenteditable><span></span></div>) sets caret to the start of
1371 // the editor (i.e., before <span> in the example).
1372 rv = result.mRange.SetStart(mRootElement, 0);
1373 if (NS_WARN_IF(NS_FAILED(rv))) {
1374 return Err(rv);
1376 if (!aLength) {
1377 rv = result.mRange.SetEnd(mRootElement, 0);
1378 if (NS_WARN_IF(NS_FAILED(rv))) {
1379 return Err(rv);
1381 return result;
1383 } else {
1384 // Rule #1.5: [</root>
1385 rv = result.mRange.SetStart(mRootElement, mRootElement->GetChildCount());
1386 if (NS_WARN_IF(NS_FAILED(rv))) {
1387 return result;
1390 result.mAdjustedOffset = offset;
1392 // Rule #2.5: ]</root>
1393 rv = result.mRange.SetEnd(mRootElement, mRootElement->GetChildCount());
1394 if (NS_WARN_IF(NS_FAILED(rv))) {
1395 return Err(rv);
1397 return result;
1400 /* static */
1401 LineBreakType ContentEventHandler::GetLineBreakType(
1402 WidgetQueryContentEvent* aEvent) {
1403 return GetLineBreakType(aEvent->mUseNativeLineBreak);
1406 /* static */
1407 LineBreakType ContentEventHandler::GetLineBreakType(
1408 WidgetSelectionEvent* aEvent) {
1409 return GetLineBreakType(aEvent->mUseNativeLineBreak);
1412 /* static */
1413 LineBreakType ContentEventHandler::GetLineBreakType(bool aUseNativeLineBreak) {
1414 return aUseNativeLineBreak ? LINE_BREAK_TYPE_NATIVE : LINE_BREAK_TYPE_XP;
1417 nsresult ContentEventHandler::HandleQueryContentEvent(
1418 WidgetQueryContentEvent* aEvent) {
1419 nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
1420 switch (aEvent->mMessage) {
1421 case eQuerySelectedText:
1422 rv = OnQuerySelectedText(aEvent);
1423 break;
1424 case eQueryTextContent:
1425 rv = OnQueryTextContent(aEvent);
1426 break;
1427 case eQueryCaretRect:
1428 rv = OnQueryCaretRect(aEvent);
1429 break;
1430 case eQueryTextRect:
1431 rv = OnQueryTextRect(aEvent);
1432 break;
1433 case eQueryTextRectArray:
1434 rv = OnQueryTextRectArray(aEvent);
1435 break;
1436 case eQueryEditorRect:
1437 rv = OnQueryEditorRect(aEvent);
1438 break;
1439 case eQueryContentState:
1440 rv = OnQueryContentState(aEvent);
1441 break;
1442 case eQuerySelectionAsTransferable:
1443 rv = OnQuerySelectionAsTransferable(aEvent);
1444 break;
1445 case eQueryCharacterAtPoint:
1446 rv = OnQueryCharacterAtPoint(aEvent);
1447 break;
1448 case eQueryDOMWidgetHittest:
1449 rv = OnQueryDOMWidgetHittest(aEvent);
1450 break;
1451 default:
1452 break;
1454 if (NS_FAILED(rv)) {
1455 aEvent->mReply.reset(); // Mark the query failed.
1456 return rv;
1459 MOZ_ASSERT(aEvent->Succeeded());
1460 return NS_OK;
1463 // Similar to nsFrameSelection::GetFrameForNodeOffset,
1464 // but this is more flexible for OnQueryTextRect to use
1465 static Result<nsIFrame*, nsresult> GetFrameForTextRect(const nsINode* aNode,
1466 int32_t aNodeOffset,
1467 bool aHint) {
1468 const nsIContent* content = nsIContent::FromNodeOrNull(aNode);
1469 if (NS_WARN_IF(!content)) {
1470 return Err(NS_ERROR_UNEXPECTED);
1472 nsIFrame* frame = content->GetPrimaryFrame();
1473 // The node may be invisible, e.g., `display: none`, invisible text node
1474 // around block elements, etc. Therefore, don't warn when we don't find
1475 // a primary frame.
1476 if (!frame) {
1477 return nullptr;
1479 int32_t childNodeOffset = 0;
1480 nsIFrame* returnFrame = nullptr;
1481 nsresult rv = frame->GetChildFrameContainingOffset(
1482 aNodeOffset, aHint, &childNodeOffset, &returnFrame);
1483 if (NS_FAILED(rv)) {
1484 return Err(rv);
1486 return returnFrame;
1489 nsresult ContentEventHandler::OnQuerySelectedText(
1490 WidgetQueryContentEvent* aEvent) {
1491 nsresult rv = Init(aEvent);
1492 if (NS_FAILED(rv)) {
1493 return rv;
1496 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
1498 if (!mFirstSelectedSimpleRange.IsPositioned()) {
1499 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
1500 MOZ_ASSERT_IF(mSelection, !mSelection->RangeCount());
1501 // This is special case that `mReply` is emplaced, but mOffsetAndData is
1502 // not emplaced but treated as succeeded because of no selection ranges
1503 // is a usual case.
1504 return NS_OK;
1507 const UnsafeSimpleRange firstSelectedSimpleRange(mFirstSelectedSimpleRange);
1508 nsINode* const startNode = firstSelectedSimpleRange.GetStartContainer();
1509 nsINode* const endNode = firstSelectedSimpleRange.GetEndContainer();
1511 // Make sure the selection is within the root content range.
1512 if (!startNode->IsInclusiveDescendantOf(mRootElement) ||
1513 !endNode->IsInclusiveDescendantOf(mRootElement)) {
1514 return NS_ERROR_NOT_AVAILABLE;
1517 LineBreakType lineBreakType = GetLineBreakType(aEvent);
1518 uint32_t startOffset = 0;
1519 if (NS_WARN_IF(NS_FAILED(GetStartOffset(firstSelectedSimpleRange,
1520 &startOffset, lineBreakType)))) {
1521 return NS_ERROR_FAILURE;
1524 const RawRangeBoundary anchorRef = mSelection->RangeCount() > 0
1525 ? mSelection->AnchorRef().AsRaw()
1526 : firstSelectedSimpleRange.Start();
1527 const RawRangeBoundary focusRef = mSelection->RangeCount() > 0
1528 ? mSelection->FocusRef().AsRaw()
1529 : firstSelectedSimpleRange.End();
1530 if (NS_WARN_IF(!anchorRef.IsSet()) || NS_WARN_IF(!focusRef.IsSet())) {
1531 return NS_ERROR_FAILURE;
1534 if (mSelection->RangeCount()) {
1535 // If there is only one selection range, the anchor/focus node and offset
1536 // are the information of the range. Therefore, we have the direction
1537 // information.
1538 if (mSelection->RangeCount() == 1) {
1539 // The selection's points should always be comparable, independent of the
1540 // selection (see nsISelectionController.idl).
1541 Maybe<int32_t> compare =
1542 nsContentUtils::ComparePoints(anchorRef, focusRef);
1543 if (compare.isNothing()) {
1544 return NS_ERROR_FAILURE;
1547 aEvent->mReply->mReversed = compare.value() > 0;
1549 // However, if there are 2 or more selection ranges, we have no information
1550 // of that.
1551 else {
1552 aEvent->mReply->mReversed = false;
1555 nsString selectedString;
1556 if (!firstSelectedSimpleRange.Collapsed() &&
1557 NS_WARN_IF(NS_FAILED(GenerateFlatTextContent(
1558 firstSelectedSimpleRange, selectedString, lineBreakType)))) {
1559 return NS_ERROR_FAILURE;
1561 aEvent->mReply->mOffsetAndData.emplace(startOffset, selectedString,
1562 OffsetAndDataFor::SelectedString);
1563 } else {
1564 NS_ASSERTION(anchorRef == focusRef,
1565 "When mSelection doesn't have selection, "
1566 "mFirstSelectedRawRange must be collapsed");
1568 aEvent->mReply->mReversed = false;
1569 aEvent->mReply->mOffsetAndData.emplace(startOffset, EmptyString(),
1570 OffsetAndDataFor::SelectedString);
1573 Result<nsIFrame*, nsresult> frameForTextRectOrError = GetFrameForTextRect(
1574 focusRef.Container(),
1575 focusRef.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets).valueOr(0),
1576 true);
1577 if (NS_WARN_IF(frameForTextRectOrError.isErr()) ||
1578 !frameForTextRectOrError.inspect()) {
1579 aEvent->mReply->mWritingMode = WritingMode();
1580 } else {
1581 aEvent->mReply->mWritingMode =
1582 frameForTextRectOrError.inspect()->GetWritingMode();
1585 MOZ_ASSERT(aEvent->Succeeded());
1586 return NS_OK;
1589 nsresult ContentEventHandler::OnQueryTextContent(
1590 WidgetQueryContentEvent* aEvent) {
1591 nsresult rv = Init(aEvent);
1592 if (NS_FAILED(rv)) {
1593 return rv;
1596 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
1598 LineBreakType lineBreakType = GetLineBreakType(aEvent);
1600 Result<UnsafeDOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
1601 domRangeAndAdjustedOffsetOrError = ConvertFlatTextOffsetToUnsafeDOMRange(
1602 aEvent->mInput.mOffset, aEvent->mInput.mLength, lineBreakType, false);
1603 if (MOZ_UNLIKELY(domRangeAndAdjustedOffsetOrError.isErr())) {
1604 NS_WARNING(
1605 "ContentEventHandler::ConvertFlatTextOffsetToDOMRangeBase() failed");
1606 return NS_ERROR_FAILURE;
1608 const UnsafeDOMRangeAndAdjustedOffsetInFlattenedText
1609 domRangeAndAdjustedOffset = domRangeAndAdjustedOffsetOrError.unwrap();
1611 nsString textInRange;
1612 if (NS_WARN_IF(NS_FAILED(GenerateFlatTextContent(
1613 domRangeAndAdjustedOffset.mRange, textInRange, lineBreakType)))) {
1614 return NS_ERROR_FAILURE;
1617 aEvent->mReply->mOffsetAndData.emplace(
1618 domRangeAndAdjustedOffset.mAdjustedOffset, textInRange,
1619 OffsetAndDataFor::EditorString);
1621 if (aEvent->mWithFontRanges) {
1622 uint32_t fontRangeLength;
1623 if (NS_WARN_IF(NS_FAILED(GenerateFlatFontRanges(
1624 domRangeAndAdjustedOffset.mRange, aEvent->mReply->mFontRanges,
1625 fontRangeLength, lineBreakType)))) {
1626 return NS_ERROR_FAILURE;
1629 MOZ_ASSERT(fontRangeLength == aEvent->mReply->DataLength(),
1630 "Font ranges doesn't match the string");
1633 MOZ_ASSERT(aEvent->Succeeded());
1634 return NS_OK;
1637 void ContentEventHandler::EnsureNonEmptyRect(nsRect& aRect) const {
1638 // See the comment in ContentEventHandler.h why this doesn't set them to
1639 // one device pixel.
1640 aRect.height = std::max(1, aRect.height);
1641 aRect.width = std::max(1, aRect.width);
1644 void ContentEventHandler::EnsureNonEmptyRect(LayoutDeviceIntRect& aRect) const {
1645 aRect.height = std::max(1, aRect.height);
1646 aRect.width = std::max(1, aRect.width);
1649 template <typename NodeType, typename RangeBoundaryType>
1650 ContentEventHandler::FrameAndNodeOffset
1651 ContentEventHandler::GetFirstFrameInRangeForTextRect(
1652 const SimpleRangeBase<NodeType, RangeBoundaryType>& aSimpleRange) {
1653 RawNodePosition nodePosition;
1654 UnsafePreContentIterator preOrderIter;
1655 nsresult rv = preOrderIter.Init(aSimpleRange.Start().AsRaw(),
1656 aSimpleRange.End().AsRaw());
1657 if (NS_WARN_IF(NS_FAILED(rv))) {
1658 return FrameAndNodeOffset();
1660 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
1661 nsINode* node = preOrderIter.GetCurrentNode();
1662 if (NS_WARN_IF(!node)) {
1663 break;
1666 auto* content = nsIContent::FromNode(node);
1667 if (MOZ_UNLIKELY(!content)) {
1668 continue;
1671 // If the node is invisible (e.g., the node is or is in an invisible node or
1672 // it's a white-space only text node around a block boundary), we should
1673 // ignore it.
1674 if (!content->GetPrimaryFrame()) {
1675 continue;
1678 if (auto* textNode = Text::FromNode(content)) {
1679 // If the range starts at the end of a text node, we need to find
1680 // next node which causes text.
1681 const uint32_t offsetInNode = textNode == aSimpleRange.GetStartContainer()
1682 ? aSimpleRange.StartOffset()
1683 : 0u;
1684 if (offsetInNode < textNode->TextDataLength()) {
1685 nodePosition = {textNode, offsetInNode};
1686 break;
1688 continue;
1691 // If the element node causes a line break before it, it's the first
1692 // node causing text.
1693 if (ShouldBreakLineBefore(*content, mRootElement) ||
1694 IsPaddingBR(*content)) {
1695 nodePosition = {content, 0u};
1699 if (!nodePosition.IsSetAndValid()) {
1700 return FrameAndNodeOffset();
1703 Result<nsIFrame*, nsresult> firstFrameOrError = GetFrameForTextRect(
1704 nodePosition.Container(),
1705 *nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets), true);
1706 if (NS_WARN_IF(firstFrameOrError.isErr()) || !firstFrameOrError.inspect()) {
1707 return FrameAndNodeOffset();
1709 return FrameAndNodeOffset(
1710 firstFrameOrError.inspect(),
1711 *nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets));
1714 template <typename NodeType, typename RangeBoundaryType>
1715 ContentEventHandler::FrameAndNodeOffset
1716 ContentEventHandler::GetLastFrameInRangeForTextRect(
1717 const SimpleRangeBase<NodeType, RangeBoundaryType>& aSimpleRange) {
1718 RawNodePosition nodePosition;
1719 UnsafePreContentIterator preOrderIter;
1720 nsresult rv = preOrderIter.Init(aSimpleRange.Start().AsRaw(),
1721 aSimpleRange.End().AsRaw());
1722 if (NS_WARN_IF(NS_FAILED(rv))) {
1723 return FrameAndNodeOffset();
1726 const RangeBoundaryType& endPoint = aSimpleRange.End();
1727 MOZ_ASSERT(endPoint.IsSetAndValid());
1728 // If the end point is start of a text node or specified by its parent and
1729 // index, the node shouldn't be included into the range. For example,
1730 // with this case, |<p>abc[<br>]def</p>|, the range ends at 3rd children of
1731 // <p> (see the range creation rules, "2.4. Cases: <element/>]"). This causes
1732 // following frames:
1733 // +----+-----+
1734 // | abc|[<br>|
1735 // +----+-----+
1736 // +----+
1737 // |]def|
1738 // +----+
1739 // So, if this method includes the 2nd text frame's rect to its result, the
1740 // caller will return too tall rect which includes 2 lines in this case isn't
1741 // expected by native IME (e.g., popup of IME will be positioned at bottom
1742 // of "d" instead of right-bottom of "c"). Therefore, this method shouldn't
1743 // include the last frame when its content isn't really in aSimpleRange.
1744 nsINode* nextNodeOfRangeEnd = nullptr;
1745 if (endPoint.Container()->IsText()) {
1746 // Don't set nextNodeOfRangeEnd to the start node of aSimpleRange because if
1747 // the container of the end is same as start node of the range, the text
1748 // node shouldn't be next of range end even if the offset is 0. This
1749 // could occur with empty text node.
1750 if (endPoint.IsStartOfContainer() &&
1751 aSimpleRange.GetStartContainer() != endPoint.Container()) {
1752 nextNodeOfRangeEnd = endPoint.Container();
1754 } else if (endPoint.IsSetAndValid()) {
1755 nextNodeOfRangeEnd = endPoint.GetChildAtOffset();
1758 for (preOrderIter.Last(); !preOrderIter.IsDone(); preOrderIter.Prev()) {
1759 nsINode* node = preOrderIter.GetCurrentNode();
1760 if (NS_WARN_IF(!node)) {
1761 break;
1764 if (node == nextNodeOfRangeEnd) {
1765 continue;
1768 auto* content = nsIContent::FromNode(node);
1769 if (MOZ_UNLIKELY(!content)) {
1770 continue;
1773 // If the node is invisible (e.g., the node is or is in an invisible node or
1774 // it's a white-space only text node around a block boundary), we should
1775 // ignore it.
1776 if (!content->GetPrimaryFrame()) {
1777 continue;
1780 if (auto* textNode = Text::FromNode(node)) {
1781 nodePosition = {textNode, textNode == aSimpleRange.GetEndContainer()
1782 ? aSimpleRange.EndOffset()
1783 : textNode->TextDataLength()};
1785 // If the text node is empty or the last node of the range but the index
1786 // is 0, we should store current position but continue looking for
1787 // previous node (If there are no nodes before it, we should use current
1788 // node position for returning its frame).
1789 if (*nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets) ==
1790 0) {
1791 continue;
1793 break;
1796 if (ShouldBreakLineBefore(*content, mRootElement) ||
1797 IsPaddingBR(*content)) {
1798 nodePosition = {content, 0u};
1799 break;
1803 if (!nodePosition.IsSet()) {
1804 return FrameAndNodeOffset();
1807 Result<nsIFrame*, nsresult> lastFrameOrError = GetFrameForTextRect(
1808 nodePosition.Container(),
1809 *nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets), true);
1810 if (NS_WARN_IF(lastFrameOrError.isErr()) || !lastFrameOrError.inspect()) {
1811 return FrameAndNodeOffset();
1814 // If the last frame is a text frame, we need to check if the range actually
1815 // includes at least one character in the range. Therefore, if it's not a
1816 // text frame, we need to do nothing anymore.
1817 if (!lastFrameOrError.inspect()->IsTextFrame()) {
1818 return FrameAndNodeOffset(
1819 lastFrameOrError.inspect(),
1820 *nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets));
1823 int32_t start = lastFrameOrError.inspect()->GetOffsets().first;
1825 // If the start offset in the node is same as the computed offset in the
1826 // node and it's not 0, the frame shouldn't be added to the text rect. So,
1827 // this should return previous text frame and its last offset if there is
1828 // at least one text frame.
1829 if (*nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets) &&
1830 *nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets) ==
1831 static_cast<uint32_t>(start)) {
1832 const uint32_t newNodePositionOffset =
1833 *nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets);
1834 MOZ_ASSERT(newNodePositionOffset != 0);
1835 nodePosition = {nodePosition.Container(), newNodePositionOffset - 1u};
1836 lastFrameOrError = GetFrameForTextRect(
1837 nodePosition.Container(),
1838 *nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets),
1839 true);
1840 if (NS_WARN_IF(lastFrameOrError.isErr()) || !lastFrameOrError.inspect()) {
1841 return FrameAndNodeOffset();
1845 return FrameAndNodeOffset(
1846 lastFrameOrError.inspect(),
1847 *nodePosition.Offset(RawNodePosition::OffsetFilter::kValidOffsets));
1850 ContentEventHandler::FrameRelativeRect
1851 ContentEventHandler::GetLineBreakerRectBefore(nsIFrame* aFrame) {
1852 // Note that this method should be called only with an element's frame whose
1853 // open tag causes a line break or moz-<br> for computing empty last line's
1854 // rect.
1855 MOZ_ASSERT(aFrame->GetContent());
1856 MOZ_ASSERT(ShouldBreakLineBefore(*aFrame->GetContent(), mRootElement) ||
1857 IsPaddingBR(*aFrame->GetContent()));
1859 nsIFrame* frameForFontMetrics = aFrame;
1861 // If it's not a <br> frame, this method computes the line breaker's rect
1862 // outside the frame. Therefore, we need to compute with parent frame's
1863 // font metrics in such case.
1864 if (!aFrame->IsBrFrame() && aFrame->GetParent()) {
1865 frameForFontMetrics = aFrame->GetParent();
1868 // Note that <br> element's rect is decided with line-height but we need
1869 // a rect only with font height. Additionally, <br> frame's width and
1870 // height are 0 in quirks mode if it's not an empty line. So, we cannot
1871 // use frame rect information even if it's a <br> frame.
1873 RefPtr<nsFontMetrics> fontMetrics =
1874 nsLayoutUtils::GetInflatedFontMetricsForFrame(frameForFontMetrics);
1875 if (NS_WARN_IF(!fontMetrics)) {
1876 return FrameRelativeRect();
1879 const WritingMode kWritingMode = frameForFontMetrics->GetWritingMode();
1881 auto caretBlockAxisMetrics =
1882 aFrame->GetCaretBlockAxisMetrics(kWritingMode, *fontMetrics);
1883 nscoord inlineOffset = 0;
1885 // If aFrame isn't a <br> frame, caret should be at outside of it because
1886 // the line break is before its open tag. For example, case of
1887 // |<div><p>some text</p></div>|, caret is before <p> element and in <div>
1888 // element, the caret should be left of top-left corner of <p> element like:
1890 // +-<div>------------------- <div>'s border box
1891 // | I +-<p>----------------- <p>'s border box
1892 // | I |
1893 // | I |
1894 // | |
1895 // ^- caret
1897 // However, this is a hack for unusual scenario. This hack shouldn't be
1898 // used as far as possible.
1899 if (!aFrame->IsBrFrame()) {
1900 if (kWritingMode.IsVertical() && !kWritingMode.IsLineInverted()) {
1901 // above of top-right corner of aFrame.
1902 caretBlockAxisMetrics.mOffset =
1903 aFrame->GetRect().XMost() - caretBlockAxisMetrics.mExtent;
1904 } else {
1905 // above (For vertical) or left (For horizontal) of top-left corner of
1906 // aFrame.
1907 caretBlockAxisMetrics.mOffset = 0;
1909 inlineOffset = -aFrame->PresContext()->AppUnitsPerDevPixel();
1911 FrameRelativeRect result(aFrame);
1912 if (kWritingMode.IsVertical()) {
1913 result.mRect.x = caretBlockAxisMetrics.mOffset;
1914 result.mRect.y = inlineOffset;
1915 result.mRect.width = caretBlockAxisMetrics.mExtent;
1916 } else {
1917 result.mRect.x = inlineOffset;
1918 result.mRect.y = caretBlockAxisMetrics.mOffset;
1919 result.mRect.height = caretBlockAxisMetrics.mExtent;
1921 return result;
1924 ContentEventHandler::FrameRelativeRect
1925 ContentEventHandler::GuessLineBreakerRectAfter(const Text& aTextNode) {
1926 FrameRelativeRect result;
1927 const int32_t length = static_cast<int32_t>(aTextNode.TextLength());
1928 if (NS_WARN_IF(length < 0)) {
1929 return result;
1931 // Get the last nsTextFrame which is caused by aTextNode. Note that
1932 // a text node can cause multiple text frames, e.g., the text is too long
1933 // and wrapped by its parent block or the text has line breakers and its
1934 // white-space property respects the line breakers (e.g., |pre|).
1935 Result<nsIFrame*, nsresult> lastTextFrameOrError =
1936 GetFrameForTextRect(&aTextNode, length, true);
1937 if (NS_WARN_IF(lastTextFrameOrError.isErr()) ||
1938 !lastTextFrameOrError.inspect()) {
1939 return result;
1941 const nsRect kLastTextFrameRect = lastTextFrameOrError.inspect()->GetRect();
1942 if (lastTextFrameOrError.inspect()->GetWritingMode().IsVertical()) {
1943 // Below of the last text frame.
1944 result.mRect.SetRect(0, kLastTextFrameRect.height, kLastTextFrameRect.width,
1946 } else {
1947 // Right of the last text frame (not bidi-aware).
1948 result.mRect.SetRect(kLastTextFrameRect.width, 0, 0,
1949 kLastTextFrameRect.height);
1951 result.mBaseFrame = lastTextFrameOrError.unwrap();
1952 return result;
1955 ContentEventHandler::FrameRelativeRect
1956 ContentEventHandler::GuessFirstCaretRectIn(nsIFrame* aFrame) {
1957 const WritingMode kWritingMode = aFrame->GetWritingMode();
1958 nsPresContext* presContext = aFrame->PresContext();
1960 // Computes the font height, but if it's not available, we should use
1961 // default font size of Firefox. The default font size in default settings
1962 // is 16px.
1963 RefPtr<nsFontMetrics> fontMetrics =
1964 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
1965 const nscoord kMaxHeight = fontMetrics
1966 ? fontMetrics->MaxHeight()
1967 : 16 * presContext->AppUnitsPerDevPixel();
1969 nsRect caretRect;
1970 const nsRect kContentRect = aFrame->GetContentRect() - aFrame->GetPosition();
1971 caretRect.y = kContentRect.y;
1972 if (!kWritingMode.IsVertical()) {
1973 if (kWritingMode.IsBidiLTR()) {
1974 caretRect.x = kContentRect.x;
1975 } else {
1976 // Move 1px left for the space of caret itself.
1977 const nscoord kOnePixel = presContext->AppUnitsPerDevPixel();
1978 caretRect.x = kContentRect.XMost() - kOnePixel;
1980 caretRect.height = kMaxHeight;
1981 // However, don't add kOnePixel here because it may cause 2px width at
1982 // aligning the edge to device pixels.
1983 caretRect.width = 1;
1984 } else {
1985 if (kWritingMode.IsVerticalLR()) {
1986 caretRect.x = kContentRect.x;
1987 } else {
1988 caretRect.x = kContentRect.XMost() - kMaxHeight;
1990 caretRect.width = kMaxHeight;
1991 // Don't add app units for a device pixel because it may cause 2px height
1992 // at aligning the edge to device pixels.
1993 caretRect.height = 1;
1995 return FrameRelativeRect(caretRect, aFrame);
1998 // static
1999 LayoutDeviceIntRect ContentEventHandler::GetCaretRectBefore(
2000 const LayoutDeviceIntRect& aCharRect, const WritingMode& aWritingMode) {
2001 LayoutDeviceIntRect caretRectBefore(aCharRect);
2002 if (aWritingMode.IsVertical()) {
2003 caretRectBefore.height = 1;
2004 } else {
2005 // TODO: Make here bidi-aware.
2006 caretRectBefore.width = 1;
2008 return caretRectBefore;
2011 // static
2012 nsRect ContentEventHandler::GetCaretRectBefore(
2013 const nsRect& aCharRect, const WritingMode& aWritingMode) {
2014 nsRect caretRectBefore(aCharRect);
2015 if (aWritingMode.IsVertical()) {
2016 // For making the height 1 device pixel after aligning the rect edges to
2017 // device pixels, don't add one device pixel in app units here.
2018 caretRectBefore.height = 1;
2019 } else {
2020 // TODO: Make here bidi-aware.
2021 // For making the width 1 device pixel after aligning the rect edges to
2022 // device pixels, don't add one device pixel in app units here.
2023 caretRectBefore.width = 1;
2025 return caretRectBefore;
2028 // static
2029 LayoutDeviceIntRect ContentEventHandler::GetCaretRectAfter(
2030 const LayoutDeviceIntRect& aCharRect, const WritingMode& aWritingMode) {
2031 LayoutDeviceIntRect caretRectAfter(aCharRect);
2032 if (aWritingMode.IsVertical()) {
2033 caretRectAfter.y = aCharRect.YMost() + 1;
2034 caretRectAfter.height = 1;
2035 } else {
2036 // TODO: Make here bidi-aware.
2037 caretRectAfter.x = aCharRect.XMost() + 1;
2038 caretRectAfter.width = 1;
2040 return caretRectAfter;
2043 // static
2044 nsRect ContentEventHandler::GetCaretRectAfter(nsPresContext& aPresContext,
2045 const nsRect& aCharRect,
2046 const WritingMode& aWritingMode) {
2047 nsRect caretRectAfter(aCharRect);
2048 const nscoord onePixel = aPresContext.AppUnitsPerDevPixel();
2049 if (aWritingMode.IsVertical()) {
2050 caretRectAfter.y = aCharRect.YMost() + onePixel;
2051 // For making the height 1 device pixel after aligning the rect edges to
2052 // device pixels, don't add one device pixel in app units here.
2053 caretRectAfter.height = 1;
2054 } else {
2055 // TODO: Make here bidi-aware.
2056 caretRectAfter.x = aCharRect.XMost() + onePixel;
2057 // For making the width 1 device pixel after aligning the rect edges to
2058 // device pixels, don't add one device pixel in app units here.
2059 caretRectAfter.width = 1;
2061 return caretRectAfter;
2064 nsresult ContentEventHandler::OnQueryTextRectArray(
2065 WidgetQueryContentEvent* aEvent) {
2066 nsresult rv = Init(aEvent);
2067 if (NS_WARN_IF(NS_FAILED(rv))) {
2068 return rv;
2071 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
2073 LineBreakType lineBreakType = GetLineBreakType(aEvent);
2074 const uint32_t kBRLength = GetBRLength(lineBreakType);
2076 WritingMode lastVisibleFrameWritingMode;
2077 LayoutDeviceIntRect rect;
2078 uint32_t offset = aEvent->mInput.mOffset;
2079 const uint32_t kEndOffset = aEvent->mInput.EndOffset();
2080 bool wasLineBreaker = false;
2081 // lastCharRect stores the last charRect value (see below for the detail of
2082 // charRect).
2083 nsRect lastCharRect;
2084 // lastFrame is base frame of lastCharRect.
2085 // TODO: We should look for this if the first text is not visible. However,
2086 // users cannot put caret invisible text and users cannot type in it
2087 // at least only with user's operations. Therefore, we don't need to
2088 // fix this immediately.
2089 nsIFrame* lastFrame = nullptr;
2090 nsAutoString flattenedAllText;
2091 flattenedAllText.SetIsVoid(true);
2092 while (offset < kEndOffset) {
2093 Result<DOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
2094 domRangeAndAdjustedOffsetOrError =
2095 ConvertFlatTextOffsetToDOMRange(offset, 1, lineBreakType, true);
2096 if (MOZ_UNLIKELY(domRangeAndAdjustedOffsetOrError.isErr())) {
2097 NS_WARNING(
2098 "ContentEventHandler::ConvertFlatTextOffsetToDOMRangeBase() failed");
2099 return domRangeAndAdjustedOffsetOrError.unwrapErr();
2101 const DOMRangeAndAdjustedOffsetInFlattenedText domRangeAndAdjustedOffset =
2102 domRangeAndAdjustedOffsetOrError.unwrap();
2104 // TODO: When we crossed parent block boundary now, we should fill pending
2105 // character rects with caret rect after the last visible character
2106 // rect.
2108 // If the range is collapsed, offset has already reached the end of the
2109 // contents.
2110 if (domRangeAndAdjustedOffset.mRange.Collapsed()) {
2111 break;
2114 // Get the first frame which causes some text after the offset.
2115 FrameAndNodeOffset firstFrame =
2116 GetFirstFrameInRangeForTextRect(domRangeAndAdjustedOffset.mRange);
2118 // If GetFirstFrameInRangeForTextRect() does not return valid frame, that
2119 // means that the offset reached the end of contents or there is no visible
2120 // frame in the range generating flattened text.
2121 if (!firstFrame.IsValid()) {
2122 if (flattenedAllText.IsVoid()) {
2123 flattenedAllText.SetIsVoid(false);
2124 if (NS_WARN_IF(NS_FAILED(GenerateFlatTextContent(
2125 mRootElement, flattenedAllText, lineBreakType)))) {
2126 NS_WARNING("ContentEventHandler::GenerateFlatTextContent() failed");
2127 return NS_ERROR_FAILURE;
2130 // If we've reached end of the root, append caret rect at the end of
2131 // the root later.
2132 if (offset >= flattenedAllText.Length()) {
2133 break;
2135 // Otherwise, we're in an invisible node. If the node is followed by a
2136 // block boundary causing a line break, we can use the boundary.
2137 // Otherwise, if the node follows a block boundary of a parent block, we
2138 // can use caret rect at previous visible frame causing flattened text.
2139 const uint32_t remainingLengthInCurrentRange = [&]() {
2140 if (domRangeAndAdjustedOffset.mLastTextNode) {
2141 if (domRangeAndAdjustedOffset.RangeStartsFromLastTextNode()) {
2142 if (!domRangeAndAdjustedOffset.RangeStartsFromEndOfContainer()) {
2143 return domRangeAndAdjustedOffset.mLastTextNode->TextDataLength() -
2144 domRangeAndAdjustedOffset.mRange.StartOffset();
2146 return 0u;
2148 // Must be there are not nodes which may cause generating text.
2149 // Therefore, we can skip all nodes before the last found text node
2150 // and all text in the last text node.
2151 return domRangeAndAdjustedOffset.mLastTextNode->TextDataLength();
2153 if (domRangeAndAdjustedOffset.RangeStartsFromContent() &&
2154 ShouldBreakLineBefore(
2155 *domRangeAndAdjustedOffset.mRange.GetStartContainer()
2156 ->AsContent(),
2157 mRootElement)) {
2158 if (kBRLength != 1u && offset - aEvent->mInput.mOffset < kBRLength) {
2159 // Don't return kBRLength if start position is less than the length
2160 // of a line-break because the offset may be between CRLF on
2161 // Windows. In the case, we will be again here and gets same
2162 // result and we need to pay the penalty only once. Therefore, we
2163 // can keep going without complicated check.
2164 return 1u;
2166 return kBRLength;
2168 return 0u;
2169 }();
2170 offset += std::max(1u, remainingLengthInCurrentRange);
2171 continue;
2174 nsIContent* firstContent = firstFrame.mFrame->GetContent();
2175 if (NS_WARN_IF(!firstContent)) {
2176 return NS_ERROR_FAILURE;
2179 bool startsBetweenLineBreaker = false;
2180 nsAutoString chars;
2181 lastVisibleFrameWritingMode = firstFrame->GetWritingMode();
2183 nsIFrame* baseFrame = firstFrame;
2184 // charRect should have each character rect or line breaker rect relative
2185 // to the base frame.
2186 AutoTArray<nsRect, 16> charRects;
2188 // If the first frame is a text frame, the result should be computed with
2189 // the frame's API.
2190 if (firstFrame->IsTextFrame()) {
2191 rv = firstFrame->GetCharacterRectsInRange(firstFrame.mOffsetInNode,
2192 kEndOffset - offset, charRects);
2193 if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(charRects.IsEmpty())) {
2194 return rv;
2196 // Assign the characters whose rects are computed by the call of
2197 // nsTextFrame::GetCharacterRectsInRange().
2198 AppendSubString(chars, *firstContent->AsText(), firstFrame.mOffsetInNode,
2199 charRects.Length());
2200 if (NS_WARN_IF(chars.Length() != charRects.Length())) {
2201 return NS_ERROR_UNEXPECTED;
2203 if (kBRLength > 1 && chars[0] == '\n' &&
2204 offset == aEvent->mInput.mOffset && offset) {
2205 // If start of range starting from previous offset of query range is
2206 // same as the start of query range, the query range starts from
2207 // between a line breaker (i.e., the range starts between "\r" and
2208 // "\n").
2209 Result<UnsafeDOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
2210 domRangeAndAdjustedOffsetOrError =
2211 ConvertFlatTextOffsetToUnsafeDOMRange(
2212 aEvent->mInput.mOffset - 1, 1, lineBreakType, true);
2213 if (MOZ_UNLIKELY(domRangeAndAdjustedOffsetOrError.isErr())) {
2214 NS_WARNING(
2215 "ContentEventHandler::ConvertFlatTextOffsetToDOMRangeBase() "
2216 "failed");
2217 return domRangeAndAdjustedOffsetOrError.unwrapErr();
2219 const UnsafeDOMRangeAndAdjustedOffsetInFlattenedText
2220 domRangeAndAdjustedOffsetOfPreviousChar =
2221 domRangeAndAdjustedOffsetOrError.unwrap();
2222 startsBetweenLineBreaker =
2223 domRangeAndAdjustedOffset.mRange.GetStartContainer() ==
2224 domRangeAndAdjustedOffsetOfPreviousChar.mRange
2225 .GetStartContainer() &&
2226 domRangeAndAdjustedOffset.mRange.StartOffset() ==
2227 domRangeAndAdjustedOffsetOfPreviousChar.mRange.StartOffset();
2230 // Other contents should cause a line breaker rect before it.
2231 // Note that moz-<br> element does not cause any text, however,
2232 // it represents empty line at the last of current block. Therefore,
2233 // we need to compute its rect too.
2234 else if (ShouldBreakLineBefore(*firstContent, mRootElement) ||
2235 IsPaddingBR(*firstContent)) {
2236 nsRect brRect;
2237 // If the frame is not a <br> frame, we need to compute the caret rect
2238 // with last character's rect before firstContent if there is.
2239 // For example, if caret is after "c" of |<p>abc</p><p>def</p>|, IME may
2240 // query a line breaker's rect after "c". Then, if we compute it only
2241 // with the 2nd <p>'s block frame, the result will be:
2242 // +-<p>--------------------------------+
2243 // |abc |
2244 // +------------------------------------+
2246 // I+-<p>--------------------------------+
2247 // |def |
2248 // +------------------------------------+
2249 // However, users expect popup windows of IME should be positioned at
2250 // right-bottom of "c" like this:
2251 // +-<p>--------------------------------+
2252 // |abcI |
2253 // +------------------------------------+
2255 // +-<p>--------------------------------+
2256 // |def |
2257 // +------------------------------------+
2258 // Therefore, if the first frame isn't a <br> frame and there is a text
2259 // node before the first node in the queried range, we should compute the
2260 // first rect with the previous character's rect.
2261 // If we already compute a character's rect in the queried range, we can
2262 // compute it with the cached last character's rect. (However, don't
2263 // use this path if it's a <br> frame because trusting <br> frame's rect
2264 // is better than guessing the rect from the previous character.)
2265 if (!firstFrame->IsBrFrame() && !aEvent->mReply->mRectArray.IsEmpty()) {
2266 baseFrame = lastFrame;
2267 brRect = lastCharRect;
2268 if (!wasLineBreaker) {
2269 brRect = GetCaretRectAfter(*baseFrame->PresContext(), brRect,
2270 lastVisibleFrameWritingMode);
2273 // If it's not a <br> frame and it's the first character rect at the
2274 // queried range, we need the previous character rect of the start of
2275 // the queried range if there is a visible text node.
2276 else if (!firstFrame->IsBrFrame() &&
2277 domRangeAndAdjustedOffset.mLastTextNode &&
2278 domRangeAndAdjustedOffset.mLastTextNode->GetPrimaryFrame()) {
2279 FrameRelativeRect brRectRelativeToLastTextFrame =
2280 GuessLineBreakerRectAfter(*domRangeAndAdjustedOffset.mLastTextNode);
2281 if (NS_WARN_IF(!brRectRelativeToLastTextFrame.IsValid())) {
2282 return NS_ERROR_FAILURE;
2284 // Look for the last text frame for the last text node.
2285 nsIFrame* primaryFrame =
2286 domRangeAndAdjustedOffset.mLastTextNode->GetPrimaryFrame();
2287 if (NS_WARN_IF(!primaryFrame)) {
2288 return NS_ERROR_FAILURE;
2290 baseFrame = primaryFrame->LastContinuation();
2291 if (NS_WARN_IF(!baseFrame)) {
2292 return NS_ERROR_FAILURE;
2294 brRect = brRectRelativeToLastTextFrame.RectRelativeTo(baseFrame);
2296 // Otherwise, we need to compute the line breaker's rect only with the
2297 // first frame's rect. But this may be unexpected. For example,
2298 // |<div contenteditable>[<p>]abc</p></div>|. In this case, caret is
2299 // before "a", therefore, users expect the rect left of "a". However,
2300 // we don't have enough information about the next character here and
2301 // this isn't usual case (e.g., IME typically tries to query the rect
2302 // of "a" or caret rect for computing its popup position). Therefore,
2303 // we shouldn't do more complicated hack here unless we'll get some bug
2304 // reports actually.
2305 else {
2306 FrameRelativeRect relativeBRRect = GetLineBreakerRectBefore(firstFrame);
2307 brRect = relativeBRRect.RectRelativeTo(firstFrame);
2309 charRects.AppendElement(brRect);
2310 chars.AssignLiteral("\n");
2311 if (kBRLength > 1 && offset == aEvent->mInput.mOffset && offset) {
2312 // If the first frame for the previous offset of the query range and
2313 // the first frame for the start of query range are same, that means
2314 // the start offset is between the first line breaker (i.e., the range
2315 // starts between "\r" and "\n").
2316 Result<UnsafeDOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
2317 domRangeAndAdjustedOffsetOrError =
2318 ConvertFlatTextOffsetToUnsafeDOMRange(
2319 aEvent->mInput.mOffset - 1, 1, lineBreakType, true);
2320 if (MOZ_UNLIKELY(domRangeAndAdjustedOffsetOrError.isErr())) {
2321 NS_WARNING(
2322 "ContentEventHandler::ConvertFlatTextOffsetToDOMRangeBase() "
2323 "failed");
2324 return NS_ERROR_UNEXPECTED;
2326 const UnsafeDOMRangeAndAdjustedOffsetInFlattenedText
2327 domRangeAndAdjustedOffset =
2328 domRangeAndAdjustedOffsetOrError.unwrap();
2329 FrameAndNodeOffset frameForPrevious =
2330 GetFirstFrameInRangeForTextRect(domRangeAndAdjustedOffset.mRange);
2331 startsBetweenLineBreaker = frameForPrevious.mFrame == firstFrame.mFrame;
2333 } else {
2334 NS_WARNING(
2335 "The frame is neither a text frame nor a frame whose content "
2336 "causes a line break");
2337 return NS_ERROR_FAILURE;
2340 for (size_t i = 0; i < charRects.Length() && offset < kEndOffset; i++) {
2341 nsRect charRect = charRects[i];
2342 // Store lastCharRect before applying CSS transform because it may be
2343 // used for computing a line breaker rect. Then, the computed line
2344 // breaker rect will be applied CSS transform again. Therefore,
2345 // the value of lastCharRect should be raw rect value relative to the
2346 // base frame.
2347 lastCharRect = charRect;
2348 lastFrame = baseFrame;
2349 rv = ConvertToRootRelativeOffset(baseFrame, charRect);
2350 if (NS_WARN_IF(NS_FAILED(rv))) {
2351 return rv;
2354 nsPresContext* presContext = baseFrame->PresContext();
2355 rect = LayoutDeviceIntRect::FromAppUnitsToOutside(
2356 charRect, presContext->AppUnitsPerDevPixel());
2357 if (nsPresContext* rootContext =
2358 presContext->GetInProcessRootContentDocumentPresContext()) {
2359 rect = RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2360 rect, rootContext->PresShell()));
2362 // Returning empty rect may cause native IME confused, let's make sure to
2363 // return non-empty rect.
2364 EnsureNonEmptyRect(rect);
2366 // If we found some invisible characters followed by current visible
2367 // character, make their rects same as caret rect before the first visible
2368 // character because IME may want to put their UI next to the rect of the
2369 // invisible character for next input.
2370 // Note that chars do not contain the invisible characters.
2371 if (i == 0u && MOZ_LIKELY(offset > aEvent->mInput.mOffset)) {
2372 const uint32_t offsetInRange =
2373 offset - CheckedInt<uint32_t>(aEvent->mInput.mOffset).value();
2374 if (offsetInRange > aEvent->mReply->mRectArray.Length()) {
2375 LayoutDeviceIntRect caretRectBefore =
2376 GetCaretRectBefore(rect, lastVisibleFrameWritingMode);
2377 for ([[maybe_unused]] uint32_t index : IntegerRange<uint32_t>(
2378 offsetInRange - aEvent->mReply->mRectArray.Length())) {
2379 aEvent->mReply->mRectArray.AppendElement(caretRectBefore);
2381 MOZ_ASSERT(aEvent->mReply->mRectArray.Length() == offsetInRange);
2385 aEvent->mReply->mRectArray.AppendElement(rect);
2386 offset++;
2388 // If it's not a line breaker or the line breaker length is same as
2389 // XP line breaker's, we need to do nothing for current character.
2390 wasLineBreaker = chars[i] == '\n';
2391 if (!wasLineBreaker || kBRLength == 1) {
2392 continue;
2395 MOZ_ASSERT(kBRLength == 2);
2397 // If it's already reached the end of query range, we don't need to do
2398 // anymore.
2399 if (offset == kEndOffset) {
2400 break;
2403 // If the query range starts from between a line breaker, i.e., it starts
2404 // between "\r" and "\n", the appended rect was for the "\n". Therefore,
2405 // we don't need to append same rect anymore for current "\r\n".
2406 if (startsBetweenLineBreaker) {
2407 continue;
2410 // The appended rect was for "\r" of "\r\n". Therefore, we need to
2411 // append same rect for "\n" too because querying rect of "\r" and "\n"
2412 // should return same rect. E.g., IME may query previous character's
2413 // rect of first character of a line.
2414 aEvent->mReply->mRectArray.AppendElement(rect);
2415 offset++;
2419 // If we've not handled some invisible character rects, fill them as caret
2420 // rect after the last visible character.
2421 if (!aEvent->mReply->mRectArray.IsEmpty()) {
2422 const uint32_t offsetInRange =
2423 offset - CheckedInt<uint32_t>(aEvent->mInput.mOffset).value();
2424 if (offsetInRange > aEvent->mReply->mRectArray.Length()) {
2425 LayoutDeviceIntRect caretRectAfter =
2426 GetCaretRectAfter(aEvent->mReply->mRectArray.LastElement(),
2427 lastVisibleFrameWritingMode);
2428 for ([[maybe_unused]] uint32_t index : IntegerRange<uint32_t>(
2429 offsetInRange - aEvent->mReply->mRectArray.Length())) {
2430 aEvent->mReply->mRectArray.AppendElement(caretRectAfter);
2432 MOZ_ASSERT(aEvent->mReply->mRectArray.Length() == offsetInRange);
2436 // If the query range is longer than actual content length, we should append
2437 // caret rect at the end of the content as the last character rect because
2438 // native IME may want to query character rect at the end of contents for
2439 // deciding the position of a popup window (e.g., suggest window for next
2440 // word). Note that when this method hasn't appended character rects, it
2441 // means that the offset is too large or the query range is collapsed.
2442 if (offset < kEndOffset || aEvent->mReply->mRectArray.IsEmpty()) {
2443 // If we've already retrieved some character rects before current offset,
2444 // we can guess the last rect from the last character's rect unless it's a
2445 // line breaker. (If it's a line breaker, the caret rect is in next line.)
2446 if (!aEvent->mReply->mRectArray.IsEmpty() && !wasLineBreaker) {
2447 rect = GetCaretRectAfter(aEvent->mReply->mRectArray.LastElement(),
2448 lastVisibleFrameWritingMode);
2449 aEvent->mReply->mRectArray.AppendElement(rect);
2450 } else {
2451 // Note that don't use eQueryCaretRect here because if caret is at the
2452 // end of the content, it returns actual caret rect instead of computing
2453 // the rect itself. It means that the result depends on caret position.
2454 // So, we shouldn't use it for consistency result in automated tests.
2455 WidgetQueryContentEvent queryTextRectEvent(eQueryTextRect, *aEvent);
2456 WidgetQueryContentEvent::Options options(*aEvent);
2457 queryTextRectEvent.InitForQueryTextRect(offset, 1, options);
2458 if (NS_WARN_IF(NS_FAILED(OnQueryTextRect(&queryTextRectEvent))) ||
2459 NS_WARN_IF(queryTextRectEvent.Failed())) {
2460 return NS_ERROR_FAILURE;
2462 if (queryTextRectEvent.mReply->mWritingMode.IsVertical()) {
2463 queryTextRectEvent.mReply->mRect.height = 1;
2464 } else {
2465 queryTextRectEvent.mReply->mRect.width = 1;
2467 aEvent->mReply->mRectArray.AppendElement(
2468 queryTextRectEvent.mReply->mRect);
2472 MOZ_ASSERT(aEvent->Succeeded());
2473 return NS_OK;
2476 nsresult ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent) {
2477 // If mLength is 0 (this may be caused by bug of native IME), we should
2478 // redirect this event to OnQueryCaretRect().
2479 if (!aEvent->mInput.mLength) {
2480 return OnQueryCaretRect(aEvent);
2483 nsresult rv = Init(aEvent);
2484 if (NS_FAILED(rv)) {
2485 return rv;
2488 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
2490 LineBreakType lineBreakType = GetLineBreakType(aEvent);
2491 Result<DOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
2492 domRangeAndAdjustedOffsetOrError = ConvertFlatTextOffsetToDOMRange(
2493 aEvent->mInput.mOffset, aEvent->mInput.mLength, lineBreakType, true);
2494 if (MOZ_UNLIKELY(domRangeAndAdjustedOffsetOrError.isErr())) {
2495 NS_WARNING("ContentEventHandler::ConvertFlatTextOffsetToDOMRange() failed");
2496 return NS_ERROR_FAILURE;
2498 DOMRangeAndAdjustedOffsetInFlattenedText domRangeAndAdjustedOffset =
2499 domRangeAndAdjustedOffsetOrError.unwrap();
2500 nsString string;
2501 if (NS_WARN_IF(NS_FAILED(GenerateFlatTextContent(
2502 domRangeAndAdjustedOffset.mRange, string, lineBreakType)))) {
2503 return NS_ERROR_FAILURE;
2505 aEvent->mReply->mOffsetAndData.emplace(
2506 domRangeAndAdjustedOffset.mAdjustedOffset, string,
2507 OffsetAndDataFor::EditorString);
2509 // used to iterate over all contents and their frames
2510 PostContentIterator postOrderIter;
2511 rv = postOrderIter.Init(domRangeAndAdjustedOffset.mRange.Start().AsRaw(),
2512 domRangeAndAdjustedOffset.mRange.End().AsRaw());
2513 if (NS_WARN_IF(NS_FAILED(rv))) {
2514 return NS_ERROR_FAILURE;
2517 // Get the first frame which causes some text after the offset.
2518 FrameAndNodeOffset firstFrame =
2519 GetFirstFrameInRangeForTextRect(domRangeAndAdjustedOffset.mRange);
2521 // If GetFirstFrameInRangeForTextRect() does not return valid frame, that
2522 // means that there are no visible frames having text or the offset reached
2523 // the end of contents.
2524 if (!firstFrame.IsValid()) {
2525 nsAutoString allText;
2526 rv = GenerateFlatTextContent(mRootElement, allText, lineBreakType);
2527 // If the offset doesn't reach the end of contents but there is no frames
2528 // for the node, that means that current offset's node is hidden by CSS or
2529 // something. Ideally, we should handle it with the last visible text
2530 // node's last character's rect, but it's not usual cases in actual web
2531 // services. Therefore, currently, we should make this case fail.
2532 if (NS_WARN_IF(NS_FAILED(rv)) ||
2533 static_cast<uint32_t>(aEvent->mInput.mOffset) < allText.Length()) {
2534 return NS_ERROR_FAILURE;
2537 // Look for the last frame which should be included text rects.
2538 rv = domRangeAndAdjustedOffset.mRange.SelectNodeContents(mRootElement);
2539 if (NS_WARN_IF(NS_FAILED(rv))) {
2540 return NS_ERROR_UNEXPECTED;
2542 nsRect rect;
2543 FrameAndNodeOffset lastFrame =
2544 GetLastFrameInRangeForTextRect(domRangeAndAdjustedOffset.mRange);
2545 // If there is at least one frame which can be used for computing a rect
2546 // for a character or a line breaker, we should use it for guessing the
2547 // caret rect at the end of the contents.
2548 nsPresContext* presContext;
2549 if (lastFrame) {
2550 presContext = lastFrame->PresContext();
2551 if (NS_WARN_IF(!lastFrame->GetContent())) {
2552 return NS_ERROR_FAILURE;
2554 FrameRelativeRect relativeRect;
2555 // If there is a <br> frame at the end, it represents an empty line at
2556 // the end with moz-<br> or content <br> in a block level element.
2557 if (lastFrame->IsBrFrame()) {
2558 relativeRect = GetLineBreakerRectBefore(lastFrame);
2560 // If there is a text frame at the end, use its information.
2561 else if (lastFrame->IsTextFrame()) {
2562 const Text* textNode = Text::FromNode(lastFrame->GetContent());
2563 MOZ_ASSERT(textNode);
2564 if (textNode) {
2565 relativeRect = GuessLineBreakerRectAfter(*textNode);
2568 // If there is an empty frame which is neither a text frame nor a <br>
2569 // frame at the end, guess caret rect in it.
2570 else {
2571 relativeRect = GuessFirstCaretRectIn(lastFrame);
2573 if (NS_WARN_IF(!relativeRect.IsValid())) {
2574 return NS_ERROR_FAILURE;
2576 rect = relativeRect.RectRelativeTo(lastFrame);
2577 rv = ConvertToRootRelativeOffset(lastFrame, rect);
2578 if (NS_WARN_IF(NS_FAILED(rv))) {
2579 return rv;
2581 aEvent->mReply->mWritingMode = lastFrame->GetWritingMode();
2583 // Otherwise, if there are no contents in mRootElement, guess caret rect in
2584 // its frame (with its font height and content box).
2585 else {
2586 nsIFrame* rootContentFrame = mRootElement->GetPrimaryFrame();
2587 if (NS_WARN_IF(!rootContentFrame)) {
2588 return NS_ERROR_FAILURE;
2590 presContext = rootContentFrame->PresContext();
2591 FrameRelativeRect relativeRect = GuessFirstCaretRectIn(rootContentFrame);
2592 if (NS_WARN_IF(!relativeRect.IsValid())) {
2593 return NS_ERROR_FAILURE;
2595 rect = relativeRect.RectRelativeTo(rootContentFrame);
2596 rv = ConvertToRootRelativeOffset(rootContentFrame, rect);
2597 if (NS_WARN_IF(NS_FAILED(rv))) {
2598 return rv;
2600 aEvent->mReply->mWritingMode = rootContentFrame->GetWritingMode();
2602 aEvent->mReply->mRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
2603 rect, presContext->AppUnitsPerDevPixel());
2604 if (nsPresContext* rootContext =
2605 presContext->GetInProcessRootContentDocumentPresContext()) {
2606 aEvent->mReply->mRect =
2607 RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2608 aEvent->mReply->mRect, rootContext->PresShell()));
2610 EnsureNonEmptyRect(aEvent->mReply->mRect);
2612 MOZ_ASSERT(aEvent->Succeeded());
2613 return NS_OK;
2616 nsRect rect, frameRect;
2617 nsPoint ptOffset;
2619 // If the first frame is a text frame, the result should be computed with
2620 // the frame's rect but not including the rect before start point of the
2621 // queried range.
2622 if (firstFrame->IsTextFrame()) {
2623 rect.SetRect(nsPoint(0, 0), firstFrame->GetRect().Size());
2624 rv = ConvertToRootRelativeOffset(firstFrame, rect);
2625 if (NS_WARN_IF(NS_FAILED(rv))) {
2626 return rv;
2628 frameRect = rect;
2629 // Exclude the rect before start point of the queried range.
2630 firstFrame->GetPointFromOffset(firstFrame.mOffsetInNode, &ptOffset);
2631 if (firstFrame->GetWritingMode().IsVertical()) {
2632 rect.y += ptOffset.y;
2633 rect.height -= ptOffset.y;
2634 } else {
2635 rect.x += ptOffset.x;
2636 rect.width -= ptOffset.x;
2639 // If first frame causes a line breaker but it's not a <br> frame, we cannot
2640 // compute proper rect only with the frame because typically caret is at
2641 // right of the last character of it. For example, if caret is after "c" of
2642 // |<p>abc</p><p>def</p>|, IME may query a line breaker's rect after "c".
2643 // Then, if we compute it only with the 2nd <p>'s block frame, the result
2644 // will be:
2645 // +-<p>--------------------------------+
2646 // |abc |
2647 // +------------------------------------+
2649 // I+-<p>--------------------------------+
2650 // |def |
2651 // +------------------------------------+
2652 // However, users expect popup windows of IME should be positioned at
2653 // right-bottom of "c" like this:
2654 // +-<p>--------------------------------+
2655 // |abcI |
2656 // +------------------------------------+
2658 // +-<p>--------------------------------+
2659 // |def |
2660 // +------------------------------------+
2661 // Therefore, if the first frame isn't a <br> frame and there is a visible
2662 // text node before the first node in the queried range, we should compute the
2663 // first rect with the previous character's rect.
2664 else if (!firstFrame->IsBrFrame() &&
2665 domRangeAndAdjustedOffset.mLastTextNode &&
2666 domRangeAndAdjustedOffset.mLastTextNode->GetPrimaryFrame()) {
2667 FrameRelativeRect brRectAfterLastChar =
2668 GuessLineBreakerRectAfter(*domRangeAndAdjustedOffset.mLastTextNode);
2669 if (NS_WARN_IF(!brRectAfterLastChar.IsValid())) {
2670 return NS_ERROR_FAILURE;
2672 rect = brRectAfterLastChar.mRect;
2673 rv = ConvertToRootRelativeOffset(brRectAfterLastChar.mBaseFrame, rect);
2674 if (NS_WARN_IF(NS_FAILED(rv))) {
2675 return rv;
2677 frameRect = rect;
2679 // Otherwise, we need to compute the line breaker's rect only with the
2680 // first frame's rect. But this may be unexpected. For example,
2681 // |<div contenteditable>[<p>]abc</p></div>|. In this case, caret is before
2682 // "a", therefore, users expect the rect left of "a". However, we don't
2683 // have enough information about the next character here and this isn't
2684 // usual case (e.g., IME typically tries to query the rect of "a" or caret
2685 // rect for computing its popup position). Therefore, we shouldn't do
2686 // more complicated hack here unless we'll get some bug reports actually.
2687 else {
2688 FrameRelativeRect relativeRect = GetLineBreakerRectBefore(firstFrame);
2689 if (NS_WARN_IF(!relativeRect.IsValid())) {
2690 return NS_ERROR_FAILURE;
2692 rect = relativeRect.RectRelativeTo(firstFrame);
2693 rv = ConvertToRootRelativeOffset(firstFrame, rect);
2694 if (NS_WARN_IF(NS_FAILED(rv))) {
2695 return rv;
2697 frameRect = rect;
2699 // UnionRect() requires non-empty rect. So, let's make sure to get non-emtpy
2700 // rect from the first frame.
2701 EnsureNonEmptyRect(rect);
2703 // Get the last frame which causes some text in the range.
2704 FrameAndNodeOffset lastFrame =
2705 GetLastFrameInRangeForTextRect(domRangeAndAdjustedOffset.mRange);
2706 if (NS_WARN_IF(!lastFrame.IsValid())) {
2707 return NS_ERROR_FAILURE;
2710 // iterate over all covered frames
2711 for (nsIFrame* frame = firstFrame; frame != lastFrame;) {
2712 frame = frame->GetNextContinuation();
2713 if (!frame) {
2714 do {
2715 postOrderIter.Next();
2716 nsINode* node = postOrderIter.GetCurrentNode();
2717 if (!node) {
2718 break;
2720 if (!node->IsContent()) {
2721 continue;
2723 nsIFrame* primaryFrame = node->AsContent()->GetPrimaryFrame();
2724 // The node may be hidden by CSS.
2725 if (!primaryFrame) {
2726 continue;
2728 // We should take only text frame's rect and br frame's rect. We can
2729 // always use frame rect of text frame and GetLineBreakerRectBefore()
2730 // can return exactly correct rect only for <br> frame for now. On the
2731 // other hand, GetLineBreakRectBefore() returns guessed caret rect for
2732 // the other frames. We shouldn't include such odd rect to the result.
2733 if (primaryFrame->IsTextFrame() || primaryFrame->IsBrFrame()) {
2734 frame = primaryFrame;
2736 } while (!frame && !postOrderIter.IsDone());
2737 if (!frame) {
2738 break;
2741 if (frame->IsTextFrame()) {
2742 frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size());
2743 } else {
2744 MOZ_ASSERT(frame->IsBrFrame());
2745 FrameRelativeRect relativeRect = GetLineBreakerRectBefore(frame);
2746 if (NS_WARN_IF(!relativeRect.IsValid())) {
2747 return NS_ERROR_FAILURE;
2749 frameRect = relativeRect.RectRelativeTo(frame);
2751 rv = ConvertToRootRelativeOffset(frame, frameRect);
2752 if (NS_WARN_IF(NS_FAILED(rv))) {
2753 return rv;
2755 // UnionRect() requires non-empty rect. So, let's make sure to get
2756 // non-emtpy rect from the frame.
2757 EnsureNonEmptyRect(frameRect);
2758 if (frame != lastFrame) {
2759 // not last frame, so just add rect to previous result
2760 rect.UnionRect(rect, frameRect);
2764 // Get the ending frame rect.
2765 // FYI: If first frame and last frame are same, frameRect is already set
2766 // to the rect excluding the text before the query range.
2767 if (firstFrame.mFrame != lastFrame.mFrame) {
2768 frameRect.SetRect(nsPoint(0, 0), lastFrame->GetRect().Size());
2769 rv = ConvertToRootRelativeOffset(lastFrame, frameRect);
2770 if (NS_WARN_IF(NS_FAILED(rv))) {
2771 return rv;
2775 // Shrink the last frame for cutting off the text after the query range.
2776 if (lastFrame->IsTextFrame()) {
2777 lastFrame->GetPointFromOffset(lastFrame.mOffsetInNode, &ptOffset);
2778 if (lastFrame->GetWritingMode().IsVertical()) {
2779 frameRect.height -= lastFrame->GetRect().height - ptOffset.y;
2780 } else {
2781 frameRect.width -= lastFrame->GetRect().width - ptOffset.x;
2783 // UnionRect() requires non-empty rect. So, let's make sure to get
2784 // non-empty rect from the last frame.
2785 EnsureNonEmptyRect(frameRect);
2787 if (firstFrame.mFrame == lastFrame.mFrame) {
2788 rect.IntersectRect(rect, frameRect);
2789 } else {
2790 rect.UnionRect(rect, frameRect);
2794 nsPresContext* presContext = lastFrame->PresContext();
2795 aEvent->mReply->mRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
2796 rect, presContext->AppUnitsPerDevPixel());
2797 if (nsPresContext* rootContext =
2798 presContext->GetInProcessRootContentDocumentPresContext()) {
2799 aEvent->mReply->mRect =
2800 RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2801 aEvent->mReply->mRect, rootContext->PresShell()));
2803 // Returning empty rect may cause native IME confused, let's make sure to
2804 // return non-empty rect.
2805 EnsureNonEmptyRect(aEvent->mReply->mRect);
2806 aEvent->mReply->mWritingMode = lastFrame->GetWritingMode();
2808 MOZ_ASSERT(aEvent->Succeeded());
2809 return NS_OK;
2812 nsresult ContentEventHandler::OnQueryEditorRect(
2813 WidgetQueryContentEvent* aEvent) {
2814 nsresult rv = Init(aEvent);
2815 if (NS_FAILED(rv)) {
2816 return rv;
2819 if (NS_WARN_IF(NS_FAILED(QueryContentRect(mRootElement, aEvent)))) {
2820 return NS_ERROR_FAILURE;
2823 MOZ_ASSERT(aEvent->Succeeded());
2824 return NS_OK;
2827 nsresult ContentEventHandler::OnQueryCaretRect(
2828 WidgetQueryContentEvent* aEvent) {
2829 nsresult rv = Init(aEvent);
2830 if (NS_FAILED(rv)) {
2831 return rv;
2834 // When the selection is collapsed and the queried offset is current caret
2835 // position, we should return the "real" caret rect.
2836 if (mSelection->IsCollapsed()) {
2837 nsRect caretRect;
2838 nsIFrame* caretFrame = nsCaret::GetGeometry(mSelection, &caretRect);
2839 if (caretFrame) {
2840 uint32_t offset;
2841 rv = GetStartOffset(mFirstSelectedSimpleRange, &offset,
2842 GetLineBreakType(aEvent));
2843 NS_ENSURE_SUCCESS(rv, rv);
2844 if (offset == aEvent->mInput.mOffset) {
2845 rv = ConvertToRootRelativeOffset(caretFrame, caretRect);
2846 NS_ENSURE_SUCCESS(rv, rv);
2847 nsPresContext* presContext = caretFrame->PresContext();
2848 aEvent->mReply->mRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
2849 caretRect, presContext->AppUnitsPerDevPixel());
2850 if (nsPresContext* rootContext =
2851 presContext->GetInProcessRootContentDocumentPresContext()) {
2852 aEvent->mReply->mRect =
2853 RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2854 aEvent->mReply->mRect, rootContext->PresShell()));
2856 // Returning empty rect may cause native IME confused, let's make sure
2857 // to return non-empty rect.
2858 EnsureNonEmptyRect(aEvent->mReply->mRect);
2859 aEvent->mReply->mWritingMode = caretFrame->GetWritingMode();
2860 aEvent->mReply->mOffsetAndData.emplace(
2861 aEvent->mInput.mOffset, EmptyString(),
2862 OffsetAndDataFor::SelectedString);
2864 MOZ_ASSERT(aEvent->Succeeded());
2865 return NS_OK;
2870 // Otherwise, we should guess the caret rect from the character's rect.
2871 WidgetQueryContentEvent queryTextRectEvent(eQueryTextRect, *aEvent);
2872 WidgetQueryContentEvent::Options options(*aEvent);
2873 queryTextRectEvent.InitForQueryTextRect(aEvent->mInput.mOffset, 1, options);
2874 if (NS_WARN_IF(NS_FAILED(OnQueryTextRect(&queryTextRectEvent))) ||
2875 NS_WARN_IF(queryTextRectEvent.Failed())) {
2876 return NS_ERROR_FAILURE;
2878 queryTextRectEvent.mReply->TruncateData();
2879 aEvent->mReply->mOffsetAndData =
2880 std::move(queryTextRectEvent.mReply->mOffsetAndData);
2881 aEvent->mReply->mWritingMode =
2882 std::move(queryTextRectEvent.mReply->mWritingMode);
2883 aEvent->mReply->mRect = GetCaretRectBefore(queryTextRectEvent.mReply->mRect,
2884 aEvent->mReply->mWritingMode);
2886 MOZ_ASSERT(aEvent->Succeeded());
2887 return NS_OK;
2890 nsresult ContentEventHandler::OnQueryContentState(
2891 WidgetQueryContentEvent* aEvent) {
2892 if (NS_FAILED(Init(aEvent))) {
2893 return NS_ERROR_FAILURE;
2895 MOZ_ASSERT(aEvent->mReply.isSome());
2896 MOZ_ASSERT(aEvent->Succeeded());
2897 return NS_OK;
2900 nsresult ContentEventHandler::OnQuerySelectionAsTransferable(
2901 WidgetQueryContentEvent* aEvent) {
2902 nsresult rv = Init(aEvent);
2903 if (NS_FAILED(rv)) {
2904 return rv;
2907 MOZ_ASSERT(aEvent->mReply.isSome());
2909 if (mSelection->IsCollapsed()) {
2910 MOZ_ASSERT(!aEvent->mReply->mTransferable);
2911 return NS_OK;
2914 if (NS_WARN_IF(NS_FAILED(nsCopySupport::GetTransferableForSelection(
2915 mSelection, mDocument,
2916 getter_AddRefs(aEvent->mReply->mTransferable))))) {
2917 return NS_ERROR_FAILURE;
2920 MOZ_ASSERT(aEvent->Succeeded());
2921 return NS_OK;
2924 nsresult ContentEventHandler::OnQueryCharacterAtPoint(
2925 WidgetQueryContentEvent* aEvent) {
2926 nsresult rv = Init(aEvent);
2927 if (NS_FAILED(rv)) {
2928 return rv;
2931 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
2932 MOZ_ASSERT(aEvent->mReply->mTentativeCaretOffset.isNothing());
2934 PresShell* presShell = mDocument->GetPresShell();
2935 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
2936 nsIFrame* rootFrame = presShell->GetRootFrame();
2937 NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
2938 nsIWidget* rootWidget = rootFrame->GetNearestWidget();
2939 NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
2941 // The root frame's widget might be different, e.g., the event was fired on
2942 // a popup but the rootFrame is the document root.
2943 if (rootWidget != aEvent->mWidget) {
2944 MOZ_ASSERT(aEvent->mWidget, "The event must have the widget");
2945 nsView* view = nsView::GetViewFor(aEvent->mWidget);
2946 NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
2947 rootFrame = view->GetFrame();
2948 NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
2949 rootWidget = rootFrame->GetNearestWidget();
2950 NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
2953 WidgetQueryContentEvent queryCharAtPointOnRootWidgetEvent(
2954 true, eQueryCharacterAtPoint, rootWidget);
2955 queryCharAtPointOnRootWidgetEvent.mUseNativeLineBreak =
2956 aEvent->mUseNativeLineBreak;
2957 queryCharAtPointOnRootWidgetEvent.mRefPoint = aEvent->mRefPoint;
2958 if (rootWidget != aEvent->mWidget) {
2959 queryCharAtPointOnRootWidgetEvent.mRefPoint +=
2960 aEvent->mWidget->WidgetToScreenOffset() -
2961 rootWidget->WidgetToScreenOffset();
2963 nsPoint ptInRoot = nsLayoutUtils::GetEventCoordinatesRelativeTo(
2964 &queryCharAtPointOnRootWidgetEvent, RelativeTo{rootFrame});
2966 nsIFrame* targetFrame =
2967 nsLayoutUtils::GetFrameForPoint(RelativeTo{rootFrame}, ptInRoot);
2968 if (!targetFrame || !targetFrame->GetContent() ||
2969 !targetFrame->GetContent()->IsInclusiveDescendantOf(mRootElement)) {
2970 // There is no character at the point.
2971 MOZ_ASSERT(aEvent->Succeeded());
2972 return NS_OK;
2974 nsPoint ptInTarget = ptInRoot + rootFrame->GetOffsetToCrossDoc(targetFrame);
2975 int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel();
2976 int32_t targetAPD = targetFrame->PresContext()->AppUnitsPerDevPixel();
2977 ptInTarget = ptInTarget.ScaleToOtherAppUnits(rootAPD, targetAPD);
2979 nsIFrame::ContentOffsets tentativeCaretOffsets =
2980 targetFrame->GetContentOffsetsFromPoint(ptInTarget);
2981 if (!tentativeCaretOffsets.content ||
2982 !tentativeCaretOffsets.content->IsInclusiveDescendantOf(mRootElement)) {
2983 // There is no character nor tentative caret point at the point.
2984 MOZ_ASSERT(aEvent->Succeeded());
2985 return NS_OK;
2988 uint32_t tentativeCaretOffset = 0;
2989 if (NS_WARN_IF(NS_FAILED(GetFlatTextLengthInRange(
2990 RawNodePosition(mRootElement, 0u),
2991 RawNodePosition(tentativeCaretOffsets), mRootElement,
2992 &tentativeCaretOffset, GetLineBreakType(aEvent))))) {
2993 return NS_ERROR_FAILURE;
2996 aEvent->mReply->mTentativeCaretOffset.emplace(tentativeCaretOffset);
2997 if (!targetFrame->IsTextFrame()) {
2998 // There is no character at the point but there is tentative caret point.
2999 MOZ_ASSERT(aEvent->Succeeded());
3000 return NS_OK;
3003 nsTextFrame* textframe = static_cast<nsTextFrame*>(targetFrame);
3004 nsIFrame::ContentOffsets contentOffsets =
3005 textframe->GetCharacterOffsetAtFramePoint(ptInTarget);
3006 NS_ENSURE_TRUE(contentOffsets.content, NS_ERROR_FAILURE);
3007 uint32_t offset = 0;
3008 if (NS_WARN_IF(NS_FAILED(GetFlatTextLengthInRange(
3009 RawNodePosition(mRootElement, 0u), RawNodePosition(contentOffsets),
3010 mRootElement, &offset, GetLineBreakType(aEvent))))) {
3011 return NS_ERROR_FAILURE;
3014 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect,
3015 aEvent->mWidget);
3016 WidgetQueryContentEvent::Options options(*aEvent);
3017 queryTextRectEvent.InitForQueryTextRect(offset, 1, options);
3018 if (NS_WARN_IF(NS_FAILED(OnQueryTextRect(&queryTextRectEvent))) ||
3019 NS_WARN_IF(queryTextRectEvent.Failed())) {
3020 return NS_ERROR_FAILURE;
3023 aEvent->mReply->mOffsetAndData =
3024 std::move(queryTextRectEvent.mReply->mOffsetAndData);
3025 aEvent->mReply->mRect = queryTextRectEvent.mReply->mRect;
3027 MOZ_ASSERT(aEvent->Succeeded());
3028 return NS_OK;
3031 nsresult ContentEventHandler::OnQueryDOMWidgetHittest(
3032 WidgetQueryContentEvent* aEvent) {
3033 NS_ASSERTION(aEvent, "aEvent must not be null");
3035 nsresult rv = InitBasic();
3036 if (NS_FAILED(rv)) {
3037 return rv;
3040 aEvent->mReply->mWidgetIsHit = false;
3042 NS_ENSURE_TRUE(aEvent->mWidget, NS_ERROR_FAILURE);
3044 PresShell* presShell = mDocument->GetPresShell();
3045 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
3046 nsIFrame* docFrame = presShell->GetRootFrame();
3047 NS_ENSURE_TRUE(docFrame, NS_ERROR_FAILURE);
3049 LayoutDeviceIntPoint eventLoc =
3050 aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset();
3051 CSSIntRect docFrameRect = docFrame->GetScreenRect();
3052 CSSIntPoint eventLocCSS(
3053 docFrame->PresContext()->DevPixelsToIntCSSPixels(eventLoc.x) -
3054 docFrameRect.x,
3055 docFrame->PresContext()->DevPixelsToIntCSSPixels(eventLoc.y) -
3056 docFrameRect.y);
3058 if (Element* contentUnderMouse = mDocument->ElementFromPointHelper(
3059 eventLocCSS.x, eventLocCSS.y, false, false, ViewportType::Visual)) {
3060 if (nsIFrame* targetFrame = contentUnderMouse->GetPrimaryFrame()) {
3061 if (aEvent->mWidget == targetFrame->GetNearestWidget()) {
3062 aEvent->mReply->mWidgetIsHit = true;
3067 MOZ_ASSERT(aEvent->Succeeded());
3068 return NS_OK;
3071 /* static */
3072 nsresult ContentEventHandler::GetFlatTextLengthInRange(
3073 const RawNodePosition& aStartPosition, const RawNodePosition& aEndPosition,
3074 const Element* aRootElement, uint32_t* aLength,
3075 LineBreakType aLineBreakType, bool aIsRemovingNode /* = false */) {
3076 if (NS_WARN_IF(!aRootElement) || NS_WARN_IF(!aStartPosition.IsSet()) ||
3077 NS_WARN_IF(!aEndPosition.IsSet()) || NS_WARN_IF(!aLength)) {
3078 return NS_ERROR_INVALID_ARG;
3081 if (aStartPosition == aEndPosition) {
3082 *aLength = 0;
3083 return NS_OK;
3086 UnsafePreContentIterator preOrderIter;
3088 // Working with ContentIterator, we may need to adjust the end position for
3089 // including it forcibly.
3090 RawNodePosition endPosition(aEndPosition);
3092 // This may be called for retrieving the text of removed nodes. Even in this
3093 // case, the node thinks it's still in the tree because UnbindFromTree() will
3094 // be called after here. However, the node was already removed from the
3095 // array of children of its parent. So, be careful to handle this case.
3096 if (aIsRemovingNode) {
3097 DebugOnly<nsIContent*> parent = aStartPosition.Container()->GetParent();
3098 MOZ_ASSERT(
3099 parent &&
3100 parent->ComputeIndexOf(aStartPosition.Container()).isNothing(),
3101 "At removing the node, the node shouldn't be in the array of children "
3102 "of its parent");
3103 MOZ_ASSERT(aStartPosition.Container() == endPosition.Container(),
3104 "At removing the node, start and end node should be same");
3105 MOZ_ASSERT(*aStartPosition.Offset(
3106 RawNodePosition::OffsetFilter::kValidOrInvalidOffsets) == 0,
3107 "When the node is being removed, the start offset should be 0");
3108 MOZ_ASSERT(
3109 static_cast<uint32_t>(*endPosition.Offset(
3110 RawNodePosition::OffsetFilter::kValidOrInvalidOffsets)) ==
3111 endPosition.Container()->GetChildCount(),
3112 "When the node is being removed, the end offset should be child count");
3113 nsresult rv = preOrderIter.Init(aStartPosition.Container());
3114 if (NS_WARN_IF(NS_FAILED(rv))) {
3115 return rv;
3117 } else {
3118 SimpleRange prevSimpleRange;
3119 nsresult rv = prevSimpleRange.SetStart(aStartPosition.AsRaw());
3120 if (NS_WARN_IF(NS_FAILED(rv))) {
3121 return rv;
3124 // When the end position is immediately after non-root element's open tag,
3125 // we need to include a line break caused by the open tag.
3126 if (endPosition.Container() != aRootElement &&
3127 endPosition.IsImmediatelyAfterOpenTag()) {
3128 if (endPosition.Container()->HasChildren()) {
3129 // When the end node has some children, move the end position to before
3130 // the open tag of its first child.
3131 nsINode* firstChild = endPosition.Container()->GetFirstChild();
3132 if (NS_WARN_IF(!firstChild)) {
3133 return NS_ERROR_FAILURE;
3135 endPosition = RawNodePositionBefore(firstChild, 0u);
3136 } else {
3137 // When the end node is empty, move the end position after the node.
3138 nsIContent* parentContent = endPosition.Container()->GetParent();
3139 if (NS_WARN_IF(!parentContent)) {
3140 return NS_ERROR_FAILURE;
3142 Maybe<uint32_t> indexInParent =
3143 parentContent->ComputeIndexOf(endPosition.Container());
3144 if (MOZ_UNLIKELY(NS_WARN_IF(indexInParent.isNothing()))) {
3145 return NS_ERROR_FAILURE;
3147 MOZ_ASSERT(*indexInParent != UINT32_MAX);
3148 endPosition = RawNodePositionBefore(parentContent, *indexInParent + 1u);
3152 if (endPosition.IsSetAndValid()) {
3153 // Offset is within node's length; set end of range to that offset
3154 rv = prevSimpleRange.SetEnd(endPosition.AsRaw());
3155 if (NS_WARN_IF(NS_FAILED(rv))) {
3156 return rv;
3158 rv = preOrderIter.Init(prevSimpleRange.Start().AsRaw(),
3159 prevSimpleRange.End().AsRaw());
3160 if (NS_WARN_IF(NS_FAILED(rv))) {
3161 return rv;
3163 } else if (endPosition.Container() != aRootElement) {
3164 // Offset is past node's length; set end of range to end of node
3165 rv = prevSimpleRange.SetEndAfter(endPosition.Container());
3166 if (NS_WARN_IF(NS_FAILED(rv))) {
3167 return rv;
3169 rv = preOrderIter.Init(prevSimpleRange.Start().AsRaw(),
3170 prevSimpleRange.End().AsRaw());
3171 if (NS_WARN_IF(NS_FAILED(rv))) {
3172 return rv;
3174 } else {
3175 // Offset is past the root node; set end of range to end of root node
3176 rv = preOrderIter.Init(const_cast<Element*>(aRootElement));
3177 if (NS_WARN_IF(NS_FAILED(rv))) {
3178 return rv;
3183 *aLength = 0;
3184 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
3185 nsINode* node = preOrderIter.GetCurrentNode();
3186 if (NS_WARN_IF(!node)) {
3187 break;
3189 if (!node->IsContent()) {
3190 continue;
3192 nsIContent* content = node->AsContent();
3194 if (const Text* textNode = Text::FromNode(content)) {
3195 // Note: our range always starts from offset 0
3196 if (node == endPosition.Container()) {
3197 // NOTE: We should have an offset here, as endPosition.Container() is a
3198 // nsINode::eTEXT, which always has an offset.
3199 *aLength += GetTextLength(
3200 *textNode, aLineBreakType,
3201 *endPosition.Offset(
3202 RawNodePosition::OffsetFilter::kValidOrInvalidOffsets));
3203 } else {
3204 *aLength += GetTextLength(*textNode, aLineBreakType);
3206 } else if (ShouldBreakLineBefore(*content, aRootElement)) {
3207 // If the start position is start of this node but doesn't include the
3208 // open tag, don't append the line break length.
3209 if (node == aStartPosition.Container() &&
3210 !aStartPosition.IsBeforeOpenTag()) {
3211 continue;
3213 // If the end position is before the open tag, don't append the line
3214 // break length.
3215 if (node == endPosition.Container() && endPosition.IsBeforeOpenTag()) {
3216 continue;
3218 *aLength += GetBRLength(aLineBreakType);
3221 return NS_OK;
3224 template <typename SimpleRangeType>
3225 nsresult ContentEventHandler::GetStartOffset(
3226 const SimpleRangeType& aSimpleRange, uint32_t* aOffset,
3227 LineBreakType aLineBreakType) {
3228 // To match the "no skip start" hack in ContentIterator::Init, when range
3229 // offset is 0 and the range node is not a container, we have to assume the
3230 // range _includes_ the node, which means the start offset should _not_
3231 // include the node.
3233 // For example, for this content: <br>abc, and range (<br>, 0)-("abc", 1), the
3234 // range includes the linebreak from <br>, so the start offset should _not_
3235 // include <br>, and the start offset should be 0.
3237 // However, for this content: <p/>abc, and range (<p>, 0)-("abc", 1), the
3238 // range does _not_ include the linebreak from <p> because <p> is a container,
3239 // so the start offset _should_ include <p>, and the start offset should be 1.
3241 nsINode* startNode = aSimpleRange.GetStartContainer();
3242 bool startIsContainer = true;
3243 if (startNode->IsHTMLElement()) {
3244 nsAtom* name = startNode->NodeInfo()->NameAtom();
3245 startIsContainer =
3246 nsHTMLElement::IsContainer(nsHTMLTags::AtomTagToId(name));
3248 RawNodePosition startPos(startNode, aSimpleRange.StartOffset());
3249 startPos.mAfterOpenTag = startIsContainer;
3250 return GetFlatTextLengthInRange(RawNodePosition(mRootElement, 0u), startPos,
3251 mRootElement, aOffset, aLineBreakType);
3254 nsresult ContentEventHandler::AdjustCollapsedRangeMaybeIntoTextNode(
3255 SimpleRange& aSimpleRange) {
3256 MOZ_ASSERT(aSimpleRange.Collapsed());
3258 if (!aSimpleRange.Collapsed()) {
3259 return NS_ERROR_INVALID_ARG;
3262 const RangeBoundary& startPoint = aSimpleRange.Start();
3263 if (NS_WARN_IF(!startPoint.IsSet())) {
3264 return NS_ERROR_INVALID_ARG;
3267 // If the node does not have children like a text node, we don't need to
3268 // modify aSimpleRange.
3269 if (!startPoint.Container()->HasChildren()) {
3270 return NS_OK;
3273 // If the container is not a text node but it has a text node at the offset,
3274 // we should adjust the range into the text node.
3275 // NOTE: This is emulating similar situation of EditorBase.
3276 if (startPoint.IsStartOfContainer()) {
3277 // If the range is the start of the container, adjusted the range to the
3278 // start of the first child.
3279 if (!startPoint.Container()->GetFirstChild()->IsText()) {
3280 return NS_OK;
3282 nsresult rv = aSimpleRange.CollapseTo(
3283 RawRangeBoundary(startPoint.Container()->GetFirstChild(), 0u));
3284 if (NS_WARN_IF(NS_FAILED(rv))) {
3285 return rv;
3287 return NS_OK;
3290 if (!startPoint.IsSetAndValid()) {
3291 return NS_OK;
3294 // If start of the range is next to a child node, adjust the range to the
3295 // end of the previous child (i.e., startPoint.Ref()).
3296 if (!startPoint.Ref()->IsText()) {
3297 return NS_OK;
3299 nsresult rv = aSimpleRange.CollapseTo(
3300 RawRangeBoundary(startPoint.Ref(), startPoint.Ref()->Length()));
3301 if (NS_WARN_IF(NS_FAILED(rv))) {
3302 return rv;
3304 return NS_OK;
3307 nsresult ContentEventHandler::ConvertToRootRelativeOffset(nsIFrame* aFrame,
3308 nsRect& aRect) {
3309 NS_ASSERTION(aFrame, "aFrame must not be null");
3311 nsPresContext* thisPC = aFrame->PresContext();
3312 nsPresContext* rootPC = thisPC->GetRootPresContext();
3313 if (NS_WARN_IF(!rootPC)) {
3314 return NS_ERROR_FAILURE;
3316 nsIFrame* rootFrame = rootPC->PresShell()->GetRootFrame();
3317 if (NS_WARN_IF(!rootFrame)) {
3318 return NS_ERROR_FAILURE;
3321 aRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, aRect, rootFrame);
3323 // TransformFrameRectToAncestor returned the rect in the ancestor's appUnits,
3324 // but we want it in aFrame's units (in case of different full-zoom factors),
3325 // so convert back.
3326 aRect = aRect.ScaleToOtherAppUnitsRoundOut(rootPC->AppUnitsPerDevPixel(),
3327 thisPC->AppUnitsPerDevPixel());
3329 return NS_OK;
3332 static void AdjustRangeForSelection(const Element* aRootElement,
3333 nsINode** aNode,
3334 Maybe<uint32_t>* aNodeOffset) {
3335 nsINode* node = *aNode;
3336 Maybe<uint32_t> nodeOffset = *aNodeOffset;
3337 if (aRootElement == node || NS_WARN_IF(!node->GetParent()) ||
3338 !node->IsText()) {
3339 return;
3342 // When the offset is at the end of the text node, set it to after the
3343 // text node, to make sure the caret is drawn on a new line when the last
3344 // character of the text node is '\n' in <textarea>.
3345 const uint32_t textLength = node->AsContent()->TextLength();
3346 MOZ_ASSERT(nodeOffset.isNothing() || *nodeOffset <= textLength,
3347 "Offset is past length of text node");
3348 if (nodeOffset.isNothing() || *nodeOffset != textLength) {
3349 return;
3352 Element* rootParentElement = aRootElement->GetParentElement();
3353 if (NS_WARN_IF(!rootParentElement)) {
3354 return;
3356 // If the root node is not an anonymous div of <textarea>, we don't need to
3357 // do this hack. If you did this, ContentEventHandler couldn't distinguish
3358 // if the range includes open tag of the next node in some cases, e.g.,
3359 // textNode]<p></p> vs. textNode<p>]</p>
3360 if (!rootParentElement->IsHTMLElement(nsGkAtoms::textarea)) {
3361 return;
3364 // If the node is being removed from its parent, it holds the ex-parent,
3365 // but the parent have already removed the child from its child chain.
3366 // Therefore `ComputeIndexOf` may fail, but I don't want to make Beta/Nightly
3367 // crash at accessing `Maybe::operator*` so that here checks `isSome`, but
3368 // crashing only in debug builds may help to debug something complicated
3369 // situation, therefore, `MOZ_ASSERT` is put here.
3370 *aNode = node->GetParent();
3371 Maybe<uint32_t> index = (*aNode)->ComputeIndexOf(node);
3372 MOZ_ASSERT(index.isSome());
3373 if (index.isSome()) {
3374 MOZ_ASSERT(*index != UINT32_MAX);
3375 *aNodeOffset = Some(*index + 1u);
3376 } else {
3377 *aNodeOffset = Some(0u);
3381 nsresult ContentEventHandler::OnSelectionEvent(WidgetSelectionEvent* aEvent) {
3382 aEvent->mSucceeded = false;
3384 // Get selection to manipulate
3385 // XXX why do we need to get them from ISM? This method should work fine
3386 // without ISM.
3387 nsresult rv = IMEStateManager::GetFocusSelectionAndRootElement(
3388 getter_AddRefs(mSelection), getter_AddRefs(mRootElement));
3389 if (rv != NS_ERROR_NOT_AVAILABLE) {
3390 NS_ENSURE_SUCCESS(rv, rv);
3391 } else {
3392 rv = Init(aEvent);
3393 NS_ENSURE_SUCCESS(rv, rv);
3396 // Get range from offset and length
3397 nsINode* startNode = nullptr;
3398 nsINode* endNode = nullptr;
3399 Maybe<uint32_t> startNodeOffset;
3400 Maybe<uint32_t> endNodeOffset;
3402 Result<UnsafeDOMRangeAndAdjustedOffsetInFlattenedText, nsresult>
3403 domRangeAndAdjustedOffsetOrError =
3404 ConvertFlatTextOffsetToUnsafeDOMRange(
3405 aEvent->mOffset, aEvent->mLength, GetLineBreakType(aEvent),
3406 aEvent->mExpandToClusterBoundary);
3407 if (MOZ_UNLIKELY(domRangeAndAdjustedOffsetOrError.isErr())) {
3408 NS_WARNING(
3409 "ContentEventHandler::ConvertFlatTextOffsetToDOMRangeBase() failed");
3410 return domRangeAndAdjustedOffsetOrError.unwrapErr();
3412 const UnsafeDOMRangeAndAdjustedOffsetInFlattenedText
3413 domRangeAndAdjustedOffset = domRangeAndAdjustedOffsetOrError.unwrap();
3414 startNode = domRangeAndAdjustedOffset.mRange.GetStartContainer();
3415 endNode = domRangeAndAdjustedOffset.mRange.GetEndContainer();
3416 startNodeOffset = Some(domRangeAndAdjustedOffset.mRange.StartOffset());
3417 endNodeOffset = Some(domRangeAndAdjustedOffset.mRange.EndOffset());
3418 AdjustRangeForSelection(mRootElement, &startNode, &startNodeOffset);
3419 AdjustRangeForSelection(mRootElement, &endNode, &endNodeOffset);
3420 if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode) ||
3421 NS_WARN_IF(startNodeOffset.isNothing()) ||
3422 NS_WARN_IF(endNodeOffset.isNothing())) {
3423 return NS_ERROR_UNEXPECTED;
3427 if (aEvent->mReversed) {
3428 nsCOMPtr<nsINode> startNodeStrong(startNode);
3429 nsCOMPtr<nsINode> endNodeStrong(endNode);
3430 ErrorResult error;
3431 MOZ_KnownLive(mSelection)
3432 ->SetBaseAndExtentInLimiter(*endNodeStrong, *endNodeOffset,
3433 *startNodeStrong, *startNodeOffset, error);
3434 if (NS_WARN_IF(error.Failed())) {
3435 return error.StealNSResult();
3437 } else {
3438 nsCOMPtr<nsINode> startNodeStrong(startNode);
3439 nsCOMPtr<nsINode> endNodeStrong(endNode);
3440 ErrorResult error;
3441 MOZ_KnownLive(mSelection)
3442 ->SetBaseAndExtentInLimiter(*startNodeStrong, *startNodeOffset,
3443 *endNodeStrong, *endNodeOffset, error);
3444 if (NS_WARN_IF(error.Failed())) {
3445 return error.StealNSResult();
3449 // `ContentEventHandler` is a `MOZ_STACK_CLASS`, so `mSelection` is known to
3450 // be alive.
3451 MOZ_KnownLive(mSelection)
3452 ->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
3453 ScrollAxis(), ScrollAxis(), 0);
3454 aEvent->mSucceeded = true;
3455 return NS_OK;
3458 nsRect ContentEventHandler::FrameRelativeRect::RectRelativeTo(
3459 nsIFrame* aDestFrame) const {
3460 if (!mBaseFrame || NS_WARN_IF(!aDestFrame)) {
3461 return nsRect();
3464 if (NS_WARN_IF(aDestFrame->PresContext() != mBaseFrame->PresContext())) {
3465 return nsRect();
3468 if (aDestFrame == mBaseFrame) {
3469 return mRect;
3472 nsIFrame* rootFrame = mBaseFrame->PresShell()->GetRootFrame();
3473 nsRect baseFrameRectInRootFrame = nsLayoutUtils::TransformFrameRectToAncestor(
3474 mBaseFrame, nsRect(), rootFrame);
3475 nsRect destFrameRectInRootFrame = nsLayoutUtils::TransformFrameRectToAncestor(
3476 aDestFrame, nsRect(), rootFrame);
3477 nsPoint difference =
3478 destFrameRectInRootFrame.TopLeft() - baseFrameRectInRootFrame.TopLeft();
3479 return mRect - difference;
3482 } // namespace mozilla