Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/generic/./../../../layout/generic/nsFrameSelection.cpp
Warning:line 915, column 24
Value stored to 'tHint' during its initialization is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name Unified_cpp_layout_generic2.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/generic -fcoverage-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/generic -resource-dir /usr/lib/llvm-23/lib/clang/23 -include /root/firefox-clang/config/gcc_hidden.h -include /root/firefox-clang/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/stl_wrappers -D _GLIBCXX_ASSERTIONS=1 -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D MOZ_HAS_MOZGLUE -D MOZILLA_INTERNAL_API -D IMPL_LIBXUL -D MOZ_SUPPORT_LEAKCHECKING -D STATIC_EXPORTABLE_JS_API -I /root/firefox-clang/layout/generic -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/generic -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/ipc/ipdl/_ipdlheaders -I /root/firefox-clang/ipc/chromium/src -I /root/firefox-clang/third_party/abseil-cpp -I /root/firefox-clang/toolkit/components/telemetry -I /root/firefox-clang/xpcom/base -I /root/firefox-clang/layout/base -I /root/firefox-clang/layout/forms -I /root/firefox-clang/layout/painting -I /root/firefox-clang/layout/style -I /root/firefox-clang/layout/tables -I /root/firefox-clang/layout/xul -I /root/firefox-clang/docshell/base -I /root/firefox-clang/dom/base -I /root/firefox-clang/dom/html -I /root/firefox-clang/dom/xul -I /root/firefox-clang/gfx/cairo/cairo/src -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -I /usr/include/gtk-3.0/unix-print -I /usr/include/gtk-3.0 -I /usr/include/pango-1.0 -I /usr/include/cloudproviders -I /usr/include/at-spi2-atk/2.0 -I /usr/include/at-spi-2.0 -I /usr/include/dbus-1.0 -I /usr/lib/x86_64-linux-gnu/dbus-1.0/include -I /usr/include/fribidi -I /usr/include/harfbuzz -I /usr/include/atk-1.0 -I /usr/include/cairo -I /usr/include/pixman-1 -I /usr/include/gdk-pixbuf-2.0 -I /usr/include/glycin-2 -I /usr/include/freetype2 -I /usr/include/libpng16 -I /usr/include/gio-unix-2.0 -I /usr/include/glib-2.0 -I /usr/lib/x86_64-linux-gnu/glib-2.0/include -I /usr/include/libmount -I /usr/include/blkid -I /usr/include/sysprof-6 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/x86_64-linux-gnu/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16/backward -internal-isystem /usr/lib/llvm-23/lib/clang/23/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-error=pessimizing-move -Wno-error=large-by-value-copy=128 -Wno-error=implicit-int-float-conversion -Wno-error=thread-safety-analysis -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -Wno-character-conversion -std=gnu++20 -fdeprecated-macro -ferror-limit 19 -fstrict-flex-arrays=1 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fno-sized-deallocation -fno-aligned-allocation -fdiagnostics-absolute-paths -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -mllvm -dwarf-linkage-names=Abstract -faddrsig -fdwarf2-cfi-asm -o /tmp/scan-build-2026-09-01-224014-2642839-1 -x c++ Unified_cpp_layout_generic2.cpp
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5/*
6 * Implementation of nsFrameSelection
7 */
8
9#include "nsFrameSelection.h"
10
11#include <algorithm>
12
13#include "ErrorList.h"
14#include "mozilla/Attributes.h"
15#include "mozilla/AutoRestore.h"
16#include "mozilla/BasePrincipal.h"
17#include "mozilla/HTMLEditor.h"
18#include "mozilla/IntegerRange.h"
19#include "mozilla/Logging.h"
20#include "mozilla/MouseEvents.h"
21#include "mozilla/Preferences.h"
22#include "mozilla/PresShell.h"
23#include "mozilla/PseudoStyleType.h"
24#include "mozilla/ScrollContainerFrame.h"
25#include "mozilla/ScrollTypes.h"
26#include "mozilla/StaticAnalysisFunctions.h"
27#include "mozilla/StaticPrefs_bidi.h"
28#include "mozilla/StaticPrefs_dom.h"
29#include "mozilla/StaticPrefs_layout.h"
30#include "mozilla/TextControlElement.h"
31#include "mozilla/TextEvents.h"
32#include "mozilla/intl/BidiEmbeddingLevel.h"
33#include "nsBidiPresUtils.h"
34#include "nsCCUncollectableMarker.h"
35#include "nsCOMPtr.h"
36#include "nsCSSFrameConstructor.h"
37#include "nsCaret.h"
38#include "nsContentUtils.h"
39#include "nsDebug.h"
40#include "nsDeviceContext.h"
41#include "nsFrameTraversal.h"
42#include "nsGkAtoms.h"
43#include "nsIContent.h"
44#include "nsISelectionListener.h"
45#include "nsITableCellLayout.h"
46#include "nsLayoutUtils.h"
47#include "nsPresContext.h"
48#include "nsRange.h"
49#include "nsString.h"
50#include "nsTArray.h"
51#include "nsTableCellFrame.h"
52#include "nsTableWrapperFrame.h"
53#include "nsTextFrame.h"
54#include "nsThreadUtils.h"
55
56// notifications
57#include "SelectionMovementUtils.h"
58#include "mozilla/AsyncEventDispatcher.h"
59#include "mozilla/AutoCopyListener.h"
60#include "mozilla/ErrorResult.h"
61#include "mozilla/dom/AncestorIterator.h"
62#include "mozilla/dom/Document.h"
63#include "mozilla/dom/Element.h"
64#include "mozilla/dom/Highlight.h"
65#include "mozilla/dom/Selection.h"
66#include "mozilla/dom/SelectionBinding.h"
67#include "mozilla/dom/ShadowRoot.h"
68#include "mozilla/dom/StaticRange.h"
69#include "mozilla/dom/Text.h"
70#include "nsCopySupport.h"
71#include "nsError.h"
72#include "nsFocusManager.h"
73#include "nsIClipboard.h"
74#include "nsIFrameInlines.h"
75#include "nsISelectionController.h" //for the enums
76#include "nsPIDOMWindow.h"
77
78using namespace mozilla;
79using namespace mozilla::dom;
80
81static LazyLogModule sFrameSelectionLog("FrameSelection");
82
83std::ostream& operator<<(std::ostream& aStream,
84 const nsFrameSelection& aFrameSelection) {
85 return aStream << "{ mPresShell=" << aFrameSelection.mPresShell
86 << ", mLimiters={ mIndependentSelectionRootElement="
87 << aFrameSelection.mLimiters.mIndependentSelectionRootElement
88 << ", mAncestorLimiter="
89 << aFrameSelection.mLimiters.mAncestorLimiter
90 << "}, IsBatching()=" << std::boolalpha
91 << aFrameSelection.IsBatching()
92 << ", IsInTableSelectionMode()=" << std::boolalpha
93 << aFrameSelection.IsInTableSelectionMode()
94 << ", GetDragState()=" << std::boolalpha
95 << aFrameSelection.GetDragState()
96 << ", HighlightSelectionCount()="
97 << aFrameSelection.HighlightSelectionCount() << " }";
98}
99
100namespace mozilla {
101extern LazyLogModule sSelectionAPILog;
102extern void LogStackForSelectionAPI();
103
104static void LogSelectionAPI(const dom::Selection* aSelection,
105 const char* aFuncName, const char* aArgName,
106 const nsIContent* aContent) {
107 MOZ_LOG(sSelectionAPILog, LogLevel::Info,do { const ::mozilla::LogModule* moz_real_module = sSelectionAPILog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p nsFrameSelection::%s(%s=%s)", aSelection
, aFuncName, aArgName, aContent ? ToString(*aContent).c_str()
: "<nullptr>"); } } while (0)
108 ("%p nsFrameSelection::%s(%s=%s)", aSelection, aFuncName, aArgName,do { const ::mozilla::LogModule* moz_real_module = sSelectionAPILog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p nsFrameSelection::%s(%s=%s)", aSelection
, aFuncName, aArgName, aContent ? ToString(*aContent).c_str()
: "<nullptr>"); } } while (0)
109 aContent ? ToString(*aContent).c_str() : "<nullptr>"))do { const ::mozilla::LogModule* moz_real_module = sSelectionAPILog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p nsFrameSelection::%s(%s=%s)", aSelection
, aFuncName, aArgName, aContent ? ToString(*aContent).c_str()
: "<nullptr>"); } } while (0)
;
110}
111} // namespace mozilla
112
113// #define DEBUG_TABLE 1
114
115/**
116 * Add cells to the selection inside of the given cells range.
117 *
118 * @param aTable [in] HTML table element
119 * @param aStartRowIndex [in] row index where the cells range starts
120 * @param aStartColumnIndex [in] column index where the cells range starts
121 * @param aEndRowIndex [in] row index where the cells range ends
122 * @param aEndColumnIndex [in] column index where the cells range ends
123 */
124static nsresult AddCellsToSelection(const nsIContent* aTableContent,
125 int32_t aStartRowIndex,
126 int32_t aStartColumnIndex,
127 int32_t aEndRowIndex,
128 int32_t aEndColumnIndex,
129 Selection& aNormalSelection);
130
131static nsAtom* GetTag(nsINode* aNode);
132
133static nsINode* GetClosestInclusiveTableCellAncestor(nsINode* aDomNode);
134MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult CreateAndAddRange(
135 nsINode* aContainer, int32_t aOffset, Selection& aNormalSelection);
136static nsresult SelectCellElement(nsIContent* aCellElement,
137 Selection& aNormalSelection);
138
139// On macOS, we need to update the selection cache when we repaint a selection.
140// This runs nsHTMLCopyEncoder to serialize the selection ranges, which is
141// really complicated especially when shadow DOM selection. Therefore, that may
142// cause some assertion failures. Unfortunately, our macOS machines in the CI
143// are always busy and we need one or two days to check the result on trysever.
144// Therefore, we should run nsHTMLCopyEncoder part in all desktop platforms if
145// it's a debug build. Thus, we can check the result on Linux machines in
146// tryserver.
147#if defined(XP_MACOSX) || (defined(DEBUG1) && !defined(ANDROID))
148# define RUN_MAYBE_UPDATE_SELECTION_CACHE_REPAINT_SELECTION
149#endif
150
151#ifdef RUN_MAYBE_UPDATE_SELECTION_CACHE_REPAINT_SELECTION
152static nsresult MaybeUpdateSelectionCacheOnRepaintSelection(Selection* aSel);
153#endif // RUN_MAYBE_UPDATE_SELECTION_CACHE_REPAINT_SELECTION
154
155#ifdef PRINT_RANGE
156static void printRange(nsRange* aDomRange);
157# define DEBUG_OUT_RANGE(x) printRange(x)
158#else
159# define DEBUG_OUT_RANGE(x)
160#endif // PRINT_RANGE
161
162/******************************************************************************
163 * mozilla::PeekOffsetStruct
164 ******************************************************************************/
165
166// #define DEBUG_SELECTION // uncomment for printf describing every collapse and
167// extend. #define DEBUG_NAVIGATION
168
169// #define DEBUG_TABLE_SELECTION 1
170
171namespace mozilla {
172
173PeekOffsetStruct::PeekOffsetStruct(
174 nsSelectionAmount aAmount, nsDirection aDirection, int32_t aStartOffset,
175 nsPoint aDesiredCaretPos, const PeekOffsetOptions aOptions,
176 EWordMovementType aWordMovementType /* = eDefaultBehavior */,
177 const Element* aAncestorLimiter /* = nullptr */)
178 : mAmount(aAmount),
179 mDirection(aDirection),
180 mStartOffset(aStartOffset),
181 mDesiredCaretPos(aDesiredCaretPos),
182 mWordMovementType(aWordMovementType),
183 mOptions(aOptions),
184 mAncestorLimiter(aAncestorLimiter),
185 mResultFrame(nullptr),
186 mContentOffset(0),
187 mAttach(CaretAssociationHint::Before) {}
188
189} // namespace mozilla
190
191// Array which contains index of each SelectionType in
192// Selection::mDOMSelections. For avoiding using if nor switch to retrieve the
193// index, this needs to have -1 for SelectionTypes which won't be created its
194// Selection instance.
195static const int8_t kIndexOfSelections[] = {
196 -1, // SelectionType::eInvalid
197 -1, // SelectionType::eNone
198 0, // SelectionType::eNormal
199 1, // SelectionType::eSpellCheck
200 2, // SelectionType::eIMERawClause
201 3, // SelectionType::eIMESelectedRawClause
202 4, // SelectionType::eIMEConvertedClause
203 5, // SelectionType::eIMESelectedClause
204 6, // SelectionType::eAccessibility
205 7, // SelectionType::eFind
206 8, // SelectionType::eURLSecondary
207 9, // SelectionType::eURLStrikeout
208 10, // SelectionType::eTargetText
209 -1, // SelectionType::eHighlight
210};
211
212inline int8_t GetIndexFromSelectionType(SelectionType aSelectionType) {
213 // The enum value of eInvalid is -1 and the others are sequential value
214 // starting from 0. Therefore, |SelectionType + 1| is the index of
215 // kIndexOfSelections.
216 return kIndexOfSelections[static_cast<int8_t>(aSelectionType) + 1];
217}
218
219namespace mozilla {
220
221/*
222The limiter is used specifically for the text areas and textfields
223In that case it is the DIV tag that is anonymously created for the text
224areas/fields. Text nodes and BR nodes fall beneath it. In the case of a
225BR node the limiter will be the parent and the offset will point before or
226after the BR node. In the case of the text node the parent content is
227the text node itself and the offset will be the exact character position.
228The offset is not important to check for validity. Simply look at the
229passed in content. If it equals the limiter then the selection point is valid.
230If its parent it the limiter then the point is also valid. In the case of
231NO limiter all points are valid since you are in a topmost iframe. (browser
232or composer)
233*/
234bool SelectionLimiters::NodeIsInLimiters(const nsINode* aContainerNode) const {
235 if (!aContainerNode) {
236 return false;
237 }
238
239 // If there is a selection limiter, it must be the anonymous <div> of a text
240 // control. The <div> should have only one Text and/or a <br>. Therefore,
241 // when it's non-nullptr, selection range containers must be the container or
242 // the Text in it.
243 if (mIndependentSelectionRootElement) {
244 MOZ_ASSERT(mIndependentSelectionRootElement->GetPseudoElementType() ==do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mIndependentSelectionRootElement->GetPseudoElementType
() == PseudoStyleType::MozTextControlEditingRoot)>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(mIndependentSelectionRootElement->GetPseudoElementType(
) == PseudoStyleType::MozTextControlEditingRoot))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("mIndependentSelectionRootElement->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
, "./../../../layout/generic/nsFrameSelection.cpp", 245); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mIndependentSelectionRootElement->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
")"); do { MOZ_CrashSequence(__null, 245); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
245 PseudoStyleType::MozTextControlEditingRoot)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mIndependentSelectionRootElement->GetPseudoElementType
() == PseudoStyleType::MozTextControlEditingRoot)>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(mIndependentSelectionRootElement->GetPseudoElementType(
) == PseudoStyleType::MozTextControlEditingRoot))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("mIndependentSelectionRootElement->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
, "./../../../layout/generic/nsFrameSelection.cpp", 245); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mIndependentSelectionRootElement->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
")"); do { MOZ_CrashSequence(__null, 245); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
246 MOZ_ASSERT(mIndependentSelectionRootElement->IsHTMLElement(nsGkAtoms::div))do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mIndependentSelectionRootElement->IsHTMLElement(nsGkAtoms
::div))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(mIndependentSelectionRootElement->IsHTMLElement(nsGkAtoms
::div)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mIndependentSelectionRootElement->IsHTMLElement(nsGkAtoms::div)"
, "./../../../layout/generic/nsFrameSelection.cpp", 246); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mIndependentSelectionRootElement->IsHTMLElement(nsGkAtoms::div)"
")"); do { MOZ_CrashSequence(__null, 246); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
247 if (mIndependentSelectionRootElement == aContainerNode) {
248 return true;
249 }
250 if (mIndependentSelectionRootElement == aContainerNode->GetParent()) {
251 NS_WARNING_ASSERTION(aContainerNode->IsText(),do { if (!(aContainerNode->IsText())) { NS_DebugBreak(NS_DEBUG_WARNING
, ToString(*aContainerNode).c_str(), "aContainerNode->IsText()"
, "./../../../layout/generic/nsFrameSelection.cpp", 252); } }
while (false)
252 ToString(*aContainerNode).c_str())do { if (!(aContainerNode->IsText())) { NS_DebugBreak(NS_DEBUG_WARNING
, ToString(*aContainerNode).c_str(), "aContainerNode->IsText()"
, "./../../../layout/generic/nsFrameSelection.cpp", 252); } }
while (false)
;
253 MOZ_ASSERT(aContainerNode->IsText())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aContainerNode->IsText())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aContainerNode->IsText())
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aContainerNode->IsText()"
, "./../../../layout/generic/nsFrameSelection.cpp", 253); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aContainerNode->IsText()" ")"); do { MOZ_CrashSequence
(__null, 253); __attribute__((nomerge)) ::abort(); } while (false
); } } while (false)
;
254 return true;
255 }
256 return false;
257 }
258
259 // XXX We might need to return `false` if aContainerNode is in a native
260 // anonymous subtree, but doing it will make it impossible to select the
261 // anonymous subtree text in <details>.
262 return !mAncestorLimiter ||
263 aContainerNode->IsInclusiveDescendantOf(mAncestorLimiter);
264}
265
266struct MOZ_RAII AutoPrepareFocusRange {
267 AutoPrepareFocusRange(Selection* aSelection,
268 const bool aMultiRangeSelection) {
269 MOZ_ASSERT(aSelection)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aSelection)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aSelection))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("aSelection", "./../../../layout/generic/nsFrameSelection.cpp"
, 269); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aSelection" ")"
); do { MOZ_CrashSequence(__null, 269); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
270 MOZ_ASSERT(aSelection->GetType() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aSelection->GetType() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aSelection->GetType() == SelectionType::eNormal))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aSelection->GetType() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 270); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aSelection->GetType() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 270); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
271
272 if (aSelection->mStyledRanges.mRanges.Length() <= 1) {
273 return;
274 }
275
276 if (aSelection->mFrameSelection->IsUserSelectionReason()) {
277 mUserSelect.emplace(aSelection);
278 }
279
280 Span ranges = aSelection->mStyledRanges.Ranges();
281 if (!aSelection->mUserInitiated || aMultiRangeSelection) {
282 // Scripted command or the user is starting a new explicit multi-range
283 // selection.
284 for (const auto& range : ranges) {
285 MOZ_ASSERT(range->IsDynamicRange())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(range->IsDynamicRange())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(range->IsDynamicRange()))
), 0))) { do { } while (false); MOZ_ReportAssertionFailure("range->IsDynamicRange()"
, "./../../../layout/generic/nsFrameSelection.cpp", 285); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "range->IsDynamicRange()" ")"); do { MOZ_CrashSequence
(__null, 285); __attribute__((nomerge)) ::abort(); } while (false
); } } while (false)
;
286 range->AsDynamicRange()->SetIsGenerated(false);
287 }
288 return;
289 }
290
291 if (!IsAnchorRelativeOperation(
292 aSelection->mFrameSelection->mSelectionChangeReasons)) {
293 return;
294 }
295
296 // This operation is against the anchor but our current mAnchorFocusRange
297 // represents the focus in a multi-range selection. The anchor from a user
298 // perspective is the most distant generated range on the opposite side.
299 // Find that range and make it the mAnchorFocusRange.
300 nsRange* const newAnchorFocusRange =
301 FindGeneratedRangeMostDistantFromAnchor(*aSelection);
302
303 if (!newAnchorFocusRange) {
304 // There are no generated ranges - that's fine.
305 return;
306 }
307
308 // Setup the new mAnchorFocusRange and mark the old one as generated.
309 if (aSelection->mAnchorFocusRange) {
310 aSelection->mAnchorFocusRange->SetIsGenerated(true);
311 }
312
313 newAnchorFocusRange->SetIsGenerated(false);
314 aSelection->mAnchorFocusRange = newAnchorFocusRange;
315
316 RemoveGeneratedRanges(*aSelection);
317
318 if (aSelection->mFrameSelection) {
319 aSelection->mFrameSelection->InvalidateDesiredCaretPos();
320 }
321 }
322
323 private:
324 static nsRange* FindGeneratedRangeMostDistantFromAnchor(
325 const Selection& aSelection) {
326 const Span ranges = aSelection.mStyledRanges.Ranges();
327 // This function is only called for selections with type == eNormal.
328 // (see MOZ_ASSERT in constructor).
329 // Therefore, all ranges must be dynamic.
330 if (aSelection.GetDirection() == eDirNext) {
331 for (const auto& range : ranges) {
332 if (range->AsDynamicRange()->IsGenerated()) {
333 return range->AsDynamicRange();
334 }
335 }
336 } else {
337 for (const auto& range : Reversed(ranges)) {
338 if (range->AsDynamicRange()->IsGenerated()) {
339 return range->AsDynamicRange();
340 }
341 }
342 }
343
344 return nullptr;
345 }
346
347 static void RemoveGeneratedRanges(Selection& aSelection) {
348 RefPtr<nsPresContext> presContext = aSelection.GetPresContext();
349 Span ranges = aSelection.mStyledRanges.Ranges();
350 size_t i = ranges.Length();
351 while (i--) {
352 // This function is only called for selections with type == eNormal.
353 // (see MOZ_ASSERT in constructor).
354 // Therefore, all ranges must be dynamic.
355 if (!ranges[i]->IsDynamicRange()) {
356 continue;
357 }
358 nsRange* range = ranges[i]->AsDynamicRange();
359 if (range->IsGenerated()) {
360 range->UnregisterSelection(aSelection);
361 aSelection.SelectFrames(presContext, *range, false);
362 aSelection.mStyledRanges.mRanges.RemoveElementAt(i);
363 }
364 }
365 }
366
367 /**
368 * @aParam aSelectionChangeReasons can be multiple of the reasons defined in
369 nsISelectionListener.idl.
370 */
371 static bool IsAnchorRelativeOperation(const int16_t aSelectionChangeReasons) {
372 return aSelectionChangeReasons &
373 (nsISelectionListener::DRAG_REASON |
374 nsISelectionListener::MOUSEDOWN_REASON |
375 nsISelectionListener::MOUSEUP_REASON |
376 nsISelectionListener::COLLAPSETOSTART_REASON);
377 }
378
379 Maybe<Selection::AutoUserInitiated> mUserSelect;
380};
381
382} // namespace mozilla
383
384////////////BEGIN nsFrameSelection methods
385
386template Result<RefPtr<nsRange>, nsresult>
387nsFrameSelection::CreateRangeExtendedToSomewhere(
388 PresShell& aPresShell,
389 const mozilla::LimitersAndCaretData& aLimitersAndCaretData,
390 const AbstractRange& aRange, nsDirection aRangeDirection,
391 nsDirection aExtendDirection, nsSelectionAmount aAmount,
392 CaretMovementStyle aMovementStyle);
393template Result<RefPtr<StaticRange>, nsresult>
394nsFrameSelection::CreateRangeExtendedToSomewhere(
395 PresShell& aPresShell,
396 const mozilla::LimitersAndCaretData& aLimitersAndCaretData,
397 const AbstractRange& aRange, nsDirection aRangeDirection,
398 nsDirection aExtendDirection, nsSelectionAmount aAmount,
399 CaretMovementStyle aMovementStyle);
400
401nsFrameSelection::nsFrameSelection(
402 PresShell* aPresShell, const bool aAccessibleCaretEnabled,
403 Element* aEditorRootAnonymousDiv /* = nullptr */) {
404 for (size_t i = 0; i < std::size(mDomSelections); i++) {
405 mDomSelections[i] = new Selection(kPresentSelectionTypes[i], this);
406 }
407
408 Selection& sel = NormalSelection();
409 if (AutoCopyListener::IsEnabled()) {
410 sel.NotifyAutoCopy();
411 }
412
413 mPresShell = aPresShell;
414 mDragState = false;
415
416 MOZ_ASSERT_IF(aEditorRootAnonymousDiv,do { if (aEditorRootAnonymousDiv) { do { static_assert( mozilla
::detail::AssertionConditionType<decltype(aEditorRootAnonymousDiv
->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aEditorRootAnonymousDiv->GetPseudoElementType() ==
PseudoStyleType::MozTextControlEditingRoot))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("aEditorRootAnonymousDiv->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
, "./../../../layout/generic/nsFrameSelection.cpp", 418); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aEditorRootAnonymousDiv->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
")"); do { MOZ_CrashSequence(__null, 418); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false); } } while (
false)
417 aEditorRootAnonymousDiv->GetPseudoElementType() ==do { if (aEditorRootAnonymousDiv) { do { static_assert( mozilla
::detail::AssertionConditionType<decltype(aEditorRootAnonymousDiv
->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aEditorRootAnonymousDiv->GetPseudoElementType() ==
PseudoStyleType::MozTextControlEditingRoot))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("aEditorRootAnonymousDiv->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
, "./../../../layout/generic/nsFrameSelection.cpp", 418); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aEditorRootAnonymousDiv->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
")"); do { MOZ_CrashSequence(__null, 418); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false); } } while (
false)
418 PseudoStyleType::MozTextControlEditingRoot)do { if (aEditorRootAnonymousDiv) { do { static_assert( mozilla
::detail::AssertionConditionType<decltype(aEditorRootAnonymousDiv
->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aEditorRootAnonymousDiv->GetPseudoElementType() ==
PseudoStyleType::MozTextControlEditingRoot))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("aEditorRootAnonymousDiv->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
, "./../../../layout/generic/nsFrameSelection.cpp", 418); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aEditorRootAnonymousDiv->GetPseudoElementType() == PseudoStyleType::MozTextControlEditingRoot"
")"); do { MOZ_CrashSequence(__null, 418); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false); } } while (
false)
;
419 MOZ_ASSERT_IF(aEditorRootAnonymousDiv,do { if (aEditorRootAnonymousDiv) { do { static_assert( mozilla
::detail::AssertionConditionType<decltype(aEditorRootAnonymousDiv
->IsHTMLElement(nsGkAtoms::div))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aEditorRootAnonymousDiv->
IsHTMLElement(nsGkAtoms::div)))), 0))) { do { } while (false)
; MOZ_ReportAssertionFailure("aEditorRootAnonymousDiv->IsHTMLElement(nsGkAtoms::div)"
, "./../../../layout/generic/nsFrameSelection.cpp", 420); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aEditorRootAnonymousDiv->IsHTMLElement(nsGkAtoms::div)"
")"); do { MOZ_CrashSequence(__null, 420); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false); } } while (
false)
420 aEditorRootAnonymousDiv->IsHTMLElement(nsGkAtoms::div))do { if (aEditorRootAnonymousDiv) { do { static_assert( mozilla
::detail::AssertionConditionType<decltype(aEditorRootAnonymousDiv
->IsHTMLElement(nsGkAtoms::div))>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aEditorRootAnonymousDiv->
IsHTMLElement(nsGkAtoms::div)))), 0))) { do { } while (false)
; MOZ_ReportAssertionFailure("aEditorRootAnonymousDiv->IsHTMLElement(nsGkAtoms::div)"
, "./../../../layout/generic/nsFrameSelection.cpp", 420); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aEditorRootAnonymousDiv->IsHTMLElement(nsGkAtoms::div)"
")"); do { MOZ_CrashSequence(__null, 420); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false); } } while (
false)
;
421 mLimiters.mIndependentSelectionRootElement = aEditorRootAnonymousDiv;
422
423 // This should only ever be initialized on the main thread, so we are OK here.
424 MOZ_ASSERT(NS_IsMainThread())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(NS_IsMainThread())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(NS_IsMainThread()))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("NS_IsMainThread()"
, "./../../../layout/generic/nsFrameSelection.cpp", 424); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "NS_IsMainThread()" ")"); do { MOZ_CrashSequence
(__null, 424); __attribute__((nomerge)) ::abort(); } while (false
); } } while (false)
;
425
426 mAccessibleCaretEnabled = aAccessibleCaretEnabled;
427 if (mAccessibleCaretEnabled) {
428 sel.MaybeNotifyAccessibleCaretEventHub(aPresShell);
429 }
430
431 sel.EnableSelectionChangeEvent();
432}
433
434nsFrameSelection::~nsFrameSelection() = default;
435
436NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection)nsFrameSelection::cycleCollection nsFrameSelection::_cycleCollectorGlobal
;
437
438NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection)void nsFrameSelection::cycleCollection::Unlink(void* p) { nsFrameSelection
* tmp = DowncastCCParticipant<nsFrameSelection>(p);
439 for (size_t i = 0; i < std::size(tmp->mDomSelections); ++i) {
440 tmp->mDomSelections[i] = nullptr;
441 }
442 tmp->mHighlightSelections.Clear();
443
444 NS_IMPL_CYCLE_COLLECTION_UNLINK(ImplCycleCollectionUnlink(tmp->mTableSelection.mClosestInclusiveTableCellAncestor
);
445 mTableSelection.mClosestInclusiveTableCellAncestor)ImplCycleCollectionUnlink(tmp->mTableSelection.mClosestInclusiveTableCellAncestor
);
446 tmp->mTableSelection.mMode = TableSelectionMode::None;
447 tmp->mTableSelection.mDragSelectingCells = false;
448 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mStartSelectedCell)ImplCycleCollectionUnlink(tmp->mTableSelection.mStartSelectedCell
);
449 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mEndSelectedCell)ImplCycleCollectionUnlink(tmp->mTableSelection.mEndSelectedCell
);
450 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mAppendStartSelectedCell)ImplCycleCollectionUnlink(tmp->mTableSelection.mAppendStartSelectedCell
);
451 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTableSelection.mUnselectCellOnMouseUp)ImplCycleCollectionUnlink(tmp->mTableSelection.mUnselectCellOnMouseUp
);
452 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainedRange.mRange)ImplCycleCollectionUnlink(tmp->mMaintainedRange.mRange);
453 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters.mIndependentSelectionRootElement)ImplCycleCollectionUnlink(tmp->mLimiters.mIndependentSelectionRootElement
);
454 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiters.mAncestorLimiter)ImplCycleCollectionUnlink(tmp->mLimiters.mAncestorLimiter)
;
455NS_IMPL_CYCLE_COLLECTION_UNLINK_END(void)tmp; }
456NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection)nsresult nsFrameSelection::cycleCollection::TraverseNative( void
* p, nsCycleCollectionTraversalCallback& cb) { nsFrameSelection
* tmp = DowncastCCParticipant<nsFrameSelection>(p); cb.
DescribeRefCountedNode(tmp->mRefCnt.get(), "nsFrameSelection"
);
457 if (tmp->mPresShell && tmp->mPresShell->GetDocument() &&
458 nsCCUncollectableMarker::InGeneration(
459 cb, tmp->mPresShell->GetDocument()->GetMarkedCCGeneration())) {
460 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
461 }
462 for (size_t i = 0; i < std::size(tmp->mDomSelections); ++i) {
463 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections[i])ImplCycleCollectionTraverse(cb, tmp->mDomSelections[i], "mDomSelections[i]"
, 0);
464 }
465
466 for (const auto& value : tmp->mHighlightSelections) {
467 CycleCollectionNoteChild(cb, value.second().get(),
468 "mHighlightSelections[]");
469 }
470
471 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(ImplCycleCollectionTraverse(cb, tmp->mTableSelection.mClosestInclusiveTableCellAncestor
, "mTableSelection.mClosestInclusiveTableCellAncestor", 0);
472 mTableSelection.mClosestInclusiveTableCellAncestor)ImplCycleCollectionTraverse(cb, tmp->mTableSelection.mClosestInclusiveTableCellAncestor
, "mTableSelection.mClosestInclusiveTableCellAncestor", 0);
473 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mStartSelectedCell)ImplCycleCollectionTraverse(cb, tmp->mTableSelection.mStartSelectedCell
, "mTableSelection.mStartSelectedCell", 0);
474 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mEndSelectedCell)ImplCycleCollectionTraverse(cb, tmp->mTableSelection.mEndSelectedCell
, "mTableSelection.mEndSelectedCell", 0);
475 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mAppendStartSelectedCell)ImplCycleCollectionTraverse(cb, tmp->mTableSelection.mAppendStartSelectedCell
, "mTableSelection.mAppendStartSelectedCell", 0);
476 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTableSelection.mUnselectCellOnMouseUp)ImplCycleCollectionTraverse(cb, tmp->mTableSelection.mUnselectCellOnMouseUp
, "mTableSelection.mUnselectCellOnMouseUp", 0);
477 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainedRange.mRange)ImplCycleCollectionTraverse(cb, tmp->mMaintainedRange.mRange
, "mMaintainedRange.mRange", 0);
478 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters.mIndependentSelectionRootElement)ImplCycleCollectionTraverse(cb, tmp->mLimiters.mIndependentSelectionRootElement
, "mLimiters.mIndependentSelectionRootElement", 0);
479 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiters.mAncestorLimiter)ImplCycleCollectionTraverse(cb, tmp->mLimiters.mAncestorLimiter
, "mLimiters.mAncestorLimiter", 0);
480NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END(void)tmp; return NS_OK; }
481
482nsISelectionController* nsFrameSelection::GetSelectionController() const {
483 if (IsIndependentSelection()) {
484 auto* const textControlElement = TextControlElement::FromNodeOrNull(
485 GetIndependentSelectionRootParentElement());
486 return textControlElement ? textControlElement->GetSelectionController()
487 : nullptr;
488 }
489 return mPresShell;
490}
491
492// static
493void nsFrameSelection::WillFocusDocument(PresShell& aPresShell,
494 Document& aDocument) {
495 const RefPtr<nsFrameSelection> selection =
496 aPresShell.GetLastFocusedFrameSelection();
497 if (!selection) [[unlikely]] {
498 return;
499 }
500 const int16_t selectionStatus = selection->GetDisplaySelection();
501 // If selection was disabled, re-enable it.
502 if (selectionStatus == nsISelectionController::SELECTION_DISABLED ||
503 selectionStatus == nsISelectionController::SELECTION_HIDDEN) {
504 selection->SetDisplaySelection(nsISelectionController::SELECTION_ON);
505 selection->RepaintSelection(SelectionType::eNormal);
506 }
507 // See EditorBase::FinalizeSelection. This fixes up the case where focus
508 // left the editor's selection but returned to something else.
509 if (selection != aPresShell.ConstFrameSelection()) {
510 const bool selectionMatchesFocus =
511 selection->IsIndependentSelection() &&
512 selection->GetIndependentSelectionRootParentElement() ==
513 aDocument.GetUnretargetedFocusedContent();
514 if (NS_WARN_IF(!selectionMatchesFocus)NS_warn_if_impl(!selectionMatchesFocus, "!selectionMatchesFocus"
, "./../../../layout/generic/nsFrameSelection.cpp", 514)
) {
515 aPresShell.FrameSelectionWillLoseFocus(*selection);
516 aPresShell.SelectionWillTakeFocus();
517 }
518 }
519}
520
521// static
522void nsFrameSelection::WillBlurDocument(PresShell& aPresShell,
523 Document& aDocument) {
524 nsFrameSelection* const selection = aPresShell.GetLastFocusedFrameSelection();
525 if (!selection) [[unlikely]] {
526 return;
527 }
528 const int16_t selectionStatus = selection->GetDisplaySelection();
529 // If selection was on, disable it.
530 if (selectionStatus == nsISelectionController::SELECTION_ON ||
531 selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
532 selection->SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
533 selection->RepaintSelection(SelectionType::eNormal);
534 }
535}
536
537// static
538bool nsFrameSelection::Caret::IsVisualMovement(
539 ExtendSelection aExtendSelection, CaretMovementStyle aMovementStyle) {
540 int32_t movementFlag = StaticPrefs::bidi_edit_caret_movement_style();
541 return aMovementStyle == eVisual ||
542 (aMovementStyle == eUsePrefStyle &&
543 (movementFlag == 1 ||
544 (movementFlag == 2 && aExtendSelection == ExtendSelection::No)));
545}
546
547// Get the x (or y, in vertical writing mode) position requested
548// by the Key Handling for line-up/down
549nsresult nsFrameSelection::DesiredCaretPos::FetchPos(
550 nsPoint& aDesiredCaretPos, const PresShell& aPresShell,
551 Selection& aNormalSelection) const {
552 MOZ_ASSERT(aNormalSelection.GetType() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.GetType() == SelectionType::eNormal
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.GetType() == SelectionType::eNormal
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aNormalSelection.GetType() == SelectionType::eNormal", "./../../../layout/generic/nsFrameSelection.cpp"
, 552); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aNormalSelection.GetType() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 552); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
553
554 if (mIsSet) {
555 aDesiredCaretPos = mValue;
556 return NS_OK;
557 }
558
559 RefPtr<nsCaret> caret = aPresShell.GetActiveCaret();
560 if (!caret) {
561 return NS_ERROR_NULL_POINTER;
562 }
563
564 caret->SetSelection(&aNormalSelection);
565
566 nsRect coord;
567 nsIFrame* caretFrame = caret->GetGeometry(&coord);
568 if (!caretFrame) {
569 return NS_ERROR_FAILURE;
570 }
571 coord += caretFrame->GetOffsetToRootFrame();
572 aDesiredCaretPos = coord.TopLeft();
573 return NS_OK;
574}
575
576void nsFrameSelection::InvalidateDesiredCaretPos() // do not listen to
577 // mDesiredCaretPos.mValue;
578 // you must get another.
579{
580 mDesiredCaretPos.Invalidate();
581}
582
583void nsFrameSelection::DesiredCaretPos::Invalidate() { mIsSet = false; }
584
585void nsFrameSelection::DesiredCaretPos::Set(const nsPoint& aPos) {
586 mValue = aPos;
587 mIsSet = true;
588}
589
590nsresult nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
591 nsIFrame* aFrame, const nsPoint& aPoint, nsIFrame** aRetFrame,
592 nsPoint& aRetPoint) const {
593 //
594 // The whole point of this method is to return a frame and point that
595 // that lie within the same valid subtree as the anchor node's frame,
596 // for use with the method GetContentAndOffsetsFromPoint().
597 //
598 // A valid subtree is defined to be one where all the content nodes in
599 // the tree have a valid parent-child relationship.
600 //
601 // If the anchor frame and aFrame are in the same subtree, aFrame will
602 // be returned in aRetFrame. If they are in different subtrees, we
603 // return the frame for the root of the subtree.
604 //
605
606 if (!aFrame || !aRetFrame) {
607 return NS_ERROR_NULL_POINTER;
608 }
609
610 *aRetFrame = aFrame;
611 aRetPoint = aPoint;
612
613 //
614 // Get the frame and content for the selection's anchor point!
615 //
616
617 const Selection& sel = NormalSelection();
618
619 nsCOMPtr<nsIContent> anchorContent =
620 nsIContent::FromNodeOrNull(sel.GetMayCrossShadowBoundaryAnchorNode());
621 if (!anchorContent) {
622 return NS_ERROR_FAILURE;
623 }
624
625 //
626 // Now find the root of the subtree containing the anchor's content.
627 //
628
629 NS_ENSURE_STATE(mPresShell)do { if ((__builtin_expect(!!(!(mPresShell)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "mPresShell" ") failed",
nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 629
); return NS_ERROR_UNEXPECTED; } } while (false)
;
630 RefPtr<PresShell> presShell = mPresShell;
631 nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(
632 presShell, nsINode::IgnoreOwnIndependentSelection::Yes,
633 nsINode::AllowCrossShadowBoundary::Yes);
634 NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED)do { if ((__builtin_expect(!!(!(anchorRoot)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "anchorRoot" ") failed",
nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 634
); return NS_ERROR_UNEXPECTED; } } while (false)
;
635
636 //
637 // Now find the root of the subtree containing aFrame's content.
638 //
639
640 nsCOMPtr<nsIContent> content = aFrame->GetContent();
641
642 if (content) {
643 nsIContent* contentRoot = content->GetSelectionRootContent(
644 presShell, nsINode::IgnoreOwnIndependentSelection::Yes,
645 nsINode::AllowCrossShadowBoundary::Yes);
646 NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED)do { if ((__builtin_expect(!!(!(contentRoot)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "contentRoot" ") failed"
, nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 646
); return NS_ERROR_UNEXPECTED; } } while (false)
;
647
648 if (anchorRoot == contentRoot) {
649 // If the aFrame's content isn't the capturing content, it should be
650 // a descendant. At this time, we can return simply.
651 nsIContent* capturedContent = PresShell::GetCapturingContent();
652 if (capturedContent != content) {
653 return NS_OK;
654 }
655
656 // Find the frame under the mouse cursor with the root frame.
657 // At this time, don't use the anchor's frame because it may not have
658 // fixed positioned frames.
659 nsIFrame* rootFrame = presShell->GetRootFrame();
660 nsPoint ptInRoot = aPoint + aFrame->GetOffsetTo(rootFrame);
661 nsIFrame* cursorFrame =
662 nsLayoutUtils::GetFrameForPoint(RelativeTo{rootFrame}, ptInRoot);
663
664 // If the mouse cursor in on a frame which is descendant of same
665 // selection root, we can expand the selection to the frame.
666 if (cursorFrame && cursorFrame->PresShell() == presShell) {
667 nsCOMPtr<nsIContent> cursorContent = cursorFrame->GetContent();
668 NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE)do { if ((__builtin_expect(!!(!(cursorContent)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "cursorContent" ") failed"
, nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 668
); return NS_ERROR_FAILURE; } } while (false)
;
669 nsIContent* cursorContentRoot = cursorContent->GetSelectionRootContent(
670 presShell, nsINode::IgnoreOwnIndependentSelection::Yes,
671 nsINode::AllowCrossShadowBoundary::Yes);
672 NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED)do { if ((__builtin_expect(!!(!(cursorContentRoot)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "cursorContentRoot" ") failed"
, nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 672
); return NS_ERROR_UNEXPECTED; } } while (false)
;
673 if (cursorContentRoot == anchorRoot) {
674 *aRetFrame = cursorFrame;
675 aRetPoint = aPoint + aFrame->GetOffsetTo(cursorFrame);
676 return NS_OK;
677 }
678 }
679 // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
680 // cursor is out of the window), we should use the frame of the anchor
681 // root.
682 }
683 }
684
685 //
686 // When we can't find a frame which is under the mouse cursor and has a same
687 // selection root as the anchor node's, we should return the selection root
688 // frame.
689 //
690
691 *aRetFrame = anchorRoot->GetPrimaryFrame();
692
693 if (!*aRetFrame) {
694 return NS_ERROR_FAILURE;
695 }
696
697 //
698 // Now make sure that aRetPoint is converted to the same coordinate
699 // system used by aRetFrame.
700 //
701
702 aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame);
703
704 return NS_OK;
705}
706
707void nsFrameSelection::SetCaretBidiLevelAndMaybeSchedulePaint(
708 mozilla::intl::BidiEmbeddingLevel aLevel) {
709 // If the current level is undefined, we have just inserted new text.
710 // In this case, we don't want to reset the keyboard language
711 mCaret.mBidiLevel = aLevel;
712
713 RefPtr<nsCaret> caret;
714 if (mPresShell && (caret = mPresShell->GetActiveCaret())) {
715 caret->SchedulePaint();
716 }
717}
718
719mozilla::intl::BidiEmbeddingLevel nsFrameSelection::GetCaretBidiLevel() const {
720 return mCaret.mBidiLevel;
721}
722
723void nsFrameSelection::UndefineCaretBidiLevel() {
724 mCaret.mBidiLevel = mozilla::intl::BidiEmbeddingLevel(mCaret.mBidiLevel |
725 BIDI_LEVEL_UNDEFINEDmozilla::intl::BidiEmbeddingLevel(0x80));
726}
727
728#ifdef PRINT_RANGE
729void printRange(nsRange* aDomRange) {
730 if (!aDomRange) {
731 printf("NULL Range\n");
732 }
733 nsINode* startNode = aDomRange->GetStartContainer();
734 nsINode* endNode = aDomRange->GetEndContainer();
735 int32_t startOffset = aDomRange->StartOffset();
736 int32_t endOffset = aDomRange->EndOffset();
737
738 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
739 (unsigned long)aDomRange, (unsigned long)startNode, (long)startOffset,
740 (unsigned long)endNode, (long)endOffset);
741}
742#endif /* PRINT_RANGE */
743
744static nsAtom* GetTag(nsINode* aNode) {
745 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
746 if (!content) {
747 MOZ_ASSERT_UNREACHABLE("bad node passed to GetTag()")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"bad node passed to GetTag()" ")", "./../../../layout/generic/nsFrameSelection.cpp"
, 747); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "bad node passed to GetTag()" ")"
); do { MOZ_CrashSequence(__null, 747); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
748 return nullptr;
749 }
750
751 return content->NodeInfo()->NameAtom();
752}
753
754/**
755 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
756 */
757static nsINode* GetClosestInclusiveTableCellAncestor(nsINode* aDomNode) {
758 if (!aDomNode) {
759 return nullptr;
760 }
761 nsINode* current = aDomNode;
762 // Start with current node and look for a table cell
763 while (current) {
764 nsAtom* tag = GetTag(current);
765 if (tag == nsGkAtoms::td || tag == nsGkAtoms::th) {
766 return current;
767 }
768 current = current->GetParent();
769 }
770 return nullptr;
771}
772
773static nsDirection GetCaretDirection(const nsIFrame& aFrame,
774 nsDirection aDirection,
775 bool aVisualMovement) {
776 const mozilla::intl::BidiDirection paragraphDirection =
777 nsBidiPresUtils::ParagraphDirection(&aFrame);
778 return (aVisualMovement &&
779 paragraphDirection == mozilla::intl::BidiDirection::RTL)
780 ? nsDirection(1 - aDirection)
781 : aDirection;
782}
783
784nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
785 ExtendSelection aExtendSelection,
786 const nsSelectionAmount aAmount,
787 CaretMovementStyle aMovementStyle) {
788 NS_ENSURE_STATE(mPresShell)do { if ((__builtin_expect(!!(!(mPresShell)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "mPresShell" ") failed",
nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 788
); return NS_ERROR_UNEXPECTED; } } while (false)
;
789 // Flush out layout, since we need it to be up to date to do caret
790 // positioning.
791 OwningNonNull<PresShell> presShell(*mPresShell);
792 presShell->FlushPendingNotifications(FlushType::Layout);
793
794 if (!mPresShell) {
795 return NS_OK;
796 }
797
798 nsPresContext* context = mPresShell->GetPresContext();
799 if (!context) {
800 return NS_ERROR_FAILURE;
801 }
802
803 const RefPtr<Selection> sel = &NormalSelection();
804 if (const nsRange* anchorFocusRange = sel->GetAnchorFocusRange()) {
805 if (NS_WARN_IF(!anchorFocusRange->IsPositioned())NS_warn_if_impl(!anchorFocusRange->IsPositioned(), "!anchorFocusRange->IsPositioned()"
, "./../../../layout/generic/nsFrameSelection.cpp", 805)
) {
806 return NS_ERROR_FAILURE;
807 }
808 // If the selection range to be modified is outside the limiters, we should
809 // not touch it.
810 if (!mLimiters.RangeInLimiters(*anchorFocusRange)) [[unlikely]] {
811 // We don't want the caller to fall the per line move back to a complete
812 // move in this case. So, let's return "did nothing".
813 return NS_SUCCESS_DOM_NO_OPERATION;
814 }
815 } else {
816 // No range to modify.
817 return NS_ERROR_FAILURE;
818 }
819
820 auto scrollFlags = ScrollFlags::None;
821 if (sel->IsEditorSelection()) {
822 // If caret moves in editor, it should cause scrolling even if it's in
823 // overflow: hidden;.
824 scrollFlags |= ScrollFlags::ScrollOverflowHidden;
825 }
826
827 const bool doCollapse = [&] {
828 if (sel->IsCollapsed() || aExtendSelection == ExtendSelection::Yes) {
829 return false;
830 }
831 if (aAmount > eSelectLine) {
832 return false;
833 }
834 int32_t caretStyle = StaticPrefs::layout_selection_caret_style();
835 return caretStyle == 2 || (caretStyle == 0 && aAmount != eSelectLine);
836 }();
837
838 if (doCollapse) {
839 if (aDirection == eDirPrevious) {
840 SetChangeReasons(nsISelectionListener::COLLAPSETOSTART_REASON);
841 mCaret.mHint = CaretAssociationHint::After;
842 } else {
843 SetChangeReasons(nsISelectionListener::COLLAPSETOEND_REASON);
844 mCaret.mHint = CaretAssociationHint::Before;
845 }
846 } else {
847 SetChangeReasons(nsISelectionListener::KEYPRESS_REASON);
848 }
849
850 mCaretMoveAmount = aAmount;
851
852 AutoPrepareFocusRange prep(sel, false);
853
854 // we must keep this around and revalidate it when its just UP/DOWN
855 nsPoint desiredPos(0, 0);
856
857 if (aAmount == eSelectLine) {
858 nsresult result = mDesiredCaretPos.FetchPos(desiredPos, *mPresShell, *sel);
859 if (NS_FAILED(result)((bool)(__builtin_expect(!!(NS_FAILED_impl(result)), 0)))) {
860 return result;
861 }
862 mDesiredCaretPos.Set(desiredPos);
863 }
864
865 bool visualMovement =
866 Caret::IsVisualMovement(aExtendSelection, aMovementStyle);
867 const PrimaryFrameData frameForFocus =
868 sel->GetPrimaryFrameForCaretAtFocusNode(visualMovement);
869 if (!frameForFocus) {
870 return NS_ERROR_FAILURE;
871 }
872 if (visualMovement) {
873 // FYI: This was done during a call of GetPrimaryFrameForCaretAtFocusNode.
874 // Therefore, this may not be intended by the original author.
875 SetHint(frameForFocus.mHint);
876 }
877
878 Result<bool, nsresult> isIntraLineCaretMove =
879 SelectionMovementUtils::IsIntraLineCaretMove(aAmount);
880 nsDirection direction{aDirection};
881 if (isIntraLineCaretMove.isErr()) {
882 return isIntraLineCaretMove.unwrapErr();
883 }
884 if (isIntraLineCaretMove.inspect()) {
885 // Forget old caret position for moving caret to different line since
886 // caret position may be changed.
887 mDesiredCaretPos.Invalidate();
888 direction =
889 GetCaretDirection(*frameForFocus.mFrame, aDirection, visualMovement);
890 }
891
892 if (doCollapse) {
893 const nsRange* anchorFocusRange = sel->GetAnchorFocusRange();
894 if (anchorFocusRange) {
895 RefPtr<nsINode> node;
896 uint32_t offset;
897 if (visualMovement &&
898 nsBidiPresUtils::IsReversedDirectionFrame(frameForFocus.mFrame)) {
899 direction = nsDirection(1 - direction);
900 }
901 if (direction == eDirPrevious) {
902 node = anchorFocusRange->GetStartContainer();
903 offset = anchorFocusRange->StartOffset();
904 } else {
905 node = anchorFocusRange->GetEndContainer();
906 offset = anchorFocusRange->EndOffset();
907 }
908 sel->CollapseInLimiter(node, offset);
909 }
910 sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
911 AxisScrollParams(), AxisScrollParams(), scrollFlags);
912 return NS_OK;
913 }
914
915 CaretAssociationHint tHint(
Value stored to 'tHint' during its initialization is never read
916 mCaret.mHint); // temporary variable so we dont set
917 // mCaret.mHint until it is necessary
918
919 Result<PeekOffsetOptions, nsresult> options =
920 CreatePeekOffsetOptionsForCaretMove(sel, aExtendSelection,
921 aMovementStyle);
922 if (options.isErr()) {
923 return options.propagateErr();
924 }
925 Result<const dom::Element*, nsresult> ancestorLimiter =
926 GetAncestorLimiterForCaretMove(sel);
927 if (ancestorLimiter.isErr()) {
928 return ancestorLimiter.propagateErr();
929 }
930 nsIContent* content = nsIContent::FromNodeOrNull(sel->GetFocusNode());
931
932 Result<PeekOffsetStruct, nsresult> result =
933 SelectionMovementUtils::PeekOffsetForCaretMove(
934 content, sel->FocusOffset(), direction, GetHint(),
935 GetCaretBidiLevel(), aAmount, desiredPos, options.unwrap(),
936 ancestorLimiter.unwrap());
937 nsresult rv;
938 if (result.isOk() && result.inspect().mResultContent) {
939 const PeekOffsetStruct& pos = result.inspect();
940 nsIFrame* theFrame;
941 int32_t frameStart, frameEnd;
942
943 if (aAmount <= eSelectWordNoSpace) {
944 // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does
945 // not set pos.mAttachForward, so determine the hint here based on the
946 // result frame and offset: If we're at the end of a text frame, set the
947 // hint to ASSOCIATE_BEFORE to indicate that we want the caret displayed
948 // at the end of this frame, not at the beginning of the next one.
949 theFrame = pos.mResultFrame;
950 std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
951 if (frameEnd == pos.mContentOffset &&
952 !(frameStart == 0 && frameEnd == 0)) {
953 tHint = CaretAssociationHint::Before;
954 } else {
955 tHint = CaretAssociationHint::After;
956 }
957 } else {
958 // For up/down and home/end, pos.mResultFrame might not be set correctly,
959 // or not at all. In these cases, get the frame based on the content and
960 // hint returned by PeekOffset().
961 tHint = pos.mAttach;
962 theFrame = SelectionMovementUtils::GetFrameForNodeOffset(
963 pos.mResultContent, pos.mContentOffset, tHint);
964 if (!theFrame) {
965 return NS_ERROR_FAILURE;
966 }
967
968 std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
969 }
970
971 if (context->BidiEnabled()) {
972 switch (aAmount) {
973 case eSelectBeginLine:
974 case eSelectEndLine: {
975 // In Bidi contexts, PeekOffset calculates pos.mContentOffset
976 // differently depending on whether the movement is visual or logical.
977 // For visual movement, pos.mContentOffset depends on the direction-
978 // ality of the first/last frame on the line (theFrame), and the caret
979 // directionality must correspond.
980 FrameBidiData bidiData = theFrame->GetBidiData();
981 SetCaretBidiLevelAndMaybeSchedulePaint(
982 visualMovement ? bidiData.embeddingLevel : bidiData.baseLevel);
983 break;
984 }
985 default:
986 // If the current position is not a frame boundary, it's enough just
987 // to take the Bidi level of the current frame
988 if ((pos.mContentOffset != frameStart &&
989 pos.mContentOffset != frameEnd) ||
990 eSelectLine == aAmount) {
991 SetCaretBidiLevelAndMaybeSchedulePaint(
992 theFrame->GetEmbeddingLevel());
993 } else {
994 BidiLevelFromMove(mPresShell, pos.mResultContent,
995 pos.mContentOffset, aAmount, tHint);
996 }
997 }
998 }
999 // "pos" is on the stack, so pos.mResultContent has stack lifetime, so using
1000 // MOZ_KnownLive is ok.
1001 const FocusMode focusMode = aExtendSelection == ExtendSelection::Yes
1002 ? FocusMode::kExtendSelection
1003 : FocusMode::kCollapseToNewPoint;
1004 rv = TakeFocus(MOZ_KnownLive(*pos.mResultContent)(*pos.mResultContent), pos.mContentOffset,
1005 pos.mContentOffset, tHint, focusMode);
1006 } else if (aAmount <= eSelectWordNoSpace && direction == eDirNext &&
1007 aExtendSelection == ExtendSelection::No) {
1008 // Collapse selection if PeekOffset failed, we either
1009 // 1. bumped into the BRFrame, bug 207623
1010 // 2. had select-all in a text input (DIV range), bug 352759.
1011 bool isBRFrame = frameForFocus.mFrame->IsBrFrame();
1012 RefPtr<nsINode> node = sel->GetFocusNode();
1013 sel->CollapseInLimiter(node, sel->FocusOffset());
1014 // Note: 'frameForFocus.mFrame' might be dead here.
1015 if (!isBRFrame) {
1016 mCaret.mHint = CaretAssociationHint::Before; // We're now at the end of
1017 // the frame to the left.
1018 }
1019 rv = NS_OK;
1020 } else {
1021 rv = result.isErr() ? result.unwrapErr() : NS_OK;
1022 }
1023 if (NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1)))) {
1024 rv = sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
1025 AxisScrollParams(), AxisScrollParams(),
1026 scrollFlags);
1027 }
1028
1029 return rv;
1030}
1031
1032// static
1033Result<PeekOffsetOptions, nsresult>
1034nsFrameSelection::CreatePeekOffsetOptionsForCaretMove(
1035 const Element* aSelectionLimiter, ForceEditableRegion aForceEditableRegion,
1036 ExtendSelection aExtendSelection, CaretMovementStyle aMovementStyle) {
1037 PeekOffsetOptions options;
1038 // set data using aSelectionLimiter to stop on scroll views. If we have a
1039 // limiter then we stop peeking when we hit scrollable views. If no limiter
1040 // then just let it go ahead
1041 if (aSelectionLimiter) {
1042 options += PeekOffsetOption::StopAtScroller;
1043 }
1044 const bool visualMovement =
1045 Caret::IsVisualMovement(aExtendSelection, aMovementStyle);
1046 if (visualMovement) {
1047 options += PeekOffsetOption::Visual;
1048 }
1049 if (aExtendSelection == ExtendSelection::Yes) {
1050 options += PeekOffsetOption::Extend;
1051 }
1052 if (static_cast<bool>(aForceEditableRegion)) {
1053 options += PeekOffsetOption::ForceEditableRegion;
1054 }
1055 // A caret move wants paragraph peeking to resolve to a leaf frame at a block
1056 // boundary so the caret has a precise text position for scrolling-into-view
1057 // (bug 2041228). Selection paths build their own options and intentionally
1058 // omit this so the block container remains the boundary.
1059 options += PeekOffsetOption::ForCaretMove;
1060 return options;
1061}
1062
1063Result<Element*, nsresult> nsFrameSelection::GetAncestorLimiterForCaretMove(
1064 dom::Selection* aSelection) const {
1065 if (!mPresShell) {
1066 return Err(NS_ERROR_NULL_POINTER);
1067 }
1068
1069 MOZ_ASSERT(aSelection)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aSelection)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aSelection))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("aSelection", "./../../../layout/generic/nsFrameSelection.cpp"
, 1069); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aSelection"
")"); do { MOZ_CrashSequence(__null, 1069); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1070 nsIContent* content = nsIContent::FromNodeOrNull(aSelection->GetFocusNode());
1071 if (!content) {
1072 return Err(NS_ERROR_FAILURE);
1073 }
1074
1075 MOZ_ASSERT(mPresShell->GetDocument() == content->GetComposedDoc())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mPresShell->GetDocument() == content->GetComposedDoc
())>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(mPresShell->GetDocument() == content->GetComposedDoc
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("mPresShell->GetDocument() == content->GetComposedDoc()"
, "./../../../layout/generic/nsFrameSelection.cpp", 1075); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mPresShell->GetDocument() == content->GetComposedDoc()"
")"); do { MOZ_CrashSequence(__null, 1075); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1076
1077 Element* ancestorLimiter = GetAncestorLimiter();
1078 if (aSelection->IsEditorSelection()) {
1079 // If the editor has not receive `focus` event, it may have not set ancestor
1080 // limiter. Then, we need to compute it here for the caret move.
1081 if (!ancestorLimiter) {
1082 // Editing hosts can be nested. Therefore, computing selection root from
1083 // selection range may be different from the focused editing host.
1084 // Therefore, we may need to use a non-closest inclusive ancestor editing
1085 // host of selection range container. On the other hand, selection ranges
1086 // may be outside of focused editing host. In such case, we should use
1087 // the closest editing host as the ancestor limiter instead.
1088 PresShell* const presShell = aSelection->GetPresShell();
1089 const Document* const doc =
1090 presShell ? presShell->GetDocument() : nullptr;
1091 if (const nsPIDOMWindowInner* const win =
1092 doc ? doc->GetInnerWindow() : nullptr) {
1093 Element* const focusedElement = win->GetFocusedElement();
1094 Element* closestEditingHost = nullptr;
1095 for (Element* element : content->InclusiveAncestorsOfType<Element>()) {
1096 if (element->IsEditingHost()) {
1097 if (!closestEditingHost) {
1098 closestEditingHost = element;
1099 }
1100 if (focusedElement == element) {
1101 ancestorLimiter = focusedElement;
1102 break;
1103 }
1104 }
1105 }
1106 if (!ancestorLimiter) {
1107 ancestorLimiter = closestEditingHost;
1108 }
1109 }
1110 // If it's the root element, we don't need to limit the new caret
1111 // position.
1112 if (ancestorLimiter && !ancestorLimiter->GetParent()) {
1113 ancestorLimiter = nullptr;
1114 }
1115 }
1116 }
1117 return ancestorLimiter;
1118}
1119
1120nsPrevNextBidiLevels nsFrameSelection::GetPrevNextBidiLevels(
1121 nsIContent* aNode, uint32_t aContentOffset, bool aJumpLines) const {
1122 return SelectionMovementUtils::GetPrevNextBidiLevels(
1123 aNode, aContentOffset, mCaret.mHint, aJumpLines,
1124 GetAncestorLimiterOrIndependentSelectionRootElement());
1125}
1126
1127nsresult nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount) {
1128 const Selection& sel = NormalSelection();
1129
1130 mMaintainedRange.MaintainAnchorFocusRange(sel, aAmount);
1131
1132 return NS_OK;
1133}
1134
1135void nsFrameSelection::BidiLevelFromMove(PresShell* aPresShell,
1136 nsIContent* aNode,
1137 uint32_t aContentOffset,
1138 nsSelectionAmount aAmount,
1139 CaretAssociationHint aHint) {
1140 switch (aAmount) {
1141 // Movement within the line: the new cursor Bidi level is the level of the
1142 // last character moved over
1143 case eSelectCharacter:
1144 case eSelectCluster:
1145 case eSelectWord:
1146 case eSelectWordNoSpace:
1147 case eSelectBeginLine:
1148 case eSelectEndLine:
1149 case eSelectNoAmount: {
1150 nsPrevNextBidiLevels levels =
1151 SelectionMovementUtils::GetPrevNextBidiLevels(
1152 aNode, aContentOffset, aHint, false,
1153 GetAncestorLimiterOrIndependentSelectionRootElement());
1154
1155 SetCaretBidiLevelAndMaybeSchedulePaint(
1156 aHint == CaretAssociationHint::Before ? levels.mLevelBefore
1157 : levels.mLevelAfter);
1158 break;
1159 }
1160 /*
1161 // Up and Down: the new cursor Bidi level is the smaller of the two
1162 surrounding characters case eSelectLine: case eSelectParagraph:
1163 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame,
1164 &secondFrame, &firstLevel, &secondLevel);
1165 aPresShell->SetCaretBidiLevelAndMaybeSchedulePaint(std::min(firstLevel,
1166 secondLevel)); break;
1167 */
1168
1169 default:
1170 UndefineCaretBidiLevel();
1171 }
1172}
1173
1174void nsFrameSelection::BidiLevelFromClick(nsIContent* aNode,
1175 uint32_t aContentOffset) {
1176 nsIFrame* clickInFrame = SelectionMovementUtils::GetFrameForNodeOffset(
1177 aNode, aContentOffset, mCaret.mHint);
1178 if (!clickInFrame) {
1179 return;
1180 }
1181
1182 SetCaretBidiLevelAndMaybeSchedulePaint(clickInFrame->GetEmbeddingLevel());
1183}
1184
1185void nsFrameSelection::MaintainedRange::AdjustNormalSelection(
1186 const nsIContent* aContent, const int32_t aOffset,
1187 Selection& aNormalSelection) const {
1188 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 1188); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 1188); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1189
1190 if (!mRange || !aContent) {
1191 return;
1192 }
1193
1194 nsINode* rangeStartNode = mRange->GetStartContainer();
1195 nsINode* rangeEndNode = mRange->GetEndContainer();
1196 const uint32_t rangeStartOffset = mRange->StartOffset();
1197 const uint32_t rangeEndOffset = mRange->EndOffset();
1198
1199 NS_ASSERTION(aOffset >= 0, "aOffset should not be negative")do { if (!(aOffset >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "aOffset should not be negative", "aOffset >= 0", "./../../../layout/generic/nsFrameSelection.cpp"
, 1199); MOZ_PretendNoReturn(); } } while (0)
;
1200 const Maybe<int32_t> relToStart =
1201 nsContentUtils::ComparePoints_AllowNegativeOffsets<
1202 TreeKind::ShadowIncludingDOM>(rangeStartNode, rangeStartOffset,
1203 aContent, aOffset);
1204 if (NS_WARN_IF(!relToStart)NS_warn_if_impl(!relToStart, "!relToStart", "./../../../layout/generic/nsFrameSelection.cpp"
, 1204)
) {
1205 // Potentially handle this properly when Selection across Shadow DOM
1206 // boundary is implemented
1207 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1208 return;
1209 }
1210
1211 const Maybe<int32_t> relToEnd =
1212 nsContentUtils::ComparePoints_AllowNegativeOffsets<
1213 TreeKind::ShadowIncludingDOM>(rangeEndNode, rangeEndOffset, aContent,
1214 aOffset);
1215 if (NS_WARN_IF(!relToEnd)NS_warn_if_impl(!relToEnd, "!relToEnd", "./../../../layout/generic/nsFrameSelection.cpp"
, 1215)
) {
1216 // Potentially handle this properly when Selection across Shadow DOM
1217 // boundary is implemented
1218 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1219 return;
1220 }
1221
1222 // If aContent/aOffset is inside (or at the edge of) the maintained
1223 // selection, or if it is on the "anchor" side of the maintained selection,
1224 // we need to do something.
1225 if ((*relToStart <= 0 && *relToEnd >= 0) ||
1226 (*relToStart > 0 && aNormalSelection.GetDirection() == eDirNext) ||
1227 (*relToEnd < 0 && aNormalSelection.GetDirection() == eDirPrevious)) {
1228 // Set the current range to the maintained range.
1229 aNormalSelection.ReplaceAnchorFocusRange(mRange);
1230 // Set the direction of the selection so that the anchor will be on the
1231 // far side of the maintained selection, relative to aContent/aOffset.
1232 aNormalSelection.SetDirection(*relToStart > 0 ? eDirPrevious : eDirNext);
1233 }
1234}
1235
1236void nsFrameSelection::MaintainedRange::AdjustContentOffsets(
1237 nsIFrame::ContentOffsets& aOffsets, StopAtScroller aStopAtScroller) const {
1238 // Adjust offsets according to maintained amount
1239 if (mRange && mAmount != eSelectNoAmount) {
1240 const Maybe<int32_t> relativePosition =
1241 nsContentUtils::ComparePoints<TreeKind::ShadowIncludingDOM>(
1242 mRange->StartRef(),
1243 RawRangeBoundary(aOffsets.content, aOffsets.offset,
1244 RangeBoundarySetBy::Offset));
1245 if (NS_WARN_IF(!relativePosition)NS_warn_if_impl(!relativePosition, "!relativePosition", "./../../../layout/generic/nsFrameSelection.cpp"
, 1245)
) {
1246 // Potentially handle this properly when Selection across Shadow DOM
1247 // boundary is implemented
1248 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
1249 return;
1250 }
1251
1252 nsDirection direction = *relativePosition > 0 ? eDirPrevious : eDirNext;
1253 nsSelectionAmount amount = mAmount;
1254 if (amount == eSelectBeginLine && direction == eDirNext) {
1255 amount = eSelectEndLine;
1256 }
1257
1258 FrameAndOffset frameAndOffset =
1259 SelectionMovementUtils::GetFrameForNodeOffset(
1260 aOffsets.content, aOffsets.offset, CaretAssociationHint::After);
1261
1262 PeekOffsetOptions peekOffsetOptions{};
1263 if (aStopAtScroller == StopAtScroller::Yes) {
1264 peekOffsetOptions += PeekOffsetOption::StopAtScroller;
1265 }
1266 if (frameAndOffset && amount == eSelectWord && direction == eDirPrevious) {
1267 // To avoid selecting the previous word when at start of word,
1268 // first move one character forward.
1269 PeekOffsetStruct charPos(
1270 eSelectCharacter, eDirNext,
1271 static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent),
1272 nsPoint(0, 0), peekOffsetOptions);
1273 if (NS_SUCCEEDED(frameAndOffset->PeekOffset(&charPos))((bool)(__builtin_expect(!!(!NS_FAILED_impl(frameAndOffset->
PeekOffset(&charPos))), 1)))
) {
1274 frameAndOffset = {charPos.mResultFrame,
1275 static_cast<uint32_t>(charPos.mContentOffset)};
1276 }
1277 }
1278
1279 PeekOffsetStruct pos(
1280 amount, direction,
1281 static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent),
1282 nsPoint(0, 0), peekOffsetOptions);
1283 if (frameAndOffset && NS_SUCCEEDED(frameAndOffset->PeekOffset(&pos))((bool)(__builtin_expect(!!(!NS_FAILED_impl(frameAndOffset->
PeekOffset(&pos))), 1)))
&&
1284 pos.mResultContent) {
1285 aOffsets.content = pos.mResultContent;
1286 aOffsets.offset = pos.mContentOffset;
1287 }
1288 }
1289}
1290
1291void nsFrameSelection::MaintainedRange::MaintainAnchorFocusRange(
1292 const Selection& aNormalSelection, const nsSelectionAmount aAmount) {
1293 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 1293); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 1293); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1294
1295 mAmount = aAmount;
1296
1297 const nsRange* anchorFocusRange = aNormalSelection.GetAnchorFocusRange();
1298 if (anchorFocusRange && aAmount != eSelectNoAmount) {
1299 mRange = anchorFocusRange->CloneRange();
1300 return;
1301 }
1302
1303 mRange = nullptr;
1304}
1305
1306nsresult nsFrameSelection::HandleClick(nsIContent* aNewFocus,
1307 uint32_t aContentOffset,
1308 uint32_t aContentEndOffset,
1309 const FocusMode aFocusMode,
1310 CaretAssociationHint aHint) {
1311 if (!aNewFocus) {
1312 return NS_ERROR_INVALID_ARG;
1313 }
1314
1315 if (MOZ_LOG_TEST(sFrameSelectionLog, LogLevel::Debug)(__builtin_expect(!!(mozilla::detail::log_test(sFrameSelectionLog
, LogLevel::Debug)), 0))
) {
1316 const Selection& sel = NormalSelection();
1317 MOZ_LOG(sFrameSelectionLog, LogLevel::Debug,do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Debug)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Debug, "%s: selection=%p, new focus=%p, offsets=(%u,%u), focus mode=%i"
, __FUNCTION__, &sel, aNewFocus, aContentOffset, aContentEndOffset
, static_cast<int>(aFocusMode)); } } while (0)
1318 ("%s: selection=%p, new focus=%p, offsets=(%u,%u), focus mode=%i",do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Debug)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Debug, "%s: selection=%p, new focus=%p, offsets=(%u,%u), focus mode=%i"
, __FUNCTION__, &sel, aNewFocus, aContentOffset, aContentEndOffset
, static_cast<int>(aFocusMode)); } } while (0)
1319 __FUNCTION__, &sel, aNewFocus, aContentOffset, aContentEndOffset,do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Debug)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Debug, "%s: selection=%p, new focus=%p, offsets=(%u,%u), focus mode=%i"
, __FUNCTION__, &sel, aNewFocus, aContentOffset, aContentEndOffset
, static_cast<int>(aFocusMode)); } } while (0)
1320 static_cast<int>(aFocusMode)))do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Debug)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Debug, "%s: selection=%p, new focus=%p, offsets=(%u,%u), focus mode=%i"
, __FUNCTION__, &sel, aNewFocus, aContentOffset, aContentEndOffset
, static_cast<int>(aFocusMode)); } } while (0)
;
1321 }
1322
1323 mDesiredCaretPos.Invalidate();
1324
1325 if (aFocusMode != FocusMode::kExtendSelection) {
1326 mMaintainedRange.mRange = nullptr;
1327 if (!NodeIsInLimiters(aNewFocus)) {
1328 mLimiters.mAncestorLimiter = nullptr;
1329 }
1330 }
1331
1332 // Don't take focus when dragging off of a table
1333 if (!mTableSelection.mDragSelectingCells) {
1334 BidiLevelFromClick(aNewFocus, aContentOffset);
1335 SetChangeReasons(nsISelectionListener::MOUSEDOWN_REASON +
1336 nsISelectionListener::DRAG_REASON);
1337
1338 RefPtr<Selection> selection = &NormalSelection();
1339
1340 if (aFocusMode == FocusMode::kExtendSelection) {
1341 mMaintainedRange.AdjustNormalSelection(aNewFocus, aContentOffset,
1342 *selection);
1343 }
1344
1345 AutoPrepareFocusRange prep(selection,
1346 aFocusMode == FocusMode::kMultiRangeSelection);
1347 return TakeFocus(*aNewFocus, aContentOffset, aContentEndOffset, aHint,
1348 aFocusMode);
1349 }
1350
1351 return NS_OK;
1352}
1353
1354void nsFrameSelection::HandleDrag(nsIFrame* aFrame, const nsPoint& aPoint) {
1355 if (!aFrame || !mPresShell) {
1356 return;
1357 }
1358
1359 nsresult result;
1360 nsIFrame* newFrame = nullptr;
1361 nsPoint newPoint;
1362
1363 result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame,
1364 newPoint);
1365 if (NS_FAILED(result)((bool)(__builtin_expect(!!(NS_FAILED_impl(result)), 0)))) {
1366 return;
1367 }
1368 if (!newFrame) {
1369 return;
1370 }
1371
1372 nsIFrame::ContentOffsets offsets =
1373 newFrame->GetContentOffsetsFromPoint(newPoint);
1374 if (!offsets.content) {
1375 return;
1376 }
1377
1378 RefPtr<Selection> selection = &NormalSelection();
1379 if (newFrame->IsSelected()) {
1380 // `MOZ_KnownLive` required because of
1381 // https://bugzilla.mozilla.org/show_bug.cgi?id=1636889.
1382 mMaintainedRange.AdjustNormalSelection(MOZ_KnownLive(offsets.content)(offsets.content),
1383 offsets.offset, *selection);
1384 }
1385
1386 mMaintainedRange.AdjustContentOffsets(
1387 offsets, mLimiters.mIndependentSelectionRootElement
1388 ? MaintainedRange::StopAtScroller::Yes
1389 : MaintainedRange::StopAtScroller::No);
1390
1391 // TODO: no click has happened, rename `HandleClick`.
1392 HandleClick(MOZ_KnownLive(offsets.content)(offsets.content) /* bug 1636889 */, offsets.offset,
1393 offsets.offset, FocusMode::kExtendSelection, offsets.associate);
1394}
1395
1396nsresult nsFrameSelection::StartAutoScrollTimer(nsIFrame* aFrame,
1397 const nsPoint& aPoint,
1398 uint32_t aDelay) {
1399 RefPtr<Selection> selection = &NormalSelection();
1400 return selection->StartAutoScrollTimer(aFrame, aPoint, aDelay);
1401}
1402
1403void nsFrameSelection::StopAutoScrollTimer() {
1404 Selection& sel = NormalSelection();
1405 sel.StopAutoScrollTimer();
1406}
1407
1408// static
1409nsINode* nsFrameSelection::TableSelection::IsContentInActivelyEditableTableCell(
1410 nsPresContext* aContext, nsIContent* aContent) {
1411 if (!aContext) {
1412 return nullptr;
1413 }
1414
1415 RefPtr<HTMLEditor> htmlEditor = nsContentUtils::GetHTMLEditor(aContext);
1416 if (!htmlEditor) {
1417 return nullptr;
1418 }
1419
1420 nsINode* inclusiveTableCellAncestor =
1421 GetClosestInclusiveTableCellAncestor(aContent);
1422 if (!inclusiveTableCellAncestor) {
1423 return nullptr;
1424 }
1425
1426 const Element* editingHost = htmlEditor->ComputeEditingHost();
1427 if (!editingHost) {
1428 return nullptr;
1429 }
1430
1431 const bool editableCell =
1432 inclusiveTableCellAncestor->IsInclusiveDescendantOf(editingHost);
1433 return editableCell ? inclusiveTableCellAncestor : nullptr;
1434}
1435
1436namespace {
1437struct ParentAndOffset {
1438 explicit ParentAndOffset(const nsINode& aNode)
1439 : mParent{aNode.GetParent()},
1440 mOffset{mParent ? mParent->ComputeIndexOf_Deprecated(&aNode) : 0} {}
1441
1442 nsINode* mParent;
1443
1444 // 0, if there's no parent.
1445 int32_t mOffset;
1446};
1447
1448} // namespace
1449/**
1450hard to go from nodes to frames, easy the other way!
1451 */
1452nsresult nsFrameSelection::TakeFocus(nsIContent& aNewFocus,
1453 uint32_t aContentOffset,
1454 uint32_t aContentEndOffset,
1455 CaretAssociationHint aHint,
1456 const FocusMode aFocusMode) {
1457 NS_ENSURE_STATE(mPresShell)do { if ((__builtin_expect(!!(!(mPresShell)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "mPresShell" ") failed",
nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 1457
); return NS_ERROR_UNEXPECTED; } } while (false)
;
1458
1459 if (!NodeIsInLimiters(&aNewFocus)) {
1460 return NS_ERROR_FAILURE;
1461 }
1462
1463 MOZ_LOG(sFrameSelectionLog, LogLevel::Verbose,do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Verbose)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Verbose, "%s: new focus=%p, offsets=(%u, %u), hint=%i, focusMode=%i"
, __FUNCTION__, &aNewFocus, aContentOffset, aContentEndOffset
, static_cast<int>(aHint), static_cast<int>(aFocusMode
)); } } while (0)
1464 ("%s: new focus=%p, offsets=(%u, %u), hint=%i, focusMode=%i",do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Verbose)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Verbose, "%s: new focus=%p, offsets=(%u, %u), hint=%i, focusMode=%i"
, __FUNCTION__, &aNewFocus, aContentOffset, aContentEndOffset
, static_cast<int>(aHint), static_cast<int>(aFocusMode
)); } } while (0)
1465 __FUNCTION__, &aNewFocus, aContentOffset, aContentEndOffset,do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Verbose)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Verbose, "%s: new focus=%p, offsets=(%u, %u), hint=%i, focusMode=%i"
, __FUNCTION__, &aNewFocus, aContentOffset, aContentEndOffset
, static_cast<int>(aHint), static_cast<int>(aFocusMode
)); } } while (0)
1466 static_cast<int>(aHint), static_cast<int>(aFocusMode)))do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Verbose)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Verbose, "%s: new focus=%p, offsets=(%u, %u), hint=%i, focusMode=%i"
, __FUNCTION__, &aNewFocus, aContentOffset, aContentEndOffset
, static_cast<int>(aHint), static_cast<int>(aFocusMode
)); } } while (0)
;
1467
1468 mPresShell->FrameSelectionWillTakeFocus(
1469 *this, aNewFocus.CanStartSelectionAsWebCompatHack()
1470 ? PresShell::CanMoveLastSelectionForToString::Yes
1471 : PresShell::CanMoveLastSelectionForToString::No);
1472
1473 // Clear all table selection data
1474 mTableSelection.mMode = TableSelectionMode::None;
1475 mTableSelection.mDragSelectingCells = false;
1476 mTableSelection.mStartSelectedCell = nullptr;
1477 mTableSelection.mEndSelectedCell = nullptr;
1478 mTableSelection.mAppendStartSelectedCell = nullptr;
1479 mCaret.mHint = aHint;
1480
1481 RefPtr<Selection> selection = &NormalSelection();
1482
1483 Maybe<Selection::AutoUserInitiated> userSelect;
1484 if (IsUserSelectionReason()) {
1485 userSelect.emplace(selection);
1486 }
1487
1488 // traverse through document and unselect crap here
1489 switch (aFocusMode) {
1490 case FocusMode::kCollapseToNewPoint:
1491 [[fallthrough]];
1492 case FocusMode::kMultiRangeSelection: {
1493 // single click? setting cursor down
1494 const Batching saveBatching =
1495 mBatching; // hack to use the collapse code.
1496 mBatching.mCounter = 1;
1497
1498 if (aFocusMode == FocusMode::kMultiRangeSelection) {
1499 // Remove existing collapsed ranges as there's no point in having
1500 // non-anchor/focus collapsed ranges.
1501 selection->RemoveCollapsedRanges();
1502
1503 ErrorResult error;
1504 RefPtr<nsRange> newRange = nsRange::Create(
1505 &aNewFocus, aContentOffset, &aNewFocus, aContentOffset, error);
1506 if (NS_WARN_IF(error.Failed())NS_warn_if_impl(error.Failed(), "error.Failed()", "./../../../layout/generic/nsFrameSelection.cpp"
, 1506)
) {
1507 return error.StealNSResult();
1508 }
1509 MOZ_ASSERT(newRange)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(newRange)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(newRange))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("newRange", "./../../../layout/generic/nsFrameSelection.cpp"
, 1509); AnnotateMozCrashReason("MOZ_ASSERT" "(" "newRange" ")"
); do { MOZ_CrashSequence(__null, 1509); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1510 selection->AddRangeAndSelectFramesAndNotifyListeners(*newRange,
1511 IgnoreErrors());
1512 } else {
1513 bool oldDesiredPosSet =
1514 mDesiredCaretPos.mIsSet; // need to keep old desired
1515 // position if it was set.
1516 selection->CollapseInLimiter(&aNewFocus, aContentOffset);
1517 mDesiredCaretPos.mIsSet =
1518 oldDesiredPosSet; // now reset desired pos back.
1519 }
1520
1521 mBatching = saveBatching;
1522
1523 if (aContentEndOffset != aContentOffset) {
1524 selection->Extend(&aNewFocus, aContentEndOffset);
1525 }
1526
1527 // find out if we are inside a table. if so, find out which one and which
1528 // cell once we do that, the next time we get a takefocus, check the
1529 // parent tree. if we are no longer inside same table ,cell then switch to
1530 // table selection mode. BUT only do this in an editor
1531
1532 NS_ENSURE_STATE(mPresShell)do { if ((__builtin_expect(!!(!(mPresShell)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "mPresShell" ") failed",
nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 1532
); return NS_ERROR_UNEXPECTED; } } while (false)
;
1533 RefPtr<nsPresContext> context = mPresShell->GetPresContext();
1534 mTableSelection.mClosestInclusiveTableCellAncestor = nullptr;
1535 if (nsINode* inclusiveTableCellAncestor =
1536 TableSelection::IsContentInActivelyEditableTableCell(
1537 context, &aNewFocus)) {
1538 mTableSelection.mClosestInclusiveTableCellAncestor =
1539 inclusiveTableCellAncestor;
1540 MOZ_LOG(sFrameSelectionLog, LogLevel::Debug,do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Debug)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Debug, "%s: Collapsing into new cell", __FUNCTION__
); } } while (0)
1541 ("%s: Collapsing into new cell", __FUNCTION__))do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Debug)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Debug, "%s: Collapsing into new cell", __FUNCTION__
); } } while (0)
;
1542 }
1543
1544 break;
1545 }
1546 case FocusMode::kExtendSelection: {
1547 // Now update the range list:
1548 nsCOMPtr<nsINode> inclusiveTableCellAncestor =
1549 GetClosestInclusiveTableCellAncestor(&aNewFocus);
1550 if (mTableSelection.mClosestInclusiveTableCellAncestor &&
1551 inclusiveTableCellAncestor &&
1552 inclusiveTableCellAncestor !=
1553 mTableSelection
1554 .mClosestInclusiveTableCellAncestor) // switch to cell
1555 // selection mode
1556 {
1557 MOZ_LOG(sFrameSelectionLog, LogLevel::Debug,do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Debug)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Debug, "%s: moving into new cell", __FUNCTION__);
} } while (0)
1558 ("%s: moving into new cell", __FUNCTION__))do { const ::mozilla::LogModule* moz_real_module = sFrameSelectionLog
; if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Debug)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Debug, "%s: moving into new cell", __FUNCTION__);
} } while (0)
;
1559
1560 WidgetMouseEvent event(false, eVoidEvent, nullptr,
1561 WidgetMouseEvent::eReal);
1562
1563 // Start selecting in the cell we were in before
1564 ParentAndOffset parentAndOffset{
1565 *mTableSelection.mClosestInclusiveTableCellAncestor};
1566 if (const nsCOMPtr<nsINode> previousParent = parentAndOffset.mParent) {
1567 const nsresult result =
1568 HandleTableSelection(previousParent, parentAndOffset.mOffset,
1569 TableSelectionMode::Cell, &event);
1570 if (NS_WARN_IF(NS_FAILED(result))NS_warn_if_impl(((bool)(__builtin_expect(!!(NS_FAILED_impl(result
)), 0))), "NS_FAILED(result)", "./../../../layout/generic/nsFrameSelection.cpp"
, 1570)
) {
1571 return result;
1572 }
1573 }
1574
1575 // Find the parent of this new cell and extend selection to it
1576 parentAndOffset = ParentAndOffset{*inclusiveTableCellAncestor};
1577
1578 // XXXX We need to REALLY get the current key shift state
1579 // (we'd need to add event listener -- let's not bother for now)
1580 event.mModifiers &= ~MODIFIER_SHIFT; // aExtendSelection;
1581 if (const nsCOMPtr<nsINode> newParent = parentAndOffset.mParent) {
1582 mTableSelection.mClosestInclusiveTableCellAncestor =
1583 inclusiveTableCellAncestor;
1584 // Continue selection into next cell
1585 const nsresult result =
1586 HandleTableSelection(newParent, parentAndOffset.mOffset,
1587 TableSelectionMode::Cell, &event);
1588 if (NS_WARN_IF(NS_FAILED(result))NS_warn_if_impl(((bool)(__builtin_expect(!!(NS_FAILED_impl(result
)), 0))), "NS_FAILED(result)", "./../../../layout/generic/nsFrameSelection.cpp"
, 1588)
) {
1589 return result;
1590 }
1591 }
1592 } else {
1593 // XXXX Problem: Shift+click in browser is appending text selection to
1594 // selected table!!!
1595 // is this the place to erase selected cells ?????
1596 uint32_t offset =
1597 (selection->GetDirection() == eDirNext &&
1598 aContentEndOffset > aContentOffset) // didn't go far enough
1599 ? aContentEndOffset // this will only redraw the diff
1600 : aContentOffset;
1601 selection->Extend(&aNewFocus, offset);
1602 }
1603 break;
1604 }
1605 }
1606
1607 // Be aware, the Selection instance may be destroyed after this call.
1608 return NotifySelectionListeners(SelectionType::eNormal);
1609}
1610
1611UniquePtr<SelectionDetails> nsFrameSelection::LookUpSelection(
1612 nsIContent* aContent, int32_t aContentOffset, int32_t aContentLength,
1613 IgnoreNormalSelection aIgnoreNormalSelection) const {
1614 if (!aContent || !mPresShell) {
1615 return nullptr;
1616 }
1617
1618 // TODO: Layout should use `uint32_t` for handling offset in DOM nodes
1619 // (for example: bug 1735262)
1620 MOZ_ASSERT(aContentOffset >= 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aContentOffset >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aContentOffset >= 0))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("aContentOffset >= 0"
, "./../../../layout/generic/nsFrameSelection.cpp", 1620); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aContentOffset >= 0" ")"); do { MOZ_CrashSequence
(__null, 1620); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
1621 MOZ_ASSERT(aContentLength >= 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aContentLength >= 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aContentLength >= 0))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("aContentLength >= 0"
, "./../../../layout/generic/nsFrameSelection.cpp", 1621); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aContentLength >= 0" ")"); do { MOZ_CrashSequence
(__null, 1621); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
1622 if (MOZ_UNLIKELY(aContentOffset < 0)(__builtin_expect(!!(aContentOffset < 0), 0)) || MOZ_UNLIKELY(aContentLength < 0)(__builtin_expect(!!(aContentLength < 0), 0))) {
1623 return nullptr;
1624 }
1625
1626 UniquePtr<SelectionDetails> details;
1627 for (size_t j = aIgnoreNormalSelection == IgnoreNormalSelection::Yes ? 1 : 0;
1628 j < std::size(mDomSelections); j++) {
1629 MOZ_ASSERT(mDomSelections[j])do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mDomSelections[j])>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mDomSelections[j]))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("mDomSelections[j]"
, "./../../../layout/generic/nsFrameSelection.cpp", 1629); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mDomSelections[j]" ")"); do { MOZ_CrashSequence
(__null, 1629); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
1630 details = mDomSelections[j]->LookUpSelection(
1631 aContent, static_cast<uint32_t>(aContentOffset),
1632 static_cast<uint32_t>(aContentLength), std::move(details),
1633 kPresentSelectionTypes[j]);
1634 }
1635
1636 // This may seem counter intuitive at first. Highlight selections need to be
1637 // iterated from back to front:
1638 //
1639 // - `mHighlightSelections` is ordered by insertion, i.e. if two or more
1640 // highlights overlap, the latest must take precedence.
1641 // - however, the `LookupSelection()` algorithm reverses the order by setting
1642 // the current `details` as `mNext`.
1643 for (const auto& iter : Reversed(mHighlightSelections)) {
1644 details = iter.second()->LookUpSelection(
1645 aContent, static_cast<uint32_t>(aContentOffset),
1646 static_cast<uint32_t>(aContentLength), std::move(details),
1647 SelectionType::eHighlight);
1648 }
1649
1650 return details;
1651}
1652
1653void nsFrameSelection::SetDragState(bool aState) {
1654 if (mDragState == aState) {
1655 return;
1656 }
1657
1658 mDragState = aState;
1659
1660 if (!mDragState) {
1661 mTableSelection.mDragSelectingCells = false;
1662 // Notify that reason is mouse up.
1663 SetChangeReasons(nsISelectionListener::MOUSEUP_REASON);
1664
1665 // flag is set to NotApplicable in `Selection::NotifySelectionListeners`.
1666 // since this function call is part of click event, this would immediately
1667 // reset the flag, rendering it useless.
1668 AutoRestore<ClickSelectionType> restoreClickSelectionType(
1669 mClickSelectionType);
1670 // Be aware, the Selection instance may be destroyed after this call.
1671 NotifySelectionListeners(SelectionType::eNormal);
1672 }
1673}
1674
1675Selection* nsFrameSelection::GetSelection(SelectionType aSelectionType) const {
1676 int8_t index = GetIndexFromSelectionType(aSelectionType);
1677 if (index < 0) {
1678 return nullptr;
1679 }
1680 MOZ_ASSERT(mDomSelections[index])do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mDomSelections[index])>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mDomSelections[index]))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("mDomSelections[index]"
, "./../../../layout/generic/nsFrameSelection.cpp", 1680); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mDomSelections[index]" ")"); do { MOZ_CrashSequence
(__null, 1680); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
1681 return mDomSelections[index];
1682}
1683
1684void nsFrameSelection::PopulateHighlightSelection(
1685 Selection& aSelection, mozilla::dom::Highlight& aHighlight) {
1686 MOZ_ASSERT(GetPresShell())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(GetPresShell())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(GetPresShell()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("GetPresShell()"
, "./../../../layout/generic/nsFrameSelection.cpp", 1686); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "GetPresShell()" ")"); do { MOZ_CrashSequence
(__null, 1686); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
1687 AutoFrameSelectionBatcher selectionBatcher(__FUNCTION__);
1688 selectionBatcher.AddFrameSelection(this);
1689 const Document* doc = GetPresShell()->GetDocument();
1690 for (const RefPtr<AbstractRange>& range : aHighlight.Ranges()) {
1691 const Document* rangeDoc = range->GetComposedDocOfContainers();
1692 if (!rangeDoc || rangeDoc == doc) {
1693 // since this is run in a context guarded by a selection batcher,
1694 // no strong reference is needed to keep `range` alive.
1695 aSelection.AddHighlightRangeAndSelectFramesAndNotifyListeners(
1696 MOZ_KnownLive(*range)(*range));
1697 }
1698 }
1699}
1700
1701void nsFrameSelection::AddHighlightSelection(
1702 nsAtom* aHighlightName, mozilla::dom::Highlight& aHighlight) {
1703 // Create the selection and register it in mHighlightSelections BEFORE
1704 // adding ranges. Adding ranges triggers paint, which queries
1705 // `mHighlightSelections`.
1706 RefPtr<Selection> selection =
1707 MakeRefPtr<Selection>(SelectionType::eHighlight, this);
1708 selection->SetHighlightSelectionData({aHighlightName, &aHighlight});
1709 if (auto iter =
1710 std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
1711 [&aHighlightName](auto const& aElm) {
1712 return aElm.first() == aHighlightName;
1713 });
1714 iter != mHighlightSelections.end()) {
1715 iter->second() = selection;
1716 } else {
1717 mHighlightSelections.AppendElement(
1718 CompactPair<RefPtr<nsAtom>, RefPtr<Selection>>(aHighlightName,
1719 selection));
1720 }
1721 // Now add ranges to the registered selection.
1722 PopulateHighlightSelection(*selection, aHighlight);
1723}
1724
1725void nsFrameSelection::RepaintHighlightSelection(nsAtom* aHighlightName) {
1726 if (auto iter =
1727 std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
1728 [&aHighlightName](auto const& aElm) {
1729 return aElm.first() == aHighlightName;
1730 });
1731 iter != mHighlightSelections.end()) {
1732 RefPtr selection = iter->second();
1733 selection->Repaint(mPresShell->GetPresContext());
1734 }
1735}
1736
1737void nsFrameSelection::RemoveHighlightSelection(nsAtom* aHighlightName) {
1738 if (auto iter =
1739 std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
1740 [&aHighlightName](auto const& aElm) {
1741 return aElm.first() == aHighlightName;
1742 });
1743 iter != mHighlightSelections.end()) {
1744 RefPtr<Selection> selection = iter->second();
1745 selection->RemoveAllRanges(IgnoreErrors());
1746 mHighlightSelections.RemoveElementAt(iter);
1747 }
1748}
1749
1750void nsFrameSelection::AddHighlightSelectionRange(
1751 nsAtom* aHighlightName, mozilla::dom::Highlight& aHighlight,
1752 mozilla::dom::AbstractRange& aRange) {
1753 if (auto iter =
1754 std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
1755 [&aHighlightName](auto const& aElm) {
1756 return aElm.first() == aHighlightName;
1757 });
1758 iter != mHighlightSelections.end()) {
1759 RefPtr<Selection> selection = iter->second();
1760 selection->AddHighlightRangeAndSelectFramesAndNotifyListeners(aRange);
1761 } else {
1762 AddHighlightSelection(aHighlightName, aHighlight);
1763 }
1764}
1765
1766void nsFrameSelection::RemoveHighlightSelectionRange(
1767 nsAtom* aHighlightName, mozilla::dom::AbstractRange& aRange) {
1768 if (auto iter =
1769 std::find_if(mHighlightSelections.begin(), mHighlightSelections.end(),
1770 [&aHighlightName](auto const& aElm) {
1771 return aElm.first() == aHighlightName;
1772 });
1773 iter != mHighlightSelections.end()) {
1774 // NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
1775 RefPtr<Selection> selection = iter->second();
1776 selection->RemoveRangeAndUnselectFramesAndNotifyListeners(aRange,
1777 IgnoreErrors());
1778 }
1779}
1780
1781nsresult nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType,
1782 SelectionRegion aRegion,
1783 int16_t aFlags) const {
1784 RefPtr<Selection> sel = GetSelection(aSelectionType);
1785 if (!sel) {
1786 return NS_ERROR_INVALID_ARG;
1787 }
1788
1789 const auto vScroll = [&]() -> WhereToScroll {
1790 if (aFlags & nsISelectionController::SCROLL_VERTICAL_START) {
1791 return WhereToScroll::Start;
1792 }
1793 if (aFlags & nsISelectionController::SCROLL_VERTICAL_END) {
1794 return WhereToScroll::End;
1795 }
1796 if (aFlags & nsISelectionController::SCROLL_VERTICAL_CENTER) {
1797 return WhereToScroll::Center;
1798 }
1799 return WhereToScroll::Nearest;
1800 }();
1801
1802 auto mode = aFlags & nsISelectionController::SCROLL_SYNCHRONOUS
1803 ? SelectionScrollMode::SyncFlush
1804 : SelectionScrollMode::Async;
1805
1806 auto scrollFlags = ScrollFlags::None;
1807 if (aFlags & nsISelectionController::SCROLL_OVERFLOW_HIDDEN) {
1808 scrollFlags |= ScrollFlags::ScrollOverflowHidden;
1809 }
1810
1811 // After ScrollSelectionIntoView(), the pending notifications might be
1812 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1813 return sel->ScrollIntoView(aRegion, AxisScrollParams(vScroll),
1814 AxisScrollParams(), scrollFlags, mode);
1815}
1816
1817nsresult nsFrameSelection::RepaintSelection(SelectionType aSelectionType) {
1818 RefPtr<Selection> sel = GetSelection(aSelectionType);
1819 if (!sel) {
1820 return NS_ERROR_INVALID_ARG;
1821 }
1822 if (!mPresShell) {
1823 return NS_ERROR_UNEXPECTED;
1824 }
1825
1826// On macOS, update the selection cache to the new active selection
1827// aka the current selection.
1828// NOTE: On Linux and Windows, we don't need to run this because this just runs
1829// nsHTMLCopyEncorder without updating the selection cache. However, we run
1830// this in the debug builds on Linux and Windows. See the comment of this macro
1831// definition for the detail.
1832#ifdef RUN_MAYBE_UPDATE_SELECTION_CACHE_REPAINT_SELECTION
1833 // Check that we're in the an active window and, if this is Web content,
1834 // in the frontmost tab.
1835 // XXX This is called when the selection blurs, see
1836 // PresShell::FrameSelectionWillLoseFocus(). Cannot we skip doing this in that
1837 // case?
1838 Document* doc = mPresShell->GetDocument();
1839 if (doc && IsInActiveTab(doc) && aSelectionType == SelectionType::eNormal) {
1840 MaybeUpdateSelectionCacheOnRepaintSelection(sel);
1841 }
1842#endif // #ifdef RUN_MAYBE_UPDATE_SELECTION_CACHE_REPAINT_SELECTION
1843 return sel->Repaint(mPresShell->GetPresContext());
1844}
1845
1846nsIFrame* nsFrameSelection::GetFrameToPageSelect() const {
1847 if (NS_WARN_IF(!mPresShell)NS_warn_if_impl(!mPresShell, "!mPresShell", "./../../../layout/generic/nsFrameSelection.cpp"
, 1847)
) {
1848 return nullptr;
1849 }
1850
1851 nsIFrame* rootFrameToSelect = [&]() -> nsIFrame* {
1852 if (mLimiters.mIndependentSelectionRootElement) {
1853 return mLimiters.mIndependentSelectionRootElement->GetPrimaryFrame();
1854 }
1855 if (mLimiters.mAncestorLimiter) {
1856 return mLimiters.mAncestorLimiter->GetPrimaryFrame();
1857 }
1858 return mPresShell->GetRootScrollContainerFrame();
1859 }();
1860
1861 if (NS_WARN_IF(!rootFrameToSelect)NS_warn_if_impl(!rootFrameToSelect, "!rootFrameToSelect", "./../../../layout/generic/nsFrameSelection.cpp"
, 1861)
) {
1862 return nullptr;
1863 }
1864
1865 // If there is selected content, and it's under our root, look for nearest
1866 // and vertical scrollable parent under the root frame.
1867 nsIFrame* innerScrollableFrame = [&]() -> nsIFrame* {
1868 RefPtr contentToSelect = mPresShell->GetContentForScrolling();
1869 if (!contentToSelect) {
1870 return nullptr;
1871 }
1872 nsIFrame* frame = contentToSelect->GetPrimaryFrame();
1873 if (!frame ||
1874 !nsLayoutUtils::IsProperAncestorFrame(rootFrameToSelect, frame)) {
1875 return nullptr;
1876 }
1877 for (; frame != rootFrameToSelect; frame = frame->GetParent()) {
1878 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame);
1879 if (!scrollContainerFrame) {
1880 continue;
1881 }
1882 ScrollStyles scrollStyles = scrollContainerFrame->GetScrollStyles();
1883 if (scrollStyles.mVertical == StyleOverflow::Hidden) {
1884 continue;
1885 }
1886 layers::ScrollDirections directions =
1887 scrollContainerFrame->GetAvailableScrollingDirections();
1888 if (directions.contains(layers::ScrollDirection::eVertical)) {
1889 // If there is sub scrollable frame, let's use its page size to select.
1890 return frame;
1891 }
1892 }
1893 // Otherwise, i.e., there is no scrollable frame or only the root frame is
1894 // scrollable, let's return the root frame because Shift + PageUp/PageDown
1895 // should expand the selection in the root content even if it's not
1896 // scrollable.
1897 return nullptr;
1898 }();
1899 return innerScrollableFrame ? innerScrollableFrame : rootFrameToSelect;
1900}
1901
1902nsresult nsFrameSelection::PageMove(bool aForward, bool aExtend,
1903 nsIFrame* aFrame,
1904 SelectionIntoView aSelectionIntoView) {
1905 MOZ_ASSERT(aFrame)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aFrame)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(aFrame))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("aFrame", "./../../../layout/generic/nsFrameSelection.cpp"
, 1905); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame" ")"
); do { MOZ_CrashSequence(__null, 1905); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1906
1907 if (!IsAvailable()) [[unlikely]] {
1908 return NS_OK;
1909 }
1910
1911 // expected behavior for PageMove is to scroll AND move the caret
1912 // and remain relative position of the caret in view. see Bug 4302.
1913
1914 // find out where the caret is.
1915 // we should know mDesiredCaretPos.mValue value of nsFrameSelection, but I
1916 // haven't seen that behavior in other windows applications yet.
1917 MOZ_DIAGNOSTIC_ASSERT(GetSelection(mozilla::SelectionType::eNormal))do { static_assert( mozilla::detail::AssertionConditionType<
decltype(GetSelection(mozilla::SelectionType::eNormal))>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(GetSelection(mozilla::SelectionType::eNormal)))), 0)
)) { do { } while (false); MOZ_ReportAssertionFailure("GetSelection(mozilla::SelectionType::eNormal)"
, "./../../../layout/generic/nsFrameSelection.cpp", 1917); AnnotateMozCrashReason
("MOZ_DIAGNOSTIC_ASSERT" "(" "GetSelection(mozilla::SelectionType::eNormal)"
")"); do { MOZ_CrashSequence(__null, 1917); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1918 const OwningNonNull<Selection> selection = NormalSelection();
1919
1920 // Get the scroll container frame. If aFrame is not scrollable, this is
1921 // nullptr.
1922 ScrollContainerFrame* scrollContainerFrame = aFrame->GetScrollTargetFrame();
1923 const AutoWeakFrame scrollContainerFrameWeak(scrollContainerFrame);
1924
1925 bool scrolledFrameIsInLimiter = true;
1926 const auto offsets = [&]()
1927 MOZ_NEVER_INLINE_DEBUG__attribute__((noinline)) -> nsIFrame::ContentOffsets {
1928 // Get the scrolled frame. If aFrame is not scrollable, this is aFrame
1929 // itself.
1930 nsIFrame* scrolledFrame = scrollContainerFrame
1931 ? scrollContainerFrame->GetScrolledFrame()
1932 : aFrame;
1933 if (!scrolledFrame) [[unlikely]] {
1934 return {};
1935 }
1936
1937 nsRect caretPos;
1938 nsIFrame* caretFrame = nsCaret::GetGeometry(selection, &caretPos);
1939 if (!caretFrame) [[unlikely]] {
1940 return {};
1941 }
1942
1943 // If the scrolled frame is outside of current selection limiter,
1944 // we need to scroll the frame but keep moving selection in the limiter.
1945 nsIFrame* frameToClick = scrolledFrame;
1946 if (!NodeIsInLimiters(scrolledFrame->GetContent())) {
1947 frameToClick = GetFrameToPageSelect();
1948 scrolledFrameIsInLimiter = scrolledFrame == frameToClick;
1949 if (NS_WARN_IF(!frameToClick)NS_warn_if_impl(!frameToClick, "!frameToClick", "./../../../layout/generic/nsFrameSelection.cpp"
, 1949)
) {
1950 return {};
1951 }
1952 }
1953
1954 if (scrollContainerFrame) {
1955 // If there is a scrollable frame, adjust pseudo-click position with page
1956 // scroll amount.
1957 // XXX This may scroll more than one page if ScrollSelectionIntoView is
1958 // called later because caret may not fully visible. E.g., if
1959 // clicking line will be visible only half height with scrolling
1960 // the frame, ScrollSelectionIntoView additionally scrolls to show
1961 // the caret entirely.
1962 if (aForward) {
1963 caretPos.y += scrollContainerFrame->GetPageScrollAmount().height;
1964 } else {
1965 caretPos.y -= scrollContainerFrame->GetPageScrollAmount().height;
1966 }
1967 } else {
1968 // Otherwise, adjust pseudo-click position with the frame size.
1969 if (aForward) {
1970 caretPos.y += frameToClick->GetSize().height;
1971 } else {
1972 caretPos.y -= frameToClick->GetSize().height;
1973 }
1974 }
1975
1976 caretPos += caretFrame->GetOffsetTo(frameToClick);
1977
1978 // get a content at desired location
1979 nsPoint desiredPoint;
1980 desiredPoint.x = caretPos.x;
1981 desiredPoint.y = caretPos.y + caretPos.height / 2;
1982 return frameToClick->GetContentOffsetsFromPoint(desiredPoint);
1983 }();
1984 if (!offsets.content) [[unlikely]] {
1985 // XXX Do we need to handle ScrollSelectionIntoView in this case?
1986 return NS_OK;
1987 }
1988
1989 // First, place the caret.
1990 bool selectionChanged;
1991 {
1992 // We don't want any script to run until we check whether selection is
1993 // modified by HandleClick.
1994 SelectionBatcher ensureNoSelectionChangeNotifications(selection.ref(),
1995 __FUNCTION__);
1996
1997 RangeBoundary oldAnchor = selection->AnchorRef();
1998 RangeBoundary oldFocus = selection->FocusRef();
1999
2000 const FocusMode focusMode =
2001 aExtend ? FocusMode::kExtendSelection : FocusMode::kCollapseToNewPoint;
2002 HandleClick(MOZ_KnownLive(offsets.content)(offsets.content) /* bug 1636889 */,
2003 offsets.offset, offsets.offset, focusMode,
2004 CaretAssociationHint::After);
2005
2006 selectionChanged = selection->AnchorRef() != oldAnchor ||
2007 selection->FocusRef() != oldFocus;
2008 }
2009
2010 bool doScrollSelectionIntoView = !(
2011 aSelectionIntoView == SelectionIntoView::IfChanged && !selectionChanged);
2012
2013 // Then, scroll the given frame one page.
2014 if (scrollContainerFrameWeak.IsAlive()) {
2015 // If we'll call ScrollSelectionIntoView later and selection wasn't
2016 // changed and we scroll outside of selection limiter, we shouldn't use
2017 // smooth scroll here because ScrollContainerFrame uses normal runnable,
2018 // but ScrollSelectionIntoView uses early runner and it cancels the
2019 // pending smooth scroll. Therefore, if we used smooth scroll in such
2020 // case, ScrollSelectionIntoView would scroll to show caret instead of
2021 // page scroll of an element outside selection limiter.
2022 ScrollMode scrollMode = doScrollSelectionIntoView && !selectionChanged &&
2023 !scrolledFrameIsInLimiter
2024 ? ScrollMode::Instant
2025 : ScrollMode::Smooth;
2026 MOZ_ASSERT(scrollContainerFrameWeak.GetFrame() == scrollContainerFrame)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(scrollContainerFrameWeak.GetFrame() == scrollContainerFrame
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(scrollContainerFrameWeak.GetFrame() == scrollContainerFrame
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"scrollContainerFrameWeak.GetFrame() == scrollContainerFrame"
, "./../../../layout/generic/nsFrameSelection.cpp", 2026); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "scrollContainerFrameWeak.GetFrame() == scrollContainerFrame"
")"); do { MOZ_CrashSequence(__null, 2026); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2027 scrollContainerFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
2028 ScrollUnit::PAGES, scrollMode);
2029 }
2030
2031 // Finally, scroll selection into view if requested.
2032 if (!doScrollSelectionIntoView) {
2033 return NS_OK;
2034 }
2035 return ScrollSelectionIntoView(SelectionType::eNormal,
2036 nsISelectionController::SELECTION_FOCUS_REGION,
2037 nsISelectionController::SCROLL_SYNCHRONOUS);
2038}
2039
2040nsresult nsFrameSelection::PhysicalMove(int16_t aDirection, int16_t aAmount,
2041 bool aExtend) {
2042 NS_ENSURE_STATE(mPresShell)do { if ((__builtin_expect(!!(!(mPresShell)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "mPresShell" ") failed",
nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 2042
); return NS_ERROR_UNEXPECTED; } } while (false)
;
2043 // Flush out layout, since we need it to be up to date to do caret
2044 // positioning.
2045 OwningNonNull<PresShell> presShell(*mPresShell);
2046 presShell->FlushPendingNotifications(FlushType::Layout);
2047
2048 if (!mPresShell) {
2049 return NS_OK;
2050 }
2051
2052 // Check that parameters are safe
2053 if (aDirection < 0 || aDirection > 3 || aAmount < 0 || aAmount > 2) {
2054 return NS_ERROR_FAILURE;
2055 }
2056
2057 nsPresContext* context = mPresShell->GetPresContext();
2058 if (!context) {
2059 return NS_ERROR_FAILURE;
2060 }
2061
2062 RefPtr<Selection> sel = &NormalSelection();
2063
2064 // Map the abstract movement amounts (0-1) to direction-specific
2065 // selection units.
2066 static const nsSelectionAmount inlineAmount[] = {eSelectCluster, eSelectWord};
2067 static const nsSelectionAmount blockPrevAmount[] = {eSelectLine,
2068 eSelectBeginLine};
2069 static const nsSelectionAmount blockNextAmount[] = {eSelectLine,
2070 eSelectEndLine};
2071
2072 struct PhysicalToLogicalMapping {
2073 nsDirection direction;
2074 const nsSelectionAmount* amounts;
2075 };
2076 static const PhysicalToLogicalMapping verticalLR[4] = {
2077 {eDirPrevious, blockPrevAmount}, // left
2078 {eDirNext, blockNextAmount}, // right
2079 {eDirPrevious, inlineAmount}, // up
2080 {eDirNext, inlineAmount} // down
2081 };
2082 static const PhysicalToLogicalMapping verticalRL[4] = {
2083 {eDirNext, blockNextAmount},
2084 {eDirPrevious, blockPrevAmount},
2085 {eDirPrevious, inlineAmount},
2086 {eDirNext, inlineAmount}};
2087 static const PhysicalToLogicalMapping horizontal[4] = {
2088 {eDirPrevious, inlineAmount},
2089 {eDirNext, inlineAmount},
2090 {eDirPrevious, blockPrevAmount},
2091 {eDirNext, blockNextAmount}};
2092
2093 WritingMode wm;
2094 const PrimaryFrameData frameForFocus =
2095 sel->GetPrimaryFrameForCaretAtFocusNode(true);
2096 if (frameForFocus) {
2097 // FYI: Setting the caret association hint was done during a call of
2098 // GetPrimaryFrameForCaretAtFocusNode. Therefore, this may not be intended
2099 // by the original author.
2100 sel->GetFrameSelection()->SetHint(frameForFocus.mHint);
2101
2102 if (!frameForFocus->Style()->IsTextCombined()) {
2103 wm = frameForFocus->GetWritingMode();
2104 } else {
2105 // Using different direction for horizontal-in-vertical would
2106 // make it hard to navigate via keyboard. Inherit the moving
2107 // direction from its parent.
2108 MOZ_ASSERT(frameForFocus->IsTextFrame())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(frameForFocus->IsTextFrame())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(frameForFocus->IsTextFrame
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("frameForFocus->IsTextFrame()", "./../../../layout/generic/nsFrameSelection.cpp"
, 2108); AnnotateMozCrashReason("MOZ_ASSERT" "(" "frameForFocus->IsTextFrame()"
")"); do { MOZ_CrashSequence(__null, 2108); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2109 wm = frameForFocus->GetParent()->GetWritingMode();
2110 MOZ_ASSERT(wm.IsVertical(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(wm.IsVertical())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(wm.IsVertical()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("wm.IsVertical()"
" (" "Text combined " "can only appear in vertical text" ")"
, "./../../../layout/generic/nsFrameSelection.cpp", 2112); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "wm.IsVertical()" ") (" "Text combined " "can only appear in vertical text"
")"); do { MOZ_CrashSequence(__null, 2112); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2111 "Text combined "do { static_assert( mozilla::detail::AssertionConditionType<
decltype(wm.IsVertical())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(wm.IsVertical()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("wm.IsVertical()"
" (" "Text combined " "can only appear in vertical text" ")"
, "./../../../layout/generic/nsFrameSelection.cpp", 2112); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "wm.IsVertical()" ") (" "Text combined " "can only appear in vertical text"
")"); do { MOZ_CrashSequence(__null, 2112); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2112 "can only appear in vertical text")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(wm.IsVertical())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(wm.IsVertical()))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("wm.IsVertical()"
" (" "Text combined " "can only appear in vertical text" ")"
, "./../../../layout/generic/nsFrameSelection.cpp", 2112); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "wm.IsVertical()" ") (" "Text combined " "can only appear in vertical text"
")"); do { MOZ_CrashSequence(__null, 2112); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2113 }
2114 }
2115
2116 if (aAmount == 2) {
2117 // Amount 2 is visual intra-line movement (move to line edge).
2118 // Determine the logical forward direction from the physical direction
2119 // and the writing mode's bidi direction, then use IntraLineMove which
2120 // handles eLogical movement correctly.
2121 bool isLeftOrUp = (aDirection == nsISelectionController::MOVE_LEFT ||
2122 aDirection == nsISelectionController::MOVE_UP);
2123 // In LTR, left/up = backward (beginning), right/down = forward (end).
2124 // In RTL, left/up = forward (end), right/down = backward (beginning).
2125 bool forward = wm.IsBidiRTL() ? isLeftOrUp : !isLeftOrUp;
2126 return IntraLineMove(forward, aExtend);
2127 }
2128
2129 MOZ_ASSERT(aAmount <= 1, "aAmount == 2 should have been handled above")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aAmount <= 1)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aAmount <= 1))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aAmount <= 1"
" (" "aAmount == 2 should have been handled above" ")", "./../../../layout/generic/nsFrameSelection.cpp"
, 2129); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aAmount <= 1"
") (" "aAmount == 2 should have been handled above" ")"); do
{ MOZ_CrashSequence(__null, 2129); __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
2130
2131 const PhysicalToLogicalMapping& mapping =
2132 wm.IsVertical()
2133 ? wm.IsVerticalLR() ? verticalLR[aDirection] : verticalRL[aDirection]
2134 : horizontal[aDirection];
2135
2136 nsresult rv = MoveCaret(mapping.direction, ExtendSelection(aExtend),
2137 mapping.amounts[aAmount], eVisual);
2138 if (NS_FAILED(rv)((bool)(__builtin_expect(!!(NS_FAILED_impl(rv)), 0)))) {
2139 // If we tried to do a line move, but couldn't move in the given direction,
2140 // then we'll "promote" this to a line-edge move instead.
2141 if (mapping.amounts[aAmount] == eSelectLine) {
2142 rv = MoveCaret(mapping.direction, ExtendSelection(aExtend),
2143 mapping.amounts[aAmount + 1], eVisual);
2144 }
2145 // And if it was a next-word move that failed (which can happen when
2146 // eat_space_to_next_word is true, see bug 1153237), then just move forward
2147 // to the line-edge.
2148 else if (mapping.amounts[aAmount] == eSelectWord &&
2149 mapping.direction == eDirNext) {
2150 rv = MoveCaret(eDirNext, ExtendSelection(aExtend), eSelectEndLine,
2151 eVisual);
2152 }
2153 }
2154
2155 return rv;
2156}
2157
2158nsresult nsFrameSelection::CharacterMove(bool aForward, bool aExtend) {
2159 return MoveCaret(aForward ? eDirNext : eDirPrevious, ExtendSelection(aExtend),
2160 eSelectCluster, eUsePrefStyle);
2161}
2162
2163nsresult nsFrameSelection::WordMove(bool aForward, bool aExtend) {
2164 return MoveCaret(aForward ? eDirNext : eDirPrevious, ExtendSelection(aExtend),
2165 eSelectWord, eUsePrefStyle);
2166}
2167
2168nsresult nsFrameSelection::LineMove(bool aForward, bool aExtend) {
2169 return MoveCaret(aForward ? eDirNext : eDirPrevious, ExtendSelection(aExtend),
2170 eSelectLine, eUsePrefStyle);
2171}
2172
2173nsresult nsFrameSelection::IntraLineMove(bool aForward, bool aExtend) {
2174 if (aForward) {
2175 return MoveCaret(eDirNext, ExtendSelection(aExtend), eSelectEndLine,
2176 eLogical);
2177 }
2178 return MoveCaret(eDirPrevious, ExtendSelection(aExtend), eSelectBeginLine,
2179 eLogical);
2180}
2181
2182nsresult nsFrameSelection::ParagraphMove(bool aForward, bool aExtend) {
2183 return MoveCaret(aForward ? eDirNext : eDirPrevious, ExtendSelection(aExtend),
2184 eSelectParagraph, eLogical);
2185}
2186
2187// static
2188template <typename RangeType>
2189Result<RefPtr<RangeType>, nsresult>
2190nsFrameSelection::CreateRangeExtendedToSomewhere(
2191 PresShell& aPresShell,
2192 const mozilla::LimitersAndCaretData& aLimitersAndCaretData,
2193 const AbstractRange& aRange, nsDirection aRangeDirection,
2194 nsDirection aExtendDirection, nsSelectionAmount aAmount,
2195 CaretMovementStyle aMovementStyle) {
2196 MOZ_ASSERT(aRangeDirection == eDirNext || aRangeDirection == eDirPrevious)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aRangeDirection == eDirNext || aRangeDirection == eDirPrevious
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aRangeDirection == eDirNext || aRangeDirection == eDirPrevious
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aRangeDirection == eDirNext || aRangeDirection == eDirPrevious"
, "./../../../layout/generic/nsFrameSelection.cpp", 2196); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aRangeDirection == eDirNext || aRangeDirection == eDirPrevious"
")"); do { MOZ_CrashSequence(__null, 2196); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2197 MOZ_ASSERT(aExtendDirection == eDirNext || aExtendDirection == eDirPrevious)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aExtendDirection == eDirNext || aExtendDirection == eDirPrevious
)>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aExtendDirection == eDirNext || aExtendDirection == eDirPrevious
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aExtendDirection == eDirNext || aExtendDirection == eDirPrevious"
, "./../../../layout/generic/nsFrameSelection.cpp", 2197); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aExtendDirection == eDirNext || aExtendDirection == eDirPrevious"
")"); do { MOZ_CrashSequence(__null, 2197); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2198 MOZ_ASSERT(aAmount == eSelectCharacter || aAmount == eSelectCluster ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aAmount == eSelectCharacter || aAmount == eSelectCluster
|| aAmount == eSelectWord || aAmount == eSelectBeginLine || aAmount
== eSelectEndLine)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aAmount == eSelectCharacter ||
aAmount == eSelectCluster || aAmount == eSelectWord || aAmount
== eSelectBeginLine || aAmount == eSelectEndLine))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aAmount == eSelectCharacter || aAmount == eSelectCluster || aAmount == eSelectWord || aAmount == eSelectBeginLine || aAmount == eSelectEndLine"
, "./../../../layout/generic/nsFrameSelection.cpp", 2200); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aAmount == eSelectCharacter || aAmount == eSelectCluster || aAmount == eSelectWord || aAmount == eSelectBeginLine || aAmount == eSelectEndLine"
")"); do { MOZ_CrashSequence(__null, 2200); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2199 aAmount == eSelectWord || aAmount == eSelectBeginLine ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aAmount == eSelectCharacter || aAmount == eSelectCluster
|| aAmount == eSelectWord || aAmount == eSelectBeginLine || aAmount
== eSelectEndLine)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aAmount == eSelectCharacter ||
aAmount == eSelectCluster || aAmount == eSelectWord || aAmount
== eSelectBeginLine || aAmount == eSelectEndLine))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aAmount == eSelectCharacter || aAmount == eSelectCluster || aAmount == eSelectWord || aAmount == eSelectBeginLine || aAmount == eSelectEndLine"
, "./../../../layout/generic/nsFrameSelection.cpp", 2200); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aAmount == eSelectCharacter || aAmount == eSelectCluster || aAmount == eSelectWord || aAmount == eSelectBeginLine || aAmount == eSelectEndLine"
")"); do { MOZ_CrashSequence(__null, 2200); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2200 aAmount == eSelectEndLine)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aAmount == eSelectCharacter || aAmount == eSelectCluster
|| aAmount == eSelectWord || aAmount == eSelectBeginLine || aAmount
== eSelectEndLine)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aAmount == eSelectCharacter ||
aAmount == eSelectCluster || aAmount == eSelectWord || aAmount
== eSelectBeginLine || aAmount == eSelectEndLine))), 0))) { do
{ } while (false); MOZ_ReportAssertionFailure("aAmount == eSelectCharacter || aAmount == eSelectCluster || aAmount == eSelectWord || aAmount == eSelectBeginLine || aAmount == eSelectEndLine"
, "./../../../layout/generic/nsFrameSelection.cpp", 2200); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aAmount == eSelectCharacter || aAmount == eSelectCluster || aAmount == eSelectWord || aAmount == eSelectBeginLine || aAmount == eSelectEndLine"
")"); do { MOZ_CrashSequence(__null, 2200); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2201 MOZ_ASSERT(aMovementStyle == eLogical || aMovementStyle == eVisual ||do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aMovementStyle == eLogical || aMovementStyle == eVisual
|| aMovementStyle == eUsePrefStyle)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aMovementStyle == eLogical ||
aMovementStyle == eVisual || aMovementStyle == eUsePrefStyle
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aMovementStyle == eLogical || aMovementStyle == eVisual || aMovementStyle == eUsePrefStyle"
, "./../../../layout/generic/nsFrameSelection.cpp", 2202); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aMovementStyle == eLogical || aMovementStyle == eVisual || aMovementStyle == eUsePrefStyle"
")"); do { MOZ_CrashSequence(__null, 2202); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2202 aMovementStyle == eUsePrefStyle)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aMovementStyle == eLogical || aMovementStyle == eVisual
|| aMovementStyle == eUsePrefStyle)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aMovementStyle == eLogical ||
aMovementStyle == eVisual || aMovementStyle == eUsePrefStyle
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"aMovementStyle == eLogical || aMovementStyle == eVisual || aMovementStyle == eUsePrefStyle"
, "./../../../layout/generic/nsFrameSelection.cpp", 2202); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aMovementStyle == eLogical || aMovementStyle == eVisual || aMovementStyle == eUsePrefStyle"
")"); do { MOZ_CrashSequence(__null, 2202); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2203
2204 aPresShell.FlushPendingNotifications(FlushType::Layout);
2205 if (aPresShell.IsDestroying()) {
2206 return Err(NS_ERROR_FAILURE);
2207 }
2208 if (!aRange.IsPositioned()) {
2209 return Err(NS_ERROR_FAILURE);
2210 }
2211 const ForceEditableRegion forceEditableRegion = [&]() {
2212 if (aRange.GetStartContainer()->IsEditable()) {
2213 return ForceEditableRegion::Yes;
2214 }
2215 const auto* const element = Element::FromNode(aRange.GetStartContainer());
2216 return element && element->State().HasState(ElementState::READWRITE)
2217 ? ForceEditableRegion::Yes
2218 : ForceEditableRegion::No;
2219 }();
2220 Result<PeekOffsetOptions, nsresult> options =
2221 CreatePeekOffsetOptionsForCaretMove(
2222 aLimitersAndCaretData.mIndependentSelectionRootElement,
2223 forceEditableRegion, ExtendSelection::Yes, aMovementStyle);
2224 if (MOZ_UNLIKELY(options.isErr())(__builtin_expect(!!(options.isErr()), 0))) {
2225 return options.propagateErr();
2226 }
2227 Result<RawRangeBoundary, nsresult> result =
2228 SelectionMovementUtils::MoveRangeBoundaryToSomewhere(
2229 aRangeDirection == eDirNext ? aRange.StartRef().AsRaw()
2230 : aRange.EndRef().AsRaw(),
2231 aExtendDirection, aLimitersAndCaretData.mCaretAssociationHint,
2232 aLimitersAndCaretData.mCaretBidiLevel, aAmount, options.unwrap(),
2233 aLimitersAndCaretData.mAncestorLimiter);
2234 if (result.isErr()) {
2235 return result.propagateErr();
2236 }
2237 RefPtr<RangeType> range;
2238 RawRangeBoundary rangeBoundary = result.unwrap();
2239 if (!rangeBoundary.IsSetAndValid()) {
2240 return range;
2241 }
2242 if (aExtendDirection == eDirPrevious) {
2243 range = RangeType::Create(rangeBoundary, aRange.EndRef(), IgnoreErrors());
2244 } else {
2245 range = RangeType::Create(aRange.StartRef(), rangeBoundary, IgnoreErrors());
2246 }
2247 return range;
2248}
2249
2250//////////END FRAMESELECTION
2251
2252LazyLogModule gBatchLog("SelectionBatch");
2253
2254void nsFrameSelection::StartBatchChanges(const char* aRequesterFuncName) {
2255 MOZ_LOG(gBatchLog, LogLevel::Info,do { const ::mozilla::LogModule* moz_real_module = gBatchLog;
if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p%snsFrameSelection::StartBatchChanges(%s)"
, this, std::string((mBatching.mCounter + 1) * 2, ' ').c_str(
), aRequesterFuncName); } } while (0)
2256 ("%p%snsFrameSelection::StartBatchChanges(%s)", this,do { const ::mozilla::LogModule* moz_real_module = gBatchLog;
if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p%snsFrameSelection::StartBatchChanges(%s)"
, this, std::string((mBatching.mCounter + 1) * 2, ' ').c_str(
), aRequesterFuncName); } } while (0)
2257 std::string((mBatching.mCounter + 1) * 2, ' ').c_str(),do { const ::mozilla::LogModule* moz_real_module = gBatchLog;
if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p%snsFrameSelection::StartBatchChanges(%s)"
, this, std::string((mBatching.mCounter + 1) * 2, ' ').c_str(
), aRequesterFuncName); } } while (0)
2258 aRequesterFuncName))do { const ::mozilla::LogModule* moz_real_module = gBatchLog;
if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p%snsFrameSelection::StartBatchChanges(%s)"
, this, std::string((mBatching.mCounter + 1) * 2, ' ').c_str(
), aRequesterFuncName); } } while (0)
;
2259 mBatching.mCounter++;
2260}
2261
2262void nsFrameSelection::EndBatchChanges(const char* aRequesterFuncName,
2263 int16_t aReasons) {
2264 MOZ_LOG(gBatchLog, LogLevel::Info,do { const ::mozilla::LogModule* moz_real_module = gBatchLog;
if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p%snsFrameSelection::EndBatchChanges (%s, %s)"
, this, std::string(mBatching.mCounter * 2, ' ').c_str(), aRequesterFuncName
, SelectionChangeReasonsToCString(aReasons).get()); } } while
(0)
2265 ("%p%snsFrameSelection::EndBatchChanges (%s, %s)", this,do { const ::mozilla::LogModule* moz_real_module = gBatchLog;
if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p%snsFrameSelection::EndBatchChanges (%s, %s)"
, this, std::string(mBatching.mCounter * 2, ' ').c_str(), aRequesterFuncName
, SelectionChangeReasonsToCString(aReasons).get()); } } while
(0)
2266 std::string(mBatching.mCounter * 2, ' ').c_str(), aRequesterFuncName,do { const ::mozilla::LogModule* moz_real_module = gBatchLog;
if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p%snsFrameSelection::EndBatchChanges (%s, %s)"
, this, std::string(mBatching.mCounter * 2, ' ').c_str(), aRequesterFuncName
, SelectionChangeReasonsToCString(aReasons).get()); } } while
(0)
2267 SelectionChangeReasonsToCString(aReasons).get()))do { const ::mozilla::LogModule* moz_real_module = gBatchLog;
if ((__builtin_expect(!!(mozilla::detail::log_test(moz_real_module
, LogLevel::Info)), 0))) { mozilla::detail::log_print(moz_real_module
, LogLevel::Info, "%p%snsFrameSelection::EndBatchChanges (%s, %s)"
, this, std::string(mBatching.mCounter * 2, ' ').c_str(), aRequesterFuncName
, SelectionChangeReasonsToCString(aReasons).get()); } } while
(0)
;
2268 MOZ_ASSERT(mBatching.mCounter > 0, "Bad mBatching.mCounter")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mBatching.mCounter > 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mBatching.mCounter > 0)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("mBatching.mCounter > 0"
" (" "Bad mBatching.mCounter" ")", "./../../../layout/generic/nsFrameSelection.cpp"
, 2268); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mBatching.mCounter > 0"
") (" "Bad mBatching.mCounter" ")"); do { MOZ_CrashSequence(
__null, 2268); __attribute__((nomerge)) ::abort(); } while (false
); } } while (false)
;
2269 mBatching.mCounter--;
2270
2271 if (mBatching.mCounter == 0) {
2272 AddChangeReasons(aReasons);
2273 mCaretMoveAmount = eSelectNoAmount;
2274 // Be aware, the Selection instance may be destroyed after this call,
2275 // hence make sure that this instance remains until the end of this call.
2276 RefPtr frameSelection = this;
2277 for (auto selectionType : kPresentSelectionTypes) {
2278 // This returns NS_ERROR_FAILURE if being called for a selection that is
2279 // not present. We don't care about that here, so we silently ignore it
2280 // and continue.
2281 (void)NotifySelectionListeners(selectionType, IsBatchingEnd::Yes);
2282 }
2283 }
2284}
2285
2286nsresult nsFrameSelection::NotifySelectionListeners(
2287 SelectionType aSelectionType, IsBatchingEnd aEndBatching) {
2288 if (RefPtr<Selection> selection = GetSelection(aSelectionType)) {
2289 if (aEndBatching == IsBatchingEnd::Yes &&
2290 !selection->ChangesDuringBatching()) {
2291 return NS_OK;
2292 }
2293 selection->NotifySelectionListeners();
2294 mCaretMoveAmount = eSelectNoAmount;
2295 return NS_OK;
2296 }
2297 return NS_ERROR_FAILURE;
2298}
2299
2300// Start of Table Selection methods
2301
2302static bool IsCell(nsIContent* aContent) {
2303 return aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th);
2304}
2305
2306// static
2307nsITableCellLayout* nsFrameSelection::GetCellLayout(
2308 const nsIContent* aCellContent) {
2309 nsITableCellLayout* cellLayoutObject =
2310 do_QueryFrame(aCellContent->GetPrimaryFrame());
2311 return cellLayoutObject;
2312}
2313
2314nsresult nsFrameSelection::ClearNormalSelection() {
2315 RefPtr<Selection> selection = &NormalSelection();
2316 ErrorResult err;
2317 selection->RemoveAllRanges(err);
2318 return err.StealNSResult();
2319}
2320
2321static nsIContent* GetFirstSelectedContent(const nsRange* aRange) {
2322 if (!aRange) {
2323 return nullptr;
2324 }
2325
2326 MOZ_ASSERT(aRange->GetStartContainer(), "Must have start parent!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aRange->GetStartContainer())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(aRange->GetStartContainer
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aRange->GetStartContainer()" " (" "Must have start parent!"
")", "./../../../layout/generic/nsFrameSelection.cpp", 2326)
; AnnotateMozCrashReason("MOZ_ASSERT" "(" "aRange->GetStartContainer()"
") (" "Must have start parent!" ")"); do { MOZ_CrashSequence
(__null, 2326); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2327 MOZ_ASSERT(aRange->GetStartContainer()->IsElement(), "Unexpected parent")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aRange->GetStartContainer()->IsElement())>::
isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aRange->GetStartContainer()->IsElement()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("aRange->GetStartContainer()->IsElement()"
" (" "Unexpected parent" ")", "./../../../layout/generic/nsFrameSelection.cpp"
, 2327); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aRange->GetStartContainer()->IsElement()"
") (" "Unexpected parent" ")"); do { MOZ_CrashSequence(__null
, 2327); __attribute__((nomerge)) ::abort(); } while (false);
} } while (false)
;
2328
2329 return aRange->GetChildAtStartOffset();
2330}
2331
2332// Table selection support.
2333// TODO: Separate table methods into a separate nsITableSelection interface
2334nsresult nsFrameSelection::HandleTableSelection(nsINode* aParentContent,
2335 int32_t aContentOffset,
2336 TableSelectionMode aTarget,
2337 WidgetMouseEvent* aMouseEvent) {
2338 RefPtr<Selection> selection = &NormalSelection();
2339 return mTableSelection.HandleSelection(aParentContent, aContentOffset,
2340 aTarget, aMouseEvent, mDragState,
2341 *selection);
2342}
2343
2344nsresult nsFrameSelection::TableSelection::HandleSelection(
2345 nsINode* aParentContent, int32_t aContentOffset, TableSelectionMode aTarget,
2346 WidgetMouseEvent* aMouseEvent, bool aDragState,
2347 Selection& aNormalSelection) {
2348 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 2348); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 2348); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2349
2350 NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER)do { if ((__builtin_expect(!!(!(aParentContent)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "aParentContent" ") failed"
, nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 2350
); return NS_ERROR_NULL_POINTER; } } while (false)
;
2351 NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER)do { if ((__builtin_expect(!!(!(aMouseEvent)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "aMouseEvent" ") failed"
, nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 2351
); return NS_ERROR_NULL_POINTER; } } while (false)
;
2352
2353 if (aDragState && mDragSelectingCells &&
2354 aTarget == TableSelectionMode::Table) {
2355 // We were selecting cells and user drags mouse in table border or inbetween
2356 // cells,
2357 // just do nothing
2358 return NS_OK;
2359 }
2360
2361 RefPtr<nsIContent> childContent =
2362 aParentContent->GetChildAt_Deprecated(aContentOffset);
2363
2364 // When doing table selection, always set the direction to next so
2365 // we can be sure that anchorNode's offset always points to the
2366 // selected cell
2367 aNormalSelection.SetDirection(eDirNext);
2368
2369 // Stack-class to wrap all table selection changes in
2370 // BeginBatchChanges() / EndBatchChanges()
2371 SelectionBatcher selectionBatcher(&aNormalSelection, __FUNCTION__);
2372
2373 if (aDragState && mDragSelectingCells) {
2374 return HandleDragSelecting(aTarget, childContent, aMouseEvent,
2375 aNormalSelection);
2376 }
2377
2378 return HandleMouseUpOrDown(aTarget, aDragState, childContent, aParentContent,
2379 aContentOffset, aMouseEvent, aNormalSelection);
2380}
2381
2382class nsFrameSelection::TableSelection::RowAndColumnRelation {
2383 public:
2384 static Result<RowAndColumnRelation, nsresult> Create(
2385 const nsIContent* aFirst, const nsIContent* aSecond) {
2386 RowAndColumnRelation result;
2387
2388 nsresult errorResult =
2389 GetCellIndexes(aFirst, result.mFirst.mRow, result.mFirst.mColumn);
2390 if (NS_FAILED(errorResult)((bool)(__builtin_expect(!!(NS_FAILED_impl(errorResult)), 0))
)
) {
2391 return Err(errorResult);
2392 }
2393
2394 errorResult =
2395 GetCellIndexes(aSecond, result.mSecond.mRow, result.mSecond.mColumn);
2396 if (NS_FAILED(errorResult)((bool)(__builtin_expect(!!(NS_FAILED_impl(errorResult)), 0))
)
) {
2397 return Err(errorResult);
2398 }
2399
2400 return result;
2401 }
2402
2403 bool IsSameColumn() const { return mFirst.mColumn == mSecond.mColumn; }
2404
2405 bool IsSameRow() const { return mFirst.mRow == mSecond.mRow; }
2406
2407 private:
2408 RowAndColumnRelation() = default;
2409
2410 struct RowAndColumn {
2411 int32_t mRow = 0;
2412 int32_t mColumn = 0;
2413 };
2414
2415 RowAndColumn mFirst;
2416 RowAndColumn mSecond;
2417};
2418
2419nsresult nsFrameSelection::TableSelection::HandleDragSelecting(
2420 TableSelectionMode aTarget, nsIContent* aChildContent,
2421 const WidgetMouseEvent* aMouseEvent, Selection& aNormalSelection) {
2422 // We are drag-selecting
2423 if (aTarget != TableSelectionMode::Table) {
2424 // If dragging in the same cell as last event, do nothing
2425 if (mEndSelectedCell == aChildContent) {
2426 return NS_OK;
2427 }
2428
2429#ifdef DEBUG_TABLE_SELECTION
2430 printf(
2431 " mStartSelectedCell = %p, "
2432 "mEndSelectedCell = %p, aChildContent = %p "
2433 "\n",
2434 mStartSelectedCell.get(), mEndSelectedCell.get(), aChildContent);
2435#endif
2436 // aTarget can be any "cell mode",
2437 // so we can easily drag-select rows and columns
2438 // Once we are in row or column mode,
2439 // we can drift into any cell to stay in that mode
2440 // even if aTarget = TableSelectionMode::Cell
2441
2442 if (mMode == TableSelectionMode::Row ||
2443 mMode == TableSelectionMode::Column) {
2444 if (mEndSelectedCell) {
2445 Result<RowAndColumnRelation, nsresult> rowAndColumnRelation =
2446 RowAndColumnRelation::Create(mEndSelectedCell, aChildContent);
2447
2448 if (rowAndColumnRelation.isErr()) {
2449 return rowAndColumnRelation.unwrapErr();
2450 }
2451
2452 if ((mMode == TableSelectionMode::Row &&
2453 rowAndColumnRelation.inspect().IsSameRow()) ||
2454 (mMode == TableSelectionMode::Column &&
2455 rowAndColumnRelation.inspect().IsSameColumn())) {
2456 return NS_OK;
2457 }
2458 }
2459#ifdef DEBUG_TABLE_SELECTION
2460 printf(" Dragged into a new column or row\n");
2461#endif
2462 // Continue dragging row or column selection
2463
2464 return SelectRowOrColumn(aChildContent, aNormalSelection);
2465 }
2466 if (mMode == TableSelectionMode::Cell) {
2467#ifdef DEBUG_TABLE_SELECTION
2468 printf("HandleTableSelection: Dragged into a new cell\n");
2469#endif
2470 // Trick for quick selection of rows and columns
2471 // Hold down shift, then start selecting in one direction
2472 // If next cell dragged into is in same row, select entire row,
2473 // if next cell is in same column, select entire column
2474 if (mStartSelectedCell && aMouseEvent->IsShift()) {
2475 Result<RowAndColumnRelation, nsresult> rowAndColumnRelation =
2476 RowAndColumnRelation::Create(mStartSelectedCell, aChildContent);
2477 if (rowAndColumnRelation.isErr()) {
2478 return rowAndColumnRelation.unwrapErr();
2479 }
2480
2481 if (rowAndColumnRelation.inspect().IsSameRow() ||
2482 rowAndColumnRelation.inspect().IsSameColumn()) {
2483 // Force new selection block
2484 mStartSelectedCell = nullptr;
2485 aNormalSelection.RemoveAllRanges(IgnoreErrors());
2486
2487 if (rowAndColumnRelation.inspect().IsSameRow()) {
2488 mMode = TableSelectionMode::Row;
2489 } else {
2490 mMode = TableSelectionMode::Column;
2491 }
2492
2493 return SelectRowOrColumn(aChildContent, aNormalSelection);
2494 }
2495 }
2496
2497 // Reselect block of cells to new end location
2498 const nsCOMPtr<nsIContent> startSelectedCell = mStartSelectedCell;
2499 return SelectBlockOfCells(startSelectedCell, aChildContent,
2500 aNormalSelection);
2501 }
2502 }
2503 // Do nothing if dragging in table, but outside a cell
2504 return NS_OK;
2505}
2506
2507nsresult nsFrameSelection::TableSelection::HandleMouseUpOrDown(
2508 TableSelectionMode aTarget, bool aDragState, nsIContent* aChildContent,
2509 nsINode* aParentContent, int32_t aContentOffset,
2510 const WidgetMouseEvent* aMouseEvent, Selection& aNormalSelection) {
2511 nsresult result = NS_OK;
2512 // Not dragging -- mouse event is down or up
2513 if (aDragState) {
2514#ifdef DEBUG_TABLE_SELECTION
2515 printf("HandleTableSelection: Mouse down event\n");
2516#endif
2517 // Clear cell we stored in mouse-down
2518 mUnselectCellOnMouseUp = nullptr;
2519
2520 if (aTarget == TableSelectionMode::Cell) {
2521 bool isSelected = false;
2522
2523 // Check if we have other selected cells
2524 nsIContent* previousCellNode =
2525 GetFirstSelectedContent(GetFirstCellRange(aNormalSelection));
2526 if (previousCellNode) {
2527 // We have at least 1 other selected cell
2528
2529 // Check if new cell is already selected
2530 nsIFrame* cellFrame = aChildContent->GetPrimaryFrame();
2531 if (!cellFrame) {
2532 return NS_ERROR_NULL_POINTER;
2533 }
2534 isSelected = cellFrame->IsSelected();
2535 } else {
2536 // No cells selected -- remove non-cell selection
2537 aNormalSelection.RemoveAllRanges(IgnoreErrors());
2538 }
2539 mDragSelectingCells = true; // Signal to start drag-cell-selection
2540 mMode = aTarget;
2541 // Set start for new drag-selection block (not appended)
2542 mStartSelectedCell = aChildContent;
2543 // The initial block end is same as the start
2544 mEndSelectedCell = aChildContent;
2545
2546 if (isSelected) {
2547 // Remember this cell to (possibly) unselect it on mouseup
2548 mUnselectCellOnMouseUp = aChildContent;
2549#ifdef DEBUG_TABLE_SELECTION
2550 printf(
2551 "HandleTableSelection: Saving "
2552 "mUnselectCellOnMouseUp\n");
2553#endif
2554 } else {
2555 // Select an unselected cell
2556 // but first remove existing selection if not in same table
2557 if (previousCellNode &&
2558 !IsInSameTable(previousCellNode, aChildContent)) {
2559 aNormalSelection.RemoveAllRanges(IgnoreErrors());
2560 // Reset selection mode that is cleared in RemoveAllRanges
2561 mMode = aTarget;
2562 }
2563
2564 return ::SelectCellElement(aChildContent, aNormalSelection);
2565 }
2566
2567 return NS_OK;
2568 }
2569 if (aTarget == TableSelectionMode::Table) {
2570 // TODO: We currently select entire table when clicked between cells,
2571 // should we restrict to only around border?
2572 // *** How do we get location data for cell and click?
2573 mDragSelectingCells = false;
2574 mStartSelectedCell = nullptr;
2575 mEndSelectedCell = nullptr;
2576
2577 // Remove existing selection and select the table
2578 aNormalSelection.RemoveAllRanges(IgnoreErrors());
2579 return CreateAndAddRange(aParentContent, aContentOffset,
2580 aNormalSelection);
2581 }
2582 if (aTarget == TableSelectionMode::Row ||
2583 aTarget == TableSelectionMode::Column) {
2584#ifdef DEBUG_TABLE_SELECTION
2585 printf("aTarget == %d\n", aTarget);
2586#endif
2587
2588 // Start drag-selecting mode so multiple rows/cols can be selected
2589 // Note: Currently, nsIFrame::GetDataForTableSelection
2590 // will never call us for row or column selection on mouse down
2591 mDragSelectingCells = true;
2592
2593 // Force new selection block
2594 mStartSelectedCell = nullptr;
2595 aNormalSelection.RemoveAllRanges(IgnoreErrors());
2596 // Always do this AFTER RemoveAllRanges
2597 mMode = aTarget;
2598
2599 return SelectRowOrColumn(aChildContent, aNormalSelection);
2600 }
2601 } else {
2602#ifdef DEBUG_TABLE_SELECTION
2603 printf(
2604 "HandleTableSelection: Mouse UP event. "
2605 "mDragSelectingCells=%d, "
2606 "mStartSelectedCell=%p\n",
2607 mDragSelectingCells, mStartSelectedCell.get());
2608#endif
2609 // First check if we are extending a block selection
2610 const uint32_t rangeCount = aNormalSelection.RangeCount();
2611
2612 if (rangeCount > 0 && aMouseEvent->IsShift() && mAppendStartSelectedCell &&
2613 mAppendStartSelectedCell != aChildContent) {
2614 // Shift key is down: append a block selection
2615 mDragSelectingCells = false;
2616
2617 const OwningNonNull<nsIContent> appendStartSelectedCell =
2618 *mAppendStartSelectedCell;
2619 return SelectBlockOfCells(appendStartSelectedCell, aChildContent,
2620 aNormalSelection);
2621 }
2622
2623 if (mDragSelectingCells) {
2624 mAppendStartSelectedCell = mStartSelectedCell;
2625 }
2626
2627 mDragSelectingCells = false;
2628 mStartSelectedCell = nullptr;
2629 mEndSelectedCell = nullptr;
2630
2631 // Any other mouseup actions require that Ctrl or Cmd key is pressed
2632 // else stop table selection mode
2633 bool doMouseUpAction = false;
2634#ifdef XP_MACOSX
2635 doMouseUpAction = aMouseEvent->IsMeta();
2636#else
2637 doMouseUpAction = aMouseEvent->IsControl();
2638#endif
2639 if (!doMouseUpAction) {
2640#ifdef DEBUG_TABLE_SELECTION
2641 printf(
2642 "HandleTableSelection: Ending cell selection on mouseup: "
2643 "mAppendStartSelectedCell=%p\n",
2644 mAppendStartSelectedCell.get());
2645#endif
2646 return NS_OK;
2647 }
2648 // Unselect a cell only if it wasn't
2649 // just selected on mousedown
2650 if (aChildContent == mUnselectCellOnMouseUp) {
2651 // Scan ranges to find the cell to unselect (the selection range to
2652 // remove)
2653 // XXXbz it's really weird that this lives outside the loop, so once we
2654 // find one we keep looking at it even if we find no more cells...
2655 nsINode* previousCellParent = nullptr;
2656#ifdef DEBUG_TABLE_SELECTION
2657 printf(
2658 "HandleTableSelection: Unselecting "
2659 "mUnselectCellOnMouseUp; "
2660 "rangeCount=%d\n",
2661 rangeCount);
2662#endif
2663 for (const uint32_t i : IntegerRange(rangeCount)) {
2664 MOZ_ASSERT(aNormalSelection.RangeCount() == rangeCount)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.RangeCount() == rangeCount)>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(aNormalSelection.RangeCount() == rangeCount))), 0))) { do {
} while (false); MOZ_ReportAssertionFailure("aNormalSelection.RangeCount() == rangeCount"
, "./../../../layout/generic/nsFrameSelection.cpp", 2664); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.RangeCount() == rangeCount"
")"); do { MOZ_CrashSequence(__null, 2664); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2665 // Strong reference, because sometimes we want to remove
2666 // this range, and then we might be the only owner.
2667 RefPtr<nsRange> range = aNormalSelection.GetRangeAt(i);
2668 if (MOZ_UNLIKELY(!range)(__builtin_expect(!!(!range), 0))) {
2669 return NS_ERROR_NULL_POINTER;
2670 }
2671
2672 nsINode* container = range->GetStartContainer();
2673 if (!container) {
2674 return NS_ERROR_NULL_POINTER;
2675 }
2676
2677 int32_t offset = range->StartOffset();
2678 // Be sure previous selection is a table cell
2679 nsIContent* child = range->GetChildAtStartOffset();
2680 if (child && IsCell(child)) {
2681 previousCellParent = container;
2682 }
2683
2684 // We're done if we didn't find parent of a previously-selected cell
2685 if (!previousCellParent) {
2686 break;
2687 }
2688
2689 if (previousCellParent == aParentContent && offset == aContentOffset) {
2690 // Cell is already selected
2691 if (rangeCount == 1) {
2692#ifdef DEBUG_TABLE_SELECTION
2693 printf("HandleTableSelection: Unselecting single selected cell\n");
2694#endif
2695 // This was the only cell selected.
2696 // Collapse to "normal" selection inside the cell
2697 mStartSelectedCell = nullptr;
2698 mEndSelectedCell = nullptr;
2699 mAppendStartSelectedCell = nullptr;
2700 // TODO: We need a "Collapse to just before deepest child" routine
2701 // Even better, should we collapse to just after the LAST deepest
2702 // child
2703 // (i.e., at the end of the cell's contents)?
2704 return aNormalSelection.CollapseInLimiter(aChildContent, 0);
2705 }
2706#ifdef DEBUG_TABLE_SELECTION
2707 printf(
2708 "HandleTableSelection: Removing cell from multi-cell "
2709 "selection\n");
2710#endif
2711 // Unselecting the start of previous block
2712 // XXX What do we use now!
2713 if (aChildContent == mAppendStartSelectedCell) {
2714 mAppendStartSelectedCell = nullptr;
2715 }
2716
2717 // Deselect cell by removing its range from selection
2718 ErrorResult err;
2719 aNormalSelection.RemoveRangeAndUnselectFramesAndNotifyListeners(
2720 *range, err);
2721 return err.StealNSResult();
2722 }
2723 }
2724 mUnselectCellOnMouseUp = nullptr;
2725 }
2726 }
2727 return result;
2728}
2729
2730nsresult nsFrameSelection::TableSelection::SelectBlockOfCells(
2731 nsIContent* aStartCell, nsIContent* aEndCell, Selection& aNormalSelection) {
2732 NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER)do { if ((__builtin_expect(!!(!(aStartCell)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "aStartCell" ") failed",
nullptr, "./../../../layout/generic/nsFrameSelection.cpp", 2732
); return NS_ERROR_NULL_POINTER; } } while (false)
;
2733 NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER)do { if ((__builtin_expect(!!(!(aEndCell)), 0))) { NS_DebugBreak
(NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "aEndCell" ") failed", nullptr
, "./../../../layout/generic/nsFrameSelection.cpp", 2733); return
NS_ERROR_NULL_POINTER; } } while (false)
;
2734 mEndSelectedCell = aEndCell;
2735
2736 nsresult result = NS_OK;
2737
2738 // If new end cell is in a different table, do nothing
2739 const RefPtr<const nsIContent> table = IsInSameTable(aStartCell, aEndCell);
2740 if (!table) {
2741 return NS_OK;
2742 }
2743
2744 // Get starting and ending cells' location in the cellmap
2745 int32_t startRowIndex, startColIndex, endRowIndex, endColIndex;
2746 result = GetCellIndexes(aStartCell, startRowIndex, startColIndex);
2747 if (NS_FAILED(result)((bool)(__builtin_expect(!!(NS_FAILED_impl(result)), 0)))) {
2748 return result;
2749 }
2750 result = GetCellIndexes(aEndCell, endRowIndex, endColIndex);
2751 if (NS_FAILED(result)((bool)(__builtin_expect(!!(NS_FAILED_impl(result)), 0)))) {
2752 return result;
2753 }
2754
2755 if (mDragSelectingCells) {
2756 // Drag selecting: remove selected cells outside of new block limits
2757 // TODO: `UnselectCells`'s return value shouldn't be ignored.
2758 UnselectCells(table, startRowIndex, startColIndex, endRowIndex, endColIndex,
2759 true, aNormalSelection);
2760 }
2761
2762 // Note that we select block in the direction of user's mouse dragging,
2763 // which means start cell may be after the end cell in either row or column
2764 return AddCellsToSelection(table, startRowIndex, startColIndex, endRowIndex,
2765 endColIndex, aNormalSelection);
2766}
2767
2768nsresult nsFrameSelection::TableSelection::UnselectCells(
2769 const nsIContent* aTableContent, int32_t aStartRowIndex,
2770 int32_t aStartColumnIndex, int32_t aEndRowIndex, int32_t aEndColumnIndex,
2771 bool aRemoveOutsideOfCellRange, mozilla::dom::Selection& aNormalSelection) {
2772 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 2772); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 2772); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2773
2774 nsTableWrapperFrame* tableFrame =
2775 do_QueryFrame(aTableContent->GetPrimaryFrame());
2776 if (!tableFrame) {
2777 return NS_ERROR_FAILURE;
2778 }
2779
2780 int32_t minRowIndex = std::min(aStartRowIndex, aEndRowIndex);
2781 int32_t maxRowIndex = std::max(aStartRowIndex, aEndRowIndex);
2782 int32_t minColIndex = std::min(aStartColumnIndex, aEndColumnIndex);
2783 int32_t maxColIndex = std::max(aStartColumnIndex, aEndColumnIndex);
2784
2785 // Strong reference because we sometimes remove the range
2786 RefPtr<nsRange> range = GetFirstCellRange(aNormalSelection);
2787 nsIContent* cellNode = GetFirstSelectedContent(range);
2788 MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!range || cellNode)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!range || cellNode))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("!range || cellNode"
" (" "Must have cellNode if had a range" ")", "./../../../layout/generic/nsFrameSelection.cpp"
, 2788); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!range || cellNode"
") (" "Must have cellNode if had a range" ")"); do { MOZ_CrashSequence
(__null, 2788); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2789
2790 int32_t curRowIndex, curColIndex;
2791 while (cellNode) {
2792 nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex);
2793 if (NS_FAILED(result)((bool)(__builtin_expect(!!(NS_FAILED_impl(result)), 0)))) {
2794 return result;
2795 }
2796
2797#ifdef DEBUG_TABLE_SELECTION
2798 if (!range) printf("RemoveCellsToSelection -- range is null\n");
2799#endif
2800
2801 if (range) {
2802 if (aRemoveOutsideOfCellRange) {
2803 if (curRowIndex < minRowIndex || curRowIndex > maxRowIndex ||
2804 curColIndex < minColIndex || curColIndex > maxColIndex) {
2805 aNormalSelection.RemoveRangeAndUnselectFramesAndNotifyListeners(
2806 *range, IgnoreErrors());
2807 // Since we've removed the range, decrement pointer to next range
2808 mSelectedCellIndex--;
2809 }
2810
2811 } else {
2812 // Remove cell from selection if it belongs to the given cells range or
2813 // it is spanned onto the cells range.
2814 nsTableCellFrame* cellFrame =
2815 tableFrame->GetCellFrameAt(curRowIndex, curColIndex);
2816
2817 uint32_t origRowIndex = cellFrame->RowIndex();
2818 uint32_t origColIndex = cellFrame->ColIndex();
2819 uint32_t actualRowSpan =
2820 tableFrame->GetEffectiveRowSpanAt(origRowIndex, origColIndex);
2821 uint32_t actualColSpan =
2822 tableFrame->GetEffectiveColSpanAt(curRowIndex, curColIndex);
2823 if (origRowIndex <= static_cast<uint32_t>(maxRowIndex) &&
2824 maxRowIndex >= 0 &&
2825 origRowIndex + actualRowSpan - 1 >=
2826 static_cast<uint32_t>(minRowIndex) &&
2827 origColIndex <= static_cast<uint32_t>(maxColIndex) &&
2828 maxColIndex >= 0 &&
2829 origColIndex + actualColSpan - 1 >=
2830 static_cast<uint32_t>(minColIndex)) {
2831 aNormalSelection.RemoveRangeAndUnselectFramesAndNotifyListeners(
2832 *range, IgnoreErrors());
2833 // Since we've removed the range, decrement pointer to next range
2834 mSelectedCellIndex--;
2835 }
2836 }
2837 }
2838
2839 range = GetNextCellRange(aNormalSelection);
2840 cellNode = GetFirstSelectedContent(range);
2841 MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!range || cellNode)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!range || cellNode))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("!range || cellNode"
" (" "Must have cellNode if had a range" ")", "./../../../layout/generic/nsFrameSelection.cpp"
, 2841); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!range || cellNode"
") (" "Must have cellNode if had a range" ")"); do { MOZ_CrashSequence
(__null, 2841); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2842 }
2843
2844 return NS_OK;
2845}
2846
2847nsresult SelectCellElement(nsIContent* aCellElement,
2848 Selection& aNormalSelection) {
2849 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 2849); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 2849); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2850
2851 nsIContent* parent = aCellElement->GetParent();
2852
2853 // Get child offset
2854 const int32_t offset = parent->ComputeIndexOf_Deprecated(aCellElement);
2855
2856 return CreateAndAddRange(parent, offset, aNormalSelection);
2857}
2858
2859static nsresult AddCellsToSelection(const nsIContent* aTableContent,
2860 int32_t aStartRowIndex,
2861 int32_t aStartColumnIndex,
2862 int32_t aEndRowIndex,
2863 int32_t aEndColumnIndex,
2864 Selection& aNormalSelection) {
2865 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 2865); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 2865); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2866
2867 nsTableWrapperFrame* tableFrame =
2868 do_QueryFrame(aTableContent->GetPrimaryFrame());
2869 if (!tableFrame) { // Check that |table| is a table.
2870 return NS_ERROR_FAILURE;
2871 }
2872
2873 nsresult result = NS_OK;
2874 uint32_t row = aStartRowIndex;
2875 while (true) {
2876 uint32_t col = aStartColumnIndex;
2877 while (true) {
2878 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col);
2879
2880 // Skip cells that are spanned from previous locations or are already
2881 // selected
2882 if (cellFrame) {
2883 uint32_t origRow = cellFrame->RowIndex();
2884 uint32_t origCol = cellFrame->ColIndex();
2885 if (origRow == row && origCol == col && !cellFrame->IsSelected()) {
2886 result = SelectCellElement(cellFrame->GetContent(), aNormalSelection);
2887 if (NS_FAILED(result)((bool)(__builtin_expect(!!(NS_FAILED_impl(result)), 0)))) {
2888 return result;
2889 }
2890 }
2891 }
2892 // Done when we reach end column
2893 if (col == static_cast<uint32_t>(aEndColumnIndex)) {
2894 break;
2895 }
2896
2897 if (aStartColumnIndex < aEndColumnIndex) {
2898 col++;
2899 } else {
2900 col--;
2901 }
2902 }
2903 if (row == static_cast<uint32_t>(aEndRowIndex)) {
2904 break;
2905 }
2906
2907 if (aStartRowIndex < aEndRowIndex) {
2908 row++;
2909 } else {
2910 row--;
2911 }
2912 }
2913 return result;
2914}
2915
2916nsresult nsFrameSelection::RemoveCellsFromSelection(nsIContent* aTable,
2917 int32_t aStartRowIndex,
2918 int32_t aStartColumnIndex,
2919 int32_t aEndRowIndex,
2920 int32_t aEndColumnIndex) {
2921 const RefPtr<Selection> selection = &NormalSelection();
2922 return mTableSelection.UnselectCells(aTable, aStartRowIndex,
2923 aStartColumnIndex, aEndRowIndex,
2924 aEndColumnIndex, false, *selection);
2925}
2926
2927nsresult nsFrameSelection::RestrictCellsToSelection(nsIContent* aTable,
2928 int32_t aStartRowIndex,
2929 int32_t aStartColumnIndex,
2930 int32_t aEndRowIndex,
2931 int32_t aEndColumnIndex) {
2932 const RefPtr<Selection> selection = &NormalSelection();
2933 return mTableSelection.UnselectCells(aTable, aStartRowIndex,
2934 aStartColumnIndex, aEndRowIndex,
2935 aEndColumnIndex, true, *selection);
2936}
2937
2938Result<nsFrameSelection::TableSelection::FirstAndLastCell, nsresult>
2939nsFrameSelection::TableSelection::FindFirstAndLastCellOfRowOrColumn(
2940 const nsIContent& aCellContent) const {
2941 const nsIContent* table = GetParentTable(&aCellContent);
2942 if (!table) {
2943 return Err(NS_ERROR_NULL_POINTER);
2944 }
2945
2946 // Get table and cell layout interfaces to access
2947 // cell data based on cellmap location
2948 // Frames are not ref counted, so don't use an nsCOMPtr
2949 nsTableWrapperFrame* tableFrame = do_QueryFrame(table->GetPrimaryFrame());
2950 if (!tableFrame) {
2951 return Err(NS_ERROR_FAILURE);
2952 }
2953 nsITableCellLayout* cellLayout = GetCellLayout(&aCellContent);
2954 if (!cellLayout) {
2955 return Err(NS_ERROR_FAILURE);
2956 }
2957
2958 // Get location of target cell:
2959 int32_t rowIndex, colIndex;
2960 nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex);
2961 if (NS_FAILED(result)((bool)(__builtin_expect(!!(NS_FAILED_impl(result)), 0)))) {
2962 return Err(result);
2963 }
2964
2965 // Be sure we start at proper beginning
2966 // (This allows us to select row or col given ANY cell!)
2967 if (mMode == TableSelectionMode::Row) {
2968 colIndex = 0;
2969 }
2970 if (mMode == TableSelectionMode::Column) {
2971 rowIndex = 0;
2972 }
2973
2974 FirstAndLastCell firstAndLastCell;
2975 while (true) {
2976 // Loop through all cells in column or row to find first and last
2977 nsCOMPtr<nsIContent> curCellContent =
2978 tableFrame->GetCellAt(rowIndex, colIndex);
2979 if (!curCellContent) {
2980 break;
2981 }
2982
2983 if (!firstAndLastCell.mFirst) {
2984 firstAndLastCell.mFirst = curCellContent;
2985 }
2986
2987 firstAndLastCell.mLast = std::move(curCellContent);
2988
2989 // Move to next cell in cellmap, skipping spanned locations
2990 if (mMode == TableSelectionMode::Row) {
2991 colIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
2992 } else {
2993 rowIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
2994 }
2995 }
2996 return firstAndLastCell;
2997}
2998
2999nsresult nsFrameSelection::TableSelection::SelectRowOrColumn(
3000 nsIContent* aCellContent, Selection& aNormalSelection) {
3001 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 3001); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 3001); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3002
3003 if (!aCellContent) {
3004 return NS_ERROR_NULL_POINTER;
3005 }
3006
3007 Result<FirstAndLastCell, nsresult> firstAndLastCell =
3008 FindFirstAndLastCellOfRowOrColumn(*aCellContent);
3009 if (firstAndLastCell.isErr()) {
3010 return firstAndLastCell.unwrapErr();
3011 }
3012
3013 // Use SelectBlockOfCells:
3014 // This will replace existing selection,
3015 // but allow unselecting by dragging out of selected region
3016 if (firstAndLastCell.inspect().mFirst && firstAndLastCell.inspect().mLast) {
3017 nsresult rv{NS_OK};
3018
3019 if (!mStartSelectedCell) {
3020 // We are starting a new block, so select the first cell
3021 rv = ::SelectCellElement(firstAndLastCell.inspect().mFirst,
3022 aNormalSelection);
3023 if (NS_FAILED(rv)((bool)(__builtin_expect(!!(NS_FAILED_impl(rv)), 0)))) {
3024 return rv;
3025 }
3026 mStartSelectedCell = firstAndLastCell.inspect().mFirst;
3027 }
3028
3029 const nsCOMPtr<nsIContent> startSelectedCell = mStartSelectedCell;
3030 rv = SelectBlockOfCells(startSelectedCell,
3031 MOZ_KnownLive(firstAndLastCell.inspect().mLast)(firstAndLastCell.inspect().mLast),
3032 aNormalSelection);
3033
3034 // This gets set to the cell at end of row/col,
3035 // but we need it to be the cell under cursor
3036 mEndSelectedCell = aCellContent;
3037 return rv;
3038 }
3039
3040#if 0
3041// This is a more efficient strategy that appends row to current selection,
3042// but doesn't allow dragging OFF of an existing selection to unselect!
3043 do {
3044 // Loop through all cells in column or row
3045 result = tableLayout->GetCellDataAt(rowIndex, colIndex,
3046 getter_AddRefs(cellElement),
3047 curRowIndex, curColIndex,
3048 rowSpan, colSpan,
3049 actualRowSpan, actualColSpan,
3050 isSelected);
3051 if (NS_FAILED(result)((bool)(__builtin_expect(!!(NS_FAILED_impl(result)), 0)))) return result;
3052 // We're done when cell is not found
3053 if (!cellElement) break;
3054
3055
3056 // Check spans else we infinitely loop
3057 NS_ASSERTION(actualColSpan, "actualColSpan is 0!")do { if (!(actualColSpan)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "actualColSpan is 0!", "actualColSpan", "./../../../layout/generic/nsFrameSelection.cpp"
, 3057); MOZ_PretendNoReturn(); } } while (0)
;
3058 NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!")do { if (!(actualRowSpan)) { NS_DebugBreak(NS_DEBUG_ASSERTION
, "actualRowSpan is 0!", "actualRowSpan", "./../../../layout/generic/nsFrameSelection.cpp"
, 3058); MOZ_PretendNoReturn(); } } while (0)
;
3059
3060 // Skip cells that are already selected or span from outside our region
3061 if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex)
3062 {
3063 result = SelectCellElement(cellElement);
3064 if (NS_FAILED(result)((bool)(__builtin_expect(!!(NS_FAILED_impl(result)), 0)))) return result;
3065 }
3066 // Move to next row or column in cellmap, skipping spanned locations
3067 if (mMode == TableSelectionMode::Row)
3068 colIndex += actualColSpan;
3069 else
3070 rowIndex += actualRowSpan;
3071 }
3072 while (cellElement);
3073#endif
3074
3075 return NS_OK;
3076}
3077
3078// static
3079nsIContent* nsFrameSelection::GetFirstCellNodeInRange(const nsRange* aRange) {
3080 if (!aRange) {
3081 return nullptr;
3082 }
3083
3084 nsIContent* childContent = aRange->GetChildAtStartOffset();
3085 if (!childContent) {
3086 return nullptr;
3087 }
3088 // Don't return node if not a cell
3089 if (!IsCell(childContent)) {
3090 return nullptr;
3091 }
3092
3093 return childContent;
3094}
3095
3096nsRange* nsFrameSelection::TableSelection::GetFirstCellRange(
3097 const mozilla::dom::Selection& aNormalSelection) {
3098 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 3098); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 3098); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3099
3100 nsRange* firstRange = aNormalSelection.GetRangeAt(0);
3101 if (!GetFirstCellNodeInRange(firstRange)) {
3102 return nullptr;
3103 }
3104
3105 // Setup for next cell
3106 mSelectedCellIndex = 1;
3107
3108 return firstRange;
3109}
3110
3111nsRange* nsFrameSelection::TableSelection::GetNextCellRange(
3112 const mozilla::dom::Selection& aNormalSelection) {
3113 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 3113); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 3113); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3114
3115 nsRange* range =
3116 aNormalSelection.GetRangeAt(AssertedCast<uint32_t>(mSelectedCellIndex));
3117
3118 // Get first node in next range of selection - test if it's a cell
3119 if (!GetFirstCellNodeInRange(range)) {
3120 return nullptr;
3121 }
3122
3123 // Setup for next cell
3124 mSelectedCellIndex++;
3125
3126 return range;
3127}
3128
3129// static
3130nsresult nsFrameSelection::GetCellIndexes(const nsIContent* aCell,
3131 int32_t& aRowIndex,
3132 int32_t& aColIndex) {
3133 if (!aCell) {
3134 return NS_ERROR_NULL_POINTER;
3135 }
3136
3137 aColIndex = 0; // initialize out params
3138 aRowIndex = 0;
3139
3140 nsITableCellLayout* cellLayoutObject = GetCellLayout(aCell);
3141 if (!cellLayoutObject) {
3142 return NS_ERROR_FAILURE;
3143 }
3144 return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex);
3145}
3146
3147// static
3148nsIContent* nsFrameSelection::IsInSameTable(const nsIContent* aContent1,
3149 const nsIContent* aContent2) {
3150 if (!aContent1 || !aContent2) {
3151 return nullptr;
3152 }
3153
3154 nsIContent* tableNode1 = GetParentTable(aContent1);
3155 nsIContent* tableNode2 = GetParentTable(aContent2);
3156
3157 // Must be in the same table. Note that we want to return false for
3158 // the test if both tables are null.
3159 return (tableNode1 == tableNode2) ? tableNode1 : nullptr;
3160}
3161
3162// static
3163nsIContent* nsFrameSelection::GetParentTable(const nsIContent* aCell) {
3164 if (!aCell) {
3165 return nullptr;
3166 }
3167
3168 for (nsIContent* parent = aCell->GetParent(); parent;
3169 parent = parent->GetParent()) {
3170 if (parent->IsHTMLElement(nsGkAtoms::table)) {
3171 return parent;
3172 }
3173 }
3174
3175 return nullptr;
3176}
3177
3178nsresult nsFrameSelection::SelectCellElement(nsIContent* aCellElement) {
3179 const RefPtr<Selection> selection = &NormalSelection();
3180 return ::SelectCellElement(aCellElement, *selection);
3181}
3182
3183nsresult CreateAndAddRange(nsINode* aContainer, int32_t aOffset,
3184 Selection& aNormalSelection) {
3185 MOZ_ASSERT(aNormalSelection.Type() == SelectionType::eNormal)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aNormalSelection.Type() == SelectionType::eNormal)>
::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aNormalSelection.Type() == SelectionType::eNormal)))
, 0))) { do { } while (false); MOZ_ReportAssertionFailure("aNormalSelection.Type() == SelectionType::eNormal"
, "./../../../layout/generic/nsFrameSelection.cpp", 3185); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aNormalSelection.Type() == SelectionType::eNormal"
")"); do { MOZ_CrashSequence(__null, 3185); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3186
3187 if (!aContainer) {
3188 return NS_ERROR_NULL_POINTER;
3189 }
3190
3191 // Set range around child at given offset
3192 ErrorResult error;
3193 RefPtr<nsRange> range =
3194 nsRange::Create(aContainer, aOffset, aContainer, aOffset + 1, error);
3195 if (NS_WARN_IF(error.Failed())NS_warn_if_impl(error.Failed(), "error.Failed()", "./../../../layout/generic/nsFrameSelection.cpp"
, 3195)
) {
3196 return error.StealNSResult();
3197 }
3198 MOZ_ASSERT(range)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(range)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(range))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("range", "./../../../layout/generic/nsFrameSelection.cpp"
, 3198); AnnotateMozCrashReason("MOZ_ASSERT" "(" "range" ")")
; do { MOZ_CrashSequence(__null, 3198); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3199
3200 ErrorResult err;
3201 aNormalSelection.AddRangeAndSelectFramesAndNotifyListeners(*range, err);
3202 return err.StealNSResult();
3203}
3204
3205// End of Table Selection
3206
3207void nsFrameSelection::SetAncestorLimiter(Element* aLimiter) {
3208 if (mLimiters.mAncestorLimiter != aLimiter) {
3209 mLimiters.mAncestorLimiter = aLimiter;
3210 const Selection& sel = NormalSelection();
3211 LogSelectionAPI(&sel, __FUNCTION__, "aLimiter", aLimiter);
3212
3213 const bool hasOutOfBoundsRanges = [&]() {
3214 if (!mLimiters.HasLimiters()) {
3215 return false;
3216 }
3217 for (const uint32_t i : IntegerRange(sel.RangeCount())) {
3218 const auto* range = sel.GetRangeAt(i);
3219 MOZ_ASSERT(range)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(range)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(range))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("range", "./../../../layout/generic/nsFrameSelection.cpp"
, 3219); AnnotateMozCrashReason("MOZ_ASSERT" "(" "range" ")")
; do { MOZ_CrashSequence(__null, 3219); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3220 if (!RangeInLimiters(*range)) {
3221 NS_WARNING(fmt::format("{} (index: {}) is not in the limiters {}",NS_DebugBreak(NS_DEBUG_WARNING, fmt::format("{} (index: {}) is not in the limiters {}"
, RefPtr{range}, i, mLimiters) .c_str(), nullptr, "./../../../layout/generic/nsFrameSelection.cpp"
, 3223)
3222 RefPtr{range}, i, mLimiters)NS_DebugBreak(NS_DEBUG_WARNING, fmt::format("{} (index: {}) is not in the limiters {}"
, RefPtr{range}, i, mLimiters) .c_str(), nullptr, "./../../../layout/generic/nsFrameSelection.cpp"
, 3223)
3223 .c_str())NS_DebugBreak(NS_DEBUG_WARNING, fmt::format("{} (index: {}) is not in the limiters {}"
, RefPtr{range}, i, mLimiters) .c_str(), nullptr, "./../../../layout/generic/nsFrameSelection.cpp"
, 3223)
;
3224 return true;
3225 }
3226 }
3227 return false;
3228 }();
3229
3230 if (hasOutOfBoundsRanges) {
3231 ClearNormalSelection();
3232 if (mLimiters.mAncestorLimiter) {
3233 SetChangeReasons(nsISelectionListener::NO_REASON);
3234 nsCOMPtr<nsIContent> limiter(mLimiters.mAncestorLimiter);
3235 const nsresult rv =
3236 TakeFocus(*limiter, 0, 0, CaretAssociationHint::Before,
3237 FocusMode::kCollapseToNewPoint);
3238 (void)NS_WARN_IF(NS_FAILED(rv))NS_warn_if_impl(((bool)(__builtin_expect(!!(NS_FAILED_impl(rv
)), 0))), "NS_FAILED(rv)", "./../../../layout/generic/nsFrameSelection.cpp"
, 3238)
;
3239 // TODO: in case of failure, propagate it to the callers.
3240 }
3241 }
3242 }
3243}
3244
3245void nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent* aMouseEvent) {
3246 if (aMouseEvent) {
3247 mDelayedMouseEvent.mIsValid = true;
3248 mDelayedMouseEvent.mIsShift = aMouseEvent->IsShift();
3249 mDelayedMouseEvent.mClickCount = aMouseEvent->mClickCount;
3250 } else {
3251 mDelayedMouseEvent.mIsValid = false;
3252 }
3253}
3254
3255void nsFrameSelection::DisconnectFromPresShell() {
3256 if (mAccessibleCaretEnabled) {
3257 Selection& sel = NormalSelection();
3258 sel.StopNotifyingAccessibleCaretEventHub();
3259 }
3260
3261 StopAutoScrollTimer();
3262 for (size_t i = 0; i < std::size(mDomSelections); i++) {
3263 MOZ_ASSERT(mDomSelections[i])do { static_assert( mozilla::detail::AssertionConditionType<
decltype(mDomSelections[i])>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(mDomSelections[i]))), 0))) {
do { } while (false); MOZ_ReportAssertionFailure("mDomSelections[i]"
, "./../../../layout/generic/nsFrameSelection.cpp", 3263); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "mDomSelections[i]" ")"); do { MOZ_CrashSequence
(__null, 3263); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
3264 mDomSelections[i]->Clear(nullptr);
3265 }
3266
3267 if (auto* presshell = mPresShell) {
3268 if (const nsFrameSelection* sel = presshell->GetLastSelectionForToString();
3269 sel == this) {
3270 presshell->UpdateLastSelectionForToString(nullptr);
3271 }
3272 mPresShell = nullptr;
3273 }
3274}
3275
3276#ifdef RUN_MAYBE_UPDATE_SELECTION_CACHE_REPAINT_SELECTION
3277/**
3278 * See Bug 1288453.
3279 *
3280 * Update the selection cache on repaint to handle when a pre-existing
3281 * selection becomes active aka the current selection.
3282 *
3283 * 1. Change the current selection by click n dragging another selection.
3284 * - Make a selection on content page. Make a selection in a text editor.
3285 * - You can click n drag the content selection to make it active again.
3286 * 2. Change the current selection when switching to a tab with a selection.
3287 * - Make selection in tab.
3288 * - Switching tabs will make its respective selection active.
3289 *
3290 * Therefore, we only update the selection cache on a repaint
3291 * if the current selection being repainted is not an empty selection.
3292 *
3293 * If the current selection is empty. The current selection cache
3294 * would be cleared by AutoCopyListener::OnSelectionChange().
3295 */
3296static nsresult MaybeUpdateSelectionCacheOnRepaintSelection(Selection* aSel) {
3297 PresShell* presShell = aSel->GetPresShell();
3298 if (!presShell) {
3299 return NS_OK;
3300 }
3301 nsCOMPtr<Document> aDoc = presShell->GetDocument();
3302
3303 if (aDoc && aSel && !aSel->IsCollapsed()) {
3304 return nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
3305 aSel, aDoc, nsIClipboard::kSelectionCache, false,
3306# ifdef XP_MACOSX
3307 // Update the selection cache on macOS
3308 nsCopySupport::UpdateClipboard::Yes
3309# else
3310 // Do not update the clipboard on the other platforms, just run the
3311 // serializer to detect assertion failures.
3312 nsCopySupport::UpdateClipboard::No
3313# endif
3314 );
3315 }
3316
3317 return NS_OK;
3318}
3319#endif // RUN_MAYBE_UPDATE_SELECTION_CACHE_REPAINT_SELECTION
3320
3321// mozilla::AutoCopyListener
3322
3323/*
3324 * What we do now:
3325 * On every selection change, we copy to the clipboard anew, creating a
3326 * HTML buffer, a transferable, an nsISupportsString and
3327 * a huge mess every time. This is basically what
3328 * nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() does to move the
3329 * selection into the clipboard for Edit->Copy.
3330 *
3331 * What we should do, to make our end of the deal faster:
3332 * Create a singleton transferable with our own magic converter. When selection
3333 * changes (use a quick cache to detect ``real'' changes), we put the new
3334 * Selection in the transferable. Our magic converter will take care of
3335 * transferable->whatever-other-format when the time comes to actually
3336 * hand over the clipboard contents.
3337 *
3338 * Other issues:
3339 * - which X clipboard should we populate?
3340 * - should we use a different one than Edit->Copy, so that inadvertant
3341 * selections (or simple clicks, which currently cause a selection
3342 * notification, regardless of if they're in the document which currently has
3343 * selection!) don't lose the contents of the ``application''? Or should we
3344 * just put some intelligence in the ``is this a real selection?'' code to
3345 * protect our selection against clicks in other documents that don't create
3346 * selections?
3347 * - maybe we should just never clear the X clipboard? That would make this
3348 * problem just go away, which is very tempting.
3349 *
3350 * On macOS,
3351 * nsIClipboard::kSelectionCache is the flag for current selection cache.
3352 * Set the current selection cache on the parent process in
3353 * widget cocoa nsClipboard whenever selection changes.
3354 */
3355
3356// static
3357void AutoCopyListener::OnSelectionChange(Document* aDocument,
3358 Selection& aSelection,
3359 int16_t aReason) {
3360 MOZ_ASSERT(IsEnabled())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(IsEnabled())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(IsEnabled()))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("IsEnabled()", "./../../../layout/generic/nsFrameSelection.cpp"
, 3360); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsEnabled()"
")"); do { MOZ_CrashSequence(__null, 3360); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3361
3362 // For now, we should prevent any updates caused by a call of Selection API.
3363 // We should allow this in some cases later, though. See the valid usage in
3364 // bug 1567160.
3365 if (aReason & nsISelectionListener::JS_REASON) {
3366 return;
3367 }
3368
3369 if (sClipboardID == nsIClipboard::kSelectionCache) {
3370 // Do nothing if this isn't in the active window and,
3371 // in the case of Web content, in the frontmost tab.
3372 if (!aDocument || !IsInActiveTab(aDocument)) {
3373 return;
3374 }
3375 }
3376
3377 static const int16_t kResasonsToHandle =
3378 nsISelectionListener::MOUSEUP_REASON |
3379 nsISelectionListener::SELECTALL_REASON |
3380 nsISelectionListener::KEYPRESS_REASON;
3381 if (!(aReason & kResasonsToHandle)) {
3382 return; // Don't care if we are still dragging.
3383 }
3384
3385 if (!aDocument ||
3386 aSelection.AreNormalAndCrossShadowBoundaryRangesCollapsed()) {
3387#ifdef DEBUG_CLIPBOARD
3388 fprintf(stderrstderr, "CLIPBOARD: no selection/collapsed selection\n");
3389#endif
3390 if (sClipboardID != nsIClipboard::kSelectionCache) {
3391 // XXX Should we clear X clipboard?
3392 return;
3393 }
3394
3395 // If on macOS, clear the current selection transferable cached
3396 // on the parent process (nsClipboard) when the selection is empty.
3397 DebugOnly<nsresult> rv = nsCopySupport::ClearSelectionCache();
3398 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),do { if (!(((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1
))))) { NS_DebugBreak(NS_DEBUG_WARNING, "nsCopySupport::ClearSelectionCache() failed"
, "NS_SUCCEEDED(rv)", "./../../../layout/generic/nsFrameSelection.cpp"
, 3399); } } while (false)
3399 "nsCopySupport::ClearSelectionCache() failed")do { if (!(((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1
))))) { NS_DebugBreak(NS_DEBUG_WARNING, "nsCopySupport::ClearSelectionCache() failed"
, "NS_SUCCEEDED(rv)", "./../../../layout/generic/nsFrameSelection.cpp"
, 3399); } } while (false)
;
3400 return;
3401 }
3402
3403 DebugOnly<nsresult> rv =
3404 nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
3405 &aSelection, aDocument, sClipboardID, false);
3406 NS_WARNING_ASSERTION(do { if (!(((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1
))))) { NS_DebugBreak(NS_DEBUG_WARNING, "nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() failed"
, "NS_SUCCEEDED(rv)", "./../../../layout/generic/nsFrameSelection.cpp"
, 3408); } } while (false)
3407 NS_SUCCEEDED(rv),do { if (!(((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1
))))) { NS_DebugBreak(NS_DEBUG_WARNING, "nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() failed"
, "NS_SUCCEEDED(rv)", "./../../../layout/generic/nsFrameSelection.cpp"
, 3408); } } while (false)
3408 "nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() failed")do { if (!(((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1
))))) { NS_DebugBreak(NS_DEBUG_WARNING, "nsCopySupport::EncodeDocumentWithContextAndPutToClipboard() failed"
, "NS_SUCCEEDED(rv)", "./../../../layout/generic/nsFrameSelection.cpp"
, 3408); } } while (false)
;
3409}