Bug 1836072 [wpt PR 40324] - Fix scrollbar-width repaint issues on non viewports...
[gecko.git] / dom / events / ContentEventHandler.cpp
blob3ecc554d28c1a5093708932f85395988a87b02a1
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ContentEventHandler.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/ContentIterator.h"
12 #include "mozilla/IMEStateManager.h"
13 #include "mozilla/IntegerRange.h"
14 #include "mozilla/Maybe.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/RangeBoundary.h"
17 #include "mozilla/RangeUtils.h"
18 #include "mozilla/TextComposition.h"
19 #include "mozilla/TextEditor.h"
20 #include "mozilla/TextEvents.h"
21 #include "mozilla/dom/Element.h"
22 #include "mozilla/dom/HTMLBRElement.h"
23 #include "mozilla/dom/HTMLUnknownElement.h"
24 #include "mozilla/dom/Selection.h"
25 #include "mozilla/dom/Text.h"
26 #include "nsCaret.h"
27 #include "nsCOMPtr.h"
28 #include "nsContentUtils.h"
29 #include "nsCopySupport.h"
30 #include "nsElementTable.h"
31 #include "nsFocusManager.h"
32 #include "nsFontMetrics.h"
33 #include "nsFrameSelection.h"
34 #include "nsIFrame.h"
35 #include "nsLayoutUtils.h"
36 #include "nsPresContext.h"
37 #include "nsQueryObject.h"
38 #include "nsRange.h"
39 #include "nsTextFragment.h"
40 #include "nsTextFrame.h"
41 #include "nsView.h"
42 #include "mozilla/ViewportUtils.h"
44 #include <algorithm>
46 // Work around conflicting define in rpcndr.h
47 #if defined(small)
48 # undef small
49 #endif // defined(small)
51 namespace mozilla {
53 using namespace dom;
54 using namespace widget;
56 /******************************************************************/
57 /* ContentEventHandler::RawRange */
58 /******************************************************************/
60 void ContentEventHandler::RawRange::AssertStartIsBeforeOrEqualToEnd() {
61 MOZ_ASSERT(
62 *nsContentUtils::ComparePoints(
63 mStart.Container(),
64 *mStart.Offset(NodePosition::OffsetFilter::kValidOrInvalidOffsets),
65 mEnd.Container(),
66 *mEnd.Offset(NodePosition::OffsetFilter::kValidOrInvalidOffsets)) <=
67 0);
70 nsresult ContentEventHandler::RawRange::SetStart(
71 const RawRangeBoundary& aStart) {
72 nsINode* newRoot = RangeUtils::ComputeRootNode(aStart.Container());
73 if (!newRoot) {
74 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
77 if (!aStart.IsSetAndValid()) {
78 return NS_ERROR_DOM_INDEX_SIZE_ERR;
81 // Collapse if not positioned yet, or if positioned in another document.
82 if (!IsPositioned() || newRoot != mRoot) {
83 mRoot = newRoot;
84 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
85 mEnd.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
86 return NS_OK;
89 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
90 AssertStartIsBeforeOrEqualToEnd();
91 return NS_OK;
94 nsresult ContentEventHandler::RawRange::SetEnd(const RawRangeBoundary& aEnd) {
95 nsINode* newRoot = RangeUtils::ComputeRootNode(aEnd.Container());
96 if (!newRoot) {
97 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
100 if (!aEnd.IsSetAndValid()) {
101 return NS_ERROR_DOM_INDEX_SIZE_ERR;
104 // Collapse if not positioned yet, or if positioned in another document.
105 if (!IsPositioned() || newRoot != mRoot) {
106 mRoot = newRoot;
107 mStart.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
108 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
109 return NS_OK;
112 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
113 AssertStartIsBeforeOrEqualToEnd();
114 return NS_OK;
117 nsresult ContentEventHandler::RawRange::SetEndAfter(nsINode* aEndContainer) {
118 return SetEnd(RangeUtils::GetRawRangeBoundaryAfter(aEndContainer));
121 void ContentEventHandler::RawRange::SetStartAndEnd(const nsRange* aRange) {
122 DebugOnly<nsresult> rv =
123 SetStartAndEnd(aRange->StartRef().AsRaw(), aRange->EndRef().AsRaw());
124 MOZ_ASSERT(!aRange->IsPositioned() || NS_SUCCEEDED(rv));
127 nsresult ContentEventHandler::RawRange::SetStartAndEnd(
128 const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) {
129 nsINode* newStartRoot = RangeUtils::ComputeRootNode(aStart.Container());
130 if (!newStartRoot) {
131 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
133 if (!aStart.IsSetAndValid()) {
134 return NS_ERROR_DOM_INDEX_SIZE_ERR;
137 if (aStart.Container() == aEnd.Container()) {
138 if (!aEnd.IsSetAndValid()) {
139 return NS_ERROR_DOM_INDEX_SIZE_ERR;
141 MOZ_ASSERT(*aStart.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets) <=
142 *aEnd.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets));
143 mRoot = newStartRoot;
144 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
145 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
146 return NS_OK;
149 nsINode* newEndRoot = RangeUtils::ComputeRootNode(aEnd.Container());
150 if (!newEndRoot) {
151 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
153 if (!aEnd.IsSetAndValid()) {
154 return NS_ERROR_DOM_INDEX_SIZE_ERR;
157 // If they have different root, this should be collapsed at the end point.
158 if (newStartRoot != newEndRoot) {
159 mRoot = newEndRoot;
160 mStart.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
161 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
162 return NS_OK;
165 // Otherwise, set the range as specified.
166 mRoot = newStartRoot;
167 mStart.CopyFrom(aStart, RangeBoundaryIsMutationObserved::Yes);
168 mEnd.CopyFrom(aEnd, RangeBoundaryIsMutationObserved::Yes);
169 AssertStartIsBeforeOrEqualToEnd();
170 return NS_OK;
173 nsresult ContentEventHandler::RawRange::SelectNodeContents(
174 const nsINode* aNodeToSelectContents) {
175 nsINode* const newRoot =
176 RangeUtils::ComputeRootNode(const_cast<nsINode*>(aNodeToSelectContents));
177 if (!newRoot) {
178 return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
180 mRoot = newRoot;
181 mStart = RangeBoundary(const_cast<nsINode*>(aNodeToSelectContents), nullptr);
182 mEnd = RangeBoundary(const_cast<nsINode*>(aNodeToSelectContents),
183 aNodeToSelectContents->GetLastChild());
184 return NS_OK;
187 /******************************************************************/
188 /* ContentEventHandler */
189 /******************************************************************/
191 // NOTE
193 // ContentEventHandler *creates* ranges as following rules:
194 // 1. Start of range:
195 // 1.1. Cases: [textNode or text[Node or textNode[
196 // When text node is start of a range, start node is the text node and
197 // start offset is any number between 0 and the length of the text.
198 // 1.2. Case: [<element>:
199 // When start of an element node is start of a range, start node is
200 // parent of the element and start offset is the element's index in the
201 // parent.
202 // 1.3. Case: <element/>[
203 // When after an empty element node is start of a range, start node is
204 // parent of the element and start offset is the element's index in the
205 // parent + 1.
206 // 1.4. Case: <element>[
207 // When start of a non-empty element is start of a range, start node is
208 // the element and start offset is 0.
209 // 1.5. Case: <root>[
210 // When start of a range is 0 and there are no nodes causing text,
211 // start node is the root node and start offset is 0.
212 // 1.6. Case: [</root>
213 // When start of a range is out of bounds, start node is the root node
214 // and start offset is number of the children.
215 // 2. End of range:
216 // 2.1. Cases: ]textNode or text]Node or textNode]
217 // When a text node is end of a range, end node is the text node and
218 // end offset is any number between 0 and the length of the text.
219 // 2.2. Case: ]<element>
220 // When before an element node (meaning before the open tag of the
221 // element) is end of a range, end node is previous node causing text.
222 // Note that this case shouldn't be handled directly. If rule 2.1 and
223 // 2.3 are handled correctly, the loop with ContentIterator shouldn't
224 // reach the element node since the loop should've finished already at
225 // handling the last node which caused some text.
226 // 2.3. Case: <element>]
227 // When a line break is caused before a non-empty element node and it's
228 // end of a range, end node is the element and end offset is 0.
229 // (i.e., including open tag of the element)
230 // 2.4. Cases: <element/>]
231 // When after an empty element node is end of a range, end node is
232 // parent of the element node and end offset is the element's index in
233 // the parent + 1. (i.e., including close tag of the element or empty
234 // element)
235 // 2.5. Case: ]</root>
236 // When end of a range is out of bounds, end node is the root node and
237 // end offset is number of the children.
239 // ContentEventHandler *treats* ranges as following additional rules:
240 // 1. When the start node is an element node which doesn't have children,
241 // it includes a line break caused before itself (i.e., includes its open
242 // tag). For example, if start position is { <br>, 0 }, the line break
243 // caused by <br> should be included into the flatten text.
244 // 2. When the end node is an element node which doesn't have children,
245 // it includes the end (i.e., includes its close tag except empty element).
246 // Although, currently, any close tags don't cause line break, this also
247 // includes its open tag. For example, if end position is { <br>, 0 }, the
248 // line break caused by the <br> should be included into the flatten text.
250 ContentEventHandler::ContentEventHandler(nsPresContext* aPresContext)
251 : mDocument(aPresContext->Document()) {}
253 nsresult ContentEventHandler::InitBasic(bool aRequireFlush) {
254 NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
255 if (aRequireFlush) {
256 // If text frame which has overflowing selection underline is dirty,
257 // we need to flush the pending reflow here.
258 mDocument->FlushPendingNotifications(FlushType::Layout);
260 return NS_OK;
263 nsresult ContentEventHandler::InitRootContent(
264 const Selection& aNormalSelection) {
265 // Root content should be computed with normal selection because normal
266 // selection is typically has at least one range but the other selections
267 // not so. If there is a range, computing its root is easy, but if
268 // there are no ranges, we need to use ancestor limit instead.
269 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal);
271 if (!aNormalSelection.RangeCount()) {
272 // If there is no selection range, we should compute the selection root
273 // from ancestor limiter or root content of the document.
274 mRootElement =
275 Element::FromNodeOrNull(aNormalSelection.GetAncestorLimiter());
276 if (!mRootElement) {
277 mRootElement = mDocument->GetRootElement();
278 if (NS_WARN_IF(!mRootElement)) {
279 return NS_ERROR_NOT_AVAILABLE;
282 return NS_OK;
285 RefPtr<const nsRange> range(aNormalSelection.GetRangeAt(0));
286 if (NS_WARN_IF(!range)) {
287 return NS_ERROR_UNEXPECTED;
290 // If there is a selection, we should retrieve the selection root from
291 // the range since when the window is inactivated, the ancestor limiter
292 // of selection was cleared by blur event handler of EditorBase but the
293 // selection range still keeps storing the nodes. If the active element of
294 // the deactive window is <input> or <textarea>, we can compute the
295 // selection root from them.
296 nsCOMPtr<nsINode> startNode = range->GetStartContainer();
297 nsINode* endNode = range->GetEndContainer();
298 if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
299 return NS_ERROR_FAILURE;
302 // See bug 537041 comment 5, the range could have removed node.
303 if (NS_WARN_IF(startNode->GetComposedDoc() != mDocument)) {
304 return NS_ERROR_FAILURE;
307 NS_ASSERTION(startNode->GetComposedDoc() == endNode->GetComposedDoc(),
308 "firstNormalSelectionRange crosses the document boundary");
310 RefPtr<PresShell> presShell = mDocument->GetPresShell();
311 mRootElement =
312 Element::FromNodeOrNull(startNode->GetSelectionRootContent(presShell));
313 if (NS_WARN_IF(!mRootElement)) {
314 return NS_ERROR_FAILURE;
317 return NS_OK;
320 nsresult ContentEventHandler::InitCommon(EventMessage aEventMessage,
321 SelectionType aSelectionType,
322 bool aRequireFlush) {
323 if (mSelection && mSelection->Type() == aSelectionType) {
324 return NS_OK;
327 mSelection = nullptr;
328 mRootElement = nullptr;
329 mFirstSelectedRawRange.Clear();
331 nsresult rv = InitBasic(aRequireFlush);
332 NS_ENSURE_SUCCESS(rv, rv);
334 RefPtr<nsFrameSelection> frameSel;
335 if (PresShell* presShell = mDocument->GetPresShell()) {
336 frameSel = presShell->GetLastFocusedFrameSelection();
338 if (NS_WARN_IF(!frameSel)) {
339 return NS_ERROR_NOT_AVAILABLE;
342 mSelection = frameSel->GetSelection(aSelectionType);
343 if (NS_WARN_IF(!mSelection)) {
344 return NS_ERROR_NOT_AVAILABLE;
347 RefPtr<Selection> normalSelection;
348 if (mSelection->Type() == SelectionType::eNormal) {
349 normalSelection = mSelection;
350 } else {
351 normalSelection = frameSel->GetSelection(SelectionType::eNormal);
352 if (NS_WARN_IF(!normalSelection)) {
353 return NS_ERROR_NOT_AVAILABLE;
357 rv = InitRootContent(*normalSelection);
358 if (NS_WARN_IF(NS_FAILED(rv))) {
359 return rv;
362 if (mSelection->RangeCount()) {
363 mFirstSelectedRawRange.SetStartAndEnd(mSelection->GetRangeAt(0));
364 return NS_OK;
367 // Even if there are no selection ranges, it's usual case if aSelectionType
368 // is a special selection or we're handling eQuerySelectedText.
369 if (aSelectionType != SelectionType::eNormal ||
370 aEventMessage == eQuerySelectedText) {
371 MOZ_ASSERT(!mFirstSelectedRawRange.IsPositioned());
372 return NS_OK;
375 // But otherwise, we need to assume that there is a selection range at the
376 // beginning of the root content if aSelectionType is eNormal.
377 rv = mFirstSelectedRawRange.CollapseTo(RawRangeBoundary(mRootElement, 0u));
378 if (NS_WARN_IF(NS_FAILED(rv))) {
379 return NS_ERROR_UNEXPECTED;
381 return NS_OK;
384 nsresult ContentEventHandler::Init(WidgetQueryContentEvent* aEvent) {
385 NS_ASSERTION(aEvent, "aEvent must not be null");
386 MOZ_ASSERT(aEvent->mMessage == eQuerySelectedText ||
387 aEvent->mInput.mSelectionType == SelectionType::eNormal);
389 if (NS_WARN_IF(!aEvent->mInput.IsValidOffset()) ||
390 NS_WARN_IF(!aEvent->mInput.IsValidEventMessage(aEvent->mMessage))) {
391 return NS_ERROR_FAILURE;
394 // Note that we should ignore WidgetQueryContentEvent::Input::mSelectionType
395 // if the event isn't eQuerySelectedText.
396 SelectionType selectionType = aEvent->mMessage == eQuerySelectedText
397 ? aEvent->mInput.mSelectionType
398 : SelectionType::eNormal;
399 if (NS_WARN_IF(selectionType == SelectionType::eNone)) {
400 return NS_ERROR_FAILURE;
403 nsresult rv =
404 InitCommon(aEvent->mMessage, selectionType, aEvent->NeedsToFlushLayout());
405 NS_ENSURE_SUCCESS(rv, rv);
407 // Be aware, WidgetQueryContentEvent::mInput::mOffset should be made absolute
408 // offset before sending it to ContentEventHandler because querying selection
409 // every time may be expensive. So, if the caller caches selection, it
410 // should initialize the event with the cached value.
411 if (aEvent->mInput.mRelativeToInsertionPoint) {
412 MOZ_ASSERT(selectionType == SelectionType::eNormal);
413 RefPtr<TextComposition> composition =
414 IMEStateManager::GetTextCompositionFor(aEvent->mWidget);
415 if (composition) {
416 uint32_t compositionStart = composition->NativeOffsetOfStartComposition();
417 if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) {
418 return NS_ERROR_FAILURE;
420 } else {
421 LineBreakType lineBreakType = GetLineBreakType(aEvent);
422 uint32_t selectionStart = 0;
423 rv = GetStartOffset(mFirstSelectedRawRange, &selectionStart,
424 lineBreakType);
425 if (NS_WARN_IF(NS_FAILED(rv))) {
426 return NS_ERROR_FAILURE;
428 if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(selectionStart))) {
429 return NS_ERROR_FAILURE;
434 // Ideally, we should emplace only when we return succeeded event.
435 // However, we need to emplace here since it's hard to store the various
436 // result. Intead, `HandleQueryContentEvent()` will reset `mReply` if
437 // corresponding handler returns error.
438 aEvent->EmplaceReply();
440 aEvent->mReply->mContentsRoot = mRootElement.get();
441 aEvent->mReply->mIsEditableContent =
442 mRootElement && mRootElement->IsEditable();
444 nsRect r;
445 nsIFrame* frame = nsCaret::GetGeometry(mSelection, &r);
446 if (!frame) {
447 frame = mRootElement->GetPrimaryFrame();
448 if (NS_WARN_IF(!frame)) {
449 return NS_ERROR_FAILURE;
452 aEvent->mReply->mFocusedWidget = frame->GetNearestWidget();
454 return NS_OK;
457 nsresult ContentEventHandler::Init(WidgetSelectionEvent* aEvent) {
458 NS_ASSERTION(aEvent, "aEvent must not be null");
460 nsresult rv = InitCommon(aEvent->mMessage);
461 NS_ENSURE_SUCCESS(rv, rv);
463 aEvent->mSucceeded = false;
465 return NS_OK;
468 nsIContent* ContentEventHandler::GetFocusedContent() {
469 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
470 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
471 return nsFocusManager::GetFocusedDescendant(
472 window, nsFocusManager::eIncludeAllDescendants,
473 getter_AddRefs(focusedWindow));
476 nsresult ContentEventHandler::QueryContentRect(
477 nsIContent* aContent, WidgetQueryContentEvent* aEvent) {
478 MOZ_ASSERT(aContent, "aContent must not be null");
480 nsIFrame* frame = aContent->GetPrimaryFrame();
481 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
483 // get rect for first frame
484 nsRect resultRect(nsPoint(0, 0), frame->GetRect().Size());
485 nsresult rv = ConvertToRootRelativeOffset(frame, resultRect);
486 NS_ENSURE_SUCCESS(rv, rv);
488 nsPresContext* presContext = frame->PresContext();
490 // account for any additional frames
491 while ((frame = frame->GetNextContinuation())) {
492 nsRect frameRect(nsPoint(0, 0), frame->GetRect().Size());
493 rv = ConvertToRootRelativeOffset(frame, frameRect);
494 NS_ENSURE_SUCCESS(rv, rv);
495 resultRect.UnionRect(resultRect, frameRect);
498 aEvent->mReply->mRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
499 resultRect, presContext->AppUnitsPerDevPixel());
500 // Returning empty rect may cause native IME confused, let's make sure to
501 // return non-empty rect.
502 EnsureNonEmptyRect(aEvent->mReply->mRect);
504 return NS_OK;
507 // Editor places a padding <br> element under its root content if the editor
508 // doesn't have any text. This happens even for single line editors.
509 // When we get text content and when we change the selection,
510 // we don't want to include the padding <br> elements at the end.
511 static bool IsContentBR(const nsIContent& aContent) {
512 const HTMLBRElement* brElement = HTMLBRElement::FromNode(aContent);
513 return brElement && !brElement->IsPaddingForEmptyLastLine() &&
514 !brElement->IsPaddingForEmptyEditor();
517 static bool IsPaddingBR(const nsIContent& aContent) {
518 return aContent.IsHTMLElement(nsGkAtoms::br) && !IsContentBR(aContent);
521 static void ConvertToNativeNewlines(nsString& aString) {
522 #if defined(XP_WIN)
523 aString.ReplaceSubstring(u"\n"_ns, u"\r\n"_ns);
524 #endif
527 static void AppendString(nsString& aString, const Text& aTextNode) {
528 const uint32_t oldXPLength = aString.Length();
529 aTextNode.TextFragment().AppendTo(aString);
530 if (aTextNode.HasFlag(NS_MAYBE_MASKED)) {
531 TextEditor::MaskString(aString, aTextNode, oldXPLength, 0);
535 static void AppendSubString(nsString& aString, const Text& aTextNode,
536 uint32_t aXPOffset, uint32_t aXPLength) {
537 const uint32_t oldXPLength = aString.Length();
538 aTextNode.TextFragment().AppendTo(aString, aXPOffset, aXPLength);
539 if (aTextNode.HasFlag(NS_MAYBE_MASKED)) {
540 TextEditor::MaskString(aString, aTextNode, oldXPLength, aXPOffset);
544 #if defined(XP_WIN)
545 template <typename StringType>
546 static uint32_t CountNewlinesInXPLength(const StringType& aString) {
547 uint32_t count = 0;
548 const auto* end = aString.EndReading();
549 for (const auto* iter = aString.BeginReading(); iter < end; ++iter) {
550 if (*iter == '\n') {
551 count++;
554 return count;
557 static uint32_t CountNewlinesInXPLength(const Text& aTextNode,
558 uint32_t aXPLength) {
559 const nsTextFragment& textFragment = aTextNode.TextFragment();
560 // For automated tests, we should abort on debug build.
561 MOZ_ASSERT(aXPLength == UINT32_MAX || aXPLength <= textFragment.GetLength(),
562 "aXPLength is out-of-bounds");
563 const uint32_t length = std::min(aXPLength, textFragment.GetLength());
564 if (!length) {
565 return 0;
567 if (textFragment.Is2b()) {
568 nsDependentSubstring str(textFragment.Get2b(), length);
569 return CountNewlinesInXPLength(str);
571 nsDependentCSubstring str(textFragment.Get1b(), length);
572 return CountNewlinesInXPLength(str);
575 template <typename StringType>
576 static uint32_t CountNewlinesInNativeLength(const StringType& aString,
577 uint32_t aNativeLength) {
578 MOZ_ASSERT(
579 (aNativeLength == UINT32_MAX || aNativeLength <= aString.Length() * 2),
580 "aNativeLength is unexpected value");
581 uint32_t count = 0;
582 uint32_t nativeOffset = 0;
583 const auto* end = aString.EndReading();
584 for (const auto* iter = aString.BeginReading();
585 iter < end && nativeOffset < aNativeLength; ++iter, ++nativeOffset) {
586 if (*iter == '\n') {
587 count++;
588 nativeOffset++;
591 return count;
594 static uint32_t CountNewlinesInNativeLength(const Text& aTextNode,
595 uint32_t aNativeLength) {
596 const nsTextFragment& textFragment = aTextNode.TextFragment();
597 const uint32_t xpLength = textFragment.GetLength();
598 if (!xpLength) {
599 return 0;
601 if (textFragment.Is2b()) {
602 nsDependentSubstring str(textFragment.Get2b(), xpLength);
603 return CountNewlinesInNativeLength(str, aNativeLength);
605 nsDependentCSubstring str(textFragment.Get1b(), xpLength);
606 return CountNewlinesInNativeLength(str, aNativeLength);
608 #endif
610 /* static */
611 uint32_t ContentEventHandler::GetNativeTextLength(const Text& aTextNode,
612 uint32_t aStartOffset,
613 uint32_t aEndOffset) {
614 MOZ_ASSERT(aEndOffset >= aStartOffset,
615 "aEndOffset must be equals or larger than aStartOffset");
616 if (aStartOffset == aEndOffset) {
617 return 0;
619 return GetTextLength(aTextNode, LINE_BREAK_TYPE_NATIVE, aEndOffset) -
620 GetTextLength(aTextNode, LINE_BREAK_TYPE_NATIVE, aStartOffset);
623 /* static */
624 uint32_t ContentEventHandler::GetNativeTextLength(const Text& aTextNode,
625 uint32_t aMaxLength) {
626 return GetTextLength(aTextNode, LINE_BREAK_TYPE_NATIVE, aMaxLength);
629 /* static inline */
630 uint32_t ContentEventHandler::GetBRLength(LineBreakType aLineBreakType) {
631 #if defined(XP_WIN)
632 // Length of \r\n
633 return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1;
634 #else
635 return 1;
636 #endif
639 /* static */
640 uint32_t ContentEventHandler::GetTextLength(const Text& aTextNode,
641 LineBreakType aLineBreakType,
642 uint32_t aMaxLength) {
643 const uint32_t textLengthDifference =
644 #if defined(XP_WIN)
645 // On Windows, the length of a native newline ("\r\n") is twice the length
646 // of the XP newline ("\n"), so XP length is equal to the length of the
647 // native offset plus the number of newlines encountered in the string.
648 (aLineBreakType == LINE_BREAK_TYPE_NATIVE)
649 ? CountNewlinesInXPLength(aTextNode, aMaxLength)
650 : 0;
651 #else
652 // On other platforms, the native and XP newlines are the same.
654 #endif
656 const uint32_t length =
657 std::min(aTextNode.TextFragment().GetLength(), aMaxLength);
658 return length + textLengthDifference;
661 static uint32_t ConvertToXPOffset(const Text& aTextNode,
662 uint32_t aNativeOffset) {
663 #if defined(XP_WIN)
664 // On Windows, the length of a native newline ("\r\n") is twice the length of
665 // the XP newline ("\n"), so XP offset is equal to the length of the native
666 // offset minus the number of newlines encountered in the string.
667 return aNativeOffset - CountNewlinesInNativeLength(aTextNode, aNativeOffset);
668 #else
669 // On other platforms, the native and XP newlines are the same.
670 return aNativeOffset;
671 #endif
674 /* static */
675 uint32_t ContentEventHandler::GetNativeTextLength(const nsAString& aText) {
676 const uint32_t textLengthDifference =
677 #if defined(XP_WIN)
678 // On Windows, the length of a native newline ("\r\n") is twice the length
679 // of the XP newline ("\n"), so XP length is equal to the length of the
680 // native offset plus the number of newlines encountered in the string.
681 CountNewlinesInXPLength(aText);
682 #else
683 // On other platforms, the native and XP newlines are the same.
685 #endif
686 return aText.Length() + textLengthDifference;
689 /* static */
690 bool ContentEventHandler::ShouldBreakLineBefore(const nsIContent& aContent,
691 const Element* aRootElement) {
692 // We don't need to append linebreak at the start of the root element.
693 if (&aContent == aRootElement) {
694 return false;
697 // If it's not an HTML element (including other markup language's elements),
698 // we shouldn't insert like break before that for now. Becoming this is a
699 // problem must be edge case. E.g., when ContentEventHandler is used with
700 // MathML or SVG elements.
701 if (!aContent.IsHTMLElement()) {
702 return false;
705 // If the element is <br>, we need to check if the <br> is caused by web
706 // content. Otherwise, i.e., it's caused by internal reason of Gecko,
707 // it shouldn't be exposed as a line break to flatten text.
708 if (aContent.IsHTMLElement(nsGkAtoms::br)) {
709 return IsContentBR(aContent);
712 // Note that ideally, we should refer the style of the primary frame of
713 // aContent for deciding if it's an inline. However, it's difficult
714 // IMEContentObserver to notify IME of text change caused by style change.
715 // Therefore, currently, we should check only from the tag for now.
716 if (aContent.IsAnyOfHTMLElements(
717 nsGkAtoms::a, nsGkAtoms::abbr, nsGkAtoms::acronym, nsGkAtoms::b,
718 nsGkAtoms::bdi, nsGkAtoms::bdo, nsGkAtoms::big, nsGkAtoms::cite,
719 nsGkAtoms::code, nsGkAtoms::data, nsGkAtoms::del, nsGkAtoms::dfn,
720 nsGkAtoms::em, nsGkAtoms::font, nsGkAtoms::i, nsGkAtoms::ins,
721 nsGkAtoms::kbd, nsGkAtoms::mark, nsGkAtoms::s, nsGkAtoms::samp,
722 nsGkAtoms::small, nsGkAtoms::span, nsGkAtoms::strike,
723 nsGkAtoms::strong, nsGkAtoms::sub, nsGkAtoms::sup, nsGkAtoms::time,
724 nsGkAtoms::tt, nsGkAtoms::u, nsGkAtoms::var)) {
725 return false;
728 // If the element is unknown element, we shouldn't insert line breaks before
729 // it since unknown elements should be ignored.
730 RefPtr<HTMLUnknownElement> unknownHTMLElement =
731 do_QueryObject(const_cast<nsIContent*>(&aContent));
732 return !unknownHTMLElement;
735 nsresult ContentEventHandler::GenerateFlatTextContent(
736 const Element* aElement, nsString& aString, LineBreakType aLineBreakType) {
737 MOZ_ASSERT(aString.IsEmpty());
739 RawRange rawRange;
740 nsresult rv = rawRange.SelectNodeContents(aElement);
741 if (NS_WARN_IF(NS_FAILED(rv))) {
742 return rv;
744 return GenerateFlatTextContent(rawRange, aString, aLineBreakType);
747 nsresult ContentEventHandler::GenerateFlatTextContent(
748 const RawRange& aRawRange, nsString& aString,
749 LineBreakType aLineBreakType) {
750 MOZ_ASSERT(aString.IsEmpty());
752 if (aRawRange.Collapsed()) {
753 return NS_OK;
756 nsINode* startNode = aRawRange.GetStartContainer();
757 nsINode* endNode = aRawRange.GetEndContainer();
758 if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
759 return NS_ERROR_FAILURE;
762 if (startNode == endNode && startNode->IsText()) {
763 AppendSubString(aString, *startNode->AsText(), aRawRange.StartOffset(),
764 aRawRange.EndOffset() - aRawRange.StartOffset());
765 ConvertToNativeNewlines(aString);
766 return NS_OK;
769 PreContentIterator preOrderIter;
770 nsresult rv =
771 preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
772 if (NS_WARN_IF(NS_FAILED(rv))) {
773 return rv;
775 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
776 nsINode* node = preOrderIter.GetCurrentNode();
777 if (NS_WARN_IF(!node)) {
778 break;
780 if (!node->IsContent()) {
781 continue;
784 if (const Text* textNode = Text::FromNode(node)) {
785 if (textNode == startNode) {
786 AppendSubString(aString, *textNode, aRawRange.StartOffset(),
787 textNode->TextLength() - aRawRange.StartOffset());
788 } else if (textNode == endNode) {
789 AppendSubString(aString, *textNode, 0, aRawRange.EndOffset());
790 } else {
791 AppendString(aString, *textNode);
793 } else if (ShouldBreakLineBefore(*node->AsContent(), mRootElement)) {
794 aString.Append(char16_t('\n'));
797 if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
798 ConvertToNativeNewlines(aString);
800 return NS_OK;
803 static FontRange* AppendFontRange(nsTArray<FontRange>& aFontRanges,
804 uint32_t aBaseOffset) {
805 FontRange* fontRange = aFontRanges.AppendElement();
806 fontRange->mStartOffset = aBaseOffset;
807 return fontRange;
810 /* static */
811 uint32_t ContentEventHandler::GetTextLengthInRange(
812 const Text& aTextNode, uint32_t aXPStartOffset, uint32_t aXPEndOffset,
813 LineBreakType aLineBreakType) {
814 return aLineBreakType == LINE_BREAK_TYPE_NATIVE
815 ? GetNativeTextLength(aTextNode, aXPStartOffset, aXPEndOffset)
816 : aXPEndOffset - aXPStartOffset;
819 /* static */
820 void ContentEventHandler::AppendFontRanges(FontRangeArray& aFontRanges,
821 const Text& aTextNode,
822 uint32_t aBaseOffset,
823 uint32_t aXPStartOffset,
824 uint32_t aXPEndOffset,
825 LineBreakType aLineBreakType) {
826 nsIFrame* frame = aTextNode.GetPrimaryFrame();
827 if (!frame) {
828 // It is a non-rendered content, create an empty range for it.
829 AppendFontRange(aFontRanges, aBaseOffset);
830 return;
833 uint32_t baseOffset = aBaseOffset;
834 #ifdef DEBUG
836 nsTextFrame* text = do_QueryFrame(frame);
837 MOZ_ASSERT(text, "Not a text frame");
839 #endif
840 auto* curr = static_cast<nsTextFrame*>(frame);
841 while (curr) {
842 uint32_t frameXPStart = std::max(
843 static_cast<uint32_t>(curr->GetContentOffset()), aXPStartOffset);
844 uint32_t frameXPEnd =
845 std::min(static_cast<uint32_t>(curr->GetContentEnd()), aXPEndOffset);
846 if (frameXPStart >= frameXPEnd) {
847 curr = curr->GetNextContinuation();
848 continue;
851 gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated);
852 gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated);
854 nsTextFrame* next = nullptr;
855 if (frameXPEnd < aXPEndOffset) {
856 next = curr->GetNextContinuation();
857 while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) {
858 frameXPEnd = std::min(static_cast<uint32_t>(next->GetContentEnd()),
859 aXPEndOffset);
860 next =
861 frameXPEnd < aXPEndOffset ? next->GetNextContinuation() : nullptr;
865 gfxTextRun::Range skipRange(iter.ConvertOriginalToSkipped(frameXPStart),
866 iter.ConvertOriginalToSkipped(frameXPEnd));
867 uint32_t lastXPEndOffset = frameXPStart;
868 for (gfxTextRun::GlyphRunIterator runIter(textRun, skipRange);
869 !runIter.AtEnd(); runIter.NextRun()) {
870 gfxFont* font = runIter.GlyphRun()->mFont.get();
871 uint32_t startXPOffset =
872 iter.ConvertSkippedToOriginal(runIter.StringStart());
873 // It is possible that the first glyph run has exceeded the frame,
874 // because the whole frame is filled by skipped chars.
875 if (startXPOffset >= frameXPEnd) {
876 break;
879 if (startXPOffset > lastXPEndOffset) {
880 // Create range for skipped leading chars.
881 AppendFontRange(aFontRanges, baseOffset);
882 baseOffset += GetTextLengthInRange(aTextNode, lastXPEndOffset,
883 startXPOffset, aLineBreakType);
886 FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset);
887 fontRange->mFontName.Append(NS_ConvertUTF8toUTF16(font->GetName()));
889 ParentLayerToScreenScale2D cumulativeResolution =
890 ParentLayerToParentLayerScale(
891 frame->PresShell()->GetCumulativeResolution()) *
892 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
893 frame);
894 float scale =
895 std::max(cumulativeResolution.xScale, cumulativeResolution.yScale);
897 fontRange->mFontSize = font->GetAdjustedSize() * scale;
899 // The converted original offset may exceed the range,
900 // hence we need to clamp it.
901 uint32_t endXPOffset = iter.ConvertSkippedToOriginal(runIter.StringEnd());
902 endXPOffset = std::min(frameXPEnd, endXPOffset);
903 baseOffset += GetTextLengthInRange(aTextNode, startXPOffset, endXPOffset,
904 aLineBreakType);
905 lastXPEndOffset = endXPOffset;
907 if (lastXPEndOffset < frameXPEnd) {
908 // Create range for skipped trailing chars. It also handles case
909 // that the whole frame contains only skipped chars.
910 AppendFontRange(aFontRanges, baseOffset);
911 baseOffset += GetTextLengthInRange(aTextNode, lastXPEndOffset, frameXPEnd,
912 aLineBreakType);
915 curr = next;
919 nsresult ContentEventHandler::GenerateFlatFontRanges(
920 const RawRange& aRawRange, FontRangeArray& aFontRanges, uint32_t& aLength,
921 LineBreakType aLineBreakType) {
922 MOZ_ASSERT(aFontRanges.IsEmpty(), "aRanges must be empty array");
924 if (aRawRange.Collapsed()) {
925 return NS_OK;
928 nsINode* startNode = aRawRange.GetStartContainer();
929 nsINode* endNode = aRawRange.GetEndContainer();
930 if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
931 return NS_ERROR_FAILURE;
934 // baseOffset is the flattened offset of each content node.
935 uint32_t baseOffset = 0;
936 PreContentIterator preOrderIter;
937 nsresult rv =
938 preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
939 if (NS_WARN_IF(NS_FAILED(rv))) {
940 return rv;
942 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
943 nsINode* node = preOrderIter.GetCurrentNode();
944 if (NS_WARN_IF(!node)) {
945 break;
947 if (!node->IsContent()) {
948 continue;
950 nsIContent* content = node->AsContent();
952 if (const Text* textNode = Text::FromNode(content)) {
953 const uint32_t startOffset =
954 textNode != startNode ? 0 : aRawRange.StartOffset();
955 const uint32_t endOffset =
956 textNode != endNode ? textNode->TextLength() : aRawRange.EndOffset();
957 AppendFontRanges(aFontRanges, *textNode, baseOffset, startOffset,
958 endOffset, aLineBreakType);
959 baseOffset += GetTextLengthInRange(*textNode, startOffset, endOffset,
960 aLineBreakType);
961 } else if (ShouldBreakLineBefore(*content, mRootElement)) {
962 if (aFontRanges.IsEmpty()) {
963 MOZ_ASSERT(baseOffset == 0);
964 FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset);
965 if (nsIFrame* frame = content->GetPrimaryFrame()) {
966 const nsFont& font = frame->GetParent()->StyleFont()->mFont;
967 const StyleFontFamilyList& fontList = font.family.families;
968 MOZ_ASSERT(!fontList.list.IsEmpty(), "Empty font family?");
969 const StyleSingleFontFamily* fontName =
970 fontList.list.IsEmpty() ? nullptr : &fontList.list.AsSpan()[0];
971 nsAutoCString name;
972 if (fontName) {
973 fontName->AppendToString(name, false);
975 AppendUTF8toUTF16(name, fontRange->mFontName);
977 ParentLayerToScreenScale2D cumulativeResolution =
978 ParentLayerToParentLayerScale(
979 frame->PresShell()->GetCumulativeResolution()) *
980 nsLayoutUtils::
981 GetTransformToAncestorScaleCrossProcessForFrameMetrics(frame);
983 float scale = std::max(cumulativeResolution.xScale,
984 cumulativeResolution.yScale);
986 fontRange->mFontSize = frame->PresContext()->CSSPixelsToDevPixels(
987 font.size.ToCSSPixels() * scale);
990 baseOffset += GetBRLength(aLineBreakType);
994 aLength = baseOffset;
995 return NS_OK;
998 nsresult ContentEventHandler::ExpandToClusterBoundary(
999 Text& aTextNode, bool aForward, uint32_t* aXPOffset) const {
1000 // XXX This method assumes that the frame boundaries must be cluster
1001 // boundaries. It's false, but no problem now, maybe.
1002 if (*aXPOffset == 0 || *aXPOffset == aTextNode.TextLength()) {
1003 return NS_OK;
1006 NS_ASSERTION(*aXPOffset <= aTextNode.TextLength(), "offset is out of range.");
1008 MOZ_DIAGNOSTIC_ASSERT(mDocument->GetPresShell());
1009 int32_t offsetInFrame;
1010 CaretAssociationHint hint =
1011 aForward ? CARET_ASSOCIATE_BEFORE : CARET_ASSOCIATE_AFTER;
1012 nsIFrame* frame = nsFrameSelection::GetFrameForNodeOffset(
1013 &aTextNode, int32_t(*aXPOffset), hint, &offsetInFrame);
1014 if (frame) {
1015 auto [startOffset, endOffset] = frame->GetOffsets();
1016 if (*aXPOffset == static_cast<uint32_t>(startOffset) ||
1017 *aXPOffset == static_cast<uint32_t>(endOffset)) {
1018 return NS_OK;
1020 if (!frame->IsTextFrame()) {
1021 return NS_ERROR_FAILURE;
1023 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
1024 int32_t newOffsetInFrame = *aXPOffset - startOffset;
1025 newOffsetInFrame += aForward ? -1 : 1;
1026 // PeekOffsetCharacter() should respect cluster but ignore user-select
1027 // style. If it returns "FOUND", we should use the result. Otherwise,
1028 // we shouldn't use the result because the offset was moved to reversed
1029 // direction.
1030 nsTextFrame::PeekOffsetCharacterOptions options;
1031 options.mRespectClusters = true;
1032 options.mIgnoreUserStyleAll = true;
1033 if (textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame, options) ==
1034 nsIFrame::FOUND) {
1035 *aXPOffset = startOffset + newOffsetInFrame;
1036 return NS_OK;
1040 // If the frame isn't available, we only can check surrogate pair...
1041 if (aTextNode.TextFragment().IsLowSurrogateFollowingHighSurrogateAt(
1042 *aXPOffset)) {
1043 *aXPOffset += aForward ? 1 : -1;
1045 return NS_OK;
1048 nsresult ContentEventHandler::SetRawRangeFromFlatTextOffset(
1049 RawRange* aRawRange, uint32_t aOffset, uint32_t aLength,
1050 LineBreakType aLineBreakType, bool aExpandToClusterBoundaries,
1051 uint32_t* aNewOffset, Text** aLastTextNode) {
1052 if (aNewOffset) {
1053 *aNewOffset = aOffset;
1055 if (aLastTextNode) {
1056 *aLastTextNode = nullptr;
1059 // Special case like <br contenteditable>
1060 if (!mRootElement->HasChildren()) {
1061 nsresult rv = aRawRange->CollapseTo(RawRangeBoundary(mRootElement, 0u));
1062 if (NS_WARN_IF(NS_FAILED(rv))) {
1063 return rv;
1067 PreContentIterator preOrderIter;
1068 nsresult rv = preOrderIter.Init(mRootElement);
1069 if (NS_WARN_IF(NS_FAILED(rv))) {
1070 return rv;
1073 uint32_t offset = 0;
1074 uint32_t endOffset = aOffset + aLength;
1075 bool startSet = false;
1076 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
1077 nsINode* node = preOrderIter.GetCurrentNode();
1078 if (NS_WARN_IF(!node)) {
1079 break;
1081 // FYI: mRootElement shouldn't cause any text. So, we can skip it simply.
1082 if (node == mRootElement || !node->IsContent()) {
1083 continue;
1085 nsIContent* const content = node->AsContent();
1086 Text* const contentAsText = Text::FromNode(content);
1088 if (aLastTextNode && contentAsText) {
1089 NS_IF_RELEASE(*aLastTextNode);
1090 NS_ADDREF(*aLastTextNode = contentAsText);
1093 uint32_t textLength = contentAsText
1094 ? GetTextLength(*contentAsText, aLineBreakType)
1095 : (ShouldBreakLineBefore(*content, mRootElement)
1096 ? GetBRLength(aLineBreakType)
1097 : 0);
1098 if (!textLength) {
1099 continue;
1102 // When the start offset is in between accumulated offset and the last
1103 // offset of the node, the node is the start node of the range.
1104 if (!startSet && aOffset <= offset + textLength) {
1105 nsINode* startNode = nullptr;
1106 Maybe<uint32_t> startNodeOffset;
1107 if (contentAsText) {
1108 // Rule #1.1: [textNode or text[Node or textNode[
1109 uint32_t xpOffset = aOffset - offset;
1110 if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
1111 xpOffset = ConvertToXPOffset(*contentAsText, xpOffset);
1114 if (aExpandToClusterBoundaries) {
1115 const uint32_t oldXPOffset = xpOffset;
1116 nsresult rv =
1117 ExpandToClusterBoundary(*contentAsText, false, &xpOffset);
1118 if (NS_WARN_IF(NS_FAILED(rv))) {
1119 return rv;
1121 if (aNewOffset) {
1122 // This is correct since a cluster shouldn't include line break.
1123 *aNewOffset -= (oldXPOffset - xpOffset);
1126 startNode = contentAsText;
1127 startNodeOffset = Some(xpOffset);
1128 } else if (aOffset < offset + textLength) {
1129 // Rule #1.2 [<element>
1130 startNode = content->GetParent();
1131 if (NS_WARN_IF(!startNode)) {
1132 return NS_ERROR_FAILURE;
1134 startNodeOffset = startNode->ComputeIndexOf(content);
1135 if (MOZ_UNLIKELY(NS_WARN_IF(startNodeOffset.isNothing()))) {
1136 // The content is being removed from the parent!
1137 return NS_ERROR_FAILURE;
1139 } else if (!content->HasChildren()) {
1140 // Rule #1.3: <element/>[
1141 startNode = content->GetParent();
1142 if (NS_WARN_IF(!startNode)) {
1143 return NS_ERROR_FAILURE;
1145 startNodeOffset = startNode->ComputeIndexOf(content);
1146 if (MOZ_UNLIKELY(NS_WARN_IF(startNodeOffset.isNothing()))) {
1147 // The content is being removed from the parent!
1148 return NS_ERROR_FAILURE;
1150 MOZ_ASSERT(*startNodeOffset != UINT32_MAX);
1151 ++(*startNodeOffset);
1152 } else {
1153 // Rule #1.4: <element>[
1154 startNode = content;
1155 startNodeOffset = Some(0);
1157 NS_ASSERTION(startNode, "startNode must not be nullptr");
1158 MOZ_ASSERT(startNodeOffset.isSome(),
1159 "startNodeOffset must not be Nothing");
1160 rv = aRawRange->SetStart(startNode, *startNodeOffset);
1161 if (NS_WARN_IF(NS_FAILED(rv))) {
1162 return rv;
1164 startSet = true;
1166 if (!aLength) {
1167 rv = aRawRange->SetEnd(startNode, *startNodeOffset);
1168 if (NS_WARN_IF(NS_FAILED(rv))) {
1169 return rv;
1171 return NS_OK;
1175 // When the end offset is in the content, the node is the end node of the
1176 // range.
1177 if (endOffset <= offset + textLength) {
1178 MOZ_ASSERT(startSet, "The start of the range should've been set already");
1179 if (contentAsText) {
1180 // Rule #2.1: ]textNode or text]Node or textNode]
1181 uint32_t xpOffset = endOffset - offset;
1182 if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
1183 const uint32_t xpOffsetCurrent =
1184 ConvertToXPOffset(*contentAsText, xpOffset);
1185 if (xpOffset && GetBRLength(aLineBreakType) > 1) {
1186 MOZ_ASSERT(GetBRLength(aLineBreakType) == 2);
1187 const uint32_t xpOffsetPre =
1188 ConvertToXPOffset(*contentAsText, xpOffset - 1);
1189 // If previous character's XP offset is same as current character's,
1190 // it means that the end offset is between \r and \n. So, the
1191 // range end should be after the \n.
1192 if (xpOffsetPre == xpOffsetCurrent) {
1193 xpOffset = xpOffsetCurrent + 1;
1194 } else {
1195 xpOffset = xpOffsetCurrent;
1199 if (aExpandToClusterBoundaries) {
1200 nsresult rv =
1201 ExpandToClusterBoundary(*contentAsText, true, &xpOffset);
1202 if (NS_WARN_IF(NS_FAILED(rv))) {
1203 return rv;
1206 NS_ASSERTION(xpOffset <= INT32_MAX, "The end node offset is too large");
1207 nsresult rv = aRawRange->SetEnd(contentAsText, xpOffset);
1208 if (NS_WARN_IF(NS_FAILED(rv))) {
1209 return rv;
1211 return NS_OK;
1214 if (endOffset == offset) {
1215 // Rule #2.2: ]<element>
1216 // NOTE: Please don't crash on release builds because it must be
1217 // overreaction but we shouldn't allow this bug when some
1218 // automated tests find this.
1219 MOZ_ASSERT(false,
1220 "This case should've already been handled at "
1221 "the last node which caused some text");
1222 return NS_ERROR_FAILURE;
1225 if (content->HasChildren() &&
1226 ShouldBreakLineBefore(*content, mRootElement)) {
1227 // Rule #2.3: </element>]
1228 rv = aRawRange->SetEnd(content, 0);
1229 if (NS_WARN_IF(NS_FAILED(rv))) {
1230 return rv;
1232 return NS_OK;
1235 // Rule #2.4: <element/>]
1236 nsINode* endNode = content->GetParent();
1237 if (NS_WARN_IF(!endNode)) {
1238 return NS_ERROR_FAILURE;
1240 const Maybe<uint32_t> indexInParent = endNode->ComputeIndexOf(content);
1241 if (MOZ_UNLIKELY(NS_WARN_IF(indexInParent.isNothing()))) {
1242 // The content is being removed from the parent!
1243 return NS_ERROR_FAILURE;
1245 MOZ_ASSERT(*indexInParent != UINT32_MAX);
1246 rv = aRawRange->SetEnd(endNode, *indexInParent + 1);
1247 if (NS_WARN_IF(NS_FAILED(rv))) {
1248 return rv;
1250 return NS_OK;
1253 offset += textLength;
1256 if (!startSet) {
1257 if (!offset) {
1258 // Rule #1.5: <root>[</root>
1259 // When there are no nodes causing text, the start of the DOM range
1260 // should be start of the root node since clicking on such editor (e.g.,
1261 // <div contenteditable><span></span></div>) sets caret to the start of
1262 // the editor (i.e., before <span> in the example).
1263 rv = aRawRange->SetStart(mRootElement, 0);
1264 if (NS_WARN_IF(NS_FAILED(rv))) {
1265 return rv;
1267 if (!aLength) {
1268 rv = aRawRange->SetEnd(mRootElement, 0);
1269 if (NS_WARN_IF(NS_FAILED(rv))) {
1270 return rv;
1272 return NS_OK;
1274 } else {
1275 // Rule #1.5: [</root>
1276 rv = aRawRange->SetStart(mRootElement, mRootElement->GetChildCount());
1277 if (NS_WARN_IF(NS_FAILED(rv))) {
1278 return rv;
1281 if (aNewOffset) {
1282 *aNewOffset = offset;
1285 // Rule #2.5: ]</root>
1286 rv = aRawRange->SetEnd(mRootElement, mRootElement->GetChildCount());
1287 if (NS_WARN_IF(NS_FAILED(rv))) {
1288 return rv;
1290 return NS_OK;
1293 /* static */
1294 LineBreakType ContentEventHandler::GetLineBreakType(
1295 WidgetQueryContentEvent* aEvent) {
1296 return GetLineBreakType(aEvent->mUseNativeLineBreak);
1299 /* static */
1300 LineBreakType ContentEventHandler::GetLineBreakType(
1301 WidgetSelectionEvent* aEvent) {
1302 return GetLineBreakType(aEvent->mUseNativeLineBreak);
1305 /* static */
1306 LineBreakType ContentEventHandler::GetLineBreakType(bool aUseNativeLineBreak) {
1307 return aUseNativeLineBreak ? LINE_BREAK_TYPE_NATIVE : LINE_BREAK_TYPE_XP;
1310 nsresult ContentEventHandler::HandleQueryContentEvent(
1311 WidgetQueryContentEvent* aEvent) {
1312 nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
1313 switch (aEvent->mMessage) {
1314 case eQuerySelectedText:
1315 rv = OnQuerySelectedText(aEvent);
1316 break;
1317 case eQueryTextContent:
1318 rv = OnQueryTextContent(aEvent);
1319 break;
1320 case eQueryCaretRect:
1321 rv = OnQueryCaretRect(aEvent);
1322 break;
1323 case eQueryTextRect:
1324 rv = OnQueryTextRect(aEvent);
1325 break;
1326 case eQueryTextRectArray:
1327 rv = OnQueryTextRectArray(aEvent);
1328 break;
1329 case eQueryEditorRect:
1330 rv = OnQueryEditorRect(aEvent);
1331 break;
1332 case eQueryContentState:
1333 rv = OnQueryContentState(aEvent);
1334 break;
1335 case eQuerySelectionAsTransferable:
1336 rv = OnQuerySelectionAsTransferable(aEvent);
1337 break;
1338 case eQueryCharacterAtPoint:
1339 rv = OnQueryCharacterAtPoint(aEvent);
1340 break;
1341 case eQueryDOMWidgetHittest:
1342 rv = OnQueryDOMWidgetHittest(aEvent);
1343 break;
1344 default:
1345 break;
1347 if (NS_FAILED(rv)) {
1348 aEvent->mReply.reset(); // Mark the query failed.
1349 return rv;
1352 MOZ_ASSERT(aEvent->Succeeded());
1353 return NS_OK;
1356 // Similar to nsFrameSelection::GetFrameForNodeOffset,
1357 // but this is more flexible for OnQueryTextRect to use
1358 static Result<nsIFrame*, nsresult> GetFrameForTextRect(const nsINode* aNode,
1359 int32_t aNodeOffset,
1360 bool aHint) {
1361 const nsIContent* content = nsIContent::FromNodeOrNull(aNode);
1362 if (NS_WARN_IF(!content)) {
1363 return Err(NS_ERROR_UNEXPECTED);
1365 nsIFrame* frame = content->GetPrimaryFrame();
1366 // The node may be invisible, e.g., `display: none`, invisible text node
1367 // around block elements, etc. Therefore, don't warn when we don't find
1368 // a primary frame.
1369 if (!frame) {
1370 return nullptr;
1372 int32_t childNodeOffset = 0;
1373 nsIFrame* returnFrame = nullptr;
1374 nsresult rv = frame->GetChildFrameContainingOffset(
1375 aNodeOffset, aHint, &childNodeOffset, &returnFrame);
1376 if (NS_FAILED(rv)) {
1377 return Err(rv);
1379 return returnFrame;
1382 nsresult ContentEventHandler::OnQuerySelectedText(
1383 WidgetQueryContentEvent* aEvent) {
1384 nsresult rv = Init(aEvent);
1385 if (NS_FAILED(rv)) {
1386 return rv;
1389 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
1391 if (!mFirstSelectedRawRange.IsPositioned()) {
1392 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
1393 MOZ_ASSERT_IF(mSelection, !mSelection->RangeCount());
1394 // This is special case that `mReply` is emplaced, but mOffsetAndData is
1395 // not emplaced but treated as succeeded because of no selection ranges
1396 // is a usual case.
1397 return NS_OK;
1400 nsINode* const startNode = mFirstSelectedRawRange.GetStartContainer();
1401 nsINode* const endNode = mFirstSelectedRawRange.GetEndContainer();
1403 // Make sure the selection is within the root content range.
1404 if (!startNode->IsInclusiveDescendantOf(mRootElement) ||
1405 !endNode->IsInclusiveDescendantOf(mRootElement)) {
1406 return NS_ERROR_NOT_AVAILABLE;
1409 LineBreakType lineBreakType = GetLineBreakType(aEvent);
1410 uint32_t startOffset = 0;
1411 if (NS_WARN_IF(NS_FAILED(GetStartOffset(mFirstSelectedRawRange, &startOffset,
1412 lineBreakType)))) {
1413 return NS_ERROR_FAILURE;
1416 const RangeBoundary& anchorRef = mSelection->RangeCount() > 0
1417 ? mSelection->AnchorRef()
1418 : mFirstSelectedRawRange.Start();
1419 const RangeBoundary& focusRef = mSelection->RangeCount() > 0
1420 ? mSelection->FocusRef()
1421 : mFirstSelectedRawRange.End();
1422 if (NS_WARN_IF(!anchorRef.IsSet()) || NS_WARN_IF(!focusRef.IsSet())) {
1423 return NS_ERROR_FAILURE;
1426 if (mSelection->RangeCount()) {
1427 // If there is only one selection range, the anchor/focus node and offset
1428 // are the information of the range. Therefore, we have the direction
1429 // information.
1430 if (mSelection->RangeCount() == 1) {
1431 // The selection's points should always be comparable, independent of the
1432 // selection (see nsISelectionController.idl).
1433 Maybe<int32_t> compare =
1434 nsContentUtils::ComparePoints(anchorRef, focusRef);
1435 if (compare.isNothing()) {
1436 return NS_ERROR_FAILURE;
1439 aEvent->mReply->mReversed = compare.value() > 0;
1441 // However, if there are 2 or more selection ranges, we have no information
1442 // of that.
1443 else {
1444 aEvent->mReply->mReversed = false;
1447 nsString selectedString;
1448 if (!mFirstSelectedRawRange.Collapsed() &&
1449 NS_WARN_IF(NS_FAILED(GenerateFlatTextContent(
1450 mFirstSelectedRawRange, selectedString, lineBreakType)))) {
1451 return NS_ERROR_FAILURE;
1453 aEvent->mReply->mOffsetAndData.emplace(startOffset, selectedString,
1454 OffsetAndDataFor::SelectedString);
1455 } else {
1456 NS_ASSERTION(anchorRef == focusRef,
1457 "When mSelection doesn't have selection, "
1458 "mFirstSelectedRawRange must be collapsed");
1460 aEvent->mReply->mReversed = false;
1461 aEvent->mReply->mOffsetAndData.emplace(startOffset, EmptyString(),
1462 OffsetAndDataFor::SelectedString);
1465 Result<nsIFrame*, nsresult> frameForTextRectOrError = GetFrameForTextRect(
1466 focusRef.Container(),
1467 focusRef.Offset(RangeBoundary::OffsetFilter::kValidOffsets).valueOr(0),
1468 true);
1469 if (NS_WARN_IF(frameForTextRectOrError.isErr()) ||
1470 !frameForTextRectOrError.inspect()) {
1471 aEvent->mReply->mWritingMode = WritingMode();
1472 } else {
1473 aEvent->mReply->mWritingMode =
1474 frameForTextRectOrError.inspect()->GetWritingMode();
1477 MOZ_ASSERT(aEvent->Succeeded());
1478 return NS_OK;
1481 nsresult ContentEventHandler::OnQueryTextContent(
1482 WidgetQueryContentEvent* aEvent) {
1483 nsresult rv = Init(aEvent);
1484 if (NS_FAILED(rv)) {
1485 return rv;
1488 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
1490 LineBreakType lineBreakType = GetLineBreakType(aEvent);
1492 RawRange rawRange;
1493 uint32_t startOffset = 0;
1494 if (NS_WARN_IF(NS_FAILED(SetRawRangeFromFlatTextOffset(
1495 &rawRange, aEvent->mInput.mOffset, aEvent->mInput.mLength,
1496 lineBreakType, false, &startOffset)))) {
1497 return NS_ERROR_FAILURE;
1500 nsString textInRange;
1501 if (NS_WARN_IF(NS_FAILED(
1502 GenerateFlatTextContent(rawRange, textInRange, lineBreakType)))) {
1503 return NS_ERROR_FAILURE;
1506 aEvent->mReply->mOffsetAndData.emplace(startOffset, textInRange,
1507 OffsetAndDataFor::EditorString);
1509 if (aEvent->mWithFontRanges) {
1510 uint32_t fontRangeLength;
1511 if (NS_WARN_IF(NS_FAILED(
1512 GenerateFlatFontRanges(rawRange, aEvent->mReply->mFontRanges,
1513 fontRangeLength, lineBreakType)))) {
1514 return NS_ERROR_FAILURE;
1517 MOZ_ASSERT(fontRangeLength == aEvent->mReply->DataLength(),
1518 "Font ranges doesn't match the string");
1521 MOZ_ASSERT(aEvent->Succeeded());
1522 return NS_OK;
1525 void ContentEventHandler::EnsureNonEmptyRect(nsRect& aRect) const {
1526 // See the comment in ContentEventHandler.h why this doesn't set them to
1527 // one device pixel.
1528 aRect.height = std::max(1, aRect.height);
1529 aRect.width = std::max(1, aRect.width);
1532 void ContentEventHandler::EnsureNonEmptyRect(LayoutDeviceIntRect& aRect) const {
1533 aRect.height = std::max(1, aRect.height);
1534 aRect.width = std::max(1, aRect.width);
1537 ContentEventHandler::FrameAndNodeOffset
1538 ContentEventHandler::GetFirstFrameInRangeForTextRect(
1539 const RawRange& aRawRange) {
1540 NodePosition nodePosition;
1541 PreContentIterator preOrderIter;
1542 nsresult rv =
1543 preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
1544 if (NS_WARN_IF(NS_FAILED(rv))) {
1545 return FrameAndNodeOffset();
1547 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
1548 nsINode* node = preOrderIter.GetCurrentNode();
1549 if (NS_WARN_IF(!node)) {
1550 break;
1553 auto* content = nsIContent::FromNode(node);
1554 if (MOZ_UNLIKELY(!content)) {
1555 continue;
1558 // If the node is invisible (e.g., the node is or is in an invisible node or
1559 // it's a white-space only text node around a block boundary), we should
1560 // ignore it.
1561 if (!content->GetPrimaryFrame()) {
1562 continue;
1565 if (auto* textNode = Text::FromNode(content)) {
1566 // If the range starts at the end of a text node, we need to find
1567 // next node which causes text.
1568 const uint32_t offsetInNode = textNode == aRawRange.GetStartContainer()
1569 ? aRawRange.StartOffset()
1570 : 0u;
1571 if (offsetInNode < textNode->TextDataLength()) {
1572 nodePosition = {textNode, offsetInNode};
1573 break;
1575 continue;
1578 // If the element node causes a line break before it, it's the first
1579 // node causing text.
1580 if (ShouldBreakLineBefore(*content, mRootElement) ||
1581 IsPaddingBR(*content)) {
1582 nodePosition = {content, 0u};
1586 if (!nodePosition.IsSetAndValid()) {
1587 return FrameAndNodeOffset();
1590 Result<nsIFrame*, nsresult> firstFrameOrError = GetFrameForTextRect(
1591 nodePosition.Container(),
1592 *nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets), true);
1593 if (NS_WARN_IF(firstFrameOrError.isErr()) || !firstFrameOrError.inspect()) {
1594 return FrameAndNodeOffset();
1596 return FrameAndNodeOffset(
1597 firstFrameOrError.inspect(),
1598 *nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets));
1601 ContentEventHandler::FrameAndNodeOffset
1602 ContentEventHandler::GetLastFrameInRangeForTextRect(const RawRange& aRawRange) {
1603 NodePosition nodePosition;
1604 PreContentIterator preOrderIter;
1605 nsresult rv =
1606 preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
1607 if (NS_WARN_IF(NS_FAILED(rv))) {
1608 return FrameAndNodeOffset();
1611 const RangeBoundary& endPoint = aRawRange.End();
1612 MOZ_ASSERT(endPoint.IsSetAndValid());
1613 // If the end point is start of a text node or specified by its parent and
1614 // index, the node shouldn't be included into the range. For example,
1615 // with this case, |<p>abc[<br>]def</p>|, the range ends at 3rd children of
1616 // <p> (see the range creation rules, "2.4. Cases: <element/>]"). This causes
1617 // following frames:
1618 // +----+-----+
1619 // | abc|[<br>|
1620 // +----+-----+
1621 // +----+
1622 // |]def|
1623 // +----+
1624 // So, if this method includes the 2nd text frame's rect to its result, the
1625 // caller will return too tall rect which includes 2 lines in this case isn't
1626 // expected by native IME (e.g., popup of IME will be positioned at bottom
1627 // of "d" instead of right-bottom of "c"). Therefore, this method shouldn't
1628 // include the last frame when its content isn't really in aRawRange.
1629 nsINode* nextNodeOfRangeEnd = nullptr;
1630 if (endPoint.Container()->IsText()) {
1631 // Don't set nextNodeOfRangeEnd to the start node of aRawRange because if
1632 // the container of the end is same as start node of the range, the text
1633 // node shouldn't be next of range end even if the offset is 0. This
1634 // could occur with empty text node.
1635 if (endPoint.IsStartOfContainer() &&
1636 aRawRange.GetStartContainer() != endPoint.Container()) {
1637 nextNodeOfRangeEnd = endPoint.Container();
1639 } else if (endPoint.IsSetAndValid()) {
1640 nextNodeOfRangeEnd = endPoint.GetChildAtOffset();
1643 for (preOrderIter.Last(); !preOrderIter.IsDone(); preOrderIter.Prev()) {
1644 nsINode* node = preOrderIter.GetCurrentNode();
1645 if (NS_WARN_IF(!node)) {
1646 break;
1649 if (node == nextNodeOfRangeEnd) {
1650 continue;
1653 auto* content = nsIContent::FromNode(node);
1654 if (MOZ_UNLIKELY(!content)) {
1655 continue;
1658 // If the node is invisible (e.g., the node is or is in an invisible node or
1659 // it's a white-space only text node around a block boundary), we should
1660 // ignore it.
1661 if (!content->GetPrimaryFrame()) {
1662 continue;
1665 if (auto* textNode = Text::FromNode(node)) {
1666 nodePosition = {textNode, textNode == aRawRange.GetEndContainer()
1667 ? aRawRange.EndOffset()
1668 : textNode->TextDataLength()};
1670 // If the text node is empty or the last node of the range but the index
1671 // is 0, we should store current position but continue looking for
1672 // previous node (If there are no nodes before it, we should use current
1673 // node position for returning its frame).
1674 if (*nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets) ==
1675 0) {
1676 continue;
1678 break;
1681 if (ShouldBreakLineBefore(*content, mRootElement) ||
1682 IsPaddingBR(*content)) {
1683 nodePosition = {content, 0u};
1684 break;
1688 if (!nodePosition.IsSet()) {
1689 return FrameAndNodeOffset();
1692 Result<nsIFrame*, nsresult> lastFrameOrError = GetFrameForTextRect(
1693 nodePosition.Container(),
1694 *nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets), true);
1695 if (NS_WARN_IF(lastFrameOrError.isErr()) || !lastFrameOrError.inspect()) {
1696 return FrameAndNodeOffset();
1699 // If the last frame is a text frame, we need to check if the range actually
1700 // includes at least one character in the range. Therefore, if it's not a
1701 // text frame, we need to do nothing anymore.
1702 if (!lastFrameOrError.inspect()->IsTextFrame()) {
1703 return FrameAndNodeOffset(
1704 lastFrameOrError.inspect(),
1705 *nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets));
1708 int32_t start = lastFrameOrError.inspect()->GetOffsets().first;
1710 // If the start offset in the node is same as the computed offset in the
1711 // node and it's not 0, the frame shouldn't be added to the text rect. So,
1712 // this should return previous text frame and its last offset if there is
1713 // at least one text frame.
1714 if (*nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets) &&
1715 *nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets) ==
1716 static_cast<uint32_t>(start)) {
1717 const uint32_t newNodePositionOffset =
1718 *nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets);
1719 MOZ_ASSERT(newNodePositionOffset != 0);
1720 nodePosition = {nodePosition.Container(), newNodePositionOffset - 1u};
1721 lastFrameOrError = GetFrameForTextRect(
1722 nodePosition.Container(),
1723 *nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets), true);
1724 if (NS_WARN_IF(lastFrameOrError.isErr()) || !lastFrameOrError.inspect()) {
1725 return FrameAndNodeOffset();
1729 return FrameAndNodeOffset(
1730 lastFrameOrError.inspect(),
1731 *nodePosition.Offset(NodePosition::OffsetFilter::kValidOffsets));
1734 ContentEventHandler::FrameRelativeRect
1735 ContentEventHandler::GetLineBreakerRectBefore(nsIFrame* aFrame) {
1736 // Note that this method should be called only with an element's frame whose
1737 // open tag causes a line break or moz-<br> for computing empty last line's
1738 // rect.
1739 MOZ_ASSERT(aFrame->GetContent());
1740 MOZ_ASSERT(ShouldBreakLineBefore(*aFrame->GetContent(), mRootElement) ||
1741 IsPaddingBR(*aFrame->GetContent()));
1743 nsIFrame* frameForFontMetrics = aFrame;
1745 // If it's not a <br> frame, this method computes the line breaker's rect
1746 // outside the frame. Therefore, we need to compute with parent frame's
1747 // font metrics in such case.
1748 if (!aFrame->IsBrFrame() && aFrame->GetParent()) {
1749 frameForFontMetrics = aFrame->GetParent();
1752 // Note that <br> element's rect is decided with line-height but we need
1753 // a rect only with font height. Additionally, <br> frame's width and
1754 // height are 0 in quirks mode if it's not an empty line. So, we cannot
1755 // use frame rect information even if it's a <br> frame.
1757 RefPtr<nsFontMetrics> fontMetrics =
1758 nsLayoutUtils::GetInflatedFontMetricsForFrame(frameForFontMetrics);
1759 if (NS_WARN_IF(!fontMetrics)) {
1760 return FrameRelativeRect();
1763 const WritingMode kWritingMode = frameForFontMetrics->GetWritingMode();
1765 auto caretBlockAxisMetrics =
1766 aFrame->GetCaretBlockAxisMetrics(kWritingMode, *fontMetrics);
1767 nscoord inlineOffset = 0;
1769 // If aFrame isn't a <br> frame, caret should be at outside of it because
1770 // the line break is before its open tag. For example, case of
1771 // |<div><p>some text</p></div>|, caret is before <p> element and in <div>
1772 // element, the caret should be left of top-left corner of <p> element like:
1774 // +-<div>------------------- <div>'s border box
1775 // | I +-<p>----------------- <p>'s border box
1776 // | I |
1777 // | I |
1778 // | |
1779 // ^- caret
1781 // However, this is a hack for unusual scenario. This hack shouldn't be
1782 // used as far as possible.
1783 if (!aFrame->IsBrFrame()) {
1784 if (kWritingMode.IsVertical() && !kWritingMode.IsLineInverted()) {
1785 // above of top-right corner of aFrame.
1786 caretBlockAxisMetrics.mOffset =
1787 aFrame->GetRect().XMost() - caretBlockAxisMetrics.mExtent;
1788 } else {
1789 // above (For vertical) or left (For horizontal) of top-left corner of
1790 // aFrame.
1791 caretBlockAxisMetrics.mOffset = 0;
1793 inlineOffset = -aFrame->PresContext()->AppUnitsPerDevPixel();
1795 FrameRelativeRect result(aFrame);
1796 if (kWritingMode.IsVertical()) {
1797 result.mRect.x = caretBlockAxisMetrics.mOffset;
1798 result.mRect.y = inlineOffset;
1799 result.mRect.width = caretBlockAxisMetrics.mExtent;
1800 } else {
1801 result.mRect.x = inlineOffset;
1802 result.mRect.y = caretBlockAxisMetrics.mOffset;
1803 result.mRect.height = caretBlockAxisMetrics.mExtent;
1805 return result;
1808 ContentEventHandler::FrameRelativeRect
1809 ContentEventHandler::GuessLineBreakerRectAfter(const Text& aTextNode) {
1810 FrameRelativeRect result;
1811 const int32_t length = static_cast<int32_t>(aTextNode.TextLength());
1812 if (NS_WARN_IF(length < 0)) {
1813 return result;
1815 // Get the last nsTextFrame which is caused by aTextNode. Note that
1816 // a text node can cause multiple text frames, e.g., the text is too long
1817 // and wrapped by its parent block or the text has line breakers and its
1818 // white-space property respects the line breakers (e.g., |pre|).
1819 Result<nsIFrame*, nsresult> lastTextFrameOrError =
1820 GetFrameForTextRect(&aTextNode, length, true);
1821 if (NS_WARN_IF(lastTextFrameOrError.isErr()) ||
1822 !lastTextFrameOrError.inspect()) {
1823 return result;
1825 const nsRect kLastTextFrameRect = lastTextFrameOrError.inspect()->GetRect();
1826 if (lastTextFrameOrError.inspect()->GetWritingMode().IsVertical()) {
1827 // Below of the last text frame.
1828 result.mRect.SetRect(0, kLastTextFrameRect.height, kLastTextFrameRect.width,
1830 } else {
1831 // Right of the last text frame (not bidi-aware).
1832 result.mRect.SetRect(kLastTextFrameRect.width, 0, 0,
1833 kLastTextFrameRect.height);
1835 result.mBaseFrame = lastTextFrameOrError.unwrap();
1836 return result;
1839 ContentEventHandler::FrameRelativeRect
1840 ContentEventHandler::GuessFirstCaretRectIn(nsIFrame* aFrame) {
1841 const WritingMode kWritingMode = aFrame->GetWritingMode();
1842 nsPresContext* presContext = aFrame->PresContext();
1844 // Computes the font height, but if it's not available, we should use
1845 // default font size of Firefox. The default font size in default settings
1846 // is 16px.
1847 RefPtr<nsFontMetrics> fontMetrics =
1848 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
1849 const nscoord kMaxHeight = fontMetrics
1850 ? fontMetrics->MaxHeight()
1851 : 16 * presContext->AppUnitsPerDevPixel();
1853 nsRect caretRect;
1854 const nsRect kContentRect = aFrame->GetContentRect() - aFrame->GetPosition();
1855 caretRect.y = kContentRect.y;
1856 if (!kWritingMode.IsVertical()) {
1857 if (kWritingMode.IsBidiLTR()) {
1858 caretRect.x = kContentRect.x;
1859 } else {
1860 // Move 1px left for the space of caret itself.
1861 const nscoord kOnePixel = presContext->AppUnitsPerDevPixel();
1862 caretRect.x = kContentRect.XMost() - kOnePixel;
1864 caretRect.height = kMaxHeight;
1865 // However, don't add kOnePixel here because it may cause 2px width at
1866 // aligning the edge to device pixels.
1867 caretRect.width = 1;
1868 } else {
1869 if (kWritingMode.IsVerticalLR()) {
1870 caretRect.x = kContentRect.x;
1871 } else {
1872 caretRect.x = kContentRect.XMost() - kMaxHeight;
1874 caretRect.width = kMaxHeight;
1875 // Don't add app units for a device pixel because it may cause 2px height
1876 // at aligning the edge to device pixels.
1877 caretRect.height = 1;
1879 return FrameRelativeRect(caretRect, aFrame);
1882 // static
1883 LayoutDeviceIntRect ContentEventHandler::GetCaretRectBefore(
1884 const LayoutDeviceIntRect& aCharRect, const WritingMode& aWritingMode) {
1885 LayoutDeviceIntRect caretRectBefore(aCharRect);
1886 if (aWritingMode.IsVertical()) {
1887 caretRectBefore.height = 1;
1888 } else {
1889 // TODO: Make here bidi-aware.
1890 caretRectBefore.width = 1;
1892 return caretRectBefore;
1895 // static
1896 nsRect ContentEventHandler::GetCaretRectBefore(
1897 const nsRect& aCharRect, const WritingMode& aWritingMode) {
1898 nsRect caretRectBefore(aCharRect);
1899 if (aWritingMode.IsVertical()) {
1900 // For making the height 1 device pixel after aligning the rect edges to
1901 // device pixels, don't add one device pixel in app units here.
1902 caretRectBefore.height = 1;
1903 } else {
1904 // TODO: Make here bidi-aware.
1905 // For making the width 1 device pixel after aligning the rect edges to
1906 // device pixels, don't add one device pixel in app units here.
1907 caretRectBefore.width = 1;
1909 return caretRectBefore;
1912 // static
1913 LayoutDeviceIntRect ContentEventHandler::GetCaretRectAfter(
1914 const LayoutDeviceIntRect& aCharRect, const WritingMode& aWritingMode) {
1915 LayoutDeviceIntRect caretRectAfter(aCharRect);
1916 if (aWritingMode.IsVertical()) {
1917 caretRectAfter.y = aCharRect.YMost() + 1;
1918 caretRectAfter.height = 1;
1919 } else {
1920 // TODO: Make here bidi-aware.
1921 caretRectAfter.x = aCharRect.XMost() + 1;
1922 caretRectAfter.width = 1;
1924 return caretRectAfter;
1927 // static
1928 nsRect ContentEventHandler::GetCaretRectAfter(nsPresContext& aPresContext,
1929 const nsRect& aCharRect,
1930 const WritingMode& aWritingMode) {
1931 nsRect caretRectAfter(aCharRect);
1932 const nscoord onePixel = aPresContext.AppUnitsPerDevPixel();
1933 if (aWritingMode.IsVertical()) {
1934 caretRectAfter.y = aCharRect.YMost() + onePixel;
1935 // For making the height 1 device pixel after aligning the rect edges to
1936 // device pixels, don't add one device pixel in app units here.
1937 caretRectAfter.height = 1;
1938 } else {
1939 // TODO: Make here bidi-aware.
1940 caretRectAfter.x = aCharRect.XMost() + onePixel;
1941 // For making the width 1 device pixel after aligning the rect edges to
1942 // device pixels, don't add one device pixel in app units here.
1943 caretRectAfter.width = 1;
1945 return caretRectAfter;
1948 nsresult ContentEventHandler::OnQueryTextRectArray(
1949 WidgetQueryContentEvent* aEvent) {
1950 nsresult rv = Init(aEvent);
1951 if (NS_WARN_IF(NS_FAILED(rv))) {
1952 return rv;
1955 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
1957 LineBreakType lineBreakType = GetLineBreakType(aEvent);
1958 const uint32_t kBRLength = GetBRLength(lineBreakType);
1960 WritingMode lastVisibleFrameWritingMode;
1961 LayoutDeviceIntRect rect;
1962 uint32_t offset = aEvent->mInput.mOffset;
1963 const uint32_t kEndOffset = aEvent->mInput.EndOffset();
1964 bool wasLineBreaker = false;
1965 // lastCharRect stores the last charRect value (see below for the detail of
1966 // charRect).
1967 nsRect lastCharRect;
1968 // lastFrame is base frame of lastCharRect.
1969 // TODO: We should look for this if the first text is not visible. However,
1970 // users cannot put caret invisible text and users cannot type in it
1971 // at least only with user's operations. Therefore, we don't need to
1972 // fix this immediately.
1973 nsIFrame* lastFrame = nullptr;
1974 nsAutoString flattenedAllText;
1975 flattenedAllText.SetIsVoid(true);
1976 while (offset < kEndOffset) {
1977 RefPtr<Text> lastTextNode;
1978 RawRange rawRange;
1979 nsresult rv =
1980 SetRawRangeFromFlatTextOffset(&rawRange, offset, 1, lineBreakType, true,
1981 nullptr, getter_AddRefs(lastTextNode));
1982 if (NS_WARN_IF(NS_FAILED(rv))) {
1983 return rv;
1986 // TODO: When we crossed parent block boundary now, we should fill pending
1987 // character rects with caret rect after the last visible character
1988 // rect.
1990 // If the range is collapsed, offset has already reached the end of the
1991 // contents.
1992 if (rawRange.Collapsed()) {
1993 break;
1996 // Get the first frame which causes some text after the offset.
1997 FrameAndNodeOffset firstFrame = GetFirstFrameInRangeForTextRect(rawRange);
1999 // If GetFirstFrameInRangeForTextRect() does not return valid frame, that
2000 // means that the offset reached the end of contents or there is no visible
2001 // frame in the range generating flattened text.
2002 if (!firstFrame.IsValid()) {
2003 if (flattenedAllText.IsVoid()) {
2004 flattenedAllText.SetIsVoid(false);
2005 if (NS_WARN_IF(NS_FAILED(GenerateFlatTextContent(
2006 mRootElement, flattenedAllText, lineBreakType)))) {
2007 NS_WARNING("ContentEventHandler::GenerateFlatTextContent() failed");
2008 return NS_ERROR_FAILURE;
2011 // If we've reached end of the root, append caret rect at the end of
2012 // the root later.
2013 if (offset >= flattenedAllText.Length()) {
2014 break;
2016 // Otherwise, we're in an invisible node. If the node is followed by a
2017 // block boundary causing a line break, we can use the boundary.
2018 // Otherwise, if the node follows a block boundary of a parent block, we
2019 // can use caret rect at previous visible frame causing flattened text.
2020 const uint32_t remainingLengthInCurrentRange = [&]() {
2021 if (lastTextNode) {
2022 if (rawRange.GetStartContainer() == lastTextNode) {
2023 if (rawRange.StartOffset() < lastTextNode->TextDataLength()) {
2024 return lastTextNode->TextDataLength() - rawRange.StartOffset();
2026 return 0u;
2028 // Must be there are not nodes which may cause generating text.
2029 // Therefore, we can skip all nodes before the last found text node
2030 // and all text in the last text node.
2031 return lastTextNode->TextDataLength();
2033 if (rawRange.GetStartContainer() &&
2034 rawRange.GetStartContainer()->IsContent() &&
2035 ShouldBreakLineBefore(*rawRange.GetStartContainer()->AsContent(),
2036 mRootElement)) {
2037 if (kBRLength != 1u && offset - aEvent->mInput.mOffset < kBRLength) {
2038 // Don't return kBRLength if start position is less than the length
2039 // of a line-break because the offset may be between CRLF on
2040 // Windows. In the case, we will be again here and gets same
2041 // result and we need to pay the penalty only once. Therefore, we
2042 // can keep going without complicated check.
2043 return 1u;
2045 return kBRLength;
2047 return 0u;
2048 }();
2049 offset += std::max(1u, remainingLengthInCurrentRange);
2050 continue;
2053 nsIContent* firstContent = firstFrame.mFrame->GetContent();
2054 if (NS_WARN_IF(!firstContent)) {
2055 return NS_ERROR_FAILURE;
2058 bool startsBetweenLineBreaker = false;
2059 nsAutoString chars;
2060 lastVisibleFrameWritingMode = firstFrame->GetWritingMode();
2062 nsIFrame* baseFrame = firstFrame;
2063 // charRect should have each character rect or line breaker rect relative
2064 // to the base frame.
2065 AutoTArray<nsRect, 16> charRects;
2067 // If the first frame is a text frame, the result should be computed with
2068 // the frame's API.
2069 if (firstFrame->IsTextFrame()) {
2070 rv = firstFrame->GetCharacterRectsInRange(firstFrame.mOffsetInNode,
2071 kEndOffset - offset, charRects);
2072 if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(charRects.IsEmpty())) {
2073 return rv;
2075 // Assign the characters whose rects are computed by the call of
2076 // nsTextFrame::GetCharacterRectsInRange().
2077 AppendSubString(chars, *firstContent->AsText(), firstFrame.mOffsetInNode,
2078 charRects.Length());
2079 if (NS_WARN_IF(chars.Length() != charRects.Length())) {
2080 return NS_ERROR_UNEXPECTED;
2082 if (kBRLength > 1 && chars[0] == '\n' &&
2083 offset == aEvent->mInput.mOffset && offset) {
2084 // If start of range starting from previous offset of query range is
2085 // same as the start of query range, the query range starts from
2086 // between a line breaker (i.e., the range starts between "\r" and
2087 // "\n").
2088 RawRange rawRangeToPrevOffset;
2089 nsresult rv = SetRawRangeFromFlatTextOffset(&rawRangeToPrevOffset,
2090 aEvent->mInput.mOffset - 1,
2091 1, lineBreakType, true);
2092 if (NS_WARN_IF(NS_FAILED(rv))) {
2093 return rv;
2095 startsBetweenLineBreaker =
2096 rawRange.GetStartContainer() ==
2097 rawRangeToPrevOffset.GetStartContainer() &&
2098 rawRange.StartOffset() == rawRangeToPrevOffset.StartOffset();
2101 // Other contents should cause a line breaker rect before it.
2102 // Note that moz-<br> element does not cause any text, however,
2103 // it represents empty line at the last of current block. Therefore,
2104 // we need to compute its rect too.
2105 else if (ShouldBreakLineBefore(*firstContent, mRootElement) ||
2106 IsPaddingBR(*firstContent)) {
2107 nsRect brRect;
2108 // If the frame is not a <br> frame, we need to compute the caret rect
2109 // with last character's rect before firstContent if there is.
2110 // For example, if caret is after "c" of |<p>abc</p><p>def</p>|, IME may
2111 // query a line breaker's rect after "c". Then, if we compute it only
2112 // with the 2nd <p>'s block frame, the result will be:
2113 // +-<p>--------------------------------+
2114 // |abc |
2115 // +------------------------------------+
2117 // I+-<p>--------------------------------+
2118 // |def |
2119 // +------------------------------------+
2120 // However, users expect popup windows of IME should be positioned at
2121 // right-bottom of "c" like this:
2122 // +-<p>--------------------------------+
2123 // |abcI |
2124 // +------------------------------------+
2126 // +-<p>--------------------------------+
2127 // |def |
2128 // +------------------------------------+
2129 // Therefore, if the first frame isn't a <br> frame and there is a text
2130 // node before the first node in the queried range, we should compute the
2131 // first rect with the previous character's rect.
2132 // If we already compute a character's rect in the queried range, we can
2133 // compute it with the cached last character's rect. (However, don't
2134 // use this path if it's a <br> frame because trusting <br> frame's rect
2135 // is better than guessing the rect from the previous character.)
2136 if (!firstFrame->IsBrFrame() && !aEvent->mReply->mRectArray.IsEmpty()) {
2137 baseFrame = lastFrame;
2138 brRect = lastCharRect;
2139 if (!wasLineBreaker) {
2140 brRect = GetCaretRectAfter(*baseFrame->PresContext(), brRect,
2141 lastVisibleFrameWritingMode);
2144 // If it's not a <br> frame and it's the first character rect at the
2145 // queried range, we need the previous character rect of the start of
2146 // the queried range if there is a visible text node.
2147 else if (!firstFrame->IsBrFrame() && lastTextNode &&
2148 lastTextNode->GetPrimaryFrame()) {
2149 FrameRelativeRect brRectRelativeToLastTextFrame =
2150 GuessLineBreakerRectAfter(*lastTextNode);
2151 if (NS_WARN_IF(!brRectRelativeToLastTextFrame.IsValid())) {
2152 return NS_ERROR_FAILURE;
2154 // Look for the last text frame for lastTextNode.
2155 nsIFrame* primaryFrame = lastTextNode->GetPrimaryFrame();
2156 if (NS_WARN_IF(!primaryFrame)) {
2157 return NS_ERROR_FAILURE;
2159 baseFrame = primaryFrame->LastContinuation();
2160 if (NS_WARN_IF(!baseFrame)) {
2161 return NS_ERROR_FAILURE;
2163 brRect = brRectRelativeToLastTextFrame.RectRelativeTo(baseFrame);
2165 // Otherwise, we need to compute the line breaker's rect only with the
2166 // first frame's rect. But this may be unexpected. For example,
2167 // |<div contenteditable>[<p>]abc</p></div>|. In this case, caret is
2168 // before "a", therefore, users expect the rect left of "a". However,
2169 // we don't have enough information about the next character here and
2170 // this isn't usual case (e.g., IME typically tries to query the rect
2171 // of "a" or caret rect for computing its popup position). Therefore,
2172 // we shouldn't do more complicated hack here unless we'll get some bug
2173 // reports actually.
2174 else {
2175 FrameRelativeRect relativeBRRect = GetLineBreakerRectBefore(firstFrame);
2176 brRect = relativeBRRect.RectRelativeTo(firstFrame);
2178 charRects.AppendElement(brRect);
2179 chars.AssignLiteral("\n");
2180 if (kBRLength > 1 && offset == aEvent->mInput.mOffset && offset) {
2181 // If the first frame for the previous offset of the query range and
2182 // the first frame for the start of query range are same, that means
2183 // the start offset is between the first line breaker (i.e., the range
2184 // starts between "\r" and "\n").
2185 nsresult rv = SetRawRangeFromFlatTextOffset(
2186 &rawRange, aEvent->mInput.mOffset - 1, 1, lineBreakType, true);
2187 if (NS_WARN_IF(NS_FAILED(rv))) {
2188 return NS_ERROR_UNEXPECTED;
2190 FrameAndNodeOffset frameForPrevious =
2191 GetFirstFrameInRangeForTextRect(rawRange);
2192 startsBetweenLineBreaker = frameForPrevious.mFrame == firstFrame.mFrame;
2194 } else {
2195 NS_WARNING(
2196 "The frame is neither a text frame nor a frame whose content "
2197 "causes a line break");
2198 return NS_ERROR_FAILURE;
2201 for (size_t i = 0; i < charRects.Length() && offset < kEndOffset; i++) {
2202 nsRect charRect = charRects[i];
2203 // Store lastCharRect before applying CSS transform because it may be
2204 // used for computing a line breaker rect. Then, the computed line
2205 // breaker rect will be applied CSS transform again. Therefore,
2206 // the value of lastCharRect should be raw rect value relative to the
2207 // base frame.
2208 lastCharRect = charRect;
2209 lastFrame = baseFrame;
2210 rv = ConvertToRootRelativeOffset(baseFrame, charRect);
2211 if (NS_WARN_IF(NS_FAILED(rv))) {
2212 return rv;
2215 nsPresContext* presContext = baseFrame->PresContext();
2216 rect = LayoutDeviceIntRect::FromAppUnitsToOutside(
2217 charRect, presContext->AppUnitsPerDevPixel());
2218 if (nsPresContext* rootContext =
2219 presContext->GetInProcessRootContentDocumentPresContext()) {
2220 rect = RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2221 rect, rootContext->PresShell()));
2223 // Returning empty rect may cause native IME confused, let's make sure to
2224 // return non-empty rect.
2225 EnsureNonEmptyRect(rect);
2227 // If we found some invisible characters followed by current visible
2228 // character, make their rects same as caret rect before the first visible
2229 // character because IME may want to put their UI next to the rect of the
2230 // invisible character for next input.
2231 // Note that chars do not contain the invisible characters.
2232 if (i == 0u && MOZ_LIKELY(offset > aEvent->mInput.mOffset)) {
2233 const uint32_t offsetInRange =
2234 offset - CheckedInt<uint32_t>(aEvent->mInput.mOffset).value();
2235 if (offsetInRange > aEvent->mReply->mRectArray.Length()) {
2236 LayoutDeviceIntRect caretRectBefore =
2237 GetCaretRectBefore(rect, lastVisibleFrameWritingMode);
2238 for ([[maybe_unused]] uint32_t index : IntegerRange<uint32_t>(
2239 offsetInRange - aEvent->mReply->mRectArray.Length())) {
2240 aEvent->mReply->mRectArray.AppendElement(caretRectBefore);
2242 MOZ_ASSERT(aEvent->mReply->mRectArray.Length() == offsetInRange);
2246 aEvent->mReply->mRectArray.AppendElement(rect);
2247 offset++;
2249 // If it's not a line breaker or the line breaker length is same as
2250 // XP line breaker's, we need to do nothing for current character.
2251 wasLineBreaker = chars[i] == '\n';
2252 if (!wasLineBreaker || kBRLength == 1) {
2253 continue;
2256 MOZ_ASSERT(kBRLength == 2);
2258 // If it's already reached the end of query range, we don't need to do
2259 // anymore.
2260 if (offset == kEndOffset) {
2261 break;
2264 // If the query range starts from between a line breaker, i.e., it starts
2265 // between "\r" and "\n", the appended rect was for the "\n". Therefore,
2266 // we don't need to append same rect anymore for current "\r\n".
2267 if (startsBetweenLineBreaker) {
2268 continue;
2271 // The appended rect was for "\r" of "\r\n". Therefore, we need to
2272 // append same rect for "\n" too because querying rect of "\r" and "\n"
2273 // should return same rect. E.g., IME may query previous character's
2274 // rect of first character of a line.
2275 aEvent->mReply->mRectArray.AppendElement(rect);
2276 offset++;
2280 // If we've not handled some invisible character rects, fill them as caret
2281 // rect after the last visible character.
2282 if (!aEvent->mReply->mRectArray.IsEmpty()) {
2283 const uint32_t offsetInRange =
2284 offset - CheckedInt<uint32_t>(aEvent->mInput.mOffset).value();
2285 if (offsetInRange > aEvent->mReply->mRectArray.Length()) {
2286 LayoutDeviceIntRect caretRectAfter =
2287 GetCaretRectAfter(aEvent->mReply->mRectArray.LastElement(),
2288 lastVisibleFrameWritingMode);
2289 for ([[maybe_unused]] uint32_t index : IntegerRange<uint32_t>(
2290 offsetInRange - aEvent->mReply->mRectArray.Length())) {
2291 aEvent->mReply->mRectArray.AppendElement(caretRectAfter);
2293 MOZ_ASSERT(aEvent->mReply->mRectArray.Length() == offsetInRange);
2297 // If the query range is longer than actual content length, we should append
2298 // caret rect at the end of the content as the last character rect because
2299 // native IME may want to query character rect at the end of contents for
2300 // deciding the position of a popup window (e.g., suggest window for next
2301 // word). Note that when this method hasn't appended character rects, it
2302 // means that the offset is too large or the query range is collapsed.
2303 if (offset < kEndOffset || aEvent->mReply->mRectArray.IsEmpty()) {
2304 // If we've already retrieved some character rects before current offset,
2305 // we can guess the last rect from the last character's rect unless it's a
2306 // line breaker. (If it's a line breaker, the caret rect is in next line.)
2307 if (!aEvent->mReply->mRectArray.IsEmpty() && !wasLineBreaker) {
2308 rect = GetCaretRectAfter(aEvent->mReply->mRectArray.LastElement(),
2309 lastVisibleFrameWritingMode);
2310 aEvent->mReply->mRectArray.AppendElement(rect);
2311 } else {
2312 // Note that don't use eQueryCaretRect here because if caret is at the
2313 // end of the content, it returns actual caret rect instead of computing
2314 // the rect itself. It means that the result depends on caret position.
2315 // So, we shouldn't use it for consistency result in automated tests.
2316 WidgetQueryContentEvent queryTextRectEvent(eQueryTextRect, *aEvent);
2317 WidgetQueryContentEvent::Options options(*aEvent);
2318 queryTextRectEvent.InitForQueryTextRect(offset, 1, options);
2319 if (NS_WARN_IF(NS_FAILED(OnQueryTextRect(&queryTextRectEvent))) ||
2320 NS_WARN_IF(queryTextRectEvent.Failed())) {
2321 return NS_ERROR_FAILURE;
2323 if (queryTextRectEvent.mReply->mWritingMode.IsVertical()) {
2324 queryTextRectEvent.mReply->mRect.height = 1;
2325 } else {
2326 queryTextRectEvent.mReply->mRect.width = 1;
2328 aEvent->mReply->mRectArray.AppendElement(
2329 queryTextRectEvent.mReply->mRect);
2333 MOZ_ASSERT(aEvent->Succeeded());
2334 return NS_OK;
2337 nsresult ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent) {
2338 // If mLength is 0 (this may be caused by bug of native IME), we should
2339 // redirect this event to OnQueryCaretRect().
2340 if (!aEvent->mInput.mLength) {
2341 return OnQueryCaretRect(aEvent);
2344 nsresult rv = Init(aEvent);
2345 if (NS_FAILED(rv)) {
2346 return rv;
2349 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
2351 LineBreakType lineBreakType = GetLineBreakType(aEvent);
2352 RawRange rawRange;
2353 RefPtr<Text> lastTextNode;
2354 uint32_t startOffset = 0;
2355 if (NS_WARN_IF(NS_FAILED(SetRawRangeFromFlatTextOffset(
2356 &rawRange, aEvent->mInput.mOffset, aEvent->mInput.mLength,
2357 lineBreakType, true, &startOffset, getter_AddRefs(lastTextNode))))) {
2358 return NS_ERROR_FAILURE;
2360 nsString string;
2361 if (NS_WARN_IF(NS_FAILED(
2362 GenerateFlatTextContent(rawRange, string, lineBreakType)))) {
2363 return NS_ERROR_FAILURE;
2365 aEvent->mReply->mOffsetAndData.emplace(startOffset, string,
2366 OffsetAndDataFor::EditorString);
2368 // used to iterate over all contents and their frames
2369 PostContentIterator postOrderIter;
2370 rv = postOrderIter.Init(rawRange.Start().AsRaw(), rawRange.End().AsRaw());
2371 if (NS_WARN_IF(NS_FAILED(rv))) {
2372 return NS_ERROR_FAILURE;
2375 // Get the first frame which causes some text after the offset.
2376 FrameAndNodeOffset firstFrame = GetFirstFrameInRangeForTextRect(rawRange);
2378 // If GetFirstFrameInRangeForTextRect() does not return valid frame, that
2379 // means that there are no visible frames having text or the offset reached
2380 // the end of contents.
2381 if (!firstFrame.IsValid()) {
2382 nsAutoString allText;
2383 rv = GenerateFlatTextContent(mRootElement, allText, lineBreakType);
2384 // If the offset doesn't reach the end of contents but there is no frames
2385 // for the node, that means that current offset's node is hidden by CSS or
2386 // something. Ideally, we should handle it with the last visible text
2387 // node's last character's rect, but it's not usual cases in actual web
2388 // services. Therefore, currently, we should make this case fail.
2389 if (NS_WARN_IF(NS_FAILED(rv)) ||
2390 static_cast<uint32_t>(aEvent->mInput.mOffset) < allText.Length()) {
2391 return NS_ERROR_FAILURE;
2394 // Look for the last frame which should be included text rects.
2395 rv = rawRange.SelectNodeContents(mRootElement);
2396 if (NS_WARN_IF(NS_FAILED(rv))) {
2397 return NS_ERROR_UNEXPECTED;
2399 nsRect rect;
2400 FrameAndNodeOffset lastFrame = GetLastFrameInRangeForTextRect(rawRange);
2401 // If there is at least one frame which can be used for computing a rect
2402 // for a character or a line breaker, we should use it for guessing the
2403 // caret rect at the end of the contents.
2404 nsPresContext* presContext;
2405 if (lastFrame) {
2406 presContext = lastFrame->PresContext();
2407 if (NS_WARN_IF(!lastFrame->GetContent())) {
2408 return NS_ERROR_FAILURE;
2410 FrameRelativeRect relativeRect;
2411 // If there is a <br> frame at the end, it represents an empty line at
2412 // the end with moz-<br> or content <br> in a block level element.
2413 if (lastFrame->IsBrFrame()) {
2414 relativeRect = GetLineBreakerRectBefore(lastFrame);
2416 // If there is a text frame at the end, use its information.
2417 else if (lastFrame->IsTextFrame()) {
2418 const Text* textNode = Text::FromNode(lastFrame->GetContent());
2419 MOZ_ASSERT(textNode);
2420 if (textNode) {
2421 relativeRect = GuessLineBreakerRectAfter(*textNode);
2424 // If there is an empty frame which is neither a text frame nor a <br>
2425 // frame at the end, guess caret rect in it.
2426 else {
2427 relativeRect = GuessFirstCaretRectIn(lastFrame);
2429 if (NS_WARN_IF(!relativeRect.IsValid())) {
2430 return NS_ERROR_FAILURE;
2432 rect = relativeRect.RectRelativeTo(lastFrame);
2433 rv = ConvertToRootRelativeOffset(lastFrame, rect);
2434 if (NS_WARN_IF(NS_FAILED(rv))) {
2435 return rv;
2437 aEvent->mReply->mWritingMode = lastFrame->GetWritingMode();
2439 // Otherwise, if there are no contents in mRootElement, guess caret rect in
2440 // its frame (with its font height and content box).
2441 else {
2442 nsIFrame* rootContentFrame = mRootElement->GetPrimaryFrame();
2443 if (NS_WARN_IF(!rootContentFrame)) {
2444 return NS_ERROR_FAILURE;
2446 presContext = rootContentFrame->PresContext();
2447 FrameRelativeRect relativeRect = GuessFirstCaretRectIn(rootContentFrame);
2448 if (NS_WARN_IF(!relativeRect.IsValid())) {
2449 return NS_ERROR_FAILURE;
2451 rect = relativeRect.RectRelativeTo(rootContentFrame);
2452 rv = ConvertToRootRelativeOffset(rootContentFrame, rect);
2453 if (NS_WARN_IF(NS_FAILED(rv))) {
2454 return rv;
2456 aEvent->mReply->mWritingMode = rootContentFrame->GetWritingMode();
2458 aEvent->mReply->mRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
2459 rect, presContext->AppUnitsPerDevPixel());
2460 if (nsPresContext* rootContext =
2461 presContext->GetInProcessRootContentDocumentPresContext()) {
2462 aEvent->mReply->mRect =
2463 RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2464 aEvent->mReply->mRect, rootContext->PresShell()));
2466 EnsureNonEmptyRect(aEvent->mReply->mRect);
2468 MOZ_ASSERT(aEvent->Succeeded());
2469 return NS_OK;
2472 nsRect rect, frameRect;
2473 nsPoint ptOffset;
2475 // If the first frame is a text frame, the result should be computed with
2476 // the frame's rect but not including the rect before start point of the
2477 // queried range.
2478 if (firstFrame->IsTextFrame()) {
2479 rect.SetRect(nsPoint(0, 0), firstFrame->GetRect().Size());
2480 rv = ConvertToRootRelativeOffset(firstFrame, rect);
2481 if (NS_WARN_IF(NS_FAILED(rv))) {
2482 return rv;
2484 frameRect = rect;
2485 // Exclude the rect before start point of the queried range.
2486 firstFrame->GetPointFromOffset(firstFrame.mOffsetInNode, &ptOffset);
2487 if (firstFrame->GetWritingMode().IsVertical()) {
2488 rect.y += ptOffset.y;
2489 rect.height -= ptOffset.y;
2490 } else {
2491 rect.x += ptOffset.x;
2492 rect.width -= ptOffset.x;
2495 // If first frame causes a line breaker but it's not a <br> frame, we cannot
2496 // compute proper rect only with the frame because typically caret is at
2497 // right of the last character of it. For example, if caret is after "c" of
2498 // |<p>abc</p><p>def</p>|, IME may query a line breaker's rect after "c".
2499 // Then, if we compute it only with the 2nd <p>'s block frame, the result
2500 // will be:
2501 // +-<p>--------------------------------+
2502 // |abc |
2503 // +------------------------------------+
2505 // I+-<p>--------------------------------+
2506 // |def |
2507 // +------------------------------------+
2508 // However, users expect popup windows of IME should be positioned at
2509 // right-bottom of "c" like this:
2510 // +-<p>--------------------------------+
2511 // |abcI |
2512 // +------------------------------------+
2514 // +-<p>--------------------------------+
2515 // |def |
2516 // +------------------------------------+
2517 // Therefore, if the first frame isn't a <br> frame and there is a visible
2518 // text node before the first node in the queried range, we should compute the
2519 // first rect with the previous character's rect.
2520 else if (!firstFrame->IsBrFrame() && lastTextNode &&
2521 lastTextNode->GetPrimaryFrame()) {
2522 FrameRelativeRect brRectAfterLastChar =
2523 GuessLineBreakerRectAfter(*lastTextNode);
2524 if (NS_WARN_IF(!brRectAfterLastChar.IsValid())) {
2525 return NS_ERROR_FAILURE;
2527 rect = brRectAfterLastChar.mRect;
2528 rv = ConvertToRootRelativeOffset(brRectAfterLastChar.mBaseFrame, rect);
2529 if (NS_WARN_IF(NS_FAILED(rv))) {
2530 return rv;
2532 frameRect = rect;
2534 // Otherwise, we need to compute the line breaker's rect only with the
2535 // first frame's rect. But this may be unexpected. For example,
2536 // |<div contenteditable>[<p>]abc</p></div>|. In this case, caret is before
2537 // "a", therefore, users expect the rect left of "a". However, we don't
2538 // have enough information about the next character here and this isn't
2539 // usual case (e.g., IME typically tries to query the rect of "a" or caret
2540 // rect for computing its popup position). Therefore, we shouldn't do
2541 // more complicated hack here unless we'll get some bug reports actually.
2542 else {
2543 FrameRelativeRect relativeRect = GetLineBreakerRectBefore(firstFrame);
2544 if (NS_WARN_IF(!relativeRect.IsValid())) {
2545 return NS_ERROR_FAILURE;
2547 rect = relativeRect.RectRelativeTo(firstFrame);
2548 rv = ConvertToRootRelativeOffset(firstFrame, rect);
2549 if (NS_WARN_IF(NS_FAILED(rv))) {
2550 return rv;
2552 frameRect = rect;
2554 // UnionRect() requires non-empty rect. So, let's make sure to get non-emtpy
2555 // rect from the first frame.
2556 EnsureNonEmptyRect(rect);
2558 // Get the last frame which causes some text in the range.
2559 FrameAndNodeOffset lastFrame = GetLastFrameInRangeForTextRect(rawRange);
2560 if (NS_WARN_IF(!lastFrame.IsValid())) {
2561 return NS_ERROR_FAILURE;
2564 // iterate over all covered frames
2565 for (nsIFrame* frame = firstFrame; frame != lastFrame;) {
2566 frame = frame->GetNextContinuation();
2567 if (!frame) {
2568 do {
2569 postOrderIter.Next();
2570 nsINode* node = postOrderIter.GetCurrentNode();
2571 if (!node) {
2572 break;
2574 if (!node->IsContent()) {
2575 continue;
2577 nsIFrame* primaryFrame = node->AsContent()->GetPrimaryFrame();
2578 // The node may be hidden by CSS.
2579 if (!primaryFrame) {
2580 continue;
2582 // We should take only text frame's rect and br frame's rect. We can
2583 // always use frame rect of text frame and GetLineBreakerRectBefore()
2584 // can return exactly correct rect only for <br> frame for now. On the
2585 // other hand, GetLineBreakRectBefore() returns guessed caret rect for
2586 // the other frames. We shouldn't include such odd rect to the result.
2587 if (primaryFrame->IsTextFrame() || primaryFrame->IsBrFrame()) {
2588 frame = primaryFrame;
2590 } while (!frame && !postOrderIter.IsDone());
2591 if (!frame) {
2592 break;
2595 if (frame->IsTextFrame()) {
2596 frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size());
2597 } else {
2598 MOZ_ASSERT(frame->IsBrFrame());
2599 FrameRelativeRect relativeRect = GetLineBreakerRectBefore(frame);
2600 if (NS_WARN_IF(!relativeRect.IsValid())) {
2601 return NS_ERROR_FAILURE;
2603 frameRect = relativeRect.RectRelativeTo(frame);
2605 rv = ConvertToRootRelativeOffset(frame, frameRect);
2606 if (NS_WARN_IF(NS_FAILED(rv))) {
2607 return rv;
2609 // UnionRect() requires non-empty rect. So, let's make sure to get
2610 // non-emtpy rect from the frame.
2611 EnsureNonEmptyRect(frameRect);
2612 if (frame != lastFrame) {
2613 // not last frame, so just add rect to previous result
2614 rect.UnionRect(rect, frameRect);
2618 // Get the ending frame rect.
2619 // FYI: If first frame and last frame are same, frameRect is already set
2620 // to the rect excluding the text before the query range.
2621 if (firstFrame.mFrame != lastFrame.mFrame) {
2622 frameRect.SetRect(nsPoint(0, 0), lastFrame->GetRect().Size());
2623 rv = ConvertToRootRelativeOffset(lastFrame, frameRect);
2624 if (NS_WARN_IF(NS_FAILED(rv))) {
2625 return rv;
2629 // Shrink the last frame for cutting off the text after the query range.
2630 if (lastFrame->IsTextFrame()) {
2631 lastFrame->GetPointFromOffset(lastFrame.mOffsetInNode, &ptOffset);
2632 if (lastFrame->GetWritingMode().IsVertical()) {
2633 frameRect.height -= lastFrame->GetRect().height - ptOffset.y;
2634 } else {
2635 frameRect.width -= lastFrame->GetRect().width - ptOffset.x;
2637 // UnionRect() requires non-empty rect. So, let's make sure to get
2638 // non-empty rect from the last frame.
2639 EnsureNonEmptyRect(frameRect);
2641 if (firstFrame.mFrame == lastFrame.mFrame) {
2642 rect.IntersectRect(rect, frameRect);
2643 } else {
2644 rect.UnionRect(rect, frameRect);
2648 nsPresContext* presContext = lastFrame->PresContext();
2649 aEvent->mReply->mRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
2650 rect, presContext->AppUnitsPerDevPixel());
2651 if (nsPresContext* rootContext =
2652 presContext->GetInProcessRootContentDocumentPresContext()) {
2653 aEvent->mReply->mRect =
2654 RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2655 aEvent->mReply->mRect, rootContext->PresShell()));
2657 // Returning empty rect may cause native IME confused, let's make sure to
2658 // return non-empty rect.
2659 EnsureNonEmptyRect(aEvent->mReply->mRect);
2660 aEvent->mReply->mWritingMode = lastFrame->GetWritingMode();
2662 MOZ_ASSERT(aEvent->Succeeded());
2663 return NS_OK;
2666 nsresult ContentEventHandler::OnQueryEditorRect(
2667 WidgetQueryContentEvent* aEvent) {
2668 nsresult rv = Init(aEvent);
2669 if (NS_FAILED(rv)) {
2670 return rv;
2673 if (NS_WARN_IF(NS_FAILED(QueryContentRect(mRootElement, aEvent)))) {
2674 return NS_ERROR_FAILURE;
2677 MOZ_ASSERT(aEvent->Succeeded());
2678 return NS_OK;
2681 nsresult ContentEventHandler::OnQueryCaretRect(
2682 WidgetQueryContentEvent* aEvent) {
2683 nsresult rv = Init(aEvent);
2684 if (NS_FAILED(rv)) {
2685 return rv;
2688 // When the selection is collapsed and the queried offset is current caret
2689 // position, we should return the "real" caret rect.
2690 if (mSelection->IsCollapsed()) {
2691 nsRect caretRect;
2692 nsIFrame* caretFrame = nsCaret::GetGeometry(mSelection, &caretRect);
2693 if (caretFrame) {
2694 uint32_t offset;
2695 rv = GetStartOffset(mFirstSelectedRawRange, &offset,
2696 GetLineBreakType(aEvent));
2697 NS_ENSURE_SUCCESS(rv, rv);
2698 if (offset == aEvent->mInput.mOffset) {
2699 rv = ConvertToRootRelativeOffset(caretFrame, caretRect);
2700 NS_ENSURE_SUCCESS(rv, rv);
2701 nsPresContext* presContext = caretFrame->PresContext();
2702 aEvent->mReply->mRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
2703 caretRect, presContext->AppUnitsPerDevPixel());
2704 if (nsPresContext* rootContext =
2705 presContext->GetInProcessRootContentDocumentPresContext()) {
2706 aEvent->mReply->mRect =
2707 RoundedOut(ViewportUtils::DocumentRelativeLayoutToVisual(
2708 aEvent->mReply->mRect, rootContext->PresShell()));
2710 // Returning empty rect may cause native IME confused, let's make sure
2711 // to return non-empty rect.
2712 EnsureNonEmptyRect(aEvent->mReply->mRect);
2713 aEvent->mReply->mWritingMode = caretFrame->GetWritingMode();
2714 aEvent->mReply->mOffsetAndData.emplace(
2715 aEvent->mInput.mOffset, EmptyString(),
2716 OffsetAndDataFor::SelectedString);
2718 MOZ_ASSERT(aEvent->Succeeded());
2719 return NS_OK;
2724 // Otherwise, we should guess the caret rect from the character's rect.
2725 WidgetQueryContentEvent queryTextRectEvent(eQueryTextRect, *aEvent);
2726 WidgetQueryContentEvent::Options options(*aEvent);
2727 queryTextRectEvent.InitForQueryTextRect(aEvent->mInput.mOffset, 1, options);
2728 if (NS_WARN_IF(NS_FAILED(OnQueryTextRect(&queryTextRectEvent))) ||
2729 NS_WARN_IF(queryTextRectEvent.Failed())) {
2730 return NS_ERROR_FAILURE;
2732 queryTextRectEvent.mReply->TruncateData();
2733 aEvent->mReply->mOffsetAndData =
2734 std::move(queryTextRectEvent.mReply->mOffsetAndData);
2735 aEvent->mReply->mWritingMode =
2736 std::move(queryTextRectEvent.mReply->mWritingMode);
2737 aEvent->mReply->mRect = GetCaretRectBefore(queryTextRectEvent.mReply->mRect,
2738 aEvent->mReply->mWritingMode);
2740 MOZ_ASSERT(aEvent->Succeeded());
2741 return NS_OK;
2744 nsresult ContentEventHandler::OnQueryContentState(
2745 WidgetQueryContentEvent* aEvent) {
2746 if (NS_FAILED(Init(aEvent))) {
2747 return NS_ERROR_FAILURE;
2749 MOZ_ASSERT(aEvent->mReply.isSome());
2750 MOZ_ASSERT(aEvent->Succeeded());
2751 return NS_OK;
2754 nsresult ContentEventHandler::OnQuerySelectionAsTransferable(
2755 WidgetQueryContentEvent* aEvent) {
2756 nsresult rv = Init(aEvent);
2757 if (NS_FAILED(rv)) {
2758 return rv;
2761 MOZ_ASSERT(aEvent->mReply.isSome());
2763 if (mSelection->IsCollapsed()) {
2764 MOZ_ASSERT(!aEvent->mReply->mTransferable);
2765 return NS_OK;
2768 if (NS_WARN_IF(NS_FAILED(nsCopySupport::GetTransferableForSelection(
2769 mSelection, mDocument,
2770 getter_AddRefs(aEvent->mReply->mTransferable))))) {
2771 return NS_ERROR_FAILURE;
2774 MOZ_ASSERT(aEvent->Succeeded());
2775 return NS_OK;
2778 nsresult ContentEventHandler::OnQueryCharacterAtPoint(
2779 WidgetQueryContentEvent* aEvent) {
2780 nsresult rv = Init(aEvent);
2781 if (NS_FAILED(rv)) {
2782 return rv;
2785 MOZ_ASSERT(aEvent->mReply->mOffsetAndData.isNothing());
2786 MOZ_ASSERT(aEvent->mReply->mTentativeCaretOffset.isNothing());
2788 PresShell* presShell = mDocument->GetPresShell();
2789 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
2790 nsIFrame* rootFrame = presShell->GetRootFrame();
2791 NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
2792 nsIWidget* rootWidget = rootFrame->GetNearestWidget();
2793 NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
2795 // The root frame's widget might be different, e.g., the event was fired on
2796 // a popup but the rootFrame is the document root.
2797 if (rootWidget != aEvent->mWidget) {
2798 MOZ_ASSERT(aEvent->mWidget, "The event must have the widget");
2799 nsView* view = nsView::GetViewFor(aEvent->mWidget);
2800 NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
2801 rootFrame = view->GetFrame();
2802 NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
2803 rootWidget = rootFrame->GetNearestWidget();
2804 NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
2807 WidgetQueryContentEvent queryCharAtPointOnRootWidgetEvent(
2808 true, eQueryCharacterAtPoint, rootWidget);
2809 queryCharAtPointOnRootWidgetEvent.mUseNativeLineBreak =
2810 aEvent->mUseNativeLineBreak;
2811 queryCharAtPointOnRootWidgetEvent.mRefPoint = aEvent->mRefPoint;
2812 if (rootWidget != aEvent->mWidget) {
2813 queryCharAtPointOnRootWidgetEvent.mRefPoint +=
2814 aEvent->mWidget->WidgetToScreenOffset() -
2815 rootWidget->WidgetToScreenOffset();
2817 nsPoint ptInRoot = nsLayoutUtils::GetEventCoordinatesRelativeTo(
2818 &queryCharAtPointOnRootWidgetEvent, RelativeTo{rootFrame});
2820 nsIFrame* targetFrame =
2821 nsLayoutUtils::GetFrameForPoint(RelativeTo{rootFrame}, ptInRoot);
2822 if (!targetFrame || !targetFrame->GetContent() ||
2823 !targetFrame->GetContent()->IsInclusiveDescendantOf(mRootElement)) {
2824 // There is no character at the point.
2825 MOZ_ASSERT(aEvent->Succeeded());
2826 return NS_OK;
2828 nsPoint ptInTarget = ptInRoot + rootFrame->GetOffsetToCrossDoc(targetFrame);
2829 int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel();
2830 int32_t targetAPD = targetFrame->PresContext()->AppUnitsPerDevPixel();
2831 ptInTarget = ptInTarget.ScaleToOtherAppUnits(rootAPD, targetAPD);
2833 nsIFrame::ContentOffsets tentativeCaretOffsets =
2834 targetFrame->GetContentOffsetsFromPoint(ptInTarget);
2835 if (!tentativeCaretOffsets.content ||
2836 !tentativeCaretOffsets.content->IsInclusiveDescendantOf(mRootElement)) {
2837 // There is no character nor tentative caret point at the point.
2838 MOZ_ASSERT(aEvent->Succeeded());
2839 return NS_OK;
2842 uint32_t tentativeCaretOffset = 0;
2843 if (NS_WARN_IF(NS_FAILED(GetFlatTextLengthInRange(
2844 NodePosition(mRootElement, 0u), NodePosition(tentativeCaretOffsets),
2845 mRootElement, &tentativeCaretOffset, GetLineBreakType(aEvent))))) {
2846 return NS_ERROR_FAILURE;
2849 aEvent->mReply->mTentativeCaretOffset.emplace(tentativeCaretOffset);
2850 if (!targetFrame->IsTextFrame()) {
2851 // There is no character at the point but there is tentative caret point.
2852 MOZ_ASSERT(aEvent->Succeeded());
2853 return NS_OK;
2856 nsTextFrame* textframe = static_cast<nsTextFrame*>(targetFrame);
2857 nsIFrame::ContentOffsets contentOffsets =
2858 textframe->GetCharacterOffsetAtFramePoint(ptInTarget);
2859 NS_ENSURE_TRUE(contentOffsets.content, NS_ERROR_FAILURE);
2860 uint32_t offset = 0;
2861 if (NS_WARN_IF(NS_FAILED(GetFlatTextLengthInRange(
2862 NodePosition(mRootElement, 0u), NodePosition(contentOffsets),
2863 mRootElement, &offset, GetLineBreakType(aEvent))))) {
2864 return NS_ERROR_FAILURE;
2867 WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect,
2868 aEvent->mWidget);
2869 WidgetQueryContentEvent::Options options(*aEvent);
2870 queryTextRectEvent.InitForQueryTextRect(offset, 1, options);
2871 if (NS_WARN_IF(NS_FAILED(OnQueryTextRect(&queryTextRectEvent))) ||
2872 NS_WARN_IF(queryTextRectEvent.Failed())) {
2873 return NS_ERROR_FAILURE;
2876 aEvent->mReply->mOffsetAndData =
2877 std::move(queryTextRectEvent.mReply->mOffsetAndData);
2878 aEvent->mReply->mRect = queryTextRectEvent.mReply->mRect;
2880 MOZ_ASSERT(aEvent->Succeeded());
2881 return NS_OK;
2884 nsresult ContentEventHandler::OnQueryDOMWidgetHittest(
2885 WidgetQueryContentEvent* aEvent) {
2886 NS_ASSERTION(aEvent, "aEvent must not be null");
2888 nsresult rv = InitBasic();
2889 if (NS_FAILED(rv)) {
2890 return rv;
2893 aEvent->mReply->mWidgetIsHit = false;
2895 NS_ENSURE_TRUE(aEvent->mWidget, NS_ERROR_FAILURE);
2897 PresShell* presShell = mDocument->GetPresShell();
2898 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
2899 nsIFrame* docFrame = presShell->GetRootFrame();
2900 NS_ENSURE_TRUE(docFrame, NS_ERROR_FAILURE);
2902 LayoutDeviceIntPoint eventLoc =
2903 aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset();
2904 CSSIntRect docFrameRect = docFrame->GetScreenRect();
2905 CSSIntPoint eventLocCSS(
2906 docFrame->PresContext()->DevPixelsToIntCSSPixels(eventLoc.x) -
2907 docFrameRect.x,
2908 docFrame->PresContext()->DevPixelsToIntCSSPixels(eventLoc.y) -
2909 docFrameRect.y);
2911 if (Element* contentUnderMouse = mDocument->ElementFromPointHelper(
2912 eventLocCSS.x, eventLocCSS.y, false, false, ViewportType::Visual)) {
2913 if (nsIFrame* targetFrame = contentUnderMouse->GetPrimaryFrame()) {
2914 if (aEvent->mWidget == targetFrame->GetNearestWidget()) {
2915 aEvent->mReply->mWidgetIsHit = true;
2920 MOZ_ASSERT(aEvent->Succeeded());
2921 return NS_OK;
2924 /* static */
2925 nsresult ContentEventHandler::GetFlatTextLengthInRange(
2926 const NodePosition& aStartPosition, const NodePosition& aEndPosition,
2927 const Element* aRootElement, uint32_t* aLength,
2928 LineBreakType aLineBreakType, bool aIsRemovingNode /* = false */) {
2929 if (NS_WARN_IF(!aRootElement) || NS_WARN_IF(!aStartPosition.IsSet()) ||
2930 NS_WARN_IF(!aEndPosition.IsSet()) || NS_WARN_IF(!aLength)) {
2931 return NS_ERROR_INVALID_ARG;
2934 if (aStartPosition == aEndPosition) {
2935 *aLength = 0;
2936 return NS_OK;
2939 PreContentIterator preOrderIter;
2941 // Working with ContentIterator, we may need to adjust the end position for
2942 // including it forcibly.
2943 NodePosition endPosition(aEndPosition);
2945 // This may be called for retrieving the text of removed nodes. Even in this
2946 // case, the node thinks it's still in the tree because UnbindFromTree() will
2947 // be called after here. However, the node was already removed from the
2948 // array of children of its parent. So, be careful to handle this case.
2949 if (aIsRemovingNode) {
2950 DebugOnly<nsIContent*> parent = aStartPosition.Container()->GetParent();
2951 MOZ_ASSERT(
2952 parent &&
2953 parent->ComputeIndexOf(aStartPosition.Container()).isNothing(),
2954 "At removing the node, the node shouldn't be in the array of children "
2955 "of its parent");
2956 MOZ_ASSERT(aStartPosition.Container() == endPosition.Container(),
2957 "At removing the node, start and end node should be same");
2958 MOZ_ASSERT(*aStartPosition.Offset(
2959 NodePosition::OffsetFilter::kValidOrInvalidOffsets) == 0,
2960 "When the node is being removed, the start offset should be 0");
2961 MOZ_ASSERT(
2962 static_cast<uint32_t>(*endPosition.Offset(
2963 NodePosition::OffsetFilter::kValidOrInvalidOffsets)) ==
2964 endPosition.Container()->GetChildCount(),
2965 "When the node is being removed, the end offset should be child count");
2966 nsresult rv = preOrderIter.Init(aStartPosition.Container());
2967 if (NS_WARN_IF(NS_FAILED(rv))) {
2968 return rv;
2970 } else {
2971 RawRange prevRawRange;
2972 nsresult rv = prevRawRange.SetStart(aStartPosition.AsRaw());
2973 if (NS_WARN_IF(NS_FAILED(rv))) {
2974 return rv;
2977 // When the end position is immediately after non-root element's open tag,
2978 // we need to include a line break caused by the open tag.
2979 if (endPosition.Container() != aRootElement &&
2980 endPosition.IsImmediatelyAfterOpenTag()) {
2981 if (endPosition.Container()->HasChildren()) {
2982 // When the end node has some children, move the end position to before
2983 // the open tag of its first child.
2984 nsINode* firstChild = endPosition.Container()->GetFirstChild();
2985 if (NS_WARN_IF(!firstChild)) {
2986 return NS_ERROR_FAILURE;
2988 endPosition = NodePositionBefore(firstChild, 0u);
2989 } else {
2990 // When the end node is empty, move the end position after the node.
2991 nsIContent* parentContent = endPosition.Container()->GetParent();
2992 if (NS_WARN_IF(!parentContent)) {
2993 return NS_ERROR_FAILURE;
2995 Maybe<uint32_t> indexInParent =
2996 parentContent->ComputeIndexOf(endPosition.Container());
2997 if (MOZ_UNLIKELY(NS_WARN_IF(indexInParent.isNothing()))) {
2998 return NS_ERROR_FAILURE;
3000 MOZ_ASSERT(*indexInParent != UINT32_MAX);
3001 endPosition = NodePositionBefore(parentContent, *indexInParent + 1u);
3005 if (endPosition.IsSetAndValid()) {
3006 // Offset is within node's length; set end of range to that offset
3007 rv = prevRawRange.SetEnd(endPosition.AsRaw());
3008 if (NS_WARN_IF(NS_FAILED(rv))) {
3009 return rv;
3011 rv = preOrderIter.Init(prevRawRange.Start().AsRaw(),
3012 prevRawRange.End().AsRaw());
3013 if (NS_WARN_IF(NS_FAILED(rv))) {
3014 return rv;
3016 } else if (endPosition.Container() != aRootElement) {
3017 // Offset is past node's length; set end of range to end of node
3018 rv = prevRawRange.SetEndAfter(endPosition.Container());
3019 if (NS_WARN_IF(NS_FAILED(rv))) {
3020 return rv;
3022 rv = preOrderIter.Init(prevRawRange.Start().AsRaw(),
3023 prevRawRange.End().AsRaw());
3024 if (NS_WARN_IF(NS_FAILED(rv))) {
3025 return rv;
3027 } else {
3028 // Offset is past the root node; set end of range to end of root node
3029 rv = preOrderIter.Init(const_cast<Element*>(aRootElement));
3030 if (NS_WARN_IF(NS_FAILED(rv))) {
3031 return rv;
3036 *aLength = 0;
3037 for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
3038 nsINode* node = preOrderIter.GetCurrentNode();
3039 if (NS_WARN_IF(!node)) {
3040 break;
3042 if (!node->IsContent()) {
3043 continue;
3045 nsIContent* content = node->AsContent();
3047 if (const Text* textNode = Text::FromNode(content)) {
3048 // Note: our range always starts from offset 0
3049 if (node == endPosition.Container()) {
3050 // NOTE: We should have an offset here, as endPosition.Container() is a
3051 // nsINode::eTEXT, which always has an offset.
3052 *aLength += GetTextLength(
3053 *textNode, aLineBreakType,
3054 *endPosition.Offset(
3055 NodePosition::OffsetFilter::kValidOrInvalidOffsets));
3056 } else {
3057 *aLength += GetTextLength(*textNode, aLineBreakType);
3059 } else if (ShouldBreakLineBefore(*content, aRootElement)) {
3060 // If the start position is start of this node but doesn't include the
3061 // open tag, don't append the line break length.
3062 if (node == aStartPosition.Container() &&
3063 !aStartPosition.IsBeforeOpenTag()) {
3064 continue;
3066 // If the end position is before the open tag, don't append the line
3067 // break length.
3068 if (node == endPosition.Container() && endPosition.IsBeforeOpenTag()) {
3069 continue;
3071 *aLength += GetBRLength(aLineBreakType);
3074 return NS_OK;
3077 nsresult ContentEventHandler::GetStartOffset(const RawRange& aRawRange,
3078 uint32_t* aOffset,
3079 LineBreakType aLineBreakType) {
3080 // To match the "no skip start" hack in ContentIterator::Init, when range
3081 // offset is 0 and the range node is not a container, we have to assume the
3082 // range _includes_ the node, which means the start offset should _not_
3083 // include the node.
3085 // For example, for this content: <br>abc, and range (<br>, 0)-("abc", 1), the
3086 // range includes the linebreak from <br>, so the start offset should _not_
3087 // include <br>, and the start offset should be 0.
3089 // However, for this content: <p/>abc, and range (<p>, 0)-("abc", 1), the
3090 // range does _not_ include the linebreak from <p> because <p> is a container,
3091 // so the start offset _should_ include <p>, and the start offset should be 1.
3093 nsINode* startNode = aRawRange.GetStartContainer();
3094 bool startIsContainer = true;
3095 if (startNode->IsHTMLElement()) {
3096 nsAtom* name = startNode->NodeInfo()->NameAtom();
3097 startIsContainer =
3098 nsHTMLElement::IsContainer(nsHTMLTags::AtomTagToId(name));
3100 const NodePosition& startPos =
3101 startIsContainer ? NodePosition(startNode, aRawRange.StartOffset())
3102 : NodePositionBefore(startNode, aRawRange.StartOffset());
3103 return GetFlatTextLengthInRange(NodePosition(mRootElement, 0u), startPos,
3104 mRootElement, aOffset, aLineBreakType);
3107 nsresult ContentEventHandler::AdjustCollapsedRangeMaybeIntoTextNode(
3108 RawRange& aRawRange) {
3109 MOZ_ASSERT(aRawRange.Collapsed());
3111 if (!aRawRange.Collapsed()) {
3112 return NS_ERROR_INVALID_ARG;
3115 const RangeBoundary& startPoint = aRawRange.Start();
3116 if (NS_WARN_IF(!startPoint.IsSet())) {
3117 return NS_ERROR_INVALID_ARG;
3120 // If the node does not have children like a text node, we don't need to
3121 // modify aRawRange.
3122 if (!startPoint.Container()->HasChildren()) {
3123 return NS_OK;
3126 // If the container is not a text node but it has a text node at the offset,
3127 // we should adjust the range into the text node.
3128 // NOTE: This is emulating similar situation of EditorBase.
3129 if (startPoint.IsStartOfContainer()) {
3130 // If the range is the start of the container, adjusted the range to the
3131 // start of the first child.
3132 if (!startPoint.Container()->GetFirstChild()->IsText()) {
3133 return NS_OK;
3135 nsresult rv = aRawRange.CollapseTo(
3136 RawRangeBoundary(startPoint.Container()->GetFirstChild(), 0u));
3137 if (NS_WARN_IF(NS_FAILED(rv))) {
3138 return rv;
3140 return NS_OK;
3143 if (!startPoint.IsSetAndValid()) {
3144 return NS_OK;
3147 // If start of the range is next to a child node, adjust the range to the
3148 // end of the previous child (i.e., startPoint.Ref()).
3149 if (!startPoint.Ref()->IsText()) {
3150 return NS_OK;
3152 nsresult rv = aRawRange.CollapseTo(
3153 RawRangeBoundary(startPoint.Ref(), startPoint.Ref()->Length()));
3154 if (NS_WARN_IF(NS_FAILED(rv))) {
3155 return rv;
3157 return NS_OK;
3160 nsresult ContentEventHandler::ConvertToRootRelativeOffset(nsIFrame* aFrame,
3161 nsRect& aRect) {
3162 NS_ASSERTION(aFrame, "aFrame must not be null");
3164 nsPresContext* thisPC = aFrame->PresContext();
3165 nsPresContext* rootPC = thisPC->GetRootPresContext();
3166 if (NS_WARN_IF(!rootPC)) {
3167 return NS_ERROR_FAILURE;
3169 nsIFrame* rootFrame = rootPC->PresShell()->GetRootFrame();
3170 if (NS_WARN_IF(!rootFrame)) {
3171 return NS_ERROR_FAILURE;
3174 aRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, aRect, rootFrame);
3176 // TransformFrameRectToAncestor returned the rect in the ancestor's appUnits,
3177 // but we want it in aFrame's units (in case of different full-zoom factors),
3178 // so convert back.
3179 aRect = aRect.ScaleToOtherAppUnitsRoundOut(rootPC->AppUnitsPerDevPixel(),
3180 thisPC->AppUnitsPerDevPixel());
3182 return NS_OK;
3185 static void AdjustRangeForSelection(const Element* aRootElement,
3186 nsINode** aNode,
3187 Maybe<uint32_t>* aNodeOffset) {
3188 nsINode* node = *aNode;
3189 Maybe<uint32_t> nodeOffset = *aNodeOffset;
3190 if (aRootElement == node || NS_WARN_IF(!node->GetParent()) ||
3191 !node->IsText()) {
3192 return;
3195 // When the offset is at the end of the text node, set it to after the
3196 // text node, to make sure the caret is drawn on a new line when the last
3197 // character of the text node is '\n' in <textarea>.
3198 const uint32_t textLength = node->AsContent()->TextLength();
3199 MOZ_ASSERT(nodeOffset.isNothing() || *nodeOffset <= textLength,
3200 "Offset is past length of text node");
3201 if (nodeOffset.isNothing() || *nodeOffset != textLength) {
3202 return;
3205 Element* rootParentElement = aRootElement->GetParentElement();
3206 if (NS_WARN_IF(!rootParentElement)) {
3207 return;
3209 // If the root node is not an anonymous div of <textarea>, we don't need to
3210 // do this hack. If you did this, ContentEventHandler couldn't distinguish
3211 // if the range includes open tag of the next node in some cases, e.g.,
3212 // textNode]<p></p> vs. textNode<p>]</p>
3213 if (!rootParentElement->IsHTMLElement(nsGkAtoms::textarea)) {
3214 return;
3217 // If the node is being removed from its parent, it holds the ex-parent,
3218 // but the parent have already removed the child from its child chain.
3219 // Therefore `ComputeIndexOf` may fail, but I don't want to make Beta/Nightly
3220 // crash at accessing `Maybe::operator*` so that here checks `isSome`, but
3221 // crashing only in debug builds may help to debug something complicated
3222 // situation, therefore, `MOZ_ASSERT` is put here.
3223 *aNode = node->GetParent();
3224 Maybe<uint32_t> index = (*aNode)->ComputeIndexOf(node);
3225 MOZ_ASSERT(index.isSome());
3226 if (index.isSome()) {
3227 MOZ_ASSERT(*index != UINT32_MAX);
3228 *aNodeOffset = Some(*index + 1u);
3229 } else {
3230 *aNodeOffset = Some(0u);
3234 nsresult ContentEventHandler::OnSelectionEvent(WidgetSelectionEvent* aEvent) {
3235 aEvent->mSucceeded = false;
3237 // Get selection to manipulate
3238 // XXX why do we need to get them from ISM? This method should work fine
3239 // without ISM.
3240 nsresult rv = IMEStateManager::GetFocusSelectionAndRootElement(
3241 getter_AddRefs(mSelection), getter_AddRefs(mRootElement));
3242 if (rv != NS_ERROR_NOT_AVAILABLE) {
3243 NS_ENSURE_SUCCESS(rv, rv);
3244 } else {
3245 rv = Init(aEvent);
3246 NS_ENSURE_SUCCESS(rv, rv);
3249 // Get range from offset and length
3250 RawRange rawRange;
3251 rv = SetRawRangeFromFlatTextOffset(&rawRange, aEvent->mOffset,
3252 aEvent->mLength, GetLineBreakType(aEvent),
3253 aEvent->mExpandToClusterBoundary);
3254 NS_ENSURE_SUCCESS(rv, rv);
3256 nsINode* startNode = rawRange.GetStartContainer();
3257 nsINode* endNode = rawRange.GetEndContainer();
3258 Maybe<uint32_t> startNodeOffset = Some(rawRange.StartOffset());
3259 Maybe<uint32_t> endNodeOffset = Some(rawRange.EndOffset());
3260 AdjustRangeForSelection(mRootElement, &startNode, &startNodeOffset);
3261 AdjustRangeForSelection(mRootElement, &endNode, &endNodeOffset);
3262 if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode) ||
3263 NS_WARN_IF(startNodeOffset.isNothing()) ||
3264 NS_WARN_IF(endNodeOffset.isNothing())) {
3265 return NS_ERROR_UNEXPECTED;
3268 if (aEvent->mReversed) {
3269 nsCOMPtr<nsINode> startNodeStrong(startNode);
3270 nsCOMPtr<nsINode> endNodeStrong(endNode);
3271 ErrorResult error;
3272 MOZ_KnownLive(mSelection)
3273 ->SetBaseAndExtentInLimiter(*endNodeStrong, *endNodeOffset,
3274 *startNodeStrong, *startNodeOffset, error);
3275 if (NS_WARN_IF(error.Failed())) {
3276 return error.StealNSResult();
3278 } else {
3279 nsCOMPtr<nsINode> startNodeStrong(startNode);
3280 nsCOMPtr<nsINode> endNodeStrong(endNode);
3281 ErrorResult error;
3282 MOZ_KnownLive(mSelection)
3283 ->SetBaseAndExtentInLimiter(*startNodeStrong, *startNodeOffset,
3284 *endNodeStrong, *endNodeOffset, error);
3285 if (NS_WARN_IF(error.Failed())) {
3286 return error.StealNSResult();
3290 // `ContentEventHandler` is a `MOZ_STACK_CLASS`, so `mSelection` is known to
3291 // be alive.
3292 MOZ_KnownLive(mSelection)
3293 ->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
3294 ScrollAxis(), ScrollAxis(), 0);
3295 aEvent->mSucceeded = true;
3296 return NS_OK;
3299 nsRect ContentEventHandler::FrameRelativeRect::RectRelativeTo(
3300 nsIFrame* aDestFrame) const {
3301 if (!mBaseFrame || NS_WARN_IF(!aDestFrame)) {
3302 return nsRect();
3305 if (NS_WARN_IF(aDestFrame->PresContext() != mBaseFrame->PresContext())) {
3306 return nsRect();
3309 if (aDestFrame == mBaseFrame) {
3310 return mRect;
3313 nsIFrame* rootFrame = mBaseFrame->PresShell()->GetRootFrame();
3314 nsRect baseFrameRectInRootFrame = nsLayoutUtils::TransformFrameRectToAncestor(
3315 mBaseFrame, nsRect(), rootFrame);
3316 nsRect destFrameRectInRootFrame = nsLayoutUtils::TransformFrameRectToAncestor(
3317 aDestFrame, nsRect(), rootFrame);
3318 nsPoint difference =
3319 destFrameRectInRootFrame.TopLeft() - baseFrameRectInRootFrame.TopLeft();
3320 return mRect - difference;
3323 } // namespace mozilla