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