Bug Summary

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