/src/mozilla-central/layout/generic/nsFrameSelection.cpp
Line | Count | Source (jump to first uncovered line) |
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 "mozilla/Attributes.h" |
14 | | #include "mozilla/AutoRestore.h" |
15 | | #include "mozilla/EventStates.h" |
16 | | #include "mozilla/HTMLEditor.h" |
17 | | #include "mozilla/PresShell.h" |
18 | | |
19 | | #include "nsCOMPtr.h" |
20 | | #include "nsString.h" |
21 | | #include "nsISelectionListener.h" |
22 | | #include "nsContentCID.h" |
23 | | #include "nsDeviceContext.h" |
24 | | #include "nsIContent.h" |
25 | | #include "nsRange.h" |
26 | | #include "nsITableCellLayout.h" |
27 | | #include "nsTArray.h" |
28 | | #include "nsTableWrapperFrame.h" |
29 | | #include "nsTableCellFrame.h" |
30 | | #include "nsIScrollableFrame.h" |
31 | | #include "nsCCUncollectableMarker.h" |
32 | | #include "nsIContentIterator.h" |
33 | | #include "nsIDocumentEncoder.h" |
34 | | #include "nsTextFragment.h" |
35 | | #include <algorithm> |
36 | | #include "nsContentUtils.h" |
37 | | #include "nsCSSFrameConstructor.h" |
38 | | |
39 | | #include "nsGkAtoms.h" |
40 | | #include "nsIFrameTraversal.h" |
41 | | #include "nsLayoutUtils.h" |
42 | | #include "nsLayoutCID.h" |
43 | | #include "nsBidiPresUtils.h" |
44 | | static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); |
45 | | #include "nsTextFrame.h" |
46 | | |
47 | | #include "nsContentUtils.h" |
48 | | #include "nsThreadUtils.h" |
49 | | #include "mozilla/Preferences.h" |
50 | | |
51 | | #include "nsPresContext.h" |
52 | | #include "nsIPresShell.h" |
53 | | #include "nsCaret.h" |
54 | | |
55 | | #include "mozilla/MouseEvents.h" |
56 | | #include "mozilla/TextEvents.h" |
57 | | |
58 | | #include "nsITimer.h" |
59 | | // notifications |
60 | | #include "nsIDocument.h" |
61 | | |
62 | | #include "nsISelectionController.h" //for the enums |
63 | | #include "nsCopySupport.h" |
64 | | #include "nsIClipboard.h" |
65 | | #include "nsIFrameInlines.h" |
66 | | |
67 | | #include "nsIBidiKeyboard.h" |
68 | | |
69 | | #include "nsError.h" |
70 | | #include "mozilla/AutoCopyListener.h" |
71 | | #include "mozilla/dom/Element.h" |
72 | | #include "mozilla/dom/Selection.h" |
73 | | #include "mozilla/dom/ShadowRoot.h" |
74 | | #include "mozilla/dom/Text.h" |
75 | | #include "mozilla/ErrorResult.h" |
76 | | #include "mozilla/dom/SelectionBinding.h" |
77 | | #include "mozilla/AsyncEventDispatcher.h" |
78 | | #include "mozilla/Telemetry.h" |
79 | | |
80 | | #include "nsFocusManager.h" |
81 | | #include "nsPIDOMWindow.h" |
82 | | |
83 | | using namespace mozilla; |
84 | | using namespace mozilla::dom; |
85 | | |
86 | | //#define DEBUG_TABLE 1 |
87 | | |
88 | | static bool IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode); |
89 | | |
90 | | static nsAtom *GetTag(nsINode *aNode); |
91 | | // returns the parent |
92 | | static nsINode* ParentOffset(nsINode *aNode, int32_t *aChildOffset); |
93 | | static nsINode* GetCellParent(nsINode *aDomNode); |
94 | | |
95 | | #ifdef PRINT_RANGE |
96 | | static void printRange(nsRange *aDomRange); |
97 | | #define DEBUG_OUT_RANGE(x) printRange(x) |
98 | | #else |
99 | | #define DEBUG_OUT_RANGE(x) |
100 | | #endif // PRINT_RANGE |
101 | | |
102 | | |
103 | | /****************************************************************************** |
104 | | * nsPeekOffsetStruct |
105 | | ******************************************************************************/ |
106 | | |
107 | | //#define DEBUG_SELECTION // uncomment for printf describing every collapse and extend. |
108 | | //#define DEBUG_NAVIGATION |
109 | | |
110 | | |
111 | | //#define DEBUG_TABLE_SELECTION 1 |
112 | | |
113 | | nsPeekOffsetStruct::nsPeekOffsetStruct(nsSelectionAmount aAmount, |
114 | | nsDirection aDirection, |
115 | | int32_t aStartOffset, |
116 | | nsPoint aDesiredPos, |
117 | | bool aJumpLines, |
118 | | bool aScrollViewStop, |
119 | | bool aIsKeyboardSelect, |
120 | | bool aVisual, |
121 | | bool aExtend, |
122 | | EWordMovementType aWordMovementType) |
123 | | : mAmount(aAmount) |
124 | | , mDirection(aDirection) |
125 | | , mStartOffset(aStartOffset) |
126 | | , mDesiredPos(aDesiredPos) |
127 | | , mWordMovementType(aWordMovementType) |
128 | | , mJumpLines(aJumpLines) |
129 | | , mScrollViewStop(aScrollViewStop) |
130 | | , mIsKeyboardSelect(aIsKeyboardSelect) |
131 | | , mVisual(aVisual) |
132 | | , mExtend(aExtend) |
133 | | , mResultContent() |
134 | | , mResultFrame(nullptr) |
135 | | , mContentOffset(0) |
136 | | , mAttach(CARET_ASSOCIATE_BEFORE) |
137 | 0 | { |
138 | 0 | } |
139 | | |
140 | | // Array which contains index of each SelecionType in Selection::mDOMSelections. |
141 | | // For avoiding using if nor switch to retrieve the index, this needs to have |
142 | | // -1 for SelectionTypes which won't be created its Selection instance. |
143 | | static const int8_t kIndexOfSelections[] = { |
144 | | -1, // SelectionType::eInvalid |
145 | | -1, // SelectionType::eNone |
146 | | 0, // SelectionType::eNormal |
147 | | 1, // SelectionType::eSpellCheck |
148 | | 2, // SelectionType::eIMERawClause |
149 | | 3, // SelectionType::eIMESelectedRawClause |
150 | | 4, // SelectionType::eIMEConvertedClause |
151 | | 5, // SelectionType::eIMESelectedClause |
152 | | 6, // SelectionType::eAccessibility |
153 | | 7, // SelectionType::eFind |
154 | | 8, // SelectionType::eURLSecondary |
155 | | 9, // SelectionType::eURLStrikeout |
156 | | }; |
157 | | |
158 | | inline int8_t |
159 | | GetIndexFromSelectionType(SelectionType aSelectionType) |
160 | 0 | { |
161 | 0 | // The enum value of eInvalid is -1 and the others are sequential value |
162 | 0 | // starting from 0. Therefore, |SelectionType + 1| is the index of |
163 | 0 | // kIndexOfSelections. |
164 | 0 | return kIndexOfSelections[static_cast<int8_t>(aSelectionType) + 1]; |
165 | 0 | } |
166 | | |
167 | | /* |
168 | | The limiter is used specifically for the text areas and textfields |
169 | | In that case it is the DIV tag that is anonymously created for the text |
170 | | areas/fields. Text nodes and BR nodes fall beneath it. In the case of a |
171 | | BR node the limiter will be the parent and the offset will point before or |
172 | | after the BR node. In the case of the text node the parent content is |
173 | | the text node itself and the offset will be the exact character position. |
174 | | The offset is not important to check for validity. Simply look at the |
175 | | passed in content. If it equals the limiter then the selection point is valid. |
176 | | If its parent it the limiter then the point is also valid. In the case of |
177 | | NO limiter all points are valid since you are in a topmost iframe. (browser |
178 | | or composer) |
179 | | */ |
180 | | bool |
181 | | IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode) |
182 | 0 | { |
183 | 0 | if (!aFrameSel || !aNode) |
184 | 0 | return false; |
185 | 0 | |
186 | 0 | nsIContent *limiter = aFrameSel->GetLimiter(); |
187 | 0 | if (limiter && limiter != aNode && limiter != aNode->GetParent()) { |
188 | 0 | //if newfocus == the limiter. that's ok. but if not there and not parent bad |
189 | 0 | return false; //not in the right content. tLimiter said so |
190 | 0 | } |
191 | 0 | |
192 | 0 | limiter = aFrameSel->GetAncestorLimiter(); |
193 | 0 | return !limiter || nsContentUtils::ContentIsDescendantOf(aNode, limiter); |
194 | 0 | } |
195 | | |
196 | | namespace mozilla { |
197 | | struct MOZ_RAII AutoPrepareFocusRange |
198 | | { |
199 | | AutoPrepareFocusRange(Selection* aSelection, |
200 | | bool aContinueSelection, |
201 | | bool aMultipleSelection |
202 | | MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
203 | 0 | { |
204 | 0 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
205 | 0 |
|
206 | 0 | if (aSelection->mRanges.Length() <= 1) { |
207 | 0 | return; |
208 | 0 | } |
209 | 0 | |
210 | 0 | if (aSelection->mFrameSelection->IsUserSelectionReason()) { |
211 | 0 | mUserSelect.emplace(aSelection); |
212 | 0 | } |
213 | 0 | bool userSelection = aSelection->mUserInitiated; |
214 | 0 |
|
215 | 0 | nsTArray<RangeData>& ranges = aSelection->mRanges; |
216 | 0 | if (!userSelection || |
217 | 0 | (!aContinueSelection && aMultipleSelection)) { |
218 | 0 | // Scripted command or the user is starting a new explicit multi-range |
219 | 0 | // selection. |
220 | 0 | for (RangeData& entry : ranges) { |
221 | 0 | entry.mRange->SetIsGenerated(false); |
222 | 0 | } |
223 | 0 | return; |
224 | 0 | } |
225 | 0 |
|
226 | 0 | int16_t reason = aSelection->mFrameSelection->mSelectionChangeReason; |
227 | 0 | bool isAnchorRelativeOp = (reason & (nsISelectionListener::DRAG_REASON | |
228 | 0 | nsISelectionListener::MOUSEDOWN_REASON | |
229 | 0 | nsISelectionListener::MOUSEUP_REASON | |
230 | 0 | nsISelectionListener::COLLAPSETOSTART_REASON)); |
231 | 0 | if (!isAnchorRelativeOp) { |
232 | 0 | return; |
233 | 0 | } |
234 | 0 | |
235 | 0 | // This operation is against the anchor but our current mAnchorFocusRange |
236 | 0 | // represents the focus in a multi-range selection. The anchor from a user |
237 | 0 | // perspective is the most distant generated range on the opposite side. |
238 | 0 | // Find that range and make it the mAnchorFocusRange. |
239 | 0 | const size_t len = ranges.Length(); |
240 | 0 | size_t newAnchorFocusIndex = size_t(-1); |
241 | 0 | if (aSelection->GetDirection() == eDirNext) { |
242 | 0 | for (size_t i = 0; i < len; ++i) { |
243 | 0 | if (ranges[i].mRange->IsGenerated()) { |
244 | 0 | newAnchorFocusIndex = i; |
245 | 0 | break; |
246 | 0 | } |
247 | 0 | } |
248 | 0 | } else { |
249 | 0 | size_t i = len; |
250 | 0 | while (i--) { |
251 | 0 | if (ranges[i].mRange->IsGenerated()) { |
252 | 0 | newAnchorFocusIndex = i; |
253 | 0 | break; |
254 | 0 | } |
255 | 0 | } |
256 | 0 | } |
257 | 0 |
|
258 | 0 | if (newAnchorFocusIndex == size_t(-1)) { |
259 | 0 | // There are no generated ranges - that's fine. |
260 | 0 | return; |
261 | 0 | } |
262 | 0 | |
263 | 0 | // Setup the new mAnchorFocusRange and mark the old one as generated. |
264 | 0 | if (aSelection->mAnchorFocusRange) { |
265 | 0 | aSelection->mAnchorFocusRange->SetIsGenerated(true); |
266 | 0 | } |
267 | 0 | nsRange* range = ranges[newAnchorFocusIndex].mRange; |
268 | 0 | range->SetIsGenerated(false); |
269 | 0 | aSelection->mAnchorFocusRange = range; |
270 | 0 |
|
271 | 0 | // Remove all generated ranges (including the old mAnchorFocusRange). |
272 | 0 | RefPtr<nsPresContext> presContext = aSelection->GetPresContext(); |
273 | 0 | size_t i = len; |
274 | 0 | while (i--) { |
275 | 0 | range = aSelection->mRanges[i].mRange; |
276 | 0 | if (range->IsGenerated()) { |
277 | 0 | range->SetSelection(nullptr); |
278 | 0 | aSelection->SelectFrames(presContext, range, false); |
279 | 0 | aSelection->mRanges.RemoveElementAt(i); |
280 | 0 | } |
281 | 0 | } |
282 | 0 | if (aSelection->mFrameSelection) { |
283 | 0 | aSelection->mFrameSelection->InvalidateDesiredPos(); |
284 | 0 | } |
285 | 0 | } |
286 | | |
287 | | Maybe<Selection::AutoUserInitiated> mUserSelect; |
288 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
289 | | }; |
290 | | |
291 | | } // namespace mozilla |
292 | | |
293 | | ////////////BEGIN nsFrameSelection methods |
294 | | |
295 | | nsFrameSelection::nsFrameSelection() |
296 | 0 | { |
297 | 0 | for (size_t i = 0; i < ArrayLength(mDomSelections); i++) { |
298 | 0 | mDomSelections[i] = new Selection(this); |
299 | 0 | mDomSelections[i]->SetType(kPresentSelectionTypes[i]); |
300 | 0 | } |
301 | 0 |
|
302 | | #ifdef XP_MACOSX |
303 | | // On macOS, cache the current selection to send to service menu of macOS. |
304 | | bool enableAutoCopy = true; |
305 | | AutoCopyListener::Init(nsIClipboard::kSelectionCache); |
306 | | #else // #ifdef XP_MACOSX |
307 | | // Check to see if the auto-copy pref is enabled and make the normal |
308 | 0 | // Selection notifies auto-copy listener of its changes. |
309 | 0 | bool enableAutoCopy = AutoCopyListener::IsPrefEnabled(); |
310 | 0 | if (enableAutoCopy) { |
311 | 0 | AutoCopyListener::Init(nsIClipboard::kSelectionClipboard); |
312 | 0 | } |
313 | 0 | #endif // #ifdef XP_MACOSX #else |
314 | 0 |
|
315 | 0 | if (enableAutoCopy) { |
316 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
317 | 0 | if (mDomSelections[index]) { |
318 | 0 | mDomSelections[index]->NotifyAutoCopy(); |
319 | 0 | } |
320 | 0 | } |
321 | 0 | } |
322 | | |
323 | | nsFrameSelection::~nsFrameSelection() |
324 | 0 | { |
325 | 0 | } |
326 | | |
327 | | NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection) |
328 | | |
329 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection) |
330 | 0 | for (size_t i = 0; i < ArrayLength(tmp->mDomSelections); ++i) { |
331 | 0 | tmp->mDomSelections[i] = nullptr; |
332 | 0 | } |
333 | 0 |
|
334 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mCellParent) |
335 | 0 | tmp->mSelectingTableCellMode = TableSelection::None; |
336 | 0 | tmp->mDragSelectingCells = false; |
337 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartSelectedCell) |
338 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSelectedCell) |
339 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mAppendStartSelectedCell) |
340 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnselectCellOnMouseUp) |
341 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainRange) |
342 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiter) |
343 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mAncestorLimiter) |
344 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
345 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection) |
346 | 0 | if (tmp->mShell && tmp->mShell->GetDocument() && |
347 | 0 | nsCCUncollectableMarker::InGeneration(cb, |
348 | 0 | tmp->mShell->GetDocument()-> |
349 | 0 | GetMarkedCCGeneration())) { |
350 | 0 | return NS_SUCCESS_INTERRUPTED_TRAVERSE; |
351 | 0 | } |
352 | 0 | for (size_t i = 0; i < ArrayLength(tmp->mDomSelections); ++i) { |
353 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections[i]) |
354 | 0 | } |
355 | 0 |
|
356 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCellParent) |
357 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartSelectedCell) |
358 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSelectedCell) |
359 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAppendStartSelectedCell) |
360 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnselectCellOnMouseUp) |
361 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainRange) |
362 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiter) |
363 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAncestorLimiter) |
364 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
365 | | |
366 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsFrameSelection, AddRef) |
367 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsFrameSelection, Release) |
368 | | |
369 | | // Get the x (or y, in vertical writing mode) position requested |
370 | | // by the Key Handling for line-up/down |
371 | | nsresult |
372 | | nsFrameSelection::FetchDesiredPos(nsPoint &aDesiredPos) |
373 | 0 | { |
374 | 0 | if (!mShell) { |
375 | 0 | NS_ERROR("fetch desired position failed"); |
376 | 0 | return NS_ERROR_FAILURE; |
377 | 0 | } |
378 | 0 | if (mDesiredPosSet) { |
379 | 0 | aDesiredPos = mDesiredPos; |
380 | 0 | return NS_OK; |
381 | 0 | } |
382 | 0 | |
383 | 0 | RefPtr<nsCaret> caret = mShell->GetCaret(); |
384 | 0 | if (!caret) { |
385 | 0 | return NS_ERROR_NULL_POINTER; |
386 | 0 | } |
387 | 0 | |
388 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
389 | 0 | caret->SetSelection(mDomSelections[index]); |
390 | 0 |
|
391 | 0 | nsRect coord; |
392 | 0 | nsIFrame* caretFrame = caret->GetGeometry(&coord); |
393 | 0 | if (!caretFrame) { |
394 | 0 | return NS_ERROR_FAILURE; |
395 | 0 | } |
396 | 0 | nsPoint viewOffset(0, 0); |
397 | 0 | nsView* view = nullptr; |
398 | 0 | caretFrame->GetOffsetFromView(viewOffset, &view); |
399 | 0 | if (view) { |
400 | 0 | coord += viewOffset; |
401 | 0 | } |
402 | 0 | aDesiredPos = coord.TopLeft(); |
403 | 0 | return NS_OK; |
404 | 0 | } |
405 | | |
406 | | void |
407 | | nsFrameSelection::InvalidateDesiredPos() // do not listen to mDesiredPos; |
408 | | // you must get another. |
409 | 0 | { |
410 | 0 | mDesiredPosSet = false; |
411 | 0 | } |
412 | | |
413 | | void |
414 | | nsFrameSelection::SetDesiredPos(nsPoint aPos) |
415 | 0 | { |
416 | 0 | mDesiredPos = aPos; |
417 | 0 | mDesiredPosSet = true; |
418 | 0 | } |
419 | | |
420 | | nsresult |
421 | | nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(nsIFrame* aFrame, |
422 | | const nsPoint& aPoint, |
423 | | nsIFrame** aRetFrame, |
424 | | nsPoint& aRetPoint) |
425 | 0 | { |
426 | 0 | // |
427 | 0 | // The whole point of this method is to return a frame and point that |
428 | 0 | // that lie within the same valid subtree as the anchor node's frame, |
429 | 0 | // for use with the method GetContentAndOffsetsFromPoint(). |
430 | 0 | // |
431 | 0 | // A valid subtree is defined to be one where all the content nodes in |
432 | 0 | // the tree have a valid parent-child relationship. |
433 | 0 | // |
434 | 0 | // If the anchor frame and aFrame are in the same subtree, aFrame will |
435 | 0 | // be returned in aRetFrame. If they are in different subtrees, we |
436 | 0 | // return the frame for the root of the subtree. |
437 | 0 | // |
438 | 0 |
|
439 | 0 | if (!aFrame || !aRetFrame) |
440 | 0 | return NS_ERROR_NULL_POINTER; |
441 | 0 | |
442 | 0 | *aRetFrame = aFrame; |
443 | 0 | aRetPoint = aPoint; |
444 | 0 |
|
445 | 0 | // |
446 | 0 | // Get the frame and content for the selection's anchor point! |
447 | 0 | // |
448 | 0 |
|
449 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
450 | 0 | if (!mDomSelections[index]) |
451 | 0 | return NS_ERROR_NULL_POINTER; |
452 | 0 | |
453 | 0 | nsCOMPtr<nsIContent> anchorContent = |
454 | 0 | do_QueryInterface(mDomSelections[index]->GetAnchorNode()); |
455 | 0 | if (!anchorContent) |
456 | 0 | return NS_ERROR_FAILURE; |
457 | 0 | |
458 | 0 | // |
459 | 0 | // Now find the root of the subtree containing the anchor's content. |
460 | 0 | // |
461 | 0 | |
462 | 0 | NS_ENSURE_STATE(mShell); |
463 | 0 | nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(mShell); |
464 | 0 | NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED); |
465 | 0 |
|
466 | 0 | // |
467 | 0 | // Now find the root of the subtree containing aFrame's content. |
468 | 0 | // |
469 | 0 |
|
470 | 0 | nsIContent* content = aFrame->GetContent(); |
471 | 0 |
|
472 | 0 | if (content) |
473 | 0 | { |
474 | 0 | nsIContent* contentRoot = content->GetSelectionRootContent(mShell); |
475 | 0 | NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED); |
476 | 0 |
|
477 | 0 | if (anchorRoot == contentRoot) |
478 | 0 | { |
479 | 0 | // If the aFrame's content isn't the capturing content, it should be |
480 | 0 | // a descendant. At this time, we can return simply. |
481 | 0 | nsIContent* capturedContent = nsIPresShell::GetCapturingContent(); |
482 | 0 | if (capturedContent != content) |
483 | 0 | { |
484 | 0 | return NS_OK; |
485 | 0 | } |
486 | 0 | |
487 | 0 | // Find the frame under the mouse cursor with the root frame. |
488 | 0 | // At this time, don't use the anchor's frame because it may not have |
489 | 0 | // fixed positioned frames. |
490 | 0 | nsIFrame* rootFrame = mShell->GetRootFrame(); |
491 | 0 | nsPoint ptInRoot = aPoint + aFrame->GetOffsetTo(rootFrame); |
492 | 0 | nsIFrame* cursorFrame = |
493 | 0 | nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot); |
494 | 0 |
|
495 | 0 | // If the mouse cursor in on a frame which is descendant of same |
496 | 0 | // selection root, we can expand the selection to the frame. |
497 | 0 | if (cursorFrame && cursorFrame->PresShell() == mShell) |
498 | 0 | { |
499 | 0 | nsIContent* cursorContent = cursorFrame->GetContent(); |
500 | 0 | NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE); |
501 | 0 | nsIContent* cursorContentRoot = |
502 | 0 | cursorContent->GetSelectionRootContent(mShell); |
503 | 0 | NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED); |
504 | 0 | if (cursorContentRoot == anchorRoot) |
505 | 0 | { |
506 | 0 | *aRetFrame = cursorFrame; |
507 | 0 | aRetPoint = aPoint + aFrame->GetOffsetTo(cursorFrame); |
508 | 0 | return NS_OK; |
509 | 0 | } |
510 | 0 | } |
511 | 0 | // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse |
512 | 0 | // cursor is out of the window), we should use the frame of the anchor |
513 | 0 | // root. |
514 | 0 | } |
515 | 0 | } |
516 | 0 | |
517 | 0 | // |
518 | 0 | // When we can't find a frame which is under the mouse cursor and has a same |
519 | 0 | // selection root as the anchor node's, we should return the selection root |
520 | 0 | // frame. |
521 | 0 | // |
522 | 0 | |
523 | 0 | *aRetFrame = anchorRoot->GetPrimaryFrame(); |
524 | 0 |
|
525 | 0 | if (!*aRetFrame) |
526 | 0 | return NS_ERROR_FAILURE; |
527 | 0 | |
528 | 0 | // |
529 | 0 | // Now make sure that aRetPoint is converted to the same coordinate |
530 | 0 | // system used by aRetFrame. |
531 | 0 | // |
532 | 0 | |
533 | 0 | aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame); |
534 | 0 |
|
535 | 0 | return NS_OK; |
536 | 0 | } |
537 | | |
538 | | void |
539 | | nsFrameSelection::SetCaretBidiLevel(nsBidiLevel aLevel) |
540 | 0 | { |
541 | 0 | // If the current level is undefined, we have just inserted new text. |
542 | 0 | // In this case, we don't want to reset the keyboard language |
543 | 0 | mCaretBidiLevel = aLevel; |
544 | 0 |
|
545 | 0 | RefPtr<nsCaret> caret; |
546 | 0 | if (mShell && (caret = mShell->GetCaret())) { |
547 | 0 | caret->SchedulePaint(); |
548 | 0 | } |
549 | 0 | } |
550 | | |
551 | | nsBidiLevel |
552 | | nsFrameSelection::GetCaretBidiLevel() const |
553 | 0 | { |
554 | 0 | return mCaretBidiLevel; |
555 | 0 | } |
556 | | |
557 | | void |
558 | | nsFrameSelection::UndefineCaretBidiLevel() |
559 | 0 | { |
560 | 0 | mCaretBidiLevel |= BIDI_LEVEL_UNDEFINED; |
561 | 0 | } |
562 | | |
563 | | #ifdef PRINT_RANGE |
564 | | void printRange(nsRange *aDomRange) |
565 | | { |
566 | | if (!aDomRange) |
567 | | { |
568 | | printf("NULL Range\n"); |
569 | | } |
570 | | nsINode* startNode = aDomRange->GetStartContainer(); |
571 | | nsINode* endNode = aDomRange->GetEndContainer(); |
572 | | int32_t startOffset = aDomRange->StartOffset(); |
573 | | int32_t endOffset = aDomRange->EndOffset(); |
574 | | |
575 | | printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n", |
576 | | (unsigned long)aDomRange, |
577 | | (unsigned long)startNode, (long)startOffset, |
578 | | (unsigned long)endNode, (long)endOffset); |
579 | | |
580 | | } |
581 | | #endif /* PRINT_RANGE */ |
582 | | |
583 | | static |
584 | | nsAtom *GetTag(nsINode *aNode) |
585 | 0 | { |
586 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
587 | 0 | if (!content) |
588 | 0 | { |
589 | 0 | MOZ_ASSERT_UNREACHABLE("bad node passed to GetTag()"); |
590 | 0 | return nullptr; |
591 | 0 | } |
592 | 0 |
|
593 | 0 | return content->NodeInfo()->NameAtom(); |
594 | 0 | } |
595 | | |
596 | | // Returns the parent |
597 | | nsINode* |
598 | | ParentOffset(nsINode *aNode, int32_t *aChildOffset) |
599 | 0 | { |
600 | 0 | if (!aNode || !aChildOffset) |
601 | 0 | return nullptr; |
602 | 0 | |
603 | 0 | nsIContent* parent = aNode->GetParent(); |
604 | 0 | if (parent) |
605 | 0 | { |
606 | 0 | *aChildOffset = parent->ComputeIndexOf(aNode); |
607 | 0 |
|
608 | 0 | return parent; |
609 | 0 | } |
610 | 0 | |
611 | 0 | return nullptr; |
612 | 0 | } |
613 | | |
614 | | static nsINode* |
615 | | GetCellParent(nsINode *aDomNode) |
616 | 0 | { |
617 | 0 | if (!aDomNode) |
618 | 0 | return nullptr; |
619 | 0 | nsINode* current = aDomNode; |
620 | 0 | // Start with current node and look for a table cell |
621 | 0 | while (current) |
622 | 0 | { |
623 | 0 | nsAtom* tag = GetTag(current); |
624 | 0 | if (tag == nsGkAtoms::td || tag == nsGkAtoms::th) |
625 | 0 | return current; |
626 | 0 | current = current->GetParent(); |
627 | 0 | } |
628 | 0 | return nullptr; |
629 | 0 | } |
630 | | |
631 | | void |
632 | | nsFrameSelection::Init(nsIPresShell *aShell, nsIContent *aLimiter, |
633 | | bool aAccessibleCaretEnabled) |
634 | 0 | { |
635 | 0 | mShell = aShell; |
636 | 0 | mDragState = false; |
637 | 0 | mDesiredPosSet = false; |
638 | 0 | mLimiter = aLimiter; |
639 | 0 | mCaretMovementStyle = |
640 | 0 | Preferences::GetInt("bidi.edit.caret_movement_style", 2); |
641 | 0 |
|
642 | 0 | // This should only ever be initialized on the main thread, so we are OK here. |
643 | 0 | static bool prefCachesInitialized = false; |
644 | 0 | if (!prefCachesInitialized) { |
645 | 0 | prefCachesInitialized = true; |
646 | 0 |
|
647 | 0 | Preferences::AddBoolVarCache(&sSelectionEventsEnabled, |
648 | 0 | "dom.select_events.enabled", false); |
649 | 0 | Preferences::AddBoolVarCache(&sSelectionEventsOnTextControlsEnabled, |
650 | 0 | "dom.select_events.textcontrols.enabled", false); |
651 | 0 | } |
652 | 0 |
|
653 | 0 | mAccessibleCaretEnabled = aAccessibleCaretEnabled; |
654 | 0 | if (mAccessibleCaretEnabled) { |
655 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
656 | 0 | mDomSelections[index]->MaybeNotifyAccessibleCaretEventHub(aShell); |
657 | 0 | } |
658 | 0 |
|
659 | 0 | bool plaintextControl = (aLimiter != nullptr); |
660 | 0 | bool initSelectEvents = plaintextControl ? |
661 | 0 | sSelectionEventsOnTextControlsEnabled : |
662 | 0 | sSelectionEventsEnabled; |
663 | 0 |
|
664 | 0 | nsIDocument* doc = aShell->GetDocument(); |
665 | 0 | if (initSelectEvents || |
666 | 0 | (doc && nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()))) { |
667 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
668 | 0 | if (mDomSelections[index]) { |
669 | 0 | mDomSelections[index]->EnableSelectionChangeEvent(); |
670 | 0 | } |
671 | 0 | } |
672 | 0 | } |
673 | | |
674 | | bool nsFrameSelection::sSelectionEventsEnabled = false; |
675 | | bool nsFrameSelection::sSelectionEventsOnTextControlsEnabled = false; |
676 | | |
677 | | nsresult |
678 | | nsFrameSelection::MoveCaret(nsDirection aDirection, |
679 | | bool aContinueSelection, |
680 | | nsSelectionAmount aAmount, |
681 | | CaretMovementStyle aMovementStyle) |
682 | 0 | { |
683 | 0 | bool visualMovement = aMovementStyle == eVisual || |
684 | 0 | (aMovementStyle == eUsePrefStyle && |
685 | 0 | (mCaretMovementStyle == 1 || |
686 | 0 | (mCaretMovementStyle == 2 && !aContinueSelection))); |
687 | 0 |
|
688 | 0 | NS_ENSURE_STATE(mShell); |
689 | 0 | // Flush out layout, since we need it to be up to date to do caret |
690 | 0 | // positioning. |
691 | 0 | mShell->FlushPendingNotifications(FlushType::Layout); |
692 | 0 |
|
693 | 0 | if (!mShell) { |
694 | 0 | return NS_OK; |
695 | 0 | } |
696 | 0 | |
697 | 0 | nsPresContext *context = mShell->GetPresContext(); |
698 | 0 | if (!context) |
699 | 0 | return NS_ERROR_FAILURE; |
700 | 0 | |
701 | 0 | nsPoint desiredPos(0, 0); //we must keep this around and revalidate it when its just UP/DOWN |
702 | 0 |
|
703 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
704 | 0 | RefPtr<Selection> sel = mDomSelections[index]; |
705 | 0 | if (!sel) |
706 | 0 | return NS_ERROR_NULL_POINTER; |
707 | 0 | |
708 | 0 | int32_t scrollFlags = Selection::SCROLL_FOR_CARET_MOVE; |
709 | 0 | nsINode* focusNode = sel->GetFocusNode(); |
710 | 0 | if (focusNode && |
711 | 0 | (focusNode->IsEditable() || |
712 | 0 | (focusNode->IsElement() && |
713 | 0 | focusNode->AsElement()->State(). |
714 | 0 | HasState(NS_EVENT_STATE_MOZ_READWRITE)))) { |
715 | 0 | // If caret moves in editor, it should cause scrolling even if it's in |
716 | 0 | // overflow: hidden;. |
717 | 0 | scrollFlags |= Selection::SCROLL_OVERFLOW_HIDDEN; |
718 | 0 | } |
719 | 0 |
|
720 | 0 | int32_t caretStyle = Preferences::GetInt("layout.selection.caret_style", 0); |
721 | 0 | if (caretStyle == 0 |
722 | | #ifdef XP_WIN |
723 | | && aAmount != eSelectLine |
724 | | #endif |
725 | 0 | ) { |
726 | 0 | // Put caret at the selection edge in the |aDirection| direction. |
727 | 0 | caretStyle = 2; |
728 | 0 | } |
729 | 0 |
|
730 | 0 | bool doCollapse = !sel->IsCollapsed() && !aContinueSelection && caretStyle == 2 && |
731 | 0 | aAmount <= eSelectLine; |
732 | 0 | if (doCollapse) { |
733 | 0 | if (aDirection == eDirPrevious) { |
734 | 0 | PostReason(nsISelectionListener::COLLAPSETOSTART_REASON); |
735 | 0 | mHint = CARET_ASSOCIATE_AFTER; |
736 | 0 | } else { |
737 | 0 | PostReason(nsISelectionListener::COLLAPSETOEND_REASON); |
738 | 0 | mHint = CARET_ASSOCIATE_BEFORE; |
739 | 0 | } |
740 | 0 | } else { |
741 | 0 | PostReason(nsISelectionListener::KEYPRESS_REASON); |
742 | 0 | } |
743 | 0 |
|
744 | 0 | AutoPrepareFocusRange prep(sel, aContinueSelection, false); |
745 | 0 |
|
746 | 0 | if (aAmount == eSelectLine) { |
747 | 0 | nsresult result = FetchDesiredPos(desiredPos); |
748 | 0 | if (NS_FAILED(result)) { |
749 | 0 | return result; |
750 | 0 | } |
751 | 0 | SetDesiredPos(desiredPos); |
752 | 0 | } |
753 | 0 |
|
754 | 0 | if (doCollapse) { |
755 | 0 | const nsRange* anchorFocusRange = sel->GetAnchorFocusRange(); |
756 | 0 | if (anchorFocusRange) { |
757 | 0 | nsINode* node; |
758 | 0 | int32_t offset; |
759 | 0 | if (aDirection == eDirPrevious) { |
760 | 0 | node = anchorFocusRange->GetStartContainer(); |
761 | 0 | offset = anchorFocusRange->StartOffset(); |
762 | 0 | } else { |
763 | 0 | node = anchorFocusRange->GetEndContainer(); |
764 | 0 | offset = anchorFocusRange->EndOffset(); |
765 | 0 | } |
766 | 0 | sel->Collapse(node, offset); |
767 | 0 | } |
768 | 0 | sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, |
769 | 0 | nsIPresShell::ScrollAxis(), |
770 | 0 | nsIPresShell::ScrollAxis(), scrollFlags); |
771 | 0 | return NS_OK; |
772 | 0 | } |
773 | 0 |
|
774 | 0 | nsIFrame *frame; |
775 | 0 | int32_t offsetused = 0; |
776 | 0 | nsresult result = sel->GetPrimaryFrameForFocusNode(&frame, &offsetused, |
777 | 0 | visualMovement); |
778 | 0 |
|
779 | 0 | if (NS_FAILED(result) || !frame) |
780 | 0 | return NS_FAILED(result) ? result : NS_ERROR_FAILURE; |
781 | 0 | |
782 | 0 | //set data using mLimiter to stop on scroll views. If we have a limiter then we stop peeking |
783 | 0 | //when we hit scrollable views. If no limiter then just let it go ahead |
784 | 0 | nsPeekOffsetStruct pos(aAmount, eDirPrevious, offsetused, desiredPos, |
785 | 0 | true, mLimiter != nullptr, true, visualMovement, |
786 | 0 | aContinueSelection); |
787 | 0 |
|
788 | 0 | nsBidiDirection paraDir = nsBidiPresUtils::ParagraphDirection(frame); |
789 | 0 |
|
790 | 0 | CaretAssociateHint tHint(mHint); //temporary variable so we dont set mHint until it is necessary |
791 | 0 | switch (aAmount){ |
792 | 0 | case eSelectCharacter: |
793 | 0 | case eSelectCluster: |
794 | 0 | case eSelectWord: |
795 | 0 | case eSelectWordNoSpace: |
796 | 0 | InvalidateDesiredPos(); |
797 | 0 | pos.mAmount = aAmount; |
798 | 0 | pos.mDirection = (visualMovement && paraDir == NSBIDI_RTL) |
799 | 0 | ? nsDirection(1 - aDirection) : aDirection; |
800 | 0 | break; |
801 | 0 | case eSelectLine: |
802 | 0 | pos.mAmount = aAmount; |
803 | 0 | pos.mDirection = aDirection; |
804 | 0 | break; |
805 | 0 | case eSelectBeginLine: |
806 | 0 | case eSelectEndLine: |
807 | 0 | InvalidateDesiredPos(); |
808 | 0 | pos.mAmount = aAmount; |
809 | 0 | pos.mDirection = (visualMovement && paraDir == NSBIDI_RTL) |
810 | 0 | ? nsDirection(1 - aDirection) : aDirection; |
811 | 0 | break; |
812 | 0 | default: |
813 | 0 | return NS_ERROR_FAILURE; |
814 | 0 | } |
815 | 0 | |
816 | 0 | if (NS_SUCCEEDED(result = frame->PeekOffset(&pos)) && pos.mResultContent) |
817 | 0 | { |
818 | 0 | nsIFrame *theFrame; |
819 | 0 | int32_t currentOffset, frameStart, frameEnd; |
820 | 0 |
|
821 | 0 | if (aAmount <= eSelectWordNoSpace) |
822 | 0 | { |
823 | 0 | // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does not set pos.mAttachForward, |
824 | 0 | // so determine the hint here based on the result frame and offset: |
825 | 0 | // If we're at the end of a text frame, set the hint to ASSOCIATE_BEFORE to indicate that we |
826 | 0 | // want the caret displayed at the end of this frame, not at the beginning of the next one. |
827 | 0 | theFrame = pos.mResultFrame; |
828 | 0 | theFrame->GetOffsets(frameStart, frameEnd); |
829 | 0 | currentOffset = pos.mContentOffset; |
830 | 0 | if (frameEnd == currentOffset && !(frameStart == 0 && frameEnd == 0)) |
831 | 0 | tHint = CARET_ASSOCIATE_BEFORE; |
832 | 0 | else |
833 | 0 | tHint = CARET_ASSOCIATE_AFTER; |
834 | 0 | } else { |
835 | 0 | // For up/down and home/end, pos.mResultFrame might not be set correctly, or not at all. |
836 | 0 | // In these cases, get the frame based on the content and hint returned by PeekOffset(). |
837 | 0 | tHint = pos.mAttach; |
838 | 0 | theFrame = GetFrameForNodeOffset(pos.mResultContent, pos.mContentOffset, |
839 | 0 | tHint, ¤tOffset); |
840 | 0 | if (!theFrame) |
841 | 0 | return NS_ERROR_FAILURE; |
842 | 0 | |
843 | 0 | theFrame->GetOffsets(frameStart, frameEnd); |
844 | 0 | } |
845 | 0 |
|
846 | 0 | if (context->BidiEnabled()) |
847 | 0 | { |
848 | 0 | switch (aAmount) { |
849 | 0 | case eSelectBeginLine: |
850 | 0 | case eSelectEndLine: { |
851 | 0 | // In Bidi contexts, PeekOffset calculates pos.mContentOffset |
852 | 0 | // differently depending on whether the movement is visual or logical. |
853 | 0 | // For visual movement, pos.mContentOffset depends on the direction- |
854 | 0 | // ality of the first/last frame on the line (theFrame), and the caret |
855 | 0 | // directionality must correspond. |
856 | 0 | FrameBidiData bidiData = theFrame->GetBidiData(); |
857 | 0 | SetCaretBidiLevel(visualMovement ? bidiData.embeddingLevel |
858 | 0 | : bidiData.baseLevel); |
859 | 0 | break; |
860 | 0 | } |
861 | 0 | default: |
862 | 0 | // If the current position is not a frame boundary, it's enough just |
863 | 0 | // to take the Bidi level of the current frame |
864 | 0 | if ((pos.mContentOffset != frameStart && |
865 | 0 | pos.mContentOffset != frameEnd) || |
866 | 0 | eSelectLine == aAmount) { |
867 | 0 | SetCaretBidiLevel(theFrame->GetEmbeddingLevel()); |
868 | 0 | } |
869 | 0 | else { |
870 | 0 | BidiLevelFromMove(mShell, pos.mResultContent, pos.mContentOffset, |
871 | 0 | aAmount, tHint); |
872 | 0 | } |
873 | 0 | } |
874 | 0 | } |
875 | 0 | result = TakeFocus(pos.mResultContent, pos.mContentOffset, pos.mContentOffset, |
876 | 0 | tHint, aContinueSelection, false); |
877 | 0 | } else if (aAmount <= eSelectWordNoSpace && aDirection == eDirNext && |
878 | 0 | !aContinueSelection) { |
879 | 0 | // Collapse selection if PeekOffset failed, we either |
880 | 0 | // 1. bumped into the BRFrame, bug 207623 |
881 | 0 | // 2. had select-all in a text input (DIV range), bug 352759. |
882 | 0 | bool isBRFrame = frame->IsBrFrame(); |
883 | 0 | sel->Collapse(sel->GetFocusNode(), sel->FocusOffset()); |
884 | 0 | // Note: 'frame' might be dead here. |
885 | 0 | if (!isBRFrame) { |
886 | 0 | mHint = CARET_ASSOCIATE_BEFORE; // We're now at the end of the frame to the left. |
887 | 0 | } |
888 | 0 | result = NS_OK; |
889 | 0 | } |
890 | 0 | if (NS_SUCCEEDED(result)) |
891 | 0 | { |
892 | 0 | result = mDomSelections[index]-> |
893 | 0 | ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, |
894 | 0 | nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(), |
895 | 0 | scrollFlags); |
896 | 0 | } |
897 | 0 |
|
898 | 0 | return result; |
899 | 0 | } |
900 | | |
901 | | nsPrevNextBidiLevels |
902 | | nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode, |
903 | | uint32_t aContentOffset, |
904 | | bool aJumpLines) const |
905 | 0 | { |
906 | 0 | return GetPrevNextBidiLevels(aNode, aContentOffset, mHint, aJumpLines); |
907 | 0 | } |
908 | | |
909 | | nsPrevNextBidiLevels |
910 | | nsFrameSelection::GetPrevNextBidiLevels(nsIContent* aNode, |
911 | | uint32_t aContentOffset, |
912 | | CaretAssociateHint aHint, |
913 | | bool aJumpLines) const |
914 | 0 | { |
915 | 0 | // Get the level of the frames on each side |
916 | 0 | nsIFrame *currentFrame; |
917 | 0 | int32_t currentOffset; |
918 | 0 | int32_t frameStart, frameEnd; |
919 | 0 | nsDirection direction; |
920 | 0 |
|
921 | 0 | nsPrevNextBidiLevels levels; |
922 | 0 | levels.SetData(nullptr, nullptr, 0, 0); |
923 | 0 |
|
924 | 0 | currentFrame = GetFrameForNodeOffset(aNode, aContentOffset, |
925 | 0 | aHint, ¤tOffset); |
926 | 0 | if (!currentFrame) |
927 | 0 | return levels; |
928 | 0 | |
929 | 0 | currentFrame->GetOffsets(frameStart, frameEnd); |
930 | 0 |
|
931 | 0 | if (0 == frameStart && 0 == frameEnd) |
932 | 0 | direction = eDirPrevious; |
933 | 0 | else if (frameStart == currentOffset) |
934 | 0 | direction = eDirPrevious; |
935 | 0 | else if (frameEnd == currentOffset) |
936 | 0 | direction = eDirNext; |
937 | 0 | else { |
938 | 0 | // we are neither at the beginning nor at the end of the frame, so we have no worries |
939 | 0 | nsBidiLevel currentLevel = currentFrame->GetEmbeddingLevel(); |
940 | 0 | levels.SetData(currentFrame, currentFrame, currentLevel, currentLevel); |
941 | 0 | return levels; |
942 | 0 | } |
943 | 0 | |
944 | 0 | nsIFrame *newFrame; |
945 | 0 | int32_t offset; |
946 | 0 | bool jumpedLine, movedOverNonSelectableText; |
947 | 0 | nsresult rv = currentFrame->GetFrameFromDirection(direction, false, |
948 | 0 | aJumpLines, true, |
949 | 0 | &newFrame, &offset, &jumpedLine, |
950 | 0 | &movedOverNonSelectableText); |
951 | 0 | if (NS_FAILED(rv)) |
952 | 0 | newFrame = nullptr; |
953 | 0 |
|
954 | 0 | FrameBidiData currentBidi = currentFrame->GetBidiData(); |
955 | 0 | nsBidiLevel currentLevel = currentBidi.embeddingLevel; |
956 | 0 | nsBidiLevel newLevel = newFrame ? newFrame->GetEmbeddingLevel() |
957 | 0 | : currentBidi.baseLevel; |
958 | 0 |
|
959 | 0 | // If not jumping lines, disregard br frames, since they might be positioned incorrectly. |
960 | 0 | // XXX This could be removed once bug 339786 is fixed. |
961 | 0 | if (!aJumpLines) { |
962 | 0 | if (currentFrame->IsBrFrame()) { |
963 | 0 | currentFrame = nullptr; |
964 | 0 | currentLevel = currentBidi.baseLevel; |
965 | 0 | } |
966 | 0 | if (newFrame && newFrame->IsBrFrame()) { |
967 | 0 | newFrame = nullptr; |
968 | 0 | newLevel = currentBidi.baseLevel; |
969 | 0 | } |
970 | 0 | } |
971 | 0 |
|
972 | 0 | if (direction == eDirNext) |
973 | 0 | levels.SetData(currentFrame, newFrame, currentLevel, newLevel); |
974 | 0 | else |
975 | 0 | levels.SetData(newFrame, currentFrame, newLevel, currentLevel); |
976 | 0 |
|
977 | 0 | return levels; |
978 | 0 | } |
979 | | |
980 | | nsresult |
981 | | nsFrameSelection::GetFrameFromLevel(nsIFrame *aFrameIn, |
982 | | nsDirection aDirection, |
983 | | nsBidiLevel aBidiLevel, |
984 | | nsIFrame **aFrameOut) const |
985 | 0 | { |
986 | 0 | NS_ENSURE_STATE(mShell); |
987 | 0 | nsBidiLevel foundLevel = 0; |
988 | 0 | nsIFrame *foundFrame = aFrameIn; |
989 | 0 |
|
990 | 0 | nsCOMPtr<nsIFrameEnumerator> frameTraversal; |
991 | 0 | nsresult result; |
992 | 0 | nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID,&result)); |
993 | 0 | if (NS_FAILED(result)) |
994 | 0 | return result; |
995 | 0 | |
996 | 0 | result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal), |
997 | 0 | mShell->GetPresContext(), aFrameIn, |
998 | 0 | eLeaf, |
999 | 0 | false, // aVisual |
1000 | 0 | false, // aLockInScrollView |
1001 | 0 | false, // aFollowOOFs |
1002 | 0 | false // aSkipPopupChecks |
1003 | 0 | ); |
1004 | 0 | if (NS_FAILED(result)) |
1005 | 0 | return result; |
1006 | 0 | |
1007 | 0 | do { |
1008 | 0 | *aFrameOut = foundFrame; |
1009 | 0 | if (aDirection == eDirNext) |
1010 | 0 | frameTraversal->Next(); |
1011 | 0 | else |
1012 | 0 | frameTraversal->Prev(); |
1013 | 0 |
|
1014 | 0 | foundFrame = frameTraversal->CurrentItem(); |
1015 | 0 | if (!foundFrame) |
1016 | 0 | return NS_ERROR_FAILURE; |
1017 | 0 | foundLevel = foundFrame->GetEmbeddingLevel(); |
1018 | 0 |
|
1019 | 0 | } while (foundLevel > aBidiLevel); |
1020 | 0 |
|
1021 | 0 | return NS_OK; |
1022 | 0 | } |
1023 | | |
1024 | | |
1025 | | nsresult |
1026 | | nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount) |
1027 | 0 | { |
1028 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
1029 | 0 | if (!mDomSelections[index]) |
1030 | 0 | return NS_ERROR_NULL_POINTER; |
1031 | 0 | |
1032 | 0 | mMaintainedAmount = aAmount; |
1033 | 0 |
|
1034 | 0 | const nsRange* anchorFocusRange = |
1035 | 0 | mDomSelections[index]->GetAnchorFocusRange(); |
1036 | 0 | if (anchorFocusRange && aAmount != eSelectNoAmount) { |
1037 | 0 | mMaintainRange = anchorFocusRange->CloneRange(); |
1038 | 0 | return NS_OK; |
1039 | 0 | } |
1040 | 0 | |
1041 | 0 | mMaintainRange = nullptr; |
1042 | 0 | return NS_OK; |
1043 | 0 | } |
1044 | | |
1045 | | |
1046 | | /** After moving the caret, its Bidi level is set according to the following rules: |
1047 | | * |
1048 | | * After moving over a character with left/right arrow, set to the Bidi level of the last moved over character. |
1049 | | * After Home and End, set to the paragraph embedding level. |
1050 | | * After up/down arrow, PageUp/Down, set to the lower level of the 2 surrounding characters. |
1051 | | * After mouse click, set to the level of the current frame. |
1052 | | * |
1053 | | * The following two methods use GetPrevNextBidiLevels to determine the new Bidi level. |
1054 | | * BidiLevelFromMove is called when the caret is moved in response to a keyboard event |
1055 | | * |
1056 | | * @param aPresShell is the presentation shell |
1057 | | * @param aNode is the content node |
1058 | | * @param aContentOffset is the new caret position, as an offset into aNode |
1059 | | * @param aAmount is the amount of the move that gave the caret its new position |
1060 | | * @param aHint is the hint indicating in what logical direction the caret moved |
1061 | | */ |
1062 | | void nsFrameSelection::BidiLevelFromMove(nsIPresShell* aPresShell, |
1063 | | nsIContent* aNode, |
1064 | | uint32_t aContentOffset, |
1065 | | nsSelectionAmount aAmount, |
1066 | | CaretAssociateHint aHint) |
1067 | 0 | { |
1068 | 0 | switch (aAmount) { |
1069 | 0 |
|
1070 | 0 | // Movement within the line: the new cursor Bidi level is the level of the |
1071 | 0 | // last character moved over |
1072 | 0 | case eSelectCharacter: |
1073 | 0 | case eSelectCluster: |
1074 | 0 | case eSelectWord: |
1075 | 0 | case eSelectWordNoSpace: |
1076 | 0 | case eSelectBeginLine: |
1077 | 0 | case eSelectEndLine: |
1078 | 0 | case eSelectNoAmount: |
1079 | 0 | { |
1080 | 0 | nsPrevNextBidiLevels levels = GetPrevNextBidiLevels(aNode, aContentOffset, |
1081 | 0 | aHint, false); |
1082 | 0 |
|
1083 | 0 | SetCaretBidiLevel(aHint == CARET_ASSOCIATE_BEFORE ? |
1084 | 0 | levels.mLevelBefore : levels.mLevelAfter); |
1085 | 0 | break; |
1086 | 0 | } |
1087 | 0 | /* |
1088 | 0 | // Up and Down: the new cursor Bidi level is the smaller of the two surrounding characters |
1089 | 0 | case eSelectLine: |
1090 | 0 | case eSelectParagraph: |
1091 | 0 | GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame, &secondFrame, &firstLevel, &secondLevel); |
1092 | 0 | aPresShell->SetCaretBidiLevel(std::min(firstLevel, secondLevel)); |
1093 | 0 | break; |
1094 | 0 | */ |
1095 | 0 |
|
1096 | 0 | default: |
1097 | 0 | UndefineCaretBidiLevel(); |
1098 | 0 | } |
1099 | 0 | } |
1100 | | |
1101 | | /** |
1102 | | * BidiLevelFromClick is called when the caret is repositioned by clicking the mouse |
1103 | | * |
1104 | | * @param aNode is the content node |
1105 | | * @param aContentOffset is the new caret position, as an offset into aNode |
1106 | | */ |
1107 | | void nsFrameSelection::BidiLevelFromClick(nsIContent *aNode, |
1108 | | uint32_t aContentOffset) |
1109 | 0 | { |
1110 | 0 | nsIFrame* clickInFrame=nullptr; |
1111 | 0 | int32_t OffsetNotUsed; |
1112 | 0 |
|
1113 | 0 | clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, mHint, &OffsetNotUsed); |
1114 | 0 | if (!clickInFrame) |
1115 | 0 | return; |
1116 | 0 | |
1117 | 0 | SetCaretBidiLevel(clickInFrame->GetEmbeddingLevel()); |
1118 | 0 | } |
1119 | | |
1120 | | |
1121 | | bool |
1122 | | nsFrameSelection::AdjustForMaintainedSelection(nsIContent *aContent, |
1123 | | int32_t aOffset) |
1124 | 0 | { |
1125 | 0 | if (!mMaintainRange) |
1126 | 0 | return false; |
1127 | 0 | |
1128 | 0 | if (!aContent) { |
1129 | 0 | return false; |
1130 | 0 | } |
1131 | 0 | |
1132 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
1133 | 0 | if (!mDomSelections[index]) |
1134 | 0 | return false; |
1135 | 0 | |
1136 | 0 | nsINode* rangeStartNode = mMaintainRange->GetStartContainer(); |
1137 | 0 | nsINode* rangeEndNode = mMaintainRange->GetEndContainer(); |
1138 | 0 | int32_t rangeStartOffset = mMaintainRange->StartOffset(); |
1139 | 0 | int32_t rangeEndOffset = mMaintainRange->EndOffset(); |
1140 | 0 |
|
1141 | 0 | int32_t relToStart = |
1142 | 0 | nsContentUtils::ComparePoints(rangeStartNode, rangeStartOffset, |
1143 | 0 | aContent, aOffset); |
1144 | 0 | int32_t relToEnd = |
1145 | 0 | nsContentUtils::ComparePoints(rangeEndNode, rangeEndOffset, |
1146 | 0 | aContent, aOffset); |
1147 | 0 |
|
1148 | 0 | // If aContent/aOffset is inside the maintained selection, or if it is on the |
1149 | 0 | // "anchor" side of the maintained selection, we need to do something. |
1150 | 0 | if ((relToStart < 0 && relToEnd > 0) || |
1151 | 0 | (relToStart > 0 && |
1152 | 0 | mDomSelections[index]->GetDirection() == eDirNext) || |
1153 | 0 | (relToEnd < 0 && |
1154 | 0 | mDomSelections[index]->GetDirection() == eDirPrevious)) { |
1155 | 0 | // Set the current range to the maintained range. |
1156 | 0 | mDomSelections[index]->ReplaceAnchorFocusRange(mMaintainRange); |
1157 | 0 | if (relToStart < 0 && relToEnd > 0) { |
1158 | 0 | // We're inside the maintained selection, just keep it selected. |
1159 | 0 | return true; |
1160 | 0 | } |
1161 | 0 | // Reverse the direction of the selection so that the anchor will be on the |
1162 | 0 | // far side of the maintained selection, relative to aContent/aOffset. |
1163 | 0 | mDomSelections[index]->SetDirection(relToStart > 0 ? eDirPrevious : eDirNext); |
1164 | 0 | } |
1165 | 0 | return false; |
1166 | 0 | } |
1167 | | |
1168 | | |
1169 | | nsresult |
1170 | | nsFrameSelection::HandleClick(nsIContent* aNewFocus, |
1171 | | uint32_t aContentOffset, |
1172 | | uint32_t aContentEndOffset, |
1173 | | bool aContinueSelection, |
1174 | | bool aMultipleSelection, |
1175 | | CaretAssociateHint aHint) |
1176 | 0 | { |
1177 | 0 | if (!aNewFocus) |
1178 | 0 | return NS_ERROR_INVALID_ARG; |
1179 | 0 | |
1180 | 0 | InvalidateDesiredPos(); |
1181 | 0 |
|
1182 | 0 | if (!aContinueSelection) { |
1183 | 0 | mMaintainRange = nullptr; |
1184 | 0 | if (!IsValidSelectionPoint(this, aNewFocus)) { |
1185 | 0 | mAncestorLimiter = nullptr; |
1186 | 0 | } |
1187 | 0 | } |
1188 | 0 |
|
1189 | 0 | // Don't take focus when dragging off of a table |
1190 | 0 | if (!mDragSelectingCells) |
1191 | 0 | { |
1192 | 0 | BidiLevelFromClick(aNewFocus, aContentOffset); |
1193 | 0 | PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON); |
1194 | 0 | if (aContinueSelection && |
1195 | 0 | AdjustForMaintainedSelection(aNewFocus, aContentOffset)) |
1196 | 0 | return NS_OK; //shift clicked to maintained selection. rejected. |
1197 | 0 | |
1198 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
1199 | 0 | AutoPrepareFocusRange prep(mDomSelections[index], aContinueSelection, aMultipleSelection); |
1200 | 0 | return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, aHint, |
1201 | 0 | aContinueSelection, aMultipleSelection); |
1202 | 0 | } |
1203 | 0 | |
1204 | 0 | return NS_OK; |
1205 | 0 | } |
1206 | | |
1207 | | void |
1208 | | nsFrameSelection::HandleDrag(nsIFrame* aFrame, const nsPoint& aPoint) |
1209 | 0 | { |
1210 | 0 | if (!aFrame || !mShell) |
1211 | 0 | return; |
1212 | 0 | |
1213 | 0 | nsresult result; |
1214 | 0 | nsIFrame *newFrame = 0; |
1215 | 0 | nsPoint newPoint; |
1216 | 0 |
|
1217 | 0 | result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame, newPoint); |
1218 | 0 | if (NS_FAILED(result)) |
1219 | 0 | return; |
1220 | 0 | if (!newFrame) |
1221 | 0 | return; |
1222 | 0 | |
1223 | 0 | nsIFrame::ContentOffsets offsets = |
1224 | 0 | newFrame->GetContentOffsetsFromPoint(newPoint); |
1225 | 0 | if (!offsets.content) |
1226 | 0 | return; |
1227 | 0 | |
1228 | 0 | if (newFrame->IsSelected() && |
1229 | 0 | AdjustForMaintainedSelection(offsets.content, offsets.offset)) |
1230 | 0 | return; |
1231 | 0 | |
1232 | 0 | // Adjust offsets according to maintained amount |
1233 | 0 | if (mMaintainRange && |
1234 | 0 | mMaintainedAmount != eSelectNoAmount) { |
1235 | 0 |
|
1236 | 0 | nsINode* rangenode = mMaintainRange->GetStartContainer(); |
1237 | 0 | int32_t rangeOffset = mMaintainRange->StartOffset(); |
1238 | 0 | int32_t relativePosition = |
1239 | 0 | nsContentUtils::ComparePoints(rangenode, rangeOffset, |
1240 | 0 | offsets.content, offsets.offset); |
1241 | 0 |
|
1242 | 0 | nsDirection direction = relativePosition > 0 ? eDirPrevious : eDirNext; |
1243 | 0 | nsSelectionAmount amount = mMaintainedAmount; |
1244 | 0 | if (amount == eSelectBeginLine && direction == eDirNext) |
1245 | 0 | amount = eSelectEndLine; |
1246 | 0 |
|
1247 | 0 | int32_t offset; |
1248 | 0 | nsIFrame* frame = GetFrameForNodeOffset(offsets.content, offsets.offset, |
1249 | 0 | CARET_ASSOCIATE_AFTER, &offset); |
1250 | 0 |
|
1251 | 0 | if (frame && amount == eSelectWord && direction == eDirPrevious) { |
1252 | 0 | // To avoid selecting the previous word when at start of word, |
1253 | 0 | // first move one character forward. |
1254 | 0 | nsPeekOffsetStruct charPos(eSelectCharacter, eDirNext, offset, |
1255 | 0 | nsPoint(0, 0), false, mLimiter != nullptr, |
1256 | 0 | false, false, false); |
1257 | 0 | if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) { |
1258 | 0 | frame = charPos.mResultFrame; |
1259 | 0 | offset = charPos.mContentOffset; |
1260 | 0 | } |
1261 | 0 | } |
1262 | 0 |
|
1263 | 0 | nsPeekOffsetStruct pos(amount, direction, offset, nsPoint(0, 0), |
1264 | 0 | false, mLimiter != nullptr, false, false, false); |
1265 | 0 |
|
1266 | 0 | if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) { |
1267 | 0 | offsets.content = pos.mResultContent; |
1268 | 0 | offsets.offset = pos.mContentOffset; |
1269 | 0 | } |
1270 | 0 | } |
1271 | 0 |
|
1272 | 0 | HandleClick(offsets.content, offsets.offset, offsets.offset, |
1273 | 0 | true, false, offsets.associate); |
1274 | 0 | } |
1275 | | |
1276 | | nsresult |
1277 | | nsFrameSelection::StartAutoScrollTimer(nsIFrame* aFrame, |
1278 | | const nsPoint& aPoint, |
1279 | | uint32_t aDelay) |
1280 | 0 | { |
1281 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
1282 | 0 | if (!mDomSelections[index]) { |
1283 | 0 | return NS_ERROR_NULL_POINTER; |
1284 | 0 | } |
1285 | 0 | |
1286 | 0 | return mDomSelections[index]->StartAutoScrollTimer(aFrame, aPoint, aDelay); |
1287 | 0 | } |
1288 | | |
1289 | | void |
1290 | | nsFrameSelection::StopAutoScrollTimer() |
1291 | 0 | { |
1292 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
1293 | 0 | if (!mDomSelections[index]) { |
1294 | 0 | return; |
1295 | 0 | } |
1296 | 0 | |
1297 | 0 | mDomSelections[index]->StopAutoScrollTimer(); |
1298 | 0 | } |
1299 | | |
1300 | | /** |
1301 | | hard to go from nodes to frames, easy the other way! |
1302 | | */ |
1303 | | nsresult |
1304 | | nsFrameSelection::TakeFocus(nsIContent* aNewFocus, |
1305 | | uint32_t aContentOffset, |
1306 | | uint32_t aContentEndOffset, |
1307 | | CaretAssociateHint aHint, |
1308 | | bool aContinueSelection, |
1309 | | bool aMultipleSelection) |
1310 | 0 | { |
1311 | 0 | if (!aNewFocus) |
1312 | 0 | return NS_ERROR_NULL_POINTER; |
1313 | 0 | |
1314 | 0 | NS_ENSURE_STATE(mShell); |
1315 | 0 |
|
1316 | 0 | if (!IsValidSelectionPoint(this,aNewFocus)) |
1317 | 0 | return NS_ERROR_FAILURE; |
1318 | 0 | |
1319 | 0 | // Clear all table selection data |
1320 | 0 | mSelectingTableCellMode = TableSelection::None; |
1321 | 0 | mDragSelectingCells = false; |
1322 | 0 | mStartSelectedCell = nullptr; |
1323 | 0 | mEndSelectedCell = nullptr; |
1324 | 0 | mAppendStartSelectedCell = nullptr; |
1325 | 0 | mHint = aHint; |
1326 | 0 |
|
1327 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
1328 | 0 | if (!mDomSelections[index]) |
1329 | 0 | return NS_ERROR_NULL_POINTER; |
1330 | 0 | |
1331 | 0 | Maybe<Selection::AutoUserInitiated> userSelect; |
1332 | 0 | if (IsUserSelectionReason()) { |
1333 | 0 | userSelect.emplace(mDomSelections[index]); |
1334 | 0 | } |
1335 | 0 |
|
1336 | 0 | //traverse through document and unselect crap here |
1337 | 0 | if (!aContinueSelection) {//single click? setting cursor down |
1338 | 0 | uint32_t batching = mBatching;//hack to use the collapse code. |
1339 | 0 | bool changes = mChangesDuringBatching; |
1340 | 0 | mBatching = 1; |
1341 | 0 |
|
1342 | 0 | if (aMultipleSelection) { |
1343 | 0 | // Remove existing collapsed ranges as there's no point in having |
1344 | 0 | // non-anchor/focus collapsed ranges. |
1345 | 0 | mDomSelections[index]->RemoveCollapsedRanges(); |
1346 | 0 |
|
1347 | 0 | RefPtr<nsRange> newRange = new nsRange(aNewFocus); |
1348 | 0 |
|
1349 | 0 | newRange->CollapseTo(aNewFocus, aContentOffset); |
1350 | 0 | mDomSelections[index]->AddRange(*newRange, IgnoreErrors()); |
1351 | 0 | mBatching = batching; |
1352 | 0 | mChangesDuringBatching = changes; |
1353 | 0 | } else { |
1354 | 0 | bool oldDesiredPosSet = mDesiredPosSet; //need to keep old desired position if it was set. |
1355 | 0 | mDomSelections[index]->Collapse(aNewFocus, aContentOffset); |
1356 | 0 | mDesiredPosSet = oldDesiredPosSet; //now reset desired pos back. |
1357 | 0 | mBatching = batching; |
1358 | 0 | mChangesDuringBatching = changes; |
1359 | 0 | } |
1360 | 0 | if (aContentEndOffset != aContentOffset) { |
1361 | 0 | mDomSelections[index]->Extend(aNewFocus, aContentEndOffset); |
1362 | 0 | } |
1363 | 0 |
|
1364 | 0 | //find out if we are inside a table. if so, find out which one and which cell |
1365 | 0 | //once we do that, the next time we get a takefocus, check the parent tree. |
1366 | 0 | //if we are no longer inside same table ,cell then switch to table selection mode. |
1367 | 0 | // BUT only do this in an editor |
1368 | 0 |
|
1369 | 0 | NS_ENSURE_STATE(mShell); |
1370 | 0 | bool editableCell = false; |
1371 | 0 | mCellParent = nullptr; |
1372 | 0 | RefPtr<nsPresContext> context = mShell->GetPresContext(); |
1373 | 0 | if (context) { |
1374 | 0 | RefPtr<HTMLEditor> htmlEditor = nsContentUtils::GetHTMLEditor(context); |
1375 | 0 | if (htmlEditor) { |
1376 | 0 | nsINode* cellparent = GetCellParent(aNewFocus); |
1377 | 0 | nsCOMPtr<nsINode> editorHostNode = htmlEditor->GetActiveEditingHost(); |
1378 | 0 | editableCell = cellparent && editorHostNode && |
1379 | 0 | nsContentUtils::ContentIsDescendantOf(cellparent, editorHostNode); |
1380 | 0 | if (editableCell) { |
1381 | 0 | mCellParent = cellparent; |
1382 | | #ifdef DEBUG_TABLE_SELECTION |
1383 | | printf(" * TakeFocus - Collapsing into new cell\n"); |
1384 | | #endif |
1385 | | } |
1386 | 0 | } |
1387 | 0 | } |
1388 | 0 | } |
1389 | 0 | else { |
1390 | 0 | // Now update the range list: |
1391 | 0 | if (aContinueSelection && aNewFocus) |
1392 | 0 | { |
1393 | 0 | int32_t offset; |
1394 | 0 | nsINode *cellparent = GetCellParent(aNewFocus); |
1395 | 0 | if (mCellParent && cellparent && cellparent != mCellParent) //switch to cell selection mode |
1396 | 0 | { |
1397 | | #ifdef DEBUG_TABLE_SELECTION |
1398 | | printf(" * TakeFocus - moving into new cell\n"); |
1399 | | #endif |
1400 | | WidgetMouseEvent event(false, eVoidEvent, nullptr, |
1401 | 0 | WidgetMouseEvent::eReal); |
1402 | 0 |
|
1403 | 0 | // Start selecting in the cell we were in before |
1404 | 0 | nsINode* parent = ParentOffset(mCellParent, &offset); |
1405 | 0 | if (parent) |
1406 | 0 | HandleTableSelection(parent, offset, TableSelection::Cell, &event); |
1407 | 0 |
|
1408 | 0 | // Find the parent of this new cell and extend selection to it |
1409 | 0 | parent = ParentOffset(cellparent, &offset); |
1410 | 0 |
|
1411 | 0 | // XXXX We need to REALLY get the current key shift state |
1412 | 0 | // (we'd need to add event listener -- let's not bother for now) |
1413 | 0 | event.mModifiers &= ~MODIFIER_SHIFT; //aContinueSelection; |
1414 | 0 | if (parent) |
1415 | 0 | { |
1416 | 0 | mCellParent = cellparent; |
1417 | 0 | // Continue selection into next cell |
1418 | 0 | HandleTableSelection(parent, offset, TableSelection::Cell, &event); |
1419 | 0 | } |
1420 | 0 | } |
1421 | 0 | else |
1422 | 0 | { |
1423 | 0 | // XXXX Problem: Shift+click in browser is appending text selection to selected table!!! |
1424 | 0 | // is this the place to erase seleced cells ????? |
1425 | 0 | if (mDomSelections[index]->GetDirection() == eDirNext && aContentEndOffset > aContentOffset) //didn't go far enough |
1426 | 0 | { |
1427 | 0 | mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);//this will only redraw the diff |
1428 | 0 | } |
1429 | 0 | else |
1430 | 0 | mDomSelections[index]->Extend(aNewFocus, aContentOffset); |
1431 | 0 | } |
1432 | 0 | } |
1433 | 0 | } |
1434 | 0 |
|
1435 | 0 | // Don't notify selection listeners if batching is on: |
1436 | 0 | if (GetBatching()) |
1437 | 0 | return NS_OK; |
1438 | 0 | |
1439 | 0 | // Be aware, the Selection instance may be destroyed after this call. |
1440 | 0 | return NotifySelectionListeners(SelectionType::eNormal); |
1441 | 0 | } |
1442 | | |
1443 | | |
1444 | | UniquePtr<SelectionDetails> |
1445 | | nsFrameSelection::LookUpSelection(nsIContent *aContent, |
1446 | | int32_t aContentOffset, |
1447 | | int32_t aContentLength, |
1448 | | bool aSlowCheck) const |
1449 | 0 | { |
1450 | 0 | if (!aContent || !mShell) |
1451 | 0 | return nullptr; |
1452 | 0 | |
1453 | 0 | UniquePtr<SelectionDetails> details; |
1454 | 0 |
|
1455 | 0 | for (size_t j = 0; j < ArrayLength(mDomSelections); j++) { |
1456 | 0 | if (mDomSelections[j]) { |
1457 | 0 | details = mDomSelections[j]->LookUpSelection(aContent, aContentOffset, |
1458 | 0 | aContentLength, std::move(details), |
1459 | 0 | kPresentSelectionTypes[j], |
1460 | 0 | aSlowCheck); |
1461 | 0 | } |
1462 | 0 | } |
1463 | 0 |
|
1464 | 0 | return details; |
1465 | 0 | } |
1466 | | |
1467 | | void |
1468 | | nsFrameSelection::SetDragState(bool aState) |
1469 | 0 | { |
1470 | 0 | if (mDragState == aState) |
1471 | 0 | return; |
1472 | 0 | |
1473 | 0 | mDragState = aState; |
1474 | 0 |
|
1475 | 0 | if (!mDragState) |
1476 | 0 | { |
1477 | 0 | mDragSelectingCells = false; |
1478 | 0 | // Notify that reason is mouse up. |
1479 | 0 | PostReason(nsISelectionListener::MOUSEUP_REASON); |
1480 | 0 | // Be aware, the Selection instance may be destroyed after this call. |
1481 | 0 | NotifySelectionListeners(SelectionType::eNormal); |
1482 | 0 | } |
1483 | 0 | } |
1484 | | |
1485 | | Selection* |
1486 | | nsFrameSelection::GetSelection(SelectionType aSelectionType) const |
1487 | 0 | { |
1488 | 0 | int8_t index = GetIndexFromSelectionType(aSelectionType); |
1489 | 0 | if (index < 0) |
1490 | 0 | return nullptr; |
1491 | 0 | |
1492 | 0 | return mDomSelections[index]; |
1493 | 0 | } |
1494 | | |
1495 | | nsresult |
1496 | | nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType, |
1497 | | SelectionRegion aRegion, |
1498 | | int16_t aFlags) const |
1499 | 0 | { |
1500 | 0 | int8_t index = GetIndexFromSelectionType(aSelectionType); |
1501 | 0 | if (index < 0) |
1502 | 0 | return NS_ERROR_INVALID_ARG; |
1503 | 0 | |
1504 | 0 | if (!mDomSelections[index]) |
1505 | 0 | return NS_ERROR_NULL_POINTER; |
1506 | 0 | |
1507 | 0 | nsIPresShell::ScrollAxis verticalScroll = nsIPresShell::ScrollAxis(); |
1508 | 0 | int32_t flags = Selection::SCROLL_DO_FLUSH; |
1509 | 0 | if (aFlags & nsISelectionController::SCROLL_SYNCHRONOUS) { |
1510 | 0 | flags |= Selection::SCROLL_SYNCHRONOUS; |
1511 | 0 | } else if (aFlags & nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY) { |
1512 | 0 | flags |= Selection::SCROLL_FIRST_ANCESTOR_ONLY; |
1513 | 0 | } |
1514 | 0 | if (aFlags & nsISelectionController::SCROLL_OVERFLOW_HIDDEN) { |
1515 | 0 | flags |= Selection::SCROLL_OVERFLOW_HIDDEN; |
1516 | 0 | } |
1517 | 0 | if (aFlags & nsISelectionController::SCROLL_CENTER_VERTICALLY) { |
1518 | 0 | verticalScroll = nsIPresShell::ScrollAxis( |
1519 | 0 | nsIPresShell::SCROLL_CENTER, nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE); |
1520 | 0 | } |
1521 | 0 | if (aFlags & nsISelectionController::SCROLL_FOR_CARET_MOVE) { |
1522 | 0 | flags |= Selection::SCROLL_FOR_CARET_MOVE; |
1523 | 0 | } |
1524 | 0 |
|
1525 | 0 | // After ScrollSelectionIntoView(), the pending notifications might be |
1526 | 0 | // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. |
1527 | 0 | RefPtr<Selection> sel = mDomSelections[index]; |
1528 | 0 | return sel->ScrollIntoView(aRegion, verticalScroll, |
1529 | 0 | nsIPresShell::ScrollAxis(), flags); |
1530 | 0 | } |
1531 | | |
1532 | | nsresult |
1533 | | nsFrameSelection::RepaintSelection(SelectionType aSelectionType) |
1534 | 0 | { |
1535 | 0 | int8_t index = GetIndexFromSelectionType(aSelectionType); |
1536 | 0 | if (index < 0) |
1537 | 0 | return NS_ERROR_INVALID_ARG; |
1538 | 0 | if (!mDomSelections[index]) |
1539 | 0 | return NS_ERROR_NULL_POINTER; |
1540 | 0 | NS_ENSURE_STATE(mShell); |
1541 | 0 |
|
1542 | 0 | // On macOS, update the selection cache to the new active selection |
1543 | 0 | // aka the current selection. |
1544 | | #ifdef XP_MACOSX |
1545 | | nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
1546 | | // Check an active window exists otherwise there cannot be a current selection |
1547 | | // and that it's a normal selection. |
1548 | | if (fm->GetActiveWindow() && aSelectionType == SelectionType::eNormal) { |
1549 | | UpdateSelectionCacheOnRepaintSelection(mDomSelections[index]); |
1550 | | } |
1551 | | #endif |
1552 | 0 | return mDomSelections[index]->Repaint(mShell->GetPresContext()); |
1553 | 0 | } |
1554 | | |
1555 | | static bool |
1556 | | IsDisplayContents(const nsIContent* aContent) |
1557 | 0 | { |
1558 | 0 | return aContent->IsElement() && aContent->AsElement()->IsDisplayContents(); |
1559 | 0 | } |
1560 | | |
1561 | | nsIFrame* |
1562 | | nsFrameSelection::GetFrameForNodeOffset(nsIContent* aNode, |
1563 | | int32_t aOffset, |
1564 | | CaretAssociateHint aHint, |
1565 | | int32_t* aReturnOffset) const |
1566 | 0 | { |
1567 | 0 | if (!aNode || !aReturnOffset || !mShell) |
1568 | 0 | return nullptr; |
1569 | 0 | |
1570 | 0 | if (aOffset < 0) |
1571 | 0 | return nullptr; |
1572 | 0 | |
1573 | 0 | if (!aNode->GetPrimaryFrame() && !IsDisplayContents(aNode)) { |
1574 | 0 | return nullptr; |
1575 | 0 | } |
1576 | 0 | |
1577 | 0 | nsIFrame* returnFrame = nullptr; |
1578 | 0 | nsCOMPtr<nsIContent> theNode; |
1579 | 0 |
|
1580 | 0 | while (true) { |
1581 | 0 | *aReturnOffset = aOffset; |
1582 | 0 |
|
1583 | 0 | theNode = aNode; |
1584 | 0 |
|
1585 | 0 | if (aNode->IsElement()) { |
1586 | 0 | int32_t childIndex = 0; |
1587 | 0 | int32_t numChildren = theNode->GetChildCount(); |
1588 | 0 |
|
1589 | 0 | if (aHint == CARET_ASSOCIATE_BEFORE) { |
1590 | 0 | if (aOffset > 0) { |
1591 | 0 | childIndex = aOffset - 1; |
1592 | 0 | } else { |
1593 | 0 | childIndex = aOffset; |
1594 | 0 | } |
1595 | 0 | } else { |
1596 | 0 | NS_ASSERTION(aHint == CARET_ASSOCIATE_AFTER, "unknown direction"); |
1597 | 0 | if (aOffset >= numChildren) { |
1598 | 0 | if (numChildren > 0) { |
1599 | 0 | childIndex = numChildren - 1; |
1600 | 0 | } else { |
1601 | 0 | childIndex = 0; |
1602 | 0 | } |
1603 | 0 | } else { |
1604 | 0 | childIndex = aOffset; |
1605 | 0 | } |
1606 | 0 | } |
1607 | 0 |
|
1608 | 0 | if (childIndex > 0 || numChildren > 0) { |
1609 | 0 | nsCOMPtr<nsIContent> childNode = theNode->GetChildAt_Deprecated(childIndex); |
1610 | 0 |
|
1611 | 0 | if (!childNode) { |
1612 | 0 | break; |
1613 | 0 | } |
1614 | 0 | |
1615 | 0 | theNode = childNode; |
1616 | 0 | } |
1617 | 0 |
|
1618 | 0 | // Now that we have the child node, check if it too |
1619 | 0 | // can contain children. If so, descend into child. |
1620 | 0 | if (theNode->IsElement() && |
1621 | 0 | theNode->GetChildCount() && |
1622 | 0 | !theNode->HasIndependentSelection()) { |
1623 | 0 | aNode = theNode; |
1624 | 0 | aOffset = aOffset > childIndex ? theNode->GetChildCount() : 0; |
1625 | 0 | continue; |
1626 | 0 | } else { |
1627 | 0 | // Check to see if theNode is a text node. If it is, translate |
1628 | 0 | // aOffset into an offset into the text node. |
1629 | 0 |
|
1630 | 0 | RefPtr<Text> textNode = theNode->GetAsText(); |
1631 | 0 | if (textNode) { |
1632 | 0 | if (theNode->GetPrimaryFrame()) { |
1633 | 0 | if (aOffset > childIndex) { |
1634 | 0 | uint32_t textLength = textNode->Length(); |
1635 | 0 |
|
1636 | 0 | *aReturnOffset = (int32_t)textLength; |
1637 | 0 | } else { |
1638 | 0 | *aReturnOffset = 0; |
1639 | 0 | } |
1640 | 0 | } else { |
1641 | 0 | int32_t numChildren = aNode->GetChildCount(); |
1642 | 0 | int32_t newChildIndex = |
1643 | 0 | aHint == CARET_ASSOCIATE_BEFORE ? childIndex - 1 : childIndex + 1; |
1644 | 0 |
|
1645 | 0 | if (newChildIndex >= 0 && newChildIndex < numChildren) { |
1646 | 0 | nsCOMPtr<nsIContent> newChildNode = aNode->GetChildAt_Deprecated(newChildIndex); |
1647 | 0 | if (!newChildNode) { |
1648 | 0 | return nullptr; |
1649 | 0 | } |
1650 | 0 | |
1651 | 0 | aNode = newChildNode; |
1652 | 0 | aOffset = aHint == CARET_ASSOCIATE_BEFORE ? aNode->GetChildCount() : 0; |
1653 | 0 | continue; |
1654 | 0 | } else { |
1655 | 0 | // newChildIndex is illegal which means we're at first or last |
1656 | 0 | // child. Just use original node to get the frame. |
1657 | 0 | theNode = aNode; |
1658 | 0 | } |
1659 | 0 | } |
1660 | 0 | } |
1661 | 0 | } |
1662 | 0 | } |
1663 | 0 |
|
1664 | 0 | // If the node is a ShadowRoot, the frame needs to be adjusted, |
1665 | 0 | // because a ShadowRoot does not get a frame. Its children are rendered |
1666 | 0 | // as children of the host. |
1667 | 0 | if (ShadowRoot* shadow = ShadowRoot::FromNode(theNode)) { |
1668 | 0 | theNode = shadow->GetHost(); |
1669 | 0 | } |
1670 | 0 |
|
1671 | 0 | returnFrame = theNode->GetPrimaryFrame(); |
1672 | 0 | if (!returnFrame) { |
1673 | 0 | if (aHint == CARET_ASSOCIATE_BEFORE) { |
1674 | 0 | if (aOffset > 0) { |
1675 | 0 | --aOffset; |
1676 | 0 | continue; |
1677 | 0 | } else { |
1678 | 0 | break; |
1679 | 0 | } |
1680 | 0 | } else { |
1681 | 0 | int32_t end = theNode->GetChildCount(); |
1682 | 0 | if (aOffset < end) { |
1683 | 0 | ++aOffset; |
1684 | 0 | continue; |
1685 | 0 | } else { |
1686 | 0 | break; |
1687 | 0 | } |
1688 | 0 | } |
1689 | 0 | } |
1690 | 0 | |
1691 | 0 | break; |
1692 | 0 | } // end while |
1693 | 0 |
|
1694 | 0 | if (!returnFrame) |
1695 | 0 | return nullptr; |
1696 | 0 | |
1697 | 0 | // If we ended up here and were asked to position the caret after a visible |
1698 | 0 | // break, let's return the frame on the next line instead if it exists. |
1699 | 0 | if (aOffset > 0 && (uint32_t) aOffset >= aNode->Length() && |
1700 | 0 | theNode == aNode->GetLastChild()) { |
1701 | 0 | nsIFrame* newFrame; |
1702 | 0 | nsLayoutUtils::IsInvisibleBreak(theNode, &newFrame); |
1703 | 0 | if (newFrame) { |
1704 | 0 | returnFrame = newFrame; |
1705 | 0 | *aReturnOffset = 0; |
1706 | 0 | } |
1707 | 0 | } |
1708 | 0 |
|
1709 | 0 | // find the child frame containing the offset we want |
1710 | 0 | returnFrame->GetChildFrameContainingOffset(*aReturnOffset, aHint == CARET_ASSOCIATE_AFTER, |
1711 | 0 | &aOffset, &returnFrame); |
1712 | 0 | return returnFrame; |
1713 | 0 | } |
1714 | | |
1715 | | void |
1716 | | nsFrameSelection::CommonPageMove(bool aForward, |
1717 | | bool aExtend, |
1718 | | nsIScrollableFrame* aScrollableFrame) |
1719 | 0 | { |
1720 | 0 | // expected behavior for PageMove is to scroll AND move the caret |
1721 | 0 | // and remain relative position of the caret in view. see Bug 4302. |
1722 | 0 |
|
1723 | 0 | //get the frame from the scrollable view |
1724 | 0 |
|
1725 | 0 | nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame(); |
1726 | 0 | if (!scrolledFrame) |
1727 | 0 | return; |
1728 | 0 | |
1729 | 0 | // find out where the caret is. |
1730 | 0 | // we should know mDesiredPos value of nsFrameSelection, but I havent seen that behavior in other windows applications yet. |
1731 | 0 | Selection* domSel = GetSelection(SelectionType::eNormal); |
1732 | 0 | if (!domSel) { |
1733 | 0 | return; |
1734 | 0 | } |
1735 | 0 | |
1736 | 0 | nsRect caretPos; |
1737 | 0 | nsIFrame* caretFrame = nsCaret::GetGeometry(domSel, &caretPos); |
1738 | 0 | if (!caretFrame) |
1739 | 0 | return; |
1740 | 0 | |
1741 | 0 | //need to adjust caret jump by percentage scroll |
1742 | 0 | nsSize scrollDelta = aScrollableFrame->GetPageScrollAmount(); |
1743 | 0 |
|
1744 | 0 | if (aForward) |
1745 | 0 | caretPos.y += scrollDelta.height; |
1746 | 0 | else |
1747 | 0 | caretPos.y -= scrollDelta.height; |
1748 | 0 |
|
1749 | 0 | caretPos += caretFrame->GetOffsetTo(scrolledFrame); |
1750 | 0 |
|
1751 | 0 | // get a content at desired location |
1752 | 0 | nsPoint desiredPoint; |
1753 | 0 | desiredPoint.x = caretPos.x; |
1754 | 0 | desiredPoint.y = caretPos.y + caretPos.height/2; |
1755 | 0 | nsIFrame::ContentOffsets offsets = |
1756 | 0 | scrolledFrame->GetContentOffsetsFromPoint(desiredPoint); |
1757 | 0 |
|
1758 | 0 | if (!offsets.content) |
1759 | 0 | return; |
1760 | 0 | |
1761 | 0 | // scroll one page |
1762 | 0 | aScrollableFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), |
1763 | 0 | nsIScrollableFrame::PAGES, |
1764 | 0 | nsIScrollableFrame::SMOOTH); |
1765 | 0 |
|
1766 | 0 | // place the caret |
1767 | 0 | HandleClick(offsets.content, offsets.offset, |
1768 | 0 | offsets.offset, aExtend, false, CARET_ASSOCIATE_AFTER); |
1769 | 0 | } |
1770 | | |
1771 | | nsresult |
1772 | | nsFrameSelection::PhysicalMove(int16_t aDirection, int16_t aAmount, |
1773 | | bool aExtend) |
1774 | 0 | { |
1775 | 0 | NS_ENSURE_STATE(mShell); |
1776 | 0 | // Flush out layout, since we need it to be up to date to do caret |
1777 | 0 | // positioning. |
1778 | 0 | mShell->FlushPendingNotifications(FlushType::Layout); |
1779 | 0 |
|
1780 | 0 | if (!mShell) { |
1781 | 0 | return NS_OK; |
1782 | 0 | } |
1783 | 0 | |
1784 | 0 | // Check that parameters are safe |
1785 | 0 | if (aDirection < 0 || aDirection > 3 || aAmount < 0 || aAmount > 1) { |
1786 | 0 | return NS_ERROR_FAILURE; |
1787 | 0 | } |
1788 | 0 | |
1789 | 0 | nsPresContext *context = mShell->GetPresContext(); |
1790 | 0 | if (!context) { |
1791 | 0 | return NS_ERROR_FAILURE; |
1792 | 0 | } |
1793 | 0 | |
1794 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
1795 | 0 | RefPtr<Selection> sel = mDomSelections[index]; |
1796 | 0 | if (!sel) { |
1797 | 0 | return NS_ERROR_NULL_POINTER; |
1798 | 0 | } |
1799 | 0 | |
1800 | 0 | // Map the abstract movement amounts (0-1) to direction-specific |
1801 | 0 | // selection units. |
1802 | 0 | static const nsSelectionAmount inlineAmount[] = |
1803 | 0 | { eSelectCluster, eSelectWord }; |
1804 | 0 | static const nsSelectionAmount blockPrevAmount[] = |
1805 | 0 | { eSelectLine, eSelectBeginLine }; |
1806 | 0 | static const nsSelectionAmount blockNextAmount[] = |
1807 | 0 | { eSelectLine, eSelectEndLine }; |
1808 | 0 |
|
1809 | 0 | struct PhysicalToLogicalMapping { |
1810 | 0 | nsDirection direction; |
1811 | 0 | const nsSelectionAmount *amounts; |
1812 | 0 | }; |
1813 | 0 | static const PhysicalToLogicalMapping verticalLR[4] = { |
1814 | 0 | { eDirPrevious, blockPrevAmount }, // left |
1815 | 0 | { eDirNext, blockNextAmount }, // right |
1816 | 0 | { eDirPrevious, inlineAmount }, // up |
1817 | 0 | { eDirNext, inlineAmount } // down |
1818 | 0 | }; |
1819 | 0 | static const PhysicalToLogicalMapping verticalRL[4] = { |
1820 | 0 | { eDirNext, blockNextAmount }, |
1821 | 0 | { eDirPrevious, blockPrevAmount }, |
1822 | 0 | { eDirPrevious, inlineAmount }, |
1823 | 0 | { eDirNext, inlineAmount } |
1824 | 0 | }; |
1825 | 0 | static const PhysicalToLogicalMapping horizontal[4] = { |
1826 | 0 | { eDirPrevious, inlineAmount }, |
1827 | 0 | { eDirNext, inlineAmount }, |
1828 | 0 | { eDirPrevious, blockPrevAmount }, |
1829 | 0 | { eDirNext, blockNextAmount } |
1830 | 0 | }; |
1831 | 0 |
|
1832 | 0 | WritingMode wm; |
1833 | 0 | nsIFrame *frame = nullptr; |
1834 | 0 | int32_t offsetused = 0; |
1835 | 0 | if (NS_SUCCEEDED(sel->GetPrimaryFrameForFocusNode(&frame, &offsetused, |
1836 | 0 | true))) { |
1837 | 0 | if (frame) { |
1838 | 0 | if (!frame->Style()->IsTextCombined()) { |
1839 | 0 | wm = frame->GetWritingMode(); |
1840 | 0 | } else { |
1841 | 0 | // Using different direction for horizontal-in-vertical would |
1842 | 0 | // make it hard to navigate via keyboard. Inherit the moving |
1843 | 0 | // direction from its parent. |
1844 | 0 | MOZ_ASSERT(frame->IsTextFrame()); |
1845 | 0 | wm = frame->GetParent()->GetWritingMode(); |
1846 | 0 | MOZ_ASSERT(wm.IsVertical(), "Text combined " |
1847 | 0 | "can only appear in vertical text"); |
1848 | 0 | } |
1849 | 0 | } |
1850 | 0 | } |
1851 | 0 |
|
1852 | 0 | const PhysicalToLogicalMapping& mapping = |
1853 | 0 | wm.IsVertical() |
1854 | 0 | ? wm.IsVerticalLR() ? verticalLR[aDirection] : verticalRL[aDirection] |
1855 | 0 | : horizontal[aDirection]; |
1856 | 0 |
|
1857 | 0 | nsresult rv = MoveCaret(mapping.direction, aExtend, mapping.amounts[aAmount], |
1858 | 0 | eVisual); |
1859 | 0 | if (NS_FAILED(rv)) { |
1860 | 0 | // If we tried to do a line move, but couldn't move in the given direction, |
1861 | 0 | // then we'll "promote" this to a line-edge move instead. |
1862 | 0 | if (mapping.amounts[aAmount] == eSelectLine) { |
1863 | 0 | rv = MoveCaret(mapping.direction, aExtend, mapping.amounts[aAmount + 1], |
1864 | 0 | eVisual); |
1865 | 0 | } |
1866 | 0 | // And if it was a next-word move that failed (which can happen when |
1867 | 0 | // eat_space_to_next_word is true, see bug 1153237), then just move forward |
1868 | 0 | // to the line-edge. |
1869 | 0 | else if (mapping.amounts[aAmount] == eSelectWord && |
1870 | 0 | mapping.direction == eDirNext) { |
1871 | 0 | rv = MoveCaret(eDirNext, aExtend, eSelectEndLine, eVisual); |
1872 | 0 | } |
1873 | 0 | } |
1874 | 0 |
|
1875 | 0 | return rv; |
1876 | 0 | } |
1877 | | |
1878 | | nsresult |
1879 | | nsFrameSelection::CharacterMove(bool aForward, bool aExtend) |
1880 | 0 | { |
1881 | 0 | return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectCluster, |
1882 | 0 | eUsePrefStyle); |
1883 | 0 | } |
1884 | | |
1885 | | nsresult |
1886 | | nsFrameSelection::CharacterExtendForDelete() |
1887 | 0 | { |
1888 | 0 | return MoveCaret(eDirNext, true, eSelectCluster, eLogical); |
1889 | 0 | } |
1890 | | |
1891 | | nsresult |
1892 | | nsFrameSelection::CharacterExtendForBackspace() |
1893 | 0 | { |
1894 | 0 | return MoveCaret(eDirPrevious, true, eSelectCharacter, eLogical); |
1895 | 0 | } |
1896 | | |
1897 | | nsresult |
1898 | | nsFrameSelection::WordMove(bool aForward, bool aExtend) |
1899 | 0 | { |
1900 | 0 | return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectWord, |
1901 | 0 | eUsePrefStyle); |
1902 | 0 | } |
1903 | | |
1904 | | nsresult |
1905 | | nsFrameSelection::WordExtendForDelete(bool aForward) |
1906 | 0 | { |
1907 | 0 | return MoveCaret(aForward ? eDirNext : eDirPrevious, true, eSelectWord, |
1908 | 0 | eLogical); |
1909 | 0 | } |
1910 | | |
1911 | | nsresult |
1912 | | nsFrameSelection::LineMove(bool aForward, bool aExtend) |
1913 | 0 | { |
1914 | 0 | return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectLine, |
1915 | 0 | eUsePrefStyle); |
1916 | 0 | } |
1917 | | |
1918 | | nsresult |
1919 | | nsFrameSelection::IntraLineMove(bool aForward, bool aExtend) |
1920 | 0 | { |
1921 | 0 | if (aForward) { |
1922 | 0 | return MoveCaret(eDirNext, aExtend, eSelectEndLine, eLogical); |
1923 | 0 | } else { |
1924 | 0 | return MoveCaret(eDirPrevious, aExtend, eSelectBeginLine, eLogical); |
1925 | 0 | } |
1926 | 0 | } |
1927 | | |
1928 | | nsresult |
1929 | | nsFrameSelection::SelectAll() |
1930 | 0 | { |
1931 | 0 | nsCOMPtr<nsIContent> rootContent; |
1932 | 0 | if (mLimiter) |
1933 | 0 | { |
1934 | 0 | rootContent = mLimiter;//addrefit |
1935 | 0 | } |
1936 | 0 | else if (mAncestorLimiter) { |
1937 | 0 | rootContent = mAncestorLimiter; |
1938 | 0 | } |
1939 | 0 | else |
1940 | 0 | { |
1941 | 0 | NS_ENSURE_STATE(mShell); |
1942 | 0 | nsIDocument *doc = mShell->GetDocument(); |
1943 | 0 | if (!doc) |
1944 | 0 | return NS_ERROR_FAILURE; |
1945 | 0 | rootContent = doc->GetRootElement(); |
1946 | 0 | if (!rootContent) |
1947 | 0 | return NS_ERROR_FAILURE; |
1948 | 0 | } |
1949 | 0 | int32_t numChildren = rootContent->GetChildCount(); |
1950 | 0 | PostReason(nsISelectionListener::NO_REASON); |
1951 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
1952 | 0 | AutoPrepareFocusRange prep(mDomSelections[index], false, false); |
1953 | 0 | return TakeFocus(rootContent, 0, numChildren, CARET_ASSOCIATE_BEFORE, false, false); |
1954 | 0 | } |
1955 | | |
1956 | | //////////END FRAMESELECTION |
1957 | | |
1958 | | void |
1959 | | nsFrameSelection::StartBatchChanges() |
1960 | 0 | { |
1961 | 0 | mBatching++; |
1962 | 0 | } |
1963 | | |
1964 | | void |
1965 | | nsFrameSelection::EndBatchChanges(int16_t aReason) |
1966 | 0 | { |
1967 | 0 | mBatching--; |
1968 | 0 | NS_ASSERTION(mBatching >=0,"Bad mBatching"); |
1969 | 0 |
|
1970 | 0 | if (mBatching == 0 && mChangesDuringBatching) { |
1971 | 0 | int16_t postReason = PopReason() | aReason; |
1972 | 0 | PostReason(postReason); |
1973 | 0 | mChangesDuringBatching = false; |
1974 | 0 | // Be aware, the Selection instance may be destroyed after this call. |
1975 | 0 | NotifySelectionListeners(SelectionType::eNormal); |
1976 | 0 | } |
1977 | 0 | } |
1978 | | |
1979 | | |
1980 | | nsresult |
1981 | | nsFrameSelection::NotifySelectionListeners(SelectionType aSelectionType) |
1982 | 0 | { |
1983 | 0 | int8_t index = GetIndexFromSelectionType(aSelectionType); |
1984 | 0 | if (index >=0 && mDomSelections[index]) |
1985 | 0 | { |
1986 | 0 | RefPtr<Selection> selection = mDomSelections[index]; |
1987 | 0 | return selection->NotifySelectionListeners(); |
1988 | 0 | } |
1989 | 0 | return NS_ERROR_FAILURE; |
1990 | 0 | } |
1991 | | |
1992 | | // Start of Table Selection methods |
1993 | | |
1994 | | static bool IsCell(nsIContent *aContent) |
1995 | 0 | { |
1996 | 0 | return aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th); |
1997 | 0 | } |
1998 | | |
1999 | | nsITableCellLayout* |
2000 | | nsFrameSelection::GetCellLayout(nsIContent *aCellContent) const |
2001 | 0 | { |
2002 | 0 | NS_ENSURE_TRUE(mShell, nullptr); |
2003 | 0 | nsITableCellLayout *cellLayoutObject = |
2004 | 0 | do_QueryFrame(aCellContent->GetPrimaryFrame()); |
2005 | 0 | return cellLayoutObject; |
2006 | 0 | } |
2007 | | |
2008 | | nsresult |
2009 | | nsFrameSelection::ClearNormalSelection() |
2010 | 0 | { |
2011 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2012 | 0 | if (!mDomSelections[index]) |
2013 | 0 | return NS_ERROR_NULL_POINTER; |
2014 | 0 | |
2015 | 0 | ErrorResult err; |
2016 | 0 | mDomSelections[index]->RemoveAllRanges(err); |
2017 | 0 | return err.StealNSResult(); |
2018 | 0 | } |
2019 | | |
2020 | | static nsIContent* |
2021 | | GetFirstSelectedContent(nsRange* aRange) |
2022 | 0 | { |
2023 | 0 | if (!aRange) { |
2024 | 0 | return nullptr; |
2025 | 0 | } |
2026 | 0 | |
2027 | 0 | MOZ_ASSERT(aRange->GetStartContainer(), "Must have start parent!"); |
2028 | 0 | MOZ_ASSERT(aRange->GetStartContainer()->IsElement(), "Unexpected parent"); |
2029 | 0 |
|
2030 | 0 | return aRange->GetChildAtStartOffset(); |
2031 | 0 | } |
2032 | | |
2033 | | // Table selection support. |
2034 | | // TODO: Separate table methods into a separate nsITableSelection interface |
2035 | | nsresult |
2036 | | nsFrameSelection::HandleTableSelection(nsINode* aParentContent, |
2037 | | int32_t aContentOffset, |
2038 | | TableSelection aTarget, |
2039 | | WidgetMouseEvent* aMouseEvent) |
2040 | 0 | { |
2041 | 0 | NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER); |
2042 | 0 | NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER); |
2043 | 0 |
|
2044 | 0 | if (mDragState && mDragSelectingCells && aTarget == TableSelection::Table) |
2045 | 0 | { |
2046 | 0 | // We were selecting cells and user drags mouse in table border or inbetween cells, |
2047 | 0 | // just do nothing |
2048 | 0 | return NS_OK; |
2049 | 0 | } |
2050 | 0 | |
2051 | 0 | nsresult result = NS_OK; |
2052 | 0 |
|
2053 | 0 | nsIContent *childContent = aParentContent->GetChildAt_Deprecated(aContentOffset); |
2054 | 0 |
|
2055 | 0 | // When doing table selection, always set the direction to next so |
2056 | 0 | // we can be sure that anchorNode's offset always points to the |
2057 | 0 | // selected cell |
2058 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2059 | 0 | if (!mDomSelections[index]) |
2060 | 0 | return NS_ERROR_NULL_POINTER; |
2061 | 0 | |
2062 | 0 | mDomSelections[index]->SetDirection(eDirNext); |
2063 | 0 |
|
2064 | 0 | // Stack-class to wrap all table selection changes in |
2065 | 0 | // BeginBatchChanges() / EndBatchChanges() |
2066 | 0 | SelectionBatcher selectionBatcher(mDomSelections[index]); |
2067 | 0 |
|
2068 | 0 | int32_t startRowIndex, startColIndex, curRowIndex, curColIndex; |
2069 | 0 | if (mDragState && mDragSelectingCells) |
2070 | 0 | { |
2071 | 0 | // We are drag-selecting |
2072 | 0 | if (aTarget != TableSelection::Table) |
2073 | 0 | { |
2074 | 0 | // If dragging in the same cell as last event, do nothing |
2075 | 0 | if (mEndSelectedCell == childContent) |
2076 | 0 | return NS_OK; |
2077 | 0 | |
2078 | | #ifdef DEBUG_TABLE_SELECTION |
2079 | | printf(" mStartSelectedCell = %p, mEndSelectedCell = %p, childContent = %p \n", |
2080 | | mStartSelectedCell.get(), mEndSelectedCell.get(), childContent); |
2081 | | #endif |
2082 | | // aTarget can be any "cell mode", |
2083 | 0 | // so we can easily drag-select rows and columns |
2084 | 0 | // Once we are in row or column mode, |
2085 | 0 | // we can drift into any cell to stay in that mode |
2086 | 0 | // even if aTarget = TableSelection::Cell |
2087 | 0 | |
2088 | 0 | if (mSelectingTableCellMode == TableSelection::Row || |
2089 | 0 | mSelectingTableCellMode == TableSelection::Column) |
2090 | 0 | { |
2091 | 0 | if (mEndSelectedCell) |
2092 | 0 | { |
2093 | 0 | // Also check if cell is in same row/col |
2094 | 0 | result = GetCellIndexes(mEndSelectedCell, startRowIndex, startColIndex); |
2095 | 0 | if (NS_FAILED(result)) return result; |
2096 | 0 | result = GetCellIndexes(childContent, curRowIndex, curColIndex); |
2097 | 0 | if (NS_FAILED(result)) return result; |
2098 | 0 | |
2099 | | #ifdef DEBUG_TABLE_SELECTION |
2100 | | printf(" curRowIndex = %d, startRowIndex = %d, curColIndex = %d, startColIndex = %d\n", curRowIndex, startRowIndex, curColIndex, startColIndex); |
2101 | | #endif |
2102 | 0 | if ((mSelectingTableCellMode == TableSelection::Row && startRowIndex == curRowIndex) || |
2103 | 0 | (mSelectingTableCellMode == TableSelection::Column && startColIndex == curColIndex)) |
2104 | 0 | return NS_OK; |
2105 | 0 | } |
2106 | | #ifdef DEBUG_TABLE_SELECTION |
2107 | | printf(" Dragged into a new column or row\n"); |
2108 | | #endif |
2109 | | // Continue dragging row or column selection |
2110 | 0 | return SelectRowOrColumn(childContent, mSelectingTableCellMode); |
2111 | 0 | } |
2112 | 0 | else if (mSelectingTableCellMode == TableSelection::Cell) |
2113 | 0 | { |
2114 | | #ifdef DEBUG_TABLE_SELECTION |
2115 | | printf("HandleTableSelection: Dragged into a new cell\n"); |
2116 | | #endif |
2117 | | // Trick for quick selection of rows and columns |
2118 | 0 | // Hold down shift, then start selecting in one direction |
2119 | 0 | // If next cell dragged into is in same row, select entire row, |
2120 | 0 | // if next cell is in same column, select entire column |
2121 | 0 | if (mStartSelectedCell && aMouseEvent->IsShift()) |
2122 | 0 | { |
2123 | 0 | result = GetCellIndexes(mStartSelectedCell, startRowIndex, startColIndex); |
2124 | 0 | if (NS_FAILED(result)) return result; |
2125 | 0 | result = GetCellIndexes(childContent, curRowIndex, curColIndex); |
2126 | 0 | if (NS_FAILED(result)) return result; |
2127 | 0 | |
2128 | 0 | if (startRowIndex == curRowIndex || |
2129 | 0 | startColIndex == curColIndex) |
2130 | 0 | { |
2131 | 0 | // Force new selection block |
2132 | 0 | mStartSelectedCell = nullptr; |
2133 | 0 | mDomSelections[index]->RemoveAllRanges(IgnoreErrors()); |
2134 | 0 |
|
2135 | 0 | if (startRowIndex == curRowIndex) |
2136 | 0 | mSelectingTableCellMode = TableSelection::Row; |
2137 | 0 | else |
2138 | 0 | mSelectingTableCellMode = TableSelection::Column; |
2139 | 0 |
|
2140 | 0 | return SelectRowOrColumn(childContent, mSelectingTableCellMode); |
2141 | 0 | } |
2142 | 0 | } |
2143 | 0 |
|
2144 | 0 | // Reselect block of cells to new end location |
2145 | 0 | return SelectBlockOfCells(mStartSelectedCell, childContent); |
2146 | 0 | } |
2147 | 0 | } |
2148 | 0 | // Do nothing if dragging in table, but outside a cell |
2149 | 0 | return NS_OK; |
2150 | 0 | } |
2151 | 0 | else |
2152 | 0 | { |
2153 | 0 | // Not dragging -- mouse event is down or up |
2154 | 0 | if (mDragState) |
2155 | 0 | { |
2156 | | #ifdef DEBUG_TABLE_SELECTION |
2157 | | printf("HandleTableSelection: Mouse down event\n"); |
2158 | | #endif |
2159 | | // Clear cell we stored in mouse-down |
2160 | 0 | mUnselectCellOnMouseUp = nullptr; |
2161 | 0 |
|
2162 | 0 | if (aTarget == TableSelection::Cell) |
2163 | 0 | { |
2164 | 0 | bool isSelected = false; |
2165 | 0 |
|
2166 | 0 | // Check if we have other selected cells |
2167 | 0 | nsIContent* previousCellNode = |
2168 | 0 | GetFirstSelectedContent(GetFirstCellRange()); |
2169 | 0 | if (previousCellNode) |
2170 | 0 | { |
2171 | 0 | // We have at least 1 other selected cell |
2172 | 0 |
|
2173 | 0 | // Check if new cell is already selected |
2174 | 0 | nsIFrame *cellFrame = childContent->GetPrimaryFrame(); |
2175 | 0 | if (!cellFrame) return NS_ERROR_NULL_POINTER; |
2176 | 0 | isSelected = cellFrame->IsSelected(); |
2177 | 0 | } |
2178 | 0 | else |
2179 | 0 | { |
2180 | 0 | // No cells selected -- remove non-cell selection |
2181 | 0 | mDomSelections[index]->RemoveAllRanges(IgnoreErrors()); |
2182 | 0 | } |
2183 | 0 | mDragSelectingCells = true; // Signal to start drag-cell-selection |
2184 | 0 | mSelectingTableCellMode = aTarget; |
2185 | 0 | // Set start for new drag-selection block (not appended) |
2186 | 0 | mStartSelectedCell = childContent; |
2187 | 0 | // The initial block end is same as the start |
2188 | 0 | mEndSelectedCell = childContent; |
2189 | 0 |
|
2190 | 0 | if (isSelected) |
2191 | 0 | { |
2192 | 0 | // Remember this cell to (possibly) unselect it on mouseup |
2193 | 0 | mUnselectCellOnMouseUp = childContent; |
2194 | | #ifdef DEBUG_TABLE_SELECTION |
2195 | | printf("HandleTableSelection: Saving mUnselectCellOnMouseUp\n"); |
2196 | | #endif |
2197 | | } |
2198 | 0 | else |
2199 | 0 | { |
2200 | 0 | // Select an unselected cell |
2201 | 0 | // but first remove existing selection if not in same table |
2202 | 0 | if (previousCellNode && |
2203 | 0 | !IsInSameTable(previousCellNode, childContent)) |
2204 | 0 | { |
2205 | 0 | mDomSelections[index]->RemoveAllRanges(IgnoreErrors()); |
2206 | 0 | // Reset selection mode that is cleared in RemoveAllRanges |
2207 | 0 | mSelectingTableCellMode = aTarget; |
2208 | 0 | } |
2209 | 0 |
|
2210 | 0 | return SelectCellElement(childContent); |
2211 | 0 | } |
2212 | 0 |
|
2213 | 0 | return NS_OK; |
2214 | 0 | } |
2215 | 0 | else if (aTarget == TableSelection::Table) |
2216 | 0 | { |
2217 | 0 | //TODO: We currently select entire table when clicked between cells, |
2218 | 0 | // should we restrict to only around border? |
2219 | 0 | // *** How do we get location data for cell and click? |
2220 | 0 | mDragSelectingCells = false; |
2221 | 0 | mStartSelectedCell = nullptr; |
2222 | 0 | mEndSelectedCell = nullptr; |
2223 | 0 |
|
2224 | 0 | // Remove existing selection and select the table |
2225 | 0 | mDomSelections[index]->RemoveAllRanges(IgnoreErrors()); |
2226 | 0 | return CreateAndAddRange(aParentContent, aContentOffset); |
2227 | 0 | } |
2228 | 0 | else if (aTarget == TableSelection::Row || aTarget == TableSelection::Column) |
2229 | 0 | { |
2230 | | #ifdef DEBUG_TABLE_SELECTION |
2231 | | printf("aTarget == %d\n", aTarget); |
2232 | | #endif |
2233 | |
|
2234 | 0 | // Start drag-selecting mode so multiple rows/cols can be selected |
2235 | 0 | // Note: Currently, nsFrame::GetDataForTableSelection |
2236 | 0 | // will never call us for row or column selection on mouse down |
2237 | 0 | mDragSelectingCells = true; |
2238 | 0 |
|
2239 | 0 | // Force new selection block |
2240 | 0 | mStartSelectedCell = nullptr; |
2241 | 0 | mDomSelections[index]->RemoveAllRanges(IgnoreErrors()); |
2242 | 0 | // Always do this AFTER RemoveAllRanges |
2243 | 0 | mSelectingTableCellMode = aTarget; |
2244 | 0 | return SelectRowOrColumn(childContent, aTarget); |
2245 | 0 | } |
2246 | 0 | } |
2247 | 0 | else |
2248 | 0 | { |
2249 | | #ifdef DEBUG_TABLE_SELECTION |
2250 | | printf("HandleTableSelection: Mouse UP event. mDragSelectingCells=%d, mStartSelectedCell=%p\n", |
2251 | | mDragSelectingCells, mStartSelectedCell.get()); |
2252 | | #endif |
2253 | | // First check if we are extending a block selection |
2254 | 0 | uint32_t rangeCount = mDomSelections[index]->RangeCount(); |
2255 | 0 |
|
2256 | 0 | if (rangeCount > 0 && aMouseEvent->IsShift() && |
2257 | 0 | mAppendStartSelectedCell && mAppendStartSelectedCell != childContent) |
2258 | 0 | { |
2259 | 0 | // Shift key is down: append a block selection |
2260 | 0 | mDragSelectingCells = false; |
2261 | 0 | return SelectBlockOfCells(mAppendStartSelectedCell, childContent); |
2262 | 0 | } |
2263 | 0 | |
2264 | 0 | if (mDragSelectingCells) |
2265 | 0 | mAppendStartSelectedCell = mStartSelectedCell; |
2266 | 0 |
|
2267 | 0 | mDragSelectingCells = false; |
2268 | 0 | mStartSelectedCell = nullptr; |
2269 | 0 | mEndSelectedCell = nullptr; |
2270 | 0 |
|
2271 | 0 | // Any other mouseup actions require that Ctrl or Cmd key is pressed |
2272 | 0 | // else stop table selection mode |
2273 | 0 | bool doMouseUpAction = false; |
2274 | | #ifdef XP_MACOSX |
2275 | | doMouseUpAction = aMouseEvent->IsMeta(); |
2276 | | #else |
2277 | | doMouseUpAction = aMouseEvent->IsControl(); |
2278 | 0 | #endif |
2279 | 0 | if (!doMouseUpAction) |
2280 | 0 | { |
2281 | | #ifdef DEBUG_TABLE_SELECTION |
2282 | | printf("HandleTableSelection: Ending cell selection on mouseup: mAppendStartSelectedCell=%p\n", |
2283 | | mAppendStartSelectedCell.get()); |
2284 | | #endif |
2285 | | return NS_OK; |
2286 | 0 | } |
2287 | 0 | // Unselect a cell only if it wasn't |
2288 | 0 | // just selected on mousedown |
2289 | 0 | if( childContent == mUnselectCellOnMouseUp) |
2290 | 0 | { |
2291 | 0 | // Scan ranges to find the cell to unselect (the selection range to remove) |
2292 | 0 | // XXXbz it's really weird that this lives outside the loop, so once we |
2293 | 0 | // find one we keep looking at it even if we find no more cells... |
2294 | 0 | nsINode* previousCellParent = nullptr; |
2295 | | #ifdef DEBUG_TABLE_SELECTION |
2296 | | printf("HandleTableSelection: Unselecting mUnselectCellOnMouseUp; rangeCount=%d\n", rangeCount); |
2297 | | #endif |
2298 | 0 | for (uint32_t i = 0; i < rangeCount; i++) |
2299 | 0 | { |
2300 | 0 | // Strong reference, because sometimes we want to remove |
2301 | 0 | // this range, and then we might be the only owner. |
2302 | 0 | RefPtr<nsRange> range = mDomSelections[index]->GetRangeAt(i); |
2303 | 0 | if (!range) return NS_ERROR_NULL_POINTER; |
2304 | 0 | |
2305 | 0 | nsINode* container = range->GetStartContainer(); |
2306 | 0 | if (!container) { |
2307 | 0 | return NS_ERROR_NULL_POINTER; |
2308 | 0 | } |
2309 | 0 | |
2310 | 0 | int32_t offset = range->StartOffset(); |
2311 | 0 | // Be sure previous selection is a table cell |
2312 | 0 | nsIContent* child = range->GetChildAtStartOffset(); |
2313 | 0 | if (child && IsCell(child)) { |
2314 | 0 | previousCellParent = container; |
2315 | 0 | } |
2316 | 0 |
|
2317 | 0 | // We're done if we didn't find parent of a previously-selected cell |
2318 | 0 | if (!previousCellParent) break; |
2319 | 0 | |
2320 | 0 | if (previousCellParent == aParentContent && offset == aContentOffset) |
2321 | 0 | { |
2322 | 0 | // Cell is already selected |
2323 | 0 | if (rangeCount == 1) |
2324 | 0 | { |
2325 | | #ifdef DEBUG_TABLE_SELECTION |
2326 | | printf("HandleTableSelection: Unselecting single selected cell\n"); |
2327 | | #endif |
2328 | | // This was the only cell selected. |
2329 | 0 | // Collapse to "normal" selection inside the cell |
2330 | 0 | mStartSelectedCell = nullptr; |
2331 | 0 | mEndSelectedCell = nullptr; |
2332 | 0 | mAppendStartSelectedCell = nullptr; |
2333 | 0 | //TODO: We need a "Collapse to just before deepest child" routine |
2334 | 0 | // Even better, should we collapse to just after the LAST deepest child |
2335 | 0 | // (i.e., at the end of the cell's contents)? |
2336 | 0 | return mDomSelections[index]->Collapse(childContent, 0); |
2337 | 0 | } |
2338 | | #ifdef DEBUG_TABLE_SELECTION |
2339 | | printf("HandleTableSelection: Removing cell from multi-cell selection\n"); |
2340 | | #endif |
2341 | | // Unselecting the start of previous block |
2342 | 0 | // XXX What do we use now! |
2343 | 0 | if (childContent == mAppendStartSelectedCell) |
2344 | 0 | mAppendStartSelectedCell = nullptr; |
2345 | 0 |
|
2346 | 0 | // Deselect cell by removing its range from selection |
2347 | 0 | ErrorResult err; |
2348 | 0 | mDomSelections[index]->RemoveRange(*range, err); |
2349 | 0 | return err.StealNSResult(); |
2350 | 0 | } |
2351 | 0 | } |
2352 | 0 | mUnselectCellOnMouseUp = nullptr; |
2353 | 0 | } |
2354 | 0 | } |
2355 | 0 | } |
2356 | 0 | return result; |
2357 | 0 | } |
2358 | | |
2359 | | nsresult |
2360 | | nsFrameSelection::SelectBlockOfCells(nsIContent *aStartCell, nsIContent *aEndCell) |
2361 | 0 | { |
2362 | 0 | NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER); |
2363 | 0 | NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER); |
2364 | 0 | mEndSelectedCell = aEndCell; |
2365 | 0 |
|
2366 | 0 | nsresult result = NS_OK; |
2367 | 0 |
|
2368 | 0 | // If new end cell is in a different table, do nothing |
2369 | 0 | nsIContent* table = IsInSameTable(aStartCell, aEndCell); |
2370 | 0 | if (!table) { |
2371 | 0 | return NS_OK; |
2372 | 0 | } |
2373 | 0 | |
2374 | 0 | // Get starting and ending cells' location in the cellmap |
2375 | 0 | int32_t startRowIndex, startColIndex, endRowIndex, endColIndex; |
2376 | 0 | result = GetCellIndexes(aStartCell, startRowIndex, startColIndex); |
2377 | 0 | if(NS_FAILED(result)) return result; |
2378 | 0 | result = GetCellIndexes(aEndCell, endRowIndex, endColIndex); |
2379 | 0 | if(NS_FAILED(result)) return result; |
2380 | 0 | |
2381 | 0 | if (mDragSelectingCells) |
2382 | 0 | { |
2383 | 0 | // Drag selecting: remove selected cells outside of new block limits |
2384 | 0 | UnselectCells(table, startRowIndex, startColIndex, endRowIndex, endColIndex, |
2385 | 0 | true); |
2386 | 0 | } |
2387 | 0 |
|
2388 | 0 | // Note that we select block in the direction of user's mouse dragging, |
2389 | 0 | // which means start cell may be after the end cell in either row or column |
2390 | 0 | return AddCellsToSelection(table, startRowIndex, startColIndex, |
2391 | 0 | endRowIndex, endColIndex); |
2392 | 0 | } |
2393 | | |
2394 | | nsresult |
2395 | | nsFrameSelection::UnselectCells(nsIContent *aTableContent, |
2396 | | int32_t aStartRowIndex, |
2397 | | int32_t aStartColumnIndex, |
2398 | | int32_t aEndRowIndex, |
2399 | | int32_t aEndColumnIndex, |
2400 | | bool aRemoveOutsideOfCellRange) |
2401 | 0 | { |
2402 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2403 | 0 | if (!mDomSelections[index]) |
2404 | 0 | return NS_ERROR_NULL_POINTER; |
2405 | 0 | |
2406 | 0 | nsTableWrapperFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame()); |
2407 | 0 | if (!tableFrame) |
2408 | 0 | return NS_ERROR_FAILURE; |
2409 | 0 | |
2410 | 0 | int32_t minRowIndex = std::min(aStartRowIndex, aEndRowIndex); |
2411 | 0 | int32_t maxRowIndex = std::max(aStartRowIndex, aEndRowIndex); |
2412 | 0 | int32_t minColIndex = std::min(aStartColumnIndex, aEndColumnIndex); |
2413 | 0 | int32_t maxColIndex = std::max(aStartColumnIndex, aEndColumnIndex); |
2414 | 0 |
|
2415 | 0 | // Strong reference because we sometimes remove the range |
2416 | 0 | RefPtr<nsRange> range = GetFirstCellRange(); |
2417 | 0 | nsIContent* cellNode = GetFirstSelectedContent(range); |
2418 | 0 | MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range"); |
2419 | 0 |
|
2420 | 0 | int32_t curRowIndex, curColIndex; |
2421 | 0 | while (cellNode) |
2422 | 0 | { |
2423 | 0 | nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex); |
2424 | 0 | if (NS_FAILED(result)) |
2425 | 0 | return result; |
2426 | 0 | |
2427 | | #ifdef DEBUG_TABLE_SELECTION |
2428 | | if (!range) |
2429 | | printf("RemoveCellsToSelection -- range is null\n"); |
2430 | | #endif |
2431 | | |
2432 | 0 | if (range) { |
2433 | 0 | if (aRemoveOutsideOfCellRange) { |
2434 | 0 | if (curRowIndex < minRowIndex || curRowIndex > maxRowIndex || |
2435 | 0 | curColIndex < minColIndex || curColIndex > maxColIndex) { |
2436 | 0 |
|
2437 | 0 | mDomSelections[index]->RemoveRange(*range, IgnoreErrors()); |
2438 | 0 | // Since we've removed the range, decrement pointer to next range |
2439 | 0 | mSelectedCellIndex--; |
2440 | 0 | } |
2441 | 0 |
|
2442 | 0 | } else { |
2443 | 0 | // Remove cell from selection if it belongs to the given cells range or |
2444 | 0 | // it is spanned onto the cells range. |
2445 | 0 | nsTableCellFrame* cellFrame = |
2446 | 0 | tableFrame->GetCellFrameAt(curRowIndex, curColIndex); |
2447 | 0 |
|
2448 | 0 | uint32_t origRowIndex = cellFrame->RowIndex(); |
2449 | 0 | uint32_t origColIndex = cellFrame->ColIndex(); |
2450 | 0 | uint32_t actualRowSpan = |
2451 | 0 | tableFrame->GetEffectiveRowSpanAt(origRowIndex, origColIndex); |
2452 | 0 | uint32_t actualColSpan = |
2453 | 0 | tableFrame->GetEffectiveColSpanAt(curRowIndex, curColIndex); |
2454 | 0 | if (origRowIndex <= static_cast<uint32_t>(maxRowIndex) && maxRowIndex >= 0 && |
2455 | 0 | origRowIndex + actualRowSpan - 1 >= static_cast<uint32_t>(minRowIndex) && |
2456 | 0 | origColIndex <= static_cast<uint32_t>(maxColIndex) && maxColIndex >= 0 && |
2457 | 0 | origColIndex + actualColSpan - 1 >= static_cast<uint32_t>(minColIndex)) { |
2458 | 0 |
|
2459 | 0 | mDomSelections[index]->RemoveRange(*range, IgnoreErrors()); |
2460 | 0 | // Since we've removed the range, decrement pointer to next range |
2461 | 0 | mSelectedCellIndex--; |
2462 | 0 | } |
2463 | 0 | } |
2464 | 0 | } |
2465 | 0 |
|
2466 | 0 | range = GetNextCellRange(); |
2467 | 0 | cellNode = GetFirstSelectedContent(range); |
2468 | 0 | MOZ_ASSERT(!range || cellNode, "Must have cellNode if had a range"); |
2469 | 0 | } |
2470 | 0 |
|
2471 | 0 | return NS_OK; |
2472 | 0 | } |
2473 | | |
2474 | | nsresult |
2475 | | nsFrameSelection::AddCellsToSelection(nsIContent *aTableContent, |
2476 | | int32_t aStartRowIndex, |
2477 | | int32_t aStartColumnIndex, |
2478 | | int32_t aEndRowIndex, |
2479 | | int32_t aEndColumnIndex) |
2480 | 0 | { |
2481 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2482 | 0 | if (!mDomSelections[index]) |
2483 | 0 | return NS_ERROR_NULL_POINTER; |
2484 | 0 | |
2485 | 0 | nsTableWrapperFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame()); |
2486 | 0 | if (!tableFrame) // Check that |table| is a table. |
2487 | 0 | return NS_ERROR_FAILURE; |
2488 | 0 | |
2489 | 0 | nsresult result = NS_OK; |
2490 | 0 | uint32_t row = aStartRowIndex; |
2491 | 0 | while(true) |
2492 | 0 | { |
2493 | 0 | uint32_t col = aStartColumnIndex; |
2494 | 0 | while(true) |
2495 | 0 | { |
2496 | 0 | nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col); |
2497 | 0 |
|
2498 | 0 | // Skip cells that are spanned from previous locations or are already selected |
2499 | 0 | if (cellFrame) { |
2500 | 0 | uint32_t origRow = cellFrame->RowIndex(); |
2501 | 0 | uint32_t origCol = cellFrame->ColIndex(); |
2502 | 0 | if (origRow == row && origCol == col && !cellFrame->IsSelected()) { |
2503 | 0 | result = SelectCellElement(cellFrame->GetContent()); |
2504 | 0 | if (NS_FAILED(result)) return result; |
2505 | 0 | } |
2506 | 0 | } |
2507 | 0 | // Done when we reach end column |
2508 | 0 | if (col == static_cast<uint32_t>(aEndColumnIndex)) break; |
2509 | 0 | |
2510 | 0 | if (aStartColumnIndex < aEndColumnIndex) |
2511 | 0 | col ++; |
2512 | 0 | else |
2513 | 0 | col--; |
2514 | 0 | } |
2515 | 0 | if (row == static_cast<uint32_t>(aEndRowIndex)) break; |
2516 | 0 | |
2517 | 0 | if (aStartRowIndex < aEndRowIndex) |
2518 | 0 | row++; |
2519 | 0 | else |
2520 | 0 | row--; |
2521 | 0 | } |
2522 | 0 | return result; |
2523 | 0 | } |
2524 | | |
2525 | | nsresult |
2526 | | nsFrameSelection::RemoveCellsFromSelection(nsIContent *aTable, |
2527 | | int32_t aStartRowIndex, |
2528 | | int32_t aStartColumnIndex, |
2529 | | int32_t aEndRowIndex, |
2530 | | int32_t aEndColumnIndex) |
2531 | 0 | { |
2532 | 0 | return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex, |
2533 | 0 | aEndRowIndex, aEndColumnIndex, false); |
2534 | 0 | } |
2535 | | |
2536 | | nsresult |
2537 | | nsFrameSelection::RestrictCellsToSelection(nsIContent *aTable, |
2538 | | int32_t aStartRowIndex, |
2539 | | int32_t aStartColumnIndex, |
2540 | | int32_t aEndRowIndex, |
2541 | | int32_t aEndColumnIndex) |
2542 | 0 | { |
2543 | 0 | return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex, |
2544 | 0 | aEndRowIndex, aEndColumnIndex, true); |
2545 | 0 | } |
2546 | | |
2547 | | nsresult |
2548 | | nsFrameSelection::SelectRowOrColumn(nsIContent *aCellContent, TableSelection aTarget) |
2549 | 0 | { |
2550 | 0 | if (!aCellContent) return NS_ERROR_NULL_POINTER; |
2551 | 0 | |
2552 | 0 | nsIContent* table = GetParentTable(aCellContent); |
2553 | 0 | if (!table) return NS_ERROR_NULL_POINTER; |
2554 | 0 | |
2555 | 0 | // Get table and cell layout interfaces to access |
2556 | 0 | // cell data based on cellmap location |
2557 | 0 | // Frames are not ref counted, so don't use an nsCOMPtr |
2558 | 0 | nsTableWrapperFrame* tableFrame = do_QueryFrame(table->GetPrimaryFrame()); |
2559 | 0 | if (!tableFrame) return NS_ERROR_FAILURE; |
2560 | 0 | nsITableCellLayout *cellLayout = GetCellLayout(aCellContent); |
2561 | 0 | if (!cellLayout) return NS_ERROR_FAILURE; |
2562 | 0 | |
2563 | 0 | // Get location of target cell: |
2564 | 0 | int32_t rowIndex, colIndex; |
2565 | 0 | nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex); |
2566 | 0 | if (NS_FAILED(result)) return result; |
2567 | 0 | |
2568 | 0 | // Be sure we start at proper beginning |
2569 | 0 | // (This allows us to select row or col given ANY cell!) |
2570 | 0 | if (aTarget == TableSelection::Row) |
2571 | 0 | colIndex = 0; |
2572 | 0 | if (aTarget == TableSelection::Column) |
2573 | 0 | rowIndex = 0; |
2574 | 0 |
|
2575 | 0 | nsCOMPtr<nsIContent> firstCell, lastCell; |
2576 | 0 | while (true) { |
2577 | 0 | // Loop through all cells in column or row to find first and last |
2578 | 0 | nsCOMPtr<nsIContent> curCellContent = |
2579 | 0 | tableFrame->GetCellAt(rowIndex, colIndex); |
2580 | 0 | if (!curCellContent) |
2581 | 0 | break; |
2582 | 0 | |
2583 | 0 | if (!firstCell) |
2584 | 0 | firstCell = curCellContent; |
2585 | 0 |
|
2586 | 0 | lastCell = curCellContent.forget(); |
2587 | 0 |
|
2588 | 0 | // Move to next cell in cellmap, skipping spanned locations |
2589 | 0 | if (aTarget == TableSelection::Row) |
2590 | 0 | colIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex); |
2591 | 0 | else |
2592 | 0 | rowIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex); |
2593 | 0 | } |
2594 | 0 |
|
2595 | 0 | // Use SelectBlockOfCells: |
2596 | 0 | // This will replace existing selection, |
2597 | 0 | // but allow unselecting by dragging out of selected region |
2598 | 0 | if (firstCell && lastCell) |
2599 | 0 | { |
2600 | 0 | if (!mStartSelectedCell) |
2601 | 0 | { |
2602 | 0 | // We are starting a new block, so select the first cell |
2603 | 0 | result = SelectCellElement(firstCell); |
2604 | 0 | if (NS_FAILED(result)) return result; |
2605 | 0 | mStartSelectedCell = firstCell; |
2606 | 0 | } |
2607 | 0 | result = SelectBlockOfCells(mStartSelectedCell, lastCell); |
2608 | 0 |
|
2609 | 0 | // This gets set to the cell at end of row/col, |
2610 | 0 | // but we need it to be the cell under cursor |
2611 | 0 | mEndSelectedCell = aCellContent; |
2612 | 0 | return result; |
2613 | 0 | } |
2614 | 0 | |
2615 | | #if 0 |
2616 | | // This is a more efficient strategy that appends row to current selection, |
2617 | | // but doesn't allow dragging OFF of an existing selection to unselect! |
2618 | | do { |
2619 | | // Loop through all cells in column or row |
2620 | | result = tableLayout->GetCellDataAt(rowIndex, colIndex, |
2621 | | getter_AddRefs(cellElement), |
2622 | | curRowIndex, curColIndex, |
2623 | | rowSpan, colSpan, |
2624 | | actualRowSpan, actualColSpan, |
2625 | | isSelected); |
2626 | | if (NS_FAILED(result)) return result; |
2627 | | // We're done when cell is not found |
2628 | | if (!cellElement) break; |
2629 | | |
2630 | | |
2631 | | // Check spans else we infinitely loop |
2632 | | NS_ASSERTION(actualColSpan, "actualColSpan is 0!"); |
2633 | | NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!"); |
2634 | | |
2635 | | // Skip cells that are already selected or span from outside our region |
2636 | | if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex) |
2637 | | { |
2638 | | result = SelectCellElement(cellElement); |
2639 | | if (NS_FAILED(result)) return result; |
2640 | | } |
2641 | | // Move to next row or column in cellmap, skipping spanned locations |
2642 | | if (aTarget == TableSelection::Row) |
2643 | | colIndex += actualColSpan; |
2644 | | else |
2645 | | rowIndex += actualRowSpan; |
2646 | | } |
2647 | | while (cellElement); |
2648 | | #endif |
2649 | | |
2650 | 0 | return NS_OK; |
2651 | 0 | } |
2652 | | |
2653 | | nsIContent* |
2654 | | nsFrameSelection::GetFirstCellNodeInRange(nsRange *aRange) const |
2655 | 0 | { |
2656 | 0 | if (!aRange) return nullptr; |
2657 | 0 | |
2658 | 0 | nsIContent* childContent = aRange->GetChildAtStartOffset(); |
2659 | 0 | if (!childContent) |
2660 | 0 | return nullptr; |
2661 | 0 | // Don't return node if not a cell |
2662 | 0 | if (!IsCell(childContent)) |
2663 | 0 | return nullptr; |
2664 | 0 | |
2665 | 0 | return childContent; |
2666 | 0 | } |
2667 | | |
2668 | | nsRange* |
2669 | | nsFrameSelection::GetFirstCellRange() |
2670 | 0 | { |
2671 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2672 | 0 | if (!mDomSelections[index]) |
2673 | 0 | return nullptr; |
2674 | 0 | |
2675 | 0 | nsRange* firstRange = mDomSelections[index]->GetRangeAt(0); |
2676 | 0 | if (!GetFirstCellNodeInRange(firstRange)) { |
2677 | 0 | return nullptr; |
2678 | 0 | } |
2679 | 0 | |
2680 | 0 | // Setup for next cell |
2681 | 0 | mSelectedCellIndex = 1; |
2682 | 0 |
|
2683 | 0 | return firstRange; |
2684 | 0 | } |
2685 | | |
2686 | | nsRange* |
2687 | | nsFrameSelection::GetNextCellRange() |
2688 | 0 | { |
2689 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2690 | 0 | if (!mDomSelections[index]) |
2691 | 0 | return nullptr; |
2692 | 0 | |
2693 | 0 | nsRange* range = mDomSelections[index]->GetRangeAt(mSelectedCellIndex); |
2694 | 0 |
|
2695 | 0 | // Get first node in next range of selection - test if it's a cell |
2696 | 0 | if (!GetFirstCellNodeInRange(range)) { |
2697 | 0 | return nullptr; |
2698 | 0 | } |
2699 | 0 | |
2700 | 0 | // Setup for next cell |
2701 | 0 | mSelectedCellIndex++; |
2702 | 0 |
|
2703 | 0 | return range; |
2704 | 0 | } |
2705 | | |
2706 | | nsresult |
2707 | | nsFrameSelection::GetCellIndexes(nsIContent *aCell, |
2708 | | int32_t &aRowIndex, |
2709 | | int32_t &aColIndex) |
2710 | 0 | { |
2711 | 0 | if (!aCell) return NS_ERROR_NULL_POINTER; |
2712 | 0 | |
2713 | 0 | aColIndex=0; // initialize out params |
2714 | 0 | aRowIndex=0; |
2715 | 0 |
|
2716 | 0 | nsITableCellLayout *cellLayoutObject = GetCellLayout(aCell); |
2717 | 0 | if (!cellLayoutObject) return NS_ERROR_FAILURE; |
2718 | 0 | return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex); |
2719 | 0 | } |
2720 | | |
2721 | | nsIContent* |
2722 | | nsFrameSelection::IsInSameTable(nsIContent *aContent1, |
2723 | | nsIContent *aContent2) const |
2724 | 0 | { |
2725 | 0 | if (!aContent1 || !aContent2) return nullptr; |
2726 | 0 | |
2727 | 0 | nsIContent* tableNode1 = GetParentTable(aContent1); |
2728 | 0 | nsIContent* tableNode2 = GetParentTable(aContent2); |
2729 | 0 |
|
2730 | 0 | // Must be in the same table. Note that we want to return false for |
2731 | 0 | // the test if both tables are null. |
2732 | 0 | return (tableNode1 == tableNode2) ? tableNode1 : nullptr; |
2733 | 0 | } |
2734 | | |
2735 | | nsIContent* |
2736 | | nsFrameSelection::GetParentTable(nsIContent *aCell) const |
2737 | 0 | { |
2738 | 0 | if (!aCell) { |
2739 | 0 | return nullptr; |
2740 | 0 | } |
2741 | 0 | |
2742 | 0 | for (nsIContent* parent = aCell->GetParent(); parent; |
2743 | 0 | parent = parent->GetParent()) { |
2744 | 0 | if (parent->IsHTMLElement(nsGkAtoms::table)) { |
2745 | 0 | return parent; |
2746 | 0 | } |
2747 | 0 | } |
2748 | 0 |
|
2749 | 0 | return nullptr; |
2750 | 0 | } |
2751 | | |
2752 | | nsresult |
2753 | | nsFrameSelection::SelectCellElement(nsIContent *aCellElement) |
2754 | 0 | { |
2755 | 0 | nsIContent *parent = aCellElement->GetParent(); |
2756 | 0 |
|
2757 | 0 | // Get child offset |
2758 | 0 | int32_t offset = parent->ComputeIndexOf(aCellElement); |
2759 | 0 |
|
2760 | 0 | return CreateAndAddRange(parent, offset); |
2761 | 0 | } |
2762 | | |
2763 | | nsresult |
2764 | | nsFrameSelection::CreateAndAddRange(nsINode* aContainer, int32_t aOffset) |
2765 | 0 | { |
2766 | 0 | if (!aContainer) { |
2767 | 0 | return NS_ERROR_NULL_POINTER; |
2768 | 0 | } |
2769 | 0 | |
2770 | 0 | RefPtr<nsRange> range = new nsRange(aContainer); |
2771 | 0 |
|
2772 | 0 | // Set range around child at given offset |
2773 | 0 | nsresult rv = range->SetStartAndEnd(aContainer, aOffset, |
2774 | 0 | aContainer, aOffset + 1); |
2775 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2776 | 0 | return rv; |
2777 | 0 | } |
2778 | 0 | |
2779 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2780 | 0 | if (!mDomSelections[index]) |
2781 | 0 | return NS_ERROR_NULL_POINTER; |
2782 | 0 | |
2783 | 0 | ErrorResult err; |
2784 | 0 | mDomSelections[index]->AddRange(*range, err); |
2785 | 0 | return err.StealNSResult(); |
2786 | 0 | } |
2787 | | |
2788 | | // End of Table Selection |
2789 | | |
2790 | | void |
2791 | | nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter) |
2792 | 0 | { |
2793 | 0 | if (mAncestorLimiter != aLimiter) { |
2794 | 0 | mAncestorLimiter = aLimiter; |
2795 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2796 | 0 | if (!mDomSelections[index]) |
2797 | 0 | return; |
2798 | 0 | |
2799 | 0 | if (!IsValidSelectionPoint(this, mDomSelections[index]->GetFocusNode())) { |
2800 | 0 | ClearNormalSelection(); |
2801 | 0 | if (mAncestorLimiter) { |
2802 | 0 | PostReason(nsISelectionListener::NO_REASON); |
2803 | 0 | TakeFocus(mAncestorLimiter, 0, 0, CARET_ASSOCIATE_BEFORE, false, false); |
2804 | 0 | } |
2805 | 0 | } |
2806 | 0 | } |
2807 | 0 | } |
2808 | | |
2809 | | nsresult |
2810 | | nsFrameSelection::DeleteFromDocument() |
2811 | 0 | { |
2812 | 0 | // If we're already collapsed, then we do nothing (bug 719503). |
2813 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2814 | 0 | if (!mDomSelections[index]) |
2815 | 0 | return NS_ERROR_NULL_POINTER; |
2816 | 0 | |
2817 | 0 | if (mDomSelections[index]->IsCollapsed()) |
2818 | 0 | { |
2819 | 0 | return NS_OK; |
2820 | 0 | } |
2821 | 0 | |
2822 | 0 | RefPtr<Selection> selection = mDomSelections[index]; |
2823 | 0 | for (uint32_t rangeIdx = 0; rangeIdx < selection->RangeCount(); ++rangeIdx) { |
2824 | 0 | RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx); |
2825 | 0 | ErrorResult res; |
2826 | 0 | range->DeleteContents(res); |
2827 | 0 | if (res.Failed()) { |
2828 | 0 | return res.StealNSResult(); |
2829 | 0 | } |
2830 | 0 | } |
2831 | 0 |
|
2832 | 0 | // Collapse to the new location. |
2833 | 0 | // If we deleted one character, then we move back one element. |
2834 | 0 | // FIXME We don't know how to do this past frame boundaries yet. |
2835 | 0 | if (mDomSelections[index]->AnchorOffset() > 0) |
2836 | 0 | mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset()); |
2837 | | #ifdef DEBUG |
2838 | | else |
2839 | | printf("Don't know how to set selection back past frame boundary\n"); |
2840 | | #endif |
2841 | |
|
2842 | 0 | return NS_OK; |
2843 | 0 | } |
2844 | | |
2845 | | void |
2846 | | nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent* aMouseEvent) |
2847 | 0 | { |
2848 | 0 | if (aMouseEvent) { |
2849 | 0 | mDelayedMouseEventValid = true; |
2850 | 0 | mDelayedMouseEventIsShift = aMouseEvent->IsShift(); |
2851 | 0 | mDelayedMouseEventClickCount = aMouseEvent->mClickCount; |
2852 | 0 | } else { |
2853 | 0 | mDelayedMouseEventValid = false; |
2854 | 0 | } |
2855 | 0 | } |
2856 | | |
2857 | | void |
2858 | | nsFrameSelection::DisconnectFromPresShell() |
2859 | 0 | { |
2860 | 0 | if (mAccessibleCaretEnabled) { |
2861 | 0 | int8_t index = GetIndexFromSelectionType(SelectionType::eNormal); |
2862 | 0 | mDomSelections[index]->StopNotifyingAccessibleCaretEventHub(); |
2863 | 0 | } |
2864 | 0 |
|
2865 | 0 | StopAutoScrollTimer(); |
2866 | 0 | for (size_t i = 0; i < ArrayLength(mDomSelections); i++) { |
2867 | 0 | mDomSelections[i]->Clear(nullptr); |
2868 | 0 | } |
2869 | 0 | mShell = nullptr; |
2870 | 0 | } |
2871 | | |
2872 | | /** |
2873 | | * See Bug 1288453. |
2874 | | * |
2875 | | * Update the selection cache on repaint to handle when a pre-existing |
2876 | | * selection becomes active aka the current selection. |
2877 | | * |
2878 | | * 1. Change the current selection by click n dragging another selection. |
2879 | | * - Make a selection on content page. Make a selection in a text editor. |
2880 | | * - You can click n drag the content selection to make it active again. |
2881 | | * 2. Change the current selection when switching to a tab with a selection. |
2882 | | * - Make selection in tab. |
2883 | | * - Switching tabs will make its respective selection active. |
2884 | | * |
2885 | | * Therefore, we only update the selection cache on a repaint |
2886 | | * if the current selection being repainted is not an empty selection. |
2887 | | * |
2888 | | * If the current selection is empty. The current selection cache |
2889 | | * would be cleared by AutoCopyListener::OnSelectionChange(). |
2890 | | */ |
2891 | | nsresult |
2892 | | nsFrameSelection::UpdateSelectionCacheOnRepaintSelection(Selection* aSel) |
2893 | 0 | { |
2894 | 0 | nsIPresShell* ps = aSel->GetPresShell(); |
2895 | 0 | if (!ps) { |
2896 | 0 | return NS_OK; |
2897 | 0 | } |
2898 | 0 | nsCOMPtr<nsIDocument> aDoc = ps->GetDocument(); |
2899 | 0 |
|
2900 | 0 | if (aDoc && aSel && !aSel->IsCollapsed()) { |
2901 | 0 | return nsCopySupport::HTMLCopy(aSel, aDoc, |
2902 | 0 | nsIClipboard::kSelectionCache, false); |
2903 | 0 | } |
2904 | 0 | |
2905 | 0 | return NS_OK; |
2906 | 0 | } |
2907 | | |
2908 | | // mozilla::AutoCopyListener |
2909 | | |
2910 | | int16_t AutoCopyListener::sClipboardID = -1; |
2911 | | |
2912 | | /* |
2913 | | * What we do now: |
2914 | | * On every selection change, we copy to the clipboard anew, creating a |
2915 | | * HTML buffer, a transferable, an nsISupportsString and |
2916 | | * a huge mess every time. This is basically what nsPresShell::DoCopy does |
2917 | | * to move the selection into the clipboard for Edit->Copy. |
2918 | | * |
2919 | | * What we should do, to make our end of the deal faster: |
2920 | | * Create a singleton transferable with our own magic converter. When selection |
2921 | | * changes (use a quick cache to detect ``real'' changes), we put the new |
2922 | | * Selection in the transferable. Our magic converter will take care of |
2923 | | * transferable->whatever-other-format when the time comes to actually |
2924 | | * hand over the clipboard contents. |
2925 | | * |
2926 | | * Other issues: |
2927 | | * - which X clipboard should we populate? |
2928 | | * - should we use a different one than Edit->Copy, so that inadvertant |
2929 | | * selections (or simple clicks, which currently cause a selection |
2930 | | * notification, regardless of if they're in the document which currently has |
2931 | | * selection!) don't lose the contents of the ``application''? Or should we |
2932 | | * just put some intelligence in the ``is this a real selection?'' code to |
2933 | | * protect our selection against clicks in other documents that don't create |
2934 | | * selections? |
2935 | | * - maybe we should just never clear the X clipboard? That would make this |
2936 | | * problem just go away, which is very tempting. |
2937 | | * |
2938 | | * On macOS, |
2939 | | * nsIClipboard::kSelectionCache is the flag for current selection cache. |
2940 | | * Set the current selection cache on the parent process in |
2941 | | * widget cocoa nsClipboard whenever selection changes. |
2942 | | */ |
2943 | | |
2944 | | // static |
2945 | | void |
2946 | | AutoCopyListener::OnSelectionChange(nsIDocument* aDocument, |
2947 | | Selection& aSelection, |
2948 | | int16_t aReason) |
2949 | 0 | { |
2950 | 0 | MOZ_ASSERT(IsValidClipboardID(sClipboardID)); |
2951 | 0 |
|
2952 | 0 | if (sClipboardID == nsIClipboard::kSelectionCache) { |
2953 | 0 | nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
2954 | 0 | // If no active window, do nothing because a current selection changed |
2955 | 0 | // cannot occur unless it is in the active window. |
2956 | 0 | if (!fm->GetActiveWindow()) { |
2957 | 0 | return; |
2958 | 0 | } |
2959 | 0 | } |
2960 | 0 | |
2961 | 0 | static const int16_t kResasonsToHandle = |
2962 | 0 | nsISelectionListener::MOUSEUP_REASON | |
2963 | 0 | nsISelectionListener::SELECTALL_REASON | |
2964 | 0 | nsISelectionListener::KEYPRESS_REASON; |
2965 | 0 | if (!(aReason & kResasonsToHandle)) { |
2966 | 0 | return; // Don't care if we are still dragging. |
2967 | 0 | } |
2968 | 0 | |
2969 | 0 | if (!aDocument || aSelection.IsCollapsed()) { |
2970 | | #ifdef DEBUG_CLIPBOARD |
2971 | | fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n"); |
2972 | | #endif |
2973 | 0 | if (sClipboardID != nsIClipboard::kSelectionCache) { |
2974 | 0 | // XXX Should we clear X clipboard? |
2975 | 0 | return; |
2976 | 0 | } |
2977 | 0 | |
2978 | 0 | // If on macOS, clear the current selection transferable cached |
2979 | 0 | // on the parent process (nsClipboard) when the selection is empty. |
2980 | 0 | DebugOnly<nsresult> rv = nsCopySupport::ClearSelectionCache(); |
2981 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
2982 | 0 | "nsCopySupport::ClearSelectionCache() failed"); |
2983 | 0 | return; |
2984 | 0 | } |
2985 | 0 |
|
2986 | 0 | // Call the copy code. |
2987 | 0 | DebugOnly<nsresult> rv = |
2988 | 0 | nsCopySupport::HTMLCopy(&aSelection, aDocument, sClipboardID, false); |
2989 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
2990 | 0 | "nsCopySupport::HTMLCopy() failed"); |
2991 | 0 | } |