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/. */
8 * Implementation of selection: nsISelection,nsISelectionPrivate and nsFrameSelection
11 #include "mozilla/dom/Selection.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/EventStates.h"
18 #include "nsFrameSelection.h"
19 #include "nsISelectionListener.h"
20 #include "nsContentCID.h"
21 #include "nsIContent.h"
22 #include "nsIDOMNode.h"
24 #include "nsCOMArray.h"
25 #include "nsITableCellLayout.h"
27 #include "nsTableOuterFrame.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"
36 #include "nsGkAtoms.h"
37 #include "nsIFrameTraversal.h"
38 #include "nsLayoutUtils.h"
39 #include "nsLayoutCID.h"
40 #include "nsBidiPresUtils.h"
41 static NS_DEFINE_CID(kFrameTraversalCID
, NS_FRAMETRAVERSAL_CID
);
42 #include "nsTextFrame.h"
44 #include "nsIDOMText.h"
46 #include "nsContentUtils.h"
47 #include "nsThreadUtils.h"
48 #include "mozilla/Preferences.h"
49 #include "nsDOMClassInfoID.h"
51 #include "nsPresContext.h"
52 #include "nsIPresShell.h"
54 #include "TouchCaret.h"
55 #include "SelectionCarets.h"
57 #include "mozilla/MouseEvents.h"
58 #include "mozilla/TextEvents.h"
61 #include "nsFrameManager.h"
63 #include "nsIDOMDocument.h"
64 #include "nsIDocument.h"
66 #include "nsISelectionController.h"//for the enums
67 #include "nsAutoCopyListener.h"
68 #include "nsCopySupport.h"
69 #include "nsIClipboard.h"
70 #include "nsIFrameInlines.h"
72 #include "nsIBidiKeyboard.h"
75 #include "mozilla/dom/Element.h"
76 #include "mozilla/dom/ShadowRoot.h"
77 #include "mozilla/ErrorResult.h"
78 #include "mozilla/dom/SelectionBinding.h"
80 using namespace mozilla
;
81 using namespace mozilla::dom
;
83 //#define DEBUG_TABLE 1
85 static bool IsValidSelectionPoint(nsFrameSelection
*aFrameSel
, nsINode
*aNode
);
87 static nsIAtom
*GetTag(nsINode
*aNode
);
89 static nsINode
* ParentOffset(nsINode
*aNode
, int32_t *aChildOffset
);
90 static nsINode
* GetCellParent(nsINode
*aDomNode
);
93 static void printRange(nsRange
*aDomRange
);
94 #define DEBUG_OUT_RANGE(x) printRange(x)
96 #define DEBUG_OUT_RANGE(x)
101 //#define DEBUG_SELECTION // uncomment for printf describing every collapse and extend.
102 //#define DEBUG_NAVIGATION
105 //#define DEBUG_TABLE_SELECTION 1
107 nsPeekOffsetStruct::nsPeekOffsetStruct(nsSelectionAmount aAmount
,
108 nsDirection aDirection
,
109 int32_t aStartOffset
,
112 bool aScrollViewStop
,
113 bool aIsKeyboardSelect
,
115 EWordMovementType aWordMovementType
)
117 , mDirection(aDirection
)
118 , mStartOffset(aStartOffset
)
119 , mDesiredPos(aDesiredPos
)
120 , mWordMovementType(aWordMovementType
)
121 , mJumpLines(aJumpLines
)
122 , mScrollViewStop(aScrollViewStop
)
123 , mIsKeyboardSelect(aIsKeyboardSelect
)
126 , mResultFrame(nullptr)
128 , mAttach(CARET_ASSOCIATE_BEFORE
)
132 struct CachedOffsetForFrame
{
133 CachedOffsetForFrame()
134 : mCachedFrameOffset(0, 0) // nsPoint ctor
135 , mLastCaretFrame(nullptr)
136 , mLastContentOffset(0)
137 , mCanCacheFrameOffset(false)
140 nsPoint mCachedFrameOffset
; // cached frame offset
141 nsIFrame
* mLastCaretFrame
; // store the frame the caret was last drawn in.
142 int32_t mLastContentOffset
; // store last content offset
143 bool mCanCacheFrameOffset
; // cached frame offset is valid?
146 class nsAutoScrollTimer MOZ_FINAL
: public nsITimerCallback
153 : mFrameSelection(0), mSelection(0), mPresContext(0), mPoint(0,0), mDelay(30)
157 // aPoint is relative to aPresContext's root frame
158 nsresult
Start(nsPresContext
*aPresContext
, nsPoint
&aPoint
)
162 // Store the presentation context. The timer will be
163 // stopped by the selection if the prescontext is destroyed.
164 mPresContext
= aPresContext
;
166 mContent
= nsIPresShell::GetCapturingContent();
171 mTimer
= do_CreateInstance("@mozilla.org/timer;1", &result
);
173 if (NS_FAILED(result
))
177 return mTimer
->InitWithCallback(this, mDelay
, nsITimer::TYPE_ONE_SHOT
);
192 nsresult
Init(nsFrameSelection
* aFrameSelection
, Selection
* aSelection
)
194 mFrameSelection
= aFrameSelection
;
195 mSelection
= aSelection
;
199 nsresult
SetDelay(uint32_t aDelay
)
205 NS_IMETHOD
Notify(nsITimer
*timer
) MOZ_OVERRIDE
207 if (mSelection
&& mPresContext
)
210 mContent
? mPresContext
->GetPrimaryFrameFor(mContent
) : nullptr;
215 nsPoint pt
= mPoint
-
216 frame
->GetOffsetTo(mPresContext
->PresShell()->FrameManager()->GetRootFrame());
217 mFrameSelection
->HandleDrag(frame
, pt
);
218 if (!frame
.IsAlive())
221 NS_ASSERTION(frame
->PresContext() == mPresContext
, "document mismatch?");
222 mSelection
->DoAutoScroll(frame
, pt
);
228 virtual ~nsAutoScrollTimer()
236 nsFrameSelection
*mFrameSelection
;
237 Selection
* mSelection
;
238 nsPresContext
*mPresContext
;
239 // relative to mPresContext's root frame
241 nsCOMPtr
<nsITimer
> mTimer
;
242 nsCOMPtr
<nsIContent
> mContent
;
246 NS_IMPL_ISUPPORTS(nsAutoScrollTimer
, nsITimerCallback
)
248 nsresult
NS_NewDomSelection(nsISelection
**aDomSelection
)
250 Selection
* rlist
= new Selection
;
251 *aDomSelection
= (nsISelection
*)rlist
;
257 GetIndexFromSelectionType(SelectionType aType
)
261 case nsISelectionController::SELECTION_NORMAL
: return 0; break;
262 case nsISelectionController::SELECTION_SPELLCHECK
: return 1; break;
263 case nsISelectionController::SELECTION_IME_RAWINPUT
: return 2; break;
264 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
: return 3; break;
265 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT
: return 4; break;
266 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
: return 5; break;
267 case nsISelectionController::SELECTION_ACCESSIBILITY
: return 6; break;
268 case nsISelectionController::SELECTION_FIND
: return 7; break;
269 case nsISelectionController::SELECTION_URLSECONDARY
: return 8; break;
278 GetSelectionTypeFromIndex(int8_t aIndex
)
282 case 0: return nsISelectionController::SELECTION_NORMAL
; break;
283 case 1: return nsISelectionController::SELECTION_SPELLCHECK
; break;
284 case 2: return nsISelectionController::SELECTION_IME_RAWINPUT
; break;
285 case 3: return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
; break;
286 case 4: return nsISelectionController::SELECTION_IME_CONVERTEDTEXT
; break;
287 case 5: return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
; break;
288 case 6: return nsISelectionController::SELECTION_ACCESSIBILITY
; break;
289 case 7: return nsISelectionController::SELECTION_FIND
; break;
290 case 8: return nsISelectionController::SELECTION_URLSECONDARY
; break;
292 return nsISelectionController::SELECTION_NORMAL
; break;
299 The limiter is used specifically for the text areas and textfields
300 In that case it is the DIV tag that is anonymously created for the text
301 areas/fields. Text nodes and BR nodes fall beneath it. In the case of a
302 BR node the limiter will be the parent and the offset will point before or
303 after the BR node. In the case of the text node the parent content is
304 the text node itself and the offset will be the exact character position.
305 The offset is not important to check for validity. Simply look at the
306 passed in content. If it equals the limiter then the selection point is valid.
307 If its parent it the limiter then the point is also valid. In the case of
308 NO limiter all points are valid since you are in a topmost iframe. (browser
312 IsValidSelectionPoint(nsFrameSelection
*aFrameSel
, nsINode
*aNode
)
314 if (!aFrameSel
|| !aNode
)
317 nsIContent
*limiter
= aFrameSel
->GetLimiter();
318 if (limiter
&& limiter
!= aNode
&& limiter
!= aNode
->GetParent()) {
319 //if newfocus == the limiter. that's ok. but if not there and not parent bad
320 return false; //not in the right content. tLimiter said so
323 limiter
= aFrameSel
->GetAncestorLimiter();
324 return !limiter
|| nsContentUtils::ContentIsDescendantOf(aNode
, limiter
);
328 ////////////BEGIN nsFrameSelection methods
330 nsFrameSelection::nsFrameSelection()
333 for (i
= 0;i
<nsISelectionController::NUM_SELECTIONTYPES
;i
++){
334 mDomSelections
[i
] = new Selection(this);
335 mDomSelections
[i
]->SetType(GetSelectionTypeFromIndex(i
));
338 mChangesDuringBatching
= false;
339 mNotifyFrames
= true;
341 mMouseDoubleDownState
= false;
343 mHint
= CARET_ASSOCIATE_BEFORE
;
344 mCaretBidiLevel
= BIDI_LEVEL_UNDEFINED
;
345 mKbdBidiLevel
= NSBIDI_LTR
;
347 mDragSelectingCells
= false;
348 mSelectingTableCellMode
= 0;
349 mSelectedCellIndex
= 0;
351 // Check to see if the autocopy pref is enabled
352 // and add the autocopy listener if it is
353 if (Preferences::GetBool("clipboard.autocopy")) {
354 nsAutoCopyListener
*autoCopy
= nsAutoCopyListener::GetInstance();
358 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
359 if (mDomSelections
[index
]) {
360 autoCopy
->Listen(mDomSelections
[index
]);
365 mDisplaySelection
= nsISelectionController::SELECTION_OFF
;
366 mSelectionChangeReason
= nsISelectionListener::NO_REASON
;
368 mDelayedMouseEventValid
= false;
369 // These values are not used since they are only valid when
370 // mDelayedMouseEventValid is true, and setting mDelayedMouseEventValid
371 //alwaysoverrides these values.
372 mDelayedMouseEventIsShift
= false;
373 mDelayedMouseEventClickCount
= 0;
376 nsFrameSelection::~nsFrameSelection()
380 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection
)
382 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection
)
384 for (i
= 0; i
< nsISelectionController::NUM_SELECTIONTYPES
; ++i
) {
385 tmp
->mDomSelections
[i
] = nullptr;
388 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCellParent
)
389 tmp
->mSelectingTableCellMode
= 0;
390 tmp
->mDragSelectingCells
= false;
391 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartSelectedCell
)
392 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSelectedCell
)
393 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAppendStartSelectedCell
)
394 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnselectCellOnMouseUp
)
395 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainRange
)
396 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiter
)
397 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAncestorLimiter
)
398 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
399 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection
)
400 if (tmp
->mShell
&& tmp
->mShell
->GetDocument() &&
401 nsCCUncollectableMarker::InGeneration(cb
,
402 tmp
->mShell
->GetDocument()->
403 GetMarkedCCGeneration())) {
404 return NS_SUCCESS_INTERRUPTED_TRAVERSE
;
407 for (i
= 0; i
< nsISelectionController::NUM_SELECTIONTYPES
; ++i
) {
408 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections
[i
])
411 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCellParent
)
412 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartSelectedCell
)
413 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSelectedCell
)
414 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAppendStartSelectedCell
)
415 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnselectCellOnMouseUp
)
416 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainRange
)
417 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiter
)
418 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAncestorLimiter
)
419 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
421 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsFrameSelection
, AddRef
)
422 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsFrameSelection
, Release
)
424 // Get the x (or y, in vertical writing mode) position requested
425 // by the Key Handling for line-up/down
427 nsFrameSelection::FetchDesiredPos(nsPoint
&aDesiredPos
)
430 NS_ERROR("fetch desired position failed");
431 return NS_ERROR_FAILURE
;
433 if (mDesiredPosSet
) {
434 aDesiredPos
= mDesiredPos
;
438 nsRefPtr
<nsCaret
> caret
= mShell
->GetCaret();
440 return NS_ERROR_NULL_POINTER
;
443 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
444 caret
->SetSelection(mDomSelections
[index
]);
447 nsIFrame
* caretFrame
= caret
->GetGeometry(&coord
);
449 return NS_ERROR_FAILURE
;
451 nsPoint
viewOffset(0, 0);
452 nsView
* view
= nullptr;
453 caretFrame
->GetOffsetFromView(viewOffset
, &view
);
457 aDesiredPos
= coord
.TopLeft();
462 nsFrameSelection::InvalidateDesiredPos() // do not listen to mDesiredPos;
463 // you must get another.
465 mDesiredPosSet
= false;
469 nsFrameSelection::SetDesiredPos(nsPoint aPos
)
472 mDesiredPosSet
= true;
476 nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(nsIFrame
*aFrame
,
478 nsIFrame
**aRetFrame
,
482 // The whole point of this method is to return a frame and point that
483 // that lie within the same valid subtree as the anchor node's frame,
484 // for use with the method GetContentAndOffsetsFromPoint().
486 // A valid subtree is defined to be one where all the content nodes in
487 // the tree have a valid parent-child relationship.
489 // If the anchor frame and aFrame are in the same subtree, aFrame will
490 // be returned in aRetFrame. If they are in different subtrees, we
491 // return the frame for the root of the subtree.
494 if (!aFrame
|| !aRetFrame
)
495 return NS_ERROR_NULL_POINTER
;
501 // Get the frame and content for the selection's anchor point!
505 nsCOMPtr
<nsIDOMNode
> anchorNode
;
506 int32_t anchorOffset
= 0;
508 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
509 if (!mDomSelections
[index
])
510 return NS_ERROR_NULL_POINTER
;
512 result
= mDomSelections
[index
]->GetAnchorNode(getter_AddRefs(anchorNode
));
514 if (NS_FAILED(result
))
520 result
= mDomSelections
[index
]->GetAnchorOffset(&anchorOffset
);
522 if (NS_FAILED(result
))
525 nsCOMPtr
<nsIContent
> anchorContent
= do_QueryInterface(anchorNode
);
528 return NS_ERROR_FAILURE
;
531 // Now find the root of the subtree containing the anchor's content.
534 NS_ENSURE_STATE(mShell
);
535 nsIContent
* anchorRoot
= anchorContent
->GetSelectionRootContent(mShell
);
536 NS_ENSURE_TRUE(anchorRoot
, NS_ERROR_UNEXPECTED
);
539 // Now find the root of the subtree containing aFrame's content.
542 nsIContent
* content
= aFrame
->GetContent();
546 nsIContent
* contentRoot
= content
->GetSelectionRootContent(mShell
);
547 NS_ENSURE_TRUE(contentRoot
, NS_ERROR_UNEXPECTED
);
549 if (anchorRoot
== contentRoot
)
551 // If the aFrame's content isn't the capturing content, it should be
552 // a descendant. At this time, we can return simply.
553 nsIContent
* capturedContent
= nsIPresShell::GetCapturingContent();
554 if (capturedContent
!= content
)
559 // Find the frame under the mouse cursor with the root frame.
560 // At this time, don't use the anchor's frame because it may not have
561 // fixed positioned frames.
562 nsIFrame
* rootFrame
= mShell
->FrameManager()->GetRootFrame();
563 nsPoint ptInRoot
= aPoint
+ aFrame
->GetOffsetTo(rootFrame
);
564 nsIFrame
* cursorFrame
=
565 nsLayoutUtils::GetFrameForPoint(rootFrame
, ptInRoot
);
567 // If the mouse cursor in on a frame which is descendant of same
568 // selection root, we can expand the selection to the frame.
569 if (cursorFrame
&& cursorFrame
->PresContext()->PresShell() == mShell
)
571 nsIContent
* cursorContent
= cursorFrame
->GetContent();
572 NS_ENSURE_TRUE(cursorContent
, NS_ERROR_FAILURE
);
573 nsIContent
* cursorContentRoot
=
574 cursorContent
->GetSelectionRootContent(mShell
);
575 NS_ENSURE_TRUE(cursorContentRoot
, NS_ERROR_UNEXPECTED
);
576 if (cursorContentRoot
== anchorRoot
)
578 *aRetFrame
= cursorFrame
;
579 aRetPoint
= aPoint
+ aFrame
->GetOffsetTo(cursorFrame
);
583 // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
584 // cursor is out of the window), we should use the frame of the anchor
590 // When we can't find a frame which is under the mouse cursor and has a same
591 // selection root as the anchor node's, we should return the selection root
595 *aRetFrame
= anchorRoot
->GetPrimaryFrame();
598 return NS_ERROR_FAILURE
;
601 // Now make sure that aRetPoint is converted to the same coordinate
602 // system used by aRetFrame.
605 aRetPoint
= aPoint
+ aFrame
->GetOffsetTo(*aRetFrame
);
611 nsFrameSelection::SetCaretBidiLevel(nsBidiLevel aLevel
)
613 // If the current level is undefined, we have just inserted new text.
614 // In this case, we don't want to reset the keyboard language
615 mCaretBidiLevel
= aLevel
;
617 nsRefPtr
<nsCaret
> caret
;
618 if (mShell
&& (caret
= mShell
->GetCaret())) {
619 caret
->SchedulePaint();
626 nsFrameSelection::GetCaretBidiLevel() const
628 return mCaretBidiLevel
;
632 nsFrameSelection::UndefineCaretBidiLevel()
634 mCaretBidiLevel
|= BIDI_LEVEL_UNDEFINED
;
638 void printRange(nsRange
*aDomRange
)
642 printf("NULL nsIDOMRange\n");
644 nsINode
* startNode
= aDomRange
->GetStartParent();
645 nsINode
* endNode
= aDomRange
->GetEndParent();
646 int32_t startOffset
= aDomRange
->StartOffset();
647 int32_t endOffset
= aDomRange
->EndOffset();
649 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
650 (unsigned long)aDomRange
,
651 (unsigned long)startNode
, (long)startOffset
,
652 (unsigned long)endNode
, (long)endOffset
);
655 #endif /* PRINT_RANGE */
658 nsIAtom
*GetTag(nsINode
*aNode
)
660 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aNode
);
663 NS_NOTREACHED("bad node passed to GetTag()");
667 return content
->Tag();
670 // Returns the parent
672 ParentOffset(nsINode
*aNode
, int32_t *aChildOffset
)
674 if (!aNode
|| !aChildOffset
)
677 nsIContent
* parent
= aNode
->GetParent();
680 *aChildOffset
= parent
->IndexOf(aNode
);
689 GetCellParent(nsINode
*aDomNode
)
693 nsINode
* current
= aDomNode
;
694 // Start with current node and look for a table cell
697 nsIAtom
* tag
= GetTag(current
);
698 if (tag
== nsGkAtoms::td
|| tag
== nsGkAtoms::th
)
700 current
= current
->GetParent();
706 nsFrameSelection::Init(nsIPresShell
*aShell
, nsIContent
*aLimiter
)
710 mDesiredPosSet
= false;
712 mCaretMovementStyle
=
713 Preferences::GetInt("bidi.edit.caret_movement_style", 2);
714 // Set touch caret as selection listener
715 nsRefPtr
<TouchCaret
> touchCaret
= mShell
->GetTouchCaret();
717 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
718 if (mDomSelections
[index
]) {
719 mDomSelections
[index
]->AddSelectionListener(touchCaret
);
723 // Set selection caret as selection listener
724 nsRefPtr
<SelectionCarets
> selectionCarets
= mShell
->GetSelectionCarets();
725 if (selectionCarets
) {
726 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
727 if (mDomSelections
[index
]) {
728 mDomSelections
[index
]->AddSelectionListener(selectionCarets
);
734 nsFrameSelection::MoveCaret(nsDirection aDirection
,
735 bool aContinueSelection
,
736 nsSelectionAmount aAmount
,
737 CaretMovementStyle aMovementStyle
)
739 bool visualMovement
= aMovementStyle
== eVisual
||
740 (aMovementStyle
== eUsePrefStyle
&&
741 (mCaretMovementStyle
== 1 ||
742 (mCaretMovementStyle
== 2 && !aContinueSelection
)));
744 NS_ENSURE_STATE(mShell
);
745 // Flush out layout, since we need it to be up to date to do caret
747 mShell
->FlushPendingNotifications(Flush_Layout
);
753 nsPresContext
*context
= mShell
->GetPresContext();
755 return NS_ERROR_FAILURE
;
758 nsPoint
desiredPos(0, 0); //we must keep this around and revalidate it when its just UP/DOWN
760 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
761 nsRefPtr
<Selection
> sel
= mDomSelections
[index
];
763 return NS_ERROR_NULL_POINTER
;
765 int32_t scrollFlags
= 0;
766 nsINode
* focusNode
= sel
->GetFocusNode();
768 (focusNode
->IsEditable() ||
769 (focusNode
->IsElement() &&
770 focusNode
->AsElement()->State().
771 HasState(NS_EVENT_STATE_MOZ_READWRITE
)))) {
772 // If caret moves in editor, it should cause scrolling even if it's in
773 // overflow: hidden;.
774 scrollFlags
|= Selection::SCROLL_OVERFLOW_HIDDEN
;
777 nsresult result
= sel
->GetIsCollapsed(&isCollapsed
);
778 if (NS_FAILED(result
)) {
781 if (aAmount
== eSelectLine
) {
782 result
= FetchDesiredPos(desiredPos
);
783 if (NS_FAILED(result
)) {
786 SetDesiredPos(desiredPos
);
789 int32_t caretStyle
= Preferences::GetInt("layout.selection.caret_style", 0);
792 && aAmount
!= eSelectLine
795 // Put caret at the selection edge in the |aDirection| direction.
799 if (!isCollapsed
&& !aContinueSelection
&& caretStyle
== 2 &&
800 aAmount
<= eSelectLine
) {
801 switch (aDirection
) {
804 const nsRange
* anchorFocusRange
= sel
->GetAnchorFocusRange();
805 if (anchorFocusRange
) {
806 PostReason(nsISelectionListener::COLLAPSETOSTART_REASON
);
807 sel
->Collapse(anchorFocusRange
->GetStartParent(),
808 anchorFocusRange
->StartOffset());
810 mHint
= CARET_ASSOCIATE_AFTER
;
811 sel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
812 nsIPresShell::ScrollAxis(),
813 nsIPresShell::ScrollAxis(), scrollFlags
);
819 const nsRange
* anchorFocusRange
= sel
->GetAnchorFocusRange();
820 if (anchorFocusRange
) {
821 PostReason(nsISelectionListener::COLLAPSETOEND_REASON
);
822 sel
->Collapse(anchorFocusRange
->GetEndParent(),
823 anchorFocusRange
->EndOffset());
825 mHint
= CARET_ASSOCIATE_BEFORE
;
826 sel
->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
827 nsIPresShell::ScrollAxis(),
828 nsIPresShell::ScrollAxis(), scrollFlags
);
835 int32_t offsetused
= 0;
836 result
= sel
->GetPrimaryFrameForFocusNode(&frame
, &offsetused
,
839 if (NS_FAILED(result
) || !frame
)
840 return NS_FAILED(result
) ? result
: NS_ERROR_FAILURE
;
842 //set data using mLimiter to stop on scroll views. If we have a limiter then we stop peeking
843 //when we hit scrollable views. If no limiter then just let it go ahead
844 nsPeekOffsetStruct
pos(aAmount
, eDirPrevious
, offsetused
, desiredPos
,
845 true, mLimiter
!= nullptr, true, visualMovement
);
847 nsBidiDirection paraDir
= nsBidiPresUtils::ParagraphDirection(frame
);
849 CaretAssociateHint
tHint(mHint
); //temporary variable so we dont set mHint until it is necessary
851 case eSelectCharacter
:
854 case eSelectWordNoSpace
:
855 InvalidateDesiredPos();
856 pos
.mAmount
= aAmount
;
857 pos
.mDirection
= (visualMovement
&& paraDir
== NSBIDI_RTL
)
858 ? nsDirection(1 - aDirection
) : aDirection
;
861 pos
.mAmount
= aAmount
;
862 pos
.mDirection
= aDirection
;
864 case eSelectBeginLine
:
866 InvalidateDesiredPos();
867 pos
.mAmount
= aAmount
;
868 pos
.mDirection
= (visualMovement
&& paraDir
== NSBIDI_RTL
)
869 ? nsDirection(1 - aDirection
) : aDirection
;
872 return NS_ERROR_FAILURE
;
874 PostReason(nsISelectionListener::KEYPRESS_REASON
);
875 if (NS_SUCCEEDED(result
= frame
->PeekOffset(&pos
)) && pos
.mResultContent
)
878 int32_t currentOffset
, frameStart
, frameEnd
;
880 if (aAmount
<= eSelectWordNoSpace
)
882 // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does not set pos.mAttachForward,
883 // so determine the hint here based on the result frame and offset:
884 // If we're at the end of a text frame, set the hint to ASSOCIATE_BEFORE to indicate that we
885 // want the caret displayed at the end of this frame, not at the beginning of the next one.
886 theFrame
= pos
.mResultFrame
;
887 theFrame
->GetOffsets(frameStart
, frameEnd
);
888 currentOffset
= pos
.mContentOffset
;
889 if (frameEnd
== currentOffset
&& !(frameStart
== 0 && frameEnd
== 0))
890 tHint
= CARET_ASSOCIATE_BEFORE
;
892 tHint
= CARET_ASSOCIATE_AFTER
;
894 // For up/down and home/end, pos.mResultFrame might not be set correctly, or not at all.
895 // In these cases, get the frame based on the content and hint returned by PeekOffset().
897 theFrame
= GetFrameForNodeOffset(pos
.mResultContent
, pos
.mContentOffset
,
898 tHint
, ¤tOffset
);
900 return NS_ERROR_FAILURE
;
902 theFrame
->GetOffsets(frameStart
, frameEnd
);
905 if (context
->BidiEnabled())
908 case eSelectBeginLine
:
910 // In Bidi contexts, PeekOffset calculates pos.mContentOffset
911 // differently depending on whether the movement is visual or logical.
912 // For visual movement, pos.mContentOffset depends on the direction-
913 // ality of the first/last frame on the line (theFrame), and the caret
914 // directionality must correspond.
915 SetCaretBidiLevel(visualMovement
? NS_GET_EMBEDDING_LEVEL(theFrame
) :
916 NS_GET_BASE_LEVEL(theFrame
));
920 // If the current position is not a frame boundary, it's enough just
921 // to take the Bidi level of the current frame
922 if ((pos
.mContentOffset
!= frameStart
&&
923 pos
.mContentOffset
!= frameEnd
) ||
924 eSelectLine
== aAmount
) {
925 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame
));
928 BidiLevelFromMove(mShell
, pos
.mResultContent
, pos
.mContentOffset
,
933 result
= TakeFocus(pos
.mResultContent
, pos
.mContentOffset
, pos
.mContentOffset
,
934 tHint
, aContinueSelection
, false);
935 } else if (aAmount
<= eSelectWordNoSpace
&& aDirection
== eDirNext
&&
936 !aContinueSelection
) {
937 // Collapse selection if PeekOffset failed, we either
938 // 1. bumped into the BRFrame, bug 207623
939 // 2. had select-all in a text input (DIV range), bug 352759.
940 bool isBRFrame
= frame
->GetType() == nsGkAtoms::brFrame
;
941 sel
->Collapse(sel
->GetFocusNode(), sel
->FocusOffset());
942 // Note: 'frame' might be dead here.
944 mHint
= CARET_ASSOCIATE_BEFORE
; // We're now at the end of the frame to the left.
948 if (NS_SUCCEEDED(result
))
950 result
= mDomSelections
[index
]->
951 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
,
952 nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
959 //END nsFrameSelection methods
962 //BEGIN nsFrameSelection methods
965 Selection::ToString(nsAString
& aReturn
)
967 // We need Flush_Style here to make sure frames have been created for
968 // the selected content. Use mFrameSelection->GetShell() which returns
969 // null if the Selection has been disconnected (the shell is Destroyed).
970 nsCOMPtr
<nsIPresShell
> shell
=
971 mFrameSelection
? mFrameSelection
->GetShell() : nullptr;
976 shell
->FlushPendingNotifications(Flush_Style
);
978 return ToStringWithFormat("text/plain",
979 nsIDocumentEncoder::SkipInvisibleContent
,
984 Selection::Stringify(nsAString
& aResult
)
986 // Eat the error code
991 Selection::ToStringWithFormat(const char* aFormatType
, uint32_t aFlags
,
992 int32_t aWrapCol
, nsAString
& aReturn
)
995 NS_ConvertUTF8toUTF16
format(aFormatType
);
996 ToStringWithFormat(format
, aFlags
, aWrapCol
, aReturn
, result
);
997 if (result
.Failed()) {
998 return result
.ErrorCode();
1004 Selection::ToStringWithFormat(const nsAString
& aFormatType
, uint32_t aFlags
,
1005 int32_t aWrapCol
, nsAString
& aReturn
,
1008 nsresult rv
= NS_OK
;
1009 NS_ConvertUTF8toUTF16
formatType( NS_DOC_ENCODER_CONTRACTID_BASE
);
1010 formatType
.Append(aFormatType
);
1011 nsCOMPtr
<nsIDocumentEncoder
> encoder
=
1012 do_CreateInstance(NS_ConvertUTF16toUTF8(formatType
).get(), &rv
);
1013 if (NS_FAILED(rv
)) {
1018 nsIPresShell
* shell
= GetPresShell();
1020 aRv
.Throw(NS_ERROR_FAILURE
);
1024 nsIDocument
*doc
= shell
->GetDocument();
1026 nsCOMPtr
<nsIDOMDocument
> domDoc
= do_QueryInterface(doc
);
1027 NS_ASSERTION(domDoc
, "Need a document");
1029 // Flags should always include OutputSelectionOnly if we're coming from here:
1030 aFlags
|= nsIDocumentEncoder::OutputSelectionOnly
;
1031 nsAutoString readstring
;
1032 readstring
.Assign(aFormatType
);
1033 rv
= encoder
->Init(domDoc
, readstring
, aFlags
);
1034 if (NS_FAILED(rv
)) {
1039 encoder
->SetSelection(this);
1041 encoder
->SetWrapColumn(aWrapCol
);
1043 rv
= encoder
->EncodeToString(aReturn
);
1044 if (NS_FAILED(rv
)) {
1050 Selection::SetInterlinePosition(bool aHintRight
)
1053 SetInterlinePosition(aHintRight
, result
);
1054 if (result
.Failed()) {
1055 return result
.ErrorCode();
1061 Selection::SetInterlinePosition(bool aHintRight
, ErrorResult
& aRv
)
1063 if (!mFrameSelection
) {
1064 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
); // Can't do selection
1067 mFrameSelection
->SetHint(aHintRight
? CARET_ASSOCIATE_AFTER
: CARET_ASSOCIATE_BEFORE
);
1071 Selection::GetInterlinePosition(bool* aHintRight
)
1074 *aHintRight
= GetInterlinePosition(result
);
1075 if (result
.Failed()) {
1076 return result
.ErrorCode();
1082 Selection::GetInterlinePosition(ErrorResult
& aRv
)
1084 if (!mFrameSelection
) {
1085 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
); // Can't do selection
1088 return mFrameSelection
->GetHint() == CARET_ASSOCIATE_AFTER
;
1092 Selection::GetCaretBidiLevel(mozilla::ErrorResult
& aRv
) const
1094 if (!mFrameSelection
) {
1095 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
1096 return Nullable
<int16_t>();
1098 nsBidiLevel caretBidiLevel
= mFrameSelection
->GetCaretBidiLevel();
1099 return (caretBidiLevel
& BIDI_LEVEL_UNDEFINED
) ?
1100 Nullable
<int16_t>() : Nullable
<int16_t>(caretBidiLevel
);
1104 Selection::SetCaretBidiLevel(const Nullable
<int16_t>& aCaretBidiLevel
, mozilla::ErrorResult
& aRv
)
1106 if (!mFrameSelection
) {
1107 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
1110 if (aCaretBidiLevel
.IsNull()) {
1111 mFrameSelection
->UndefineCaretBidiLevel();
1113 mFrameSelection
->SetCaretBidiLevel(aCaretBidiLevel
.Value());
1117 nsPrevNextBidiLevels
1118 nsFrameSelection::GetPrevNextBidiLevels(nsIContent
*aNode
,
1119 uint32_t aContentOffset
,
1120 bool aJumpLines
) const
1122 return GetPrevNextBidiLevels(aNode
, aContentOffset
, mHint
, aJumpLines
);
1125 nsPrevNextBidiLevels
1126 nsFrameSelection::GetPrevNextBidiLevels(nsIContent
* aNode
,
1127 uint32_t aContentOffset
,
1128 CaretAssociateHint aHint
,
1129 bool aJumpLines
) const
1131 // Get the level of the frames on each side
1132 nsIFrame
*currentFrame
;
1133 int32_t currentOffset
;
1134 int32_t frameStart
, frameEnd
;
1135 nsDirection direction
;
1137 nsPrevNextBidiLevels levels
;
1138 levels
.SetData(nullptr, nullptr, 0, 0);
1140 currentFrame
= GetFrameForNodeOffset(aNode
, aContentOffset
,
1141 aHint
, ¤tOffset
);
1145 currentFrame
->GetOffsets(frameStart
, frameEnd
);
1147 if (0 == frameStart
&& 0 == frameEnd
)
1148 direction
= eDirPrevious
;
1149 else if (frameStart
== currentOffset
)
1150 direction
= eDirPrevious
;
1151 else if (frameEnd
== currentOffset
)
1152 direction
= eDirNext
;
1154 // we are neither at the beginning nor at the end of the frame, so we have no worries
1155 levels
.SetData(currentFrame
, currentFrame
,
1156 NS_GET_EMBEDDING_LEVEL(currentFrame
),
1157 NS_GET_EMBEDDING_LEVEL(currentFrame
));
1164 nsresult rv
= currentFrame
->GetFrameFromDirection(direction
, false,
1166 &newFrame
, &offset
, &jumpedLine
);
1170 nsBidiLevel baseLevel
= NS_GET_BASE_LEVEL(currentFrame
);
1171 nsBidiLevel currentLevel
= NS_GET_EMBEDDING_LEVEL(currentFrame
);
1172 nsBidiLevel newLevel
= newFrame
? NS_GET_EMBEDDING_LEVEL(newFrame
) : baseLevel
;
1174 // If not jumping lines, disregard br frames, since they might be positioned incorrectly.
1175 // XXX This could be removed once bug 339786 is fixed.
1177 if (currentFrame
->GetType() == nsGkAtoms::brFrame
) {
1178 currentFrame
= nullptr;
1179 currentLevel
= baseLevel
;
1181 if (newFrame
&& newFrame
->GetType() == nsGkAtoms::brFrame
) {
1183 newLevel
= baseLevel
;
1187 if (direction
== eDirNext
)
1188 levels
.SetData(currentFrame
, newFrame
, currentLevel
, newLevel
);
1190 levels
.SetData(newFrame
, currentFrame
, newLevel
, currentLevel
);
1196 nsFrameSelection::GetFrameFromLevel(nsIFrame
*aFrameIn
,
1197 nsDirection aDirection
,
1198 nsBidiLevel aBidiLevel
,
1199 nsIFrame
**aFrameOut
) const
1201 NS_ENSURE_STATE(mShell
);
1202 nsBidiLevel foundLevel
= 0;
1203 nsIFrame
*foundFrame
= aFrameIn
;
1205 nsCOMPtr
<nsIFrameEnumerator
> frameTraversal
;
1207 nsCOMPtr
<nsIFrameTraversal
> trav(do_CreateInstance(kFrameTraversalCID
,&result
));
1208 if (NS_FAILED(result
))
1211 result
= trav
->NewFrameTraversal(getter_AddRefs(frameTraversal
),
1212 mShell
->GetPresContext(), aFrameIn
,
1215 false, // aLockInScrollView
1216 false // aFollowOOFs
1218 if (NS_FAILED(result
))
1222 *aFrameOut
= foundFrame
;
1223 if (aDirection
== eDirNext
)
1224 frameTraversal
->Next();
1226 frameTraversal
->Prev();
1228 foundFrame
= frameTraversal
->CurrentItem();
1230 return NS_ERROR_FAILURE
;
1231 foundLevel
= NS_GET_EMBEDDING_LEVEL(foundFrame
);
1233 } while (foundLevel
> aBidiLevel
);
1240 nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount
)
1242 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
1243 if (!mDomSelections
[index
])
1244 return NS_ERROR_NULL_POINTER
;
1246 mMaintainedAmount
= aAmount
;
1248 const nsRange
* anchorFocusRange
=
1249 mDomSelections
[index
]->GetAnchorFocusRange();
1250 if (anchorFocusRange
&& aAmount
!= eSelectNoAmount
) {
1251 mMaintainRange
= anchorFocusRange
->CloneRange();
1255 mMaintainRange
= nullptr;
1260 /** After moving the caret, its Bidi level is set according to the following rules:
1262 * After moving over a character with left/right arrow, set to the Bidi level of the last moved over character.
1263 * After Home and End, set to the paragraph embedding level.
1264 * After up/down arrow, PageUp/Down, set to the lower level of the 2 surrounding characters.
1265 * After mouse click, set to the level of the current frame.
1267 * The following two methods use GetPrevNextBidiLevels to determine the new Bidi level.
1268 * BidiLevelFromMove is called when the caret is moved in response to a keyboard event
1270 * @param aPresShell is the presentation shell
1271 * @param aNode is the content node
1272 * @param aContentOffset is the new caret position, as an offset into aNode
1273 * @param aAmount is the amount of the move that gave the caret its new position
1274 * @param aHint is the hint indicating in what logical direction the caret moved
1276 void nsFrameSelection::BidiLevelFromMove(nsIPresShell
* aPresShell
,
1278 uint32_t aContentOffset
,
1279 nsSelectionAmount aAmount
,
1280 CaretAssociateHint aHint
)
1284 // Movement within the line: the new cursor Bidi level is the level of the
1285 // last character moved over
1286 case eSelectCharacter
:
1287 case eSelectCluster
:
1289 case eSelectWordNoSpace
:
1290 case eSelectBeginLine
:
1291 case eSelectEndLine
:
1292 case eSelectNoAmount
:
1294 nsPrevNextBidiLevels levels
= GetPrevNextBidiLevels(aNode
, aContentOffset
,
1297 SetCaretBidiLevel(aHint
== CARET_ASSOCIATE_BEFORE
?
1298 levels
.mLevelBefore
: levels
.mLevelAfter
);
1302 // Up and Down: the new cursor Bidi level is the smaller of the two surrounding characters
1304 case eSelectParagraph:
1305 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame, &secondFrame, &firstLevel, &secondLevel);
1306 aPresShell->SetCaretBidiLevel(std::min(firstLevel, secondLevel));
1311 UndefineCaretBidiLevel();
1316 * BidiLevelFromClick is called when the caret is repositioned by clicking the mouse
1318 * @param aNode is the content node
1319 * @param aContentOffset is the new caret position, as an offset into aNode
1321 void nsFrameSelection::BidiLevelFromClick(nsIContent
*aNode
,
1322 uint32_t aContentOffset
)
1324 nsIFrame
* clickInFrame
=nullptr;
1325 int32_t OffsetNotUsed
;
1327 clickInFrame
= GetFrameForNodeOffset(aNode
, aContentOffset
, mHint
, &OffsetNotUsed
);
1331 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(clickInFrame
));
1336 nsFrameSelection::AdjustForMaintainedSelection(nsIContent
*aContent
,
1339 if (!mMaintainRange
)
1346 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
1347 if (!mDomSelections
[index
])
1350 nsINode
* rangeStartNode
= mMaintainRange
->GetStartParent();
1351 nsINode
* rangeEndNode
= mMaintainRange
->GetEndParent();
1352 int32_t rangeStartOffset
= mMaintainRange
->StartOffset();
1353 int32_t rangeEndOffset
= mMaintainRange
->EndOffset();
1355 int32_t relToStart
=
1356 nsContentUtils::ComparePoints(rangeStartNode
, rangeStartOffset
,
1359 nsContentUtils::ComparePoints(rangeEndNode
, rangeEndOffset
,
1362 // If aContent/aOffset is inside the maintained selection, or if it is on the
1363 // "anchor" side of the maintained selection, we need to do something.
1364 if ((relToStart
< 0 && relToEnd
> 0) ||
1366 mDomSelections
[index
]->GetDirection() == eDirNext
) ||
1368 mDomSelections
[index
]->GetDirection() == eDirPrevious
)) {
1369 // Set the current range to the maintained range.
1370 mDomSelections
[index
]->ReplaceAnchorFocusRange(mMaintainRange
);
1371 if (relToStart
< 0 && relToEnd
> 0) {
1372 // We're inside the maintained selection, just keep it selected.
1375 // Reverse the direction of the selection so that the anchor will be on the
1376 // far side of the maintained selection, relative to aContent/aOffset.
1377 mDomSelections
[index
]->SetDirection(relToStart
> 0 ? eDirPrevious
: eDirNext
);
1384 nsFrameSelection::HandleClick(nsIContent
* aNewFocus
,
1385 uint32_t aContentOffset
,
1386 uint32_t aContentEndOffset
,
1387 bool aContinueSelection
,
1388 bool aMultipleSelection
,
1389 CaretAssociateHint aHint
)
1392 return NS_ERROR_INVALID_ARG
;
1394 InvalidateDesiredPos();
1396 if (!aContinueSelection
) {
1397 mMaintainRange
= nullptr;
1398 if (!IsValidSelectionPoint(this, aNewFocus
)) {
1399 mAncestorLimiter
= nullptr;
1403 // Don't take focus when dragging off of a table
1404 if (!mDragSelectingCells
)
1406 BidiLevelFromClick(aNewFocus
, aContentOffset
);
1407 PostReason(nsISelectionListener::MOUSEDOWN_REASON
+ nsISelectionListener::DRAG_REASON
);
1408 if (aContinueSelection
&&
1409 AdjustForMaintainedSelection(aNewFocus
, aContentOffset
))
1410 return NS_OK
; //shift clicked to maintained selection. rejected.
1412 return TakeFocus(aNewFocus
, aContentOffset
, aContentEndOffset
, aHint
,
1413 aContinueSelection
, aMultipleSelection
);
1420 nsFrameSelection::HandleDrag(nsIFrame
*aFrame
, nsPoint aPoint
)
1422 if (!aFrame
|| !mShell
)
1426 nsIFrame
*newFrame
= 0;
1429 result
= ConstrainFrameAndPointToAnchorSubtree(aFrame
, aPoint
, &newFrame
, newPoint
);
1430 if (NS_FAILED(result
))
1435 nsIFrame::ContentOffsets offsets
=
1436 newFrame
->GetContentOffsetsFromPoint(newPoint
);
1437 if (!offsets
.content
)
1440 if (newFrame
->IsSelected() &&
1441 AdjustForMaintainedSelection(offsets
.content
, offsets
.offset
))
1444 // Adjust offsets according to maintained amount
1445 if (mMaintainRange
&&
1446 mMaintainedAmount
!= eSelectNoAmount
) {
1448 nsINode
* rangenode
= mMaintainRange
->GetStartParent();
1449 int32_t rangeOffset
= mMaintainRange
->StartOffset();
1450 int32_t relativePosition
=
1451 nsContentUtils::ComparePoints(rangenode
, rangeOffset
,
1452 offsets
.content
, offsets
.offset
);
1454 nsDirection direction
= relativePosition
> 0 ? eDirPrevious
: eDirNext
;
1455 nsSelectionAmount amount
= mMaintainedAmount
;
1456 if (amount
== eSelectBeginLine
&& direction
== eDirNext
)
1457 amount
= eSelectEndLine
;
1460 nsIFrame
* frame
= GetFrameForNodeOffset(offsets
.content
, offsets
.offset
,
1461 CARET_ASSOCIATE_AFTER
, &offset
);
1463 if (frame
&& amount
== eSelectWord
&& direction
== eDirPrevious
) {
1464 // To avoid selecting the previous word when at start of word,
1465 // first move one character forward.
1466 nsPeekOffsetStruct
charPos(eSelectCharacter
, eDirNext
, offset
,
1467 nsPoint(0, 0), false, mLimiter
!= nullptr,
1469 if (NS_SUCCEEDED(frame
->PeekOffset(&charPos
))) {
1470 frame
= charPos
.mResultFrame
;
1471 offset
= charPos
.mContentOffset
;
1475 nsPeekOffsetStruct
pos(amount
, direction
, offset
, nsPoint(0, 0),
1476 false, mLimiter
!= nullptr, false, false);
1478 if (frame
&& NS_SUCCEEDED(frame
->PeekOffset(&pos
)) && pos
.mResultContent
) {
1479 offsets
.content
= pos
.mResultContent
;
1480 offsets
.offset
= pos
.mContentOffset
;
1484 HandleClick(offsets
.content
, offsets
.offset
, offsets
.offset
,
1485 true, false, offsets
.associate
);
1489 nsFrameSelection::StartAutoScrollTimer(nsIFrame
*aFrame
,
1493 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
1494 if (!mDomSelections
[index
])
1495 return NS_ERROR_NULL_POINTER
;
1497 return mDomSelections
[index
]->StartAutoScrollTimer(aFrame
, aPoint
, aDelay
);
1501 nsFrameSelection::StopAutoScrollTimer()
1503 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
1504 if (!mDomSelections
[index
])
1507 mDomSelections
[index
]->StopAutoScrollTimer();
1511 hard to go from nodes to frames, easy the other way!
1514 nsFrameSelection::TakeFocus(nsIContent
* aNewFocus
,
1515 uint32_t aContentOffset
,
1516 uint32_t aContentEndOffset
,
1517 CaretAssociateHint aHint
,
1518 bool aContinueSelection
,
1519 bool aMultipleSelection
)
1522 return NS_ERROR_NULL_POINTER
;
1524 NS_ENSURE_STATE(mShell
);
1526 if (!IsValidSelectionPoint(this,aNewFocus
))
1527 return NS_ERROR_FAILURE
;
1529 // Clear all table selection data
1530 mSelectingTableCellMode
= 0;
1531 mDragSelectingCells
= false;
1532 mStartSelectedCell
= nullptr;
1533 mEndSelectedCell
= nullptr;
1534 mAppendStartSelectedCell
= nullptr;
1537 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
1538 if (!mDomSelections
[index
])
1539 return NS_ERROR_NULL_POINTER
;
1541 Maybe
<Selection::AutoApplyUserSelectStyle
> userSelect
;
1542 if (IsUserSelectionReason()) {
1543 userSelect
.emplace(mDomSelections
[index
]);
1546 //traverse through document and unselect crap here
1547 if (!aContinueSelection
) {//single click? setting cursor down
1548 uint32_t batching
= mBatching
;//hack to use the collapse code.
1549 bool changes
= mChangesDuringBatching
;
1552 if (aMultipleSelection
) {
1553 // Remove existing collapsed ranges as there's no point in having
1554 // non-anchor/focus collapsed ranges.
1555 mDomSelections
[index
]->RemoveCollapsedRanges();
1557 nsRefPtr
<nsRange
> newRange
= new nsRange(aNewFocus
);
1559 newRange
->SetStart(aNewFocus
, aContentOffset
);
1560 newRange
->SetEnd(aNewFocus
, aContentOffset
);
1561 mDomSelections
[index
]->AddRange(newRange
);
1562 mBatching
= batching
;
1563 mChangesDuringBatching
= changes
;
1565 bool oldDesiredPosSet
= mDesiredPosSet
; //need to keep old desired position if it was set.
1566 mDomSelections
[index
]->Collapse(aNewFocus
, aContentOffset
);
1567 mDesiredPosSet
= oldDesiredPosSet
; //now reset desired pos back.
1568 mBatching
= batching
;
1569 mChangesDuringBatching
= changes
;
1571 if (aContentEndOffset
!= aContentOffset
) {
1572 mDomSelections
[index
]->Extend(aNewFocus
, aContentEndOffset
);
1575 //find out if we are inside a table. if so, find out which one and which cell
1576 //once we do that, the next time we get a takefocus, check the parent tree.
1577 //if we are no longer inside same table ,cell then switch to table selection mode.
1578 // BUT only do this in an editor
1580 NS_ENSURE_STATE(mShell
);
1581 int16_t displaySelection
= mShell
->GetSelectionFlags();
1583 // Editor has DISPLAY_ALL selection type
1584 if (displaySelection
== nsISelectionDisplay::DISPLAY_ALL
)
1586 mCellParent
= GetCellParent(aNewFocus
);
1587 #ifdef DEBUG_TABLE_SELECTION
1589 printf(" * TakeFocus - Collapsing into new cell\n");
1594 // Now update the range list:
1595 if (aContinueSelection
&& aNewFocus
)
1598 nsINode
*cellparent
= GetCellParent(aNewFocus
);
1599 if (mCellParent
&& cellparent
&& cellparent
!= mCellParent
) //switch to cell selection mode
1601 #ifdef DEBUG_TABLE_SELECTION
1602 printf(" * TakeFocus - moving into new cell\n");
1604 WidgetMouseEvent
event(false, 0, nullptr, WidgetMouseEvent::eReal
);
1606 // Start selecting in the cell we were in before
1607 nsINode
* parent
= ParentOffset(mCellParent
, &offset
);
1609 HandleTableSelection(parent
, offset
,
1610 nsISelectionPrivate::TABLESELECTION_CELL
, &event
);
1612 // Find the parent of this new cell and extend selection to it
1613 parent
= ParentOffset(cellparent
, &offset
);
1615 // XXXX We need to REALLY get the current key shift state
1616 // (we'd need to add event listener -- let's not bother for now)
1617 event
.modifiers
&= ~MODIFIER_SHIFT
; //aContinueSelection;
1620 mCellParent
= cellparent
;
1621 // Continue selection into next cell
1622 HandleTableSelection(parent
, offset
,
1623 nsISelectionPrivate::TABLESELECTION_CELL
, &event
);
1628 // XXXX Problem: Shift+click in browser is appending text selection to selected table!!!
1629 // is this the place to erase seleced cells ?????
1630 if (mDomSelections
[index
]->GetDirection() == eDirNext
&& aContentEndOffset
> aContentOffset
) //didn't go far enough
1632 mDomSelections
[index
]->Extend(aNewFocus
, aContentEndOffset
);//this will only redraw the diff
1635 mDomSelections
[index
]->Extend(aNewFocus
, aContentOffset
);
1640 // Don't notify selection listeners if batching is on:
1643 return NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL
);
1648 nsFrameSelection::LookUpSelection(nsIContent
*aContent
,
1649 int32_t aContentOffset
,
1650 int32_t aContentLength
,
1651 bool aSlowCheck
) const
1653 if (!aContent
|| !mShell
)
1656 SelectionDetails
* details
= nullptr;
1658 for (int32_t j
= 0; j
< nsISelectionController::NUM_SELECTIONTYPES
; j
++) {
1659 if (mDomSelections
[j
]) {
1660 mDomSelections
[j
]->LookUpSelection(aContent
, aContentOffset
,
1661 aContentLength
, &details
, (SelectionType
)(1<<j
), aSlowCheck
);
1669 nsFrameSelection::SetDragState(bool aState
)
1671 if (mDragState
== aState
)
1674 mDragState
= aState
;
1678 mDragSelectingCells
= false;
1679 PostReason(nsISelectionListener::MOUSEUP_REASON
);
1680 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL
); //notify that reason is mouse up please.
1685 nsFrameSelection::GetSelection(SelectionType aType
) const
1687 int8_t index
= GetIndexFromSelectionType(aType
);
1691 return mDomSelections
[index
];
1695 nsFrameSelection::ScrollSelectionIntoView(SelectionType aType
,
1696 SelectionRegion aRegion
,
1697 int16_t aFlags
) const
1699 int8_t index
= GetIndexFromSelectionType(aType
);
1701 return NS_ERROR_INVALID_ARG
;
1703 if (!mDomSelections
[index
])
1704 return NS_ERROR_NULL_POINTER
;
1706 nsIPresShell::ScrollAxis verticalScroll
= nsIPresShell::ScrollAxis();
1707 int32_t flags
= Selection::SCROLL_DO_FLUSH
;
1708 if (aFlags
& nsISelectionController::SCROLL_SYNCHRONOUS
) {
1709 flags
|= Selection::SCROLL_SYNCHRONOUS
;
1710 } else if (aFlags
& nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY
) {
1711 flags
|= Selection::SCROLL_FIRST_ANCESTOR_ONLY
;
1713 if (aFlags
& nsISelectionController::SCROLL_OVERFLOW_HIDDEN
) {
1714 flags
|= Selection::SCROLL_OVERFLOW_HIDDEN
;
1716 if (aFlags
& nsISelectionController::SCROLL_CENTER_VERTICALLY
) {
1717 verticalScroll
= nsIPresShell::ScrollAxis(
1718 nsIPresShell::SCROLL_CENTER
, nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE
);
1721 // After ScrollSelectionIntoView(), the pending notifications might be
1722 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1723 return mDomSelections
[index
]->ScrollIntoView(aRegion
,
1725 nsIPresShell::ScrollAxis(),
1730 nsFrameSelection::RepaintSelection(SelectionType aType
) const
1732 int8_t index
= GetIndexFromSelectionType(aType
);
1734 return NS_ERROR_INVALID_ARG
;
1735 if (!mDomSelections
[index
])
1736 return NS_ERROR_NULL_POINTER
;
1737 NS_ENSURE_STATE(mShell
);
1738 return mDomSelections
[index
]->Repaint(mShell
->GetPresContext());
1742 nsFrameSelection::GetFrameForNodeOffset(nsIContent
* aNode
,
1744 CaretAssociateHint aHint
,
1745 int32_t* aReturnOffset
) const
1747 if (!aNode
|| !aReturnOffset
|| !mShell
)
1753 *aReturnOffset
= aOffset
;
1755 nsCOMPtr
<nsIContent
> theNode
= aNode
;
1757 if (aNode
->IsElement())
1759 int32_t childIndex
= 0;
1760 int32_t numChildren
= theNode
->GetChildCount();
1762 if (aHint
== CARET_ASSOCIATE_BEFORE
)
1765 childIndex
= aOffset
- 1;
1767 childIndex
= aOffset
;
1771 NS_ASSERTION(aHint
== CARET_ASSOCIATE_AFTER
, "unknown direction");
1772 if (aOffset
>= numChildren
)
1774 if (numChildren
> 0)
1775 childIndex
= numChildren
- 1;
1780 childIndex
= aOffset
;
1783 if (childIndex
> 0 || numChildren
> 0) {
1784 nsCOMPtr
<nsIContent
> childNode
= theNode
->GetChildAt(childIndex
);
1789 theNode
= childNode
;
1792 // Now that we have the child node, check if it too
1793 // can contain children. If so, call this method again!
1794 if (theNode
->IsElement() &&
1795 theNode
->GetChildCount() &&
1796 !theNode
->HasIndependentSelection())
1798 int32_t newOffset
= 0;
1800 if (aOffset
> childIndex
) {
1801 numChildren
= theNode
->GetChildCount();
1802 newOffset
= numChildren
;
1805 return GetFrameForNodeOffset(theNode
, newOffset
, aHint
, aReturnOffset
);
1807 // Check to see if theNode is a text node. If it is, translate
1808 // aOffset into an offset into the text node.
1810 nsCOMPtr
<nsIDOMText
> textNode
= do_QueryInterface(theNode
);
1814 if (theNode
->GetPrimaryFrame())
1816 if (aOffset
> childIndex
)
1818 uint32_t textLength
= 0;
1820 nsresult rv
= textNode
->GetLength(&textLength
);
1824 *aReturnOffset
= (int32_t)textLength
;
1829 int32_t numChildren
= aNode
->GetChildCount();
1830 int32_t newChildIndex
=
1831 aHint
== CARET_ASSOCIATE_BEFORE
? childIndex
- 1 : childIndex
+ 1;
1833 if (newChildIndex
>= 0 && newChildIndex
< numChildren
) {
1834 nsCOMPtr
<nsIContent
> newChildNode
= aNode
->GetChildAt(newChildIndex
);
1838 theNode
= newChildNode
;
1840 aHint
== CARET_ASSOCIATE_BEFORE
? theNode
->GetChildCount() : 0;
1841 return GetFrameForNodeOffset(theNode
, newOffset
, aHint
, aReturnOffset
);
1843 // newChildIndex is illegal which means we're at first or last
1844 // child. Just use original node to get the frame.
1852 // If the node is a ShadowRoot, the frame needs to be adjusted,
1853 // because a ShadowRoot does not get a frame. Its children are rendered
1854 // as children of the host.
1855 mozilla::dom::ShadowRoot
* shadowRoot
=
1856 mozilla::dom::ShadowRoot::FromNode(theNode
);
1858 theNode
= shadowRoot
->GetHost();
1861 nsIFrame
* returnFrame
= theNode
->GetPrimaryFrame();
1865 // find the child frame containing the offset we want
1866 returnFrame
->GetChildFrameContainingOffset(*aReturnOffset
, aHint
== CARET_ASSOCIATE_AFTER
,
1867 &aOffset
, &returnFrame
);
1872 nsFrameSelection::CommonPageMove(bool aForward
,
1874 nsIScrollableFrame
* aScrollableFrame
)
1876 // expected behavior for PageMove is to scroll AND move the caret
1877 // and remain relative position of the caret in view. see Bug 4302.
1879 //get the frame from the scrollable view
1881 nsIFrame
* scrolledFrame
= aScrollableFrame
->GetScrolledFrame();
1885 // find out where the caret is.
1886 // we should know mDesiredPos value of nsFrameSelection, but I havent seen that behavior in other windows applications yet.
1887 nsISelection
* domSel
= GetSelection(nsISelectionController::SELECTION_NORMAL
);
1893 nsIFrame
* caretFrame
= nsCaret::GetGeometry(domSel
, &caretPos
);
1897 //need to adjust caret jump by percentage scroll
1898 nsSize scrollDelta
= aScrollableFrame
->GetPageScrollAmount();
1901 caretPos
.y
+= scrollDelta
.height
;
1903 caretPos
.y
-= scrollDelta
.height
;
1905 caretPos
+= caretFrame
->GetOffsetTo(scrolledFrame
);
1907 // get a content at desired location
1908 nsPoint desiredPoint
;
1909 desiredPoint
.x
= caretPos
.x
;
1910 desiredPoint
.y
= caretPos
.y
+ caretPos
.height
/2;
1911 nsIFrame::ContentOffsets offsets
=
1912 scrolledFrame
->GetContentOffsetsFromPoint(desiredPoint
);
1914 if (!offsets
.content
)
1918 aScrollableFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1),
1919 nsIScrollableFrame::PAGES
,
1920 nsIScrollableFrame::SMOOTH
);
1923 HandleClick(offsets
.content
, offsets
.offset
,
1924 offsets
.offset
, aExtend
, false, CARET_ASSOCIATE_AFTER
);
1928 nsFrameSelection::PhysicalMove(int16_t aDirection
, int16_t aAmount
,
1931 NS_ENSURE_STATE(mShell
);
1932 // Flush out layout, since we need it to be up to date to do caret
1934 mShell
->FlushPendingNotifications(Flush_Layout
);
1940 // Check that parameters are safe
1941 if (aDirection
< 0 || aDirection
> 3 || aAmount
< 0 || aAmount
> 1) {
1942 return NS_ERROR_FAILURE
;
1945 nsPresContext
*context
= mShell
->GetPresContext();
1947 return NS_ERROR_FAILURE
;
1950 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
1951 nsRefPtr
<Selection
> sel
= mDomSelections
[index
];
1953 return NS_ERROR_NULL_POINTER
;
1956 // Map the abstract movement amounts (0-1) to direction-specific
1958 static const nsSelectionAmount inlineAmount
[] =
1959 { eSelectCluster
, eSelectWord
};
1960 static const nsSelectionAmount blockPrevAmount
[] =
1961 { eSelectLine
, eSelectBeginLine
};
1962 static const nsSelectionAmount blockNextAmount
[] =
1963 { eSelectLine
, eSelectEndLine
};
1965 struct PhysicalToLogicalMapping
{
1966 nsDirection direction
;
1967 const nsSelectionAmount
*amounts
;
1969 static const PhysicalToLogicalMapping verticalLR
[4] = {
1970 { eDirPrevious
, blockPrevAmount
}, // left
1971 { eDirNext
, blockNextAmount
}, // right
1972 { eDirPrevious
, inlineAmount
}, // up
1973 { eDirNext
, inlineAmount
} // down
1975 static const PhysicalToLogicalMapping verticalRL
[4] = {
1976 { eDirNext
, blockNextAmount
},
1977 { eDirPrevious
, blockPrevAmount
},
1978 { eDirPrevious
, inlineAmount
},
1979 { eDirNext
, inlineAmount
}
1981 static const PhysicalToLogicalMapping horizontal
[4] = {
1982 { eDirPrevious
, inlineAmount
},
1983 { eDirNext
, inlineAmount
},
1984 { eDirPrevious
, blockPrevAmount
},
1985 { eDirNext
, blockNextAmount
}
1989 nsIFrame
*frame
= nullptr;
1990 int32_t offsetused
= 0;
1991 if (NS_SUCCEEDED(sel
->GetPrimaryFrameForFocusNode(&frame
, &offsetused
,
1994 wm
= frame
->GetWritingMode();
1998 const PhysicalToLogicalMapping
& mapping
=
2000 ? wm
.IsVerticalLR() ? verticalLR
[aDirection
] : verticalRL
[aDirection
]
2001 : horizontal
[aDirection
];
2003 nsresult rv
= MoveCaret(mapping
.direction
, aExtend
, mapping
.amounts
[aAmount
],
2005 if (NS_FAILED(rv
)) {
2006 // If we tried to do a line move, but couldn't move in the given direction,
2007 // then we'll "promote" this to a line-edge move instead.
2008 if (mapping
.amounts
[aAmount
] == eSelectLine
) {
2009 rv
= MoveCaret(mapping
.direction
, aExtend
, mapping
.amounts
[aAmount
+ 1],
2018 nsFrameSelection::CharacterMove(bool aForward
, bool aExtend
)
2020 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectCluster
,
2025 nsFrameSelection::CharacterExtendForDelete()
2027 return MoveCaret(eDirNext
, true, eSelectCluster
, eLogical
);
2031 nsFrameSelection::CharacterExtendForBackspace()
2033 return MoveCaret(eDirPrevious
, true, eSelectCharacter
, eLogical
);
2037 nsFrameSelection::WordMove(bool aForward
, bool aExtend
)
2039 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectWord
,
2044 nsFrameSelection::WordExtendForDelete(bool aForward
)
2046 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, true, eSelectWord
,
2051 nsFrameSelection::LineMove(bool aForward
, bool aExtend
)
2053 return MoveCaret(aForward
? eDirNext
: eDirPrevious
, aExtend
, eSelectLine
,
2058 nsFrameSelection::IntraLineMove(bool aForward
, bool aExtend
)
2061 return MoveCaret(eDirNext
, aExtend
, eSelectEndLine
, eLogical
);
2063 return MoveCaret(eDirPrevious
, aExtend
, eSelectBeginLine
, eLogical
);
2068 nsFrameSelection::SelectAll()
2070 nsCOMPtr
<nsIContent
> rootContent
;
2073 rootContent
= mLimiter
;//addrefit
2075 else if (mAncestorLimiter
) {
2076 rootContent
= mAncestorLimiter
;
2080 NS_ENSURE_STATE(mShell
);
2081 nsIDocument
*doc
= mShell
->GetDocument();
2083 return NS_ERROR_FAILURE
;
2084 rootContent
= doc
->GetRootElement();
2086 return NS_ERROR_FAILURE
;
2088 int32_t numChildren
= rootContent
->GetChildCount();
2089 PostReason(nsISelectionListener::NO_REASON
);
2090 return TakeFocus(rootContent
, 0, numChildren
, CARET_ASSOCIATE_BEFORE
, false, false);
2093 //////////END FRAMESELECTION
2096 nsFrameSelection::StartBatchChanges()
2102 nsFrameSelection::EndBatchChanges()
2105 NS_ASSERTION(mBatching
>=0,"Bad mBatching");
2106 if (mBatching
== 0 && mChangesDuringBatching
){
2107 mChangesDuringBatching
= false;
2108 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL
);
2114 nsFrameSelection::NotifySelectionListeners(SelectionType aType
)
2116 int8_t index
= GetIndexFromSelectionType(aType
);
2117 if (index
>=0 && mDomSelections
[index
])
2119 return mDomSelections
[index
]->NotifySelectionListeners();
2121 return NS_ERROR_FAILURE
;
2124 // Start of Table Selection methods
2126 static bool IsCell(nsIContent
*aContent
)
2128 return ((aContent
->Tag() == nsGkAtoms::td
||
2129 aContent
->Tag() == nsGkAtoms::th
) &&
2130 aContent
->IsHTML());
2134 nsFrameSelection::GetCellLayout(nsIContent
*aCellContent
) const
2136 NS_ENSURE_TRUE(mShell
, nullptr);
2137 nsITableCellLayout
*cellLayoutObject
=
2138 do_QueryFrame(aCellContent
->GetPrimaryFrame());
2139 return cellLayoutObject
;
2143 nsFrameSelection::ClearNormalSelection()
2145 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
2146 if (!mDomSelections
[index
])
2147 return NS_ERROR_NULL_POINTER
;
2149 return mDomSelections
[index
]->RemoveAllRanges();
2153 GetFirstSelectedContent(nsRange
* aRange
)
2159 NS_PRECONDITION(aRange
->GetStartParent(), "Must have start parent!");
2160 NS_PRECONDITION(aRange
->GetStartParent()->IsElement(),
2161 "Unexpected parent");
2163 return aRange
->GetStartParent()->GetChildAt(aRange
->StartOffset());
2166 // Table selection support.
2167 // TODO: Separate table methods into a separate nsITableSelection interface
2169 nsFrameSelection::HandleTableSelection(nsINode
* aParentContent
,
2170 int32_t aContentOffset
,
2172 WidgetMouseEvent
* aMouseEvent
)
2174 NS_ENSURE_TRUE(aParentContent
, NS_ERROR_NULL_POINTER
);
2175 NS_ENSURE_TRUE(aMouseEvent
, NS_ERROR_NULL_POINTER
);
2177 if (mDragState
&& mDragSelectingCells
&& (aTarget
& nsISelectionPrivate::TABLESELECTION_TABLE
))
2179 // We were selecting cells and user drags mouse in table border or inbetween cells,
2184 nsresult result
= NS_OK
;
2186 nsIContent
*childContent
= aParentContent
->GetChildAt(aContentOffset
);
2188 // When doing table selection, always set the direction to next so
2189 // we can be sure that anchorNode's offset always points to the
2191 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
2192 if (!mDomSelections
[index
])
2193 return NS_ERROR_NULL_POINTER
;
2195 mDomSelections
[index
]->SetDirection(eDirNext
);
2197 // Stack-class to wrap all table selection changes in
2198 // BeginBatchChanges() / EndBatchChanges()
2199 SelectionBatcher
selectionBatcher(mDomSelections
[index
]);
2201 int32_t startRowIndex
, startColIndex
, curRowIndex
, curColIndex
;
2202 if (mDragState
&& mDragSelectingCells
)
2204 // We are drag-selecting
2205 if (aTarget
!= nsISelectionPrivate::TABLESELECTION_TABLE
)
2207 // If dragging in the same cell as last event, do nothing
2208 if (mEndSelectedCell
== childContent
)
2211 #ifdef DEBUG_TABLE_SELECTION
2212 printf(" mStartSelectedCell = %p, mEndSelectedCell = %p, childContent = %p \n",
2213 mStartSelectedCell
.get(), mEndSelectedCell
.get(), childContent
);
2215 // aTarget can be any "cell mode",
2216 // so we can easily drag-select rows and columns
2217 // Once we are in row or column mode,
2218 // we can drift into any cell to stay in that mode
2219 // even if aTarget = TABLESELECTION_CELL
2221 if (mSelectingTableCellMode
== nsISelectionPrivate::TABLESELECTION_ROW
||
2222 mSelectingTableCellMode
== nsISelectionPrivate::TABLESELECTION_COLUMN
)
2224 if (mEndSelectedCell
)
2226 // Also check if cell is in same row/col
2227 result
= GetCellIndexes(mEndSelectedCell
, startRowIndex
, startColIndex
);
2228 if (NS_FAILED(result
)) return result
;
2229 result
= GetCellIndexes(childContent
, curRowIndex
, curColIndex
);
2230 if (NS_FAILED(result
)) return result
;
2232 #ifdef DEBUG_TABLE_SELECTION
2233 printf(" curRowIndex = %d, startRowIndex = %d, curColIndex = %d, startColIndex = %d\n", curRowIndex
, startRowIndex
, curColIndex
, startColIndex
);
2235 if ((mSelectingTableCellMode
== nsISelectionPrivate::TABLESELECTION_ROW
&& startRowIndex
== curRowIndex
) ||
2236 (mSelectingTableCellMode
== nsISelectionPrivate::TABLESELECTION_COLUMN
&& startColIndex
== curColIndex
))
2239 #ifdef DEBUG_TABLE_SELECTION
2240 printf(" Dragged into a new column or row\n");
2242 // Continue dragging row or column selection
2243 return SelectRowOrColumn(childContent
, mSelectingTableCellMode
);
2245 else if (mSelectingTableCellMode
== nsISelectionPrivate::TABLESELECTION_CELL
)
2247 #ifdef DEBUG_TABLE_SELECTION
2248 printf("HandleTableSelection: Dragged into a new cell\n");
2250 // Trick for quick selection of rows and columns
2251 // Hold down shift, then start selecting in one direction
2252 // If next cell dragged into is in same row, select entire row,
2253 // if next cell is in same column, select entire column
2254 if (mStartSelectedCell
&& aMouseEvent
->IsShift())
2256 result
= GetCellIndexes(mStartSelectedCell
, startRowIndex
, startColIndex
);
2257 if (NS_FAILED(result
)) return result
;
2258 result
= GetCellIndexes(childContent
, curRowIndex
, curColIndex
);
2259 if (NS_FAILED(result
)) return result
;
2261 if (startRowIndex
== curRowIndex
||
2262 startColIndex
== curColIndex
)
2264 // Force new selection block
2265 mStartSelectedCell
= nullptr;
2266 mDomSelections
[index
]->RemoveAllRanges();
2268 if (startRowIndex
== curRowIndex
)
2269 mSelectingTableCellMode
= nsISelectionPrivate::TABLESELECTION_ROW
;
2271 mSelectingTableCellMode
= nsISelectionPrivate::TABLESELECTION_COLUMN
;
2273 return SelectRowOrColumn(childContent
, mSelectingTableCellMode
);
2277 // Reselect block of cells to new end location
2278 return SelectBlockOfCells(mStartSelectedCell
, childContent
);
2281 // Do nothing if dragging in table, but outside a cell
2286 // Not dragging -- mouse event is down or up
2289 #ifdef DEBUG_TABLE_SELECTION
2290 printf("HandleTableSelection: Mouse down event\n");
2292 // Clear cell we stored in mouse-down
2293 mUnselectCellOnMouseUp
= nullptr;
2295 if (aTarget
== nsISelectionPrivate::TABLESELECTION_CELL
)
2297 bool isSelected
= false;
2299 // Check if we have other selected cells
2300 nsIContent
* previousCellNode
=
2301 GetFirstSelectedContent(GetFirstCellRange());
2302 if (previousCellNode
)
2304 // We have at least 1 other selected cell
2306 // Check if new cell is already selected
2307 nsIFrame
*cellFrame
= childContent
->GetPrimaryFrame();
2308 if (!cellFrame
) return NS_ERROR_NULL_POINTER
;
2309 isSelected
= cellFrame
->IsSelected();
2313 // No cells selected -- remove non-cell selection
2314 mDomSelections
[index
]->RemoveAllRanges();
2316 mDragSelectingCells
= true; // Signal to start drag-cell-selection
2317 mSelectingTableCellMode
= aTarget
;
2318 // Set start for new drag-selection block (not appended)
2319 mStartSelectedCell
= childContent
;
2320 // The initial block end is same as the start
2321 mEndSelectedCell
= childContent
;
2325 // Remember this cell to (possibly) unselect it on mouseup
2326 mUnselectCellOnMouseUp
= childContent
;
2327 #ifdef DEBUG_TABLE_SELECTION
2328 printf("HandleTableSelection: Saving mUnselectCellOnMouseUp\n");
2333 // Select an unselected cell
2334 // but first remove existing selection if not in same table
2335 if (previousCellNode
&&
2336 !IsInSameTable(previousCellNode
, childContent
))
2338 mDomSelections
[index
]->RemoveAllRanges();
2339 // Reset selection mode that is cleared in RemoveAllRanges
2340 mSelectingTableCellMode
= aTarget
;
2343 return SelectCellElement(childContent
);
2348 else if (aTarget
== nsISelectionPrivate::TABLESELECTION_TABLE
)
2350 //TODO: We currently select entire table when clicked between cells,
2351 // should we restrict to only around border?
2352 // *** How do we get location data for cell and click?
2353 mDragSelectingCells
= false;
2354 mStartSelectedCell
= nullptr;
2355 mEndSelectedCell
= nullptr;
2357 // Remove existing selection and select the table
2358 mDomSelections
[index
]->RemoveAllRanges();
2359 return CreateAndAddRange(aParentContent
, aContentOffset
);
2361 else if (aTarget
== nsISelectionPrivate::TABLESELECTION_ROW
|| aTarget
== nsISelectionPrivate::TABLESELECTION_COLUMN
)
2363 #ifdef DEBUG_TABLE_SELECTION
2364 printf("aTarget == %d\n", aTarget
);
2367 // Start drag-selecting mode so multiple rows/cols can be selected
2368 // Note: Currently, nsFrame::GetDataForTableSelection
2369 // will never call us for row or column selection on mouse down
2370 mDragSelectingCells
= true;
2372 // Force new selection block
2373 mStartSelectedCell
= nullptr;
2374 mDomSelections
[index
]->RemoveAllRanges();
2375 // Always do this AFTER RemoveAllRanges
2376 mSelectingTableCellMode
= aTarget
;
2377 return SelectRowOrColumn(childContent
, aTarget
);
2382 #ifdef DEBUG_TABLE_SELECTION
2383 printf("HandleTableSelection: Mouse UP event. mDragSelectingCells=%d, mStartSelectedCell=%p\n",
2384 mDragSelectingCells
, mStartSelectedCell
.get());
2386 // First check if we are extending a block selection
2388 result
= mDomSelections
[index
]->GetRangeCount(&rangeCount
);
2389 if (NS_FAILED(result
))
2392 if (rangeCount
> 0 && aMouseEvent
->IsShift() &&
2393 mAppendStartSelectedCell
&& mAppendStartSelectedCell
!= childContent
)
2395 // Shift key is down: append a block selection
2396 mDragSelectingCells
= false;
2397 return SelectBlockOfCells(mAppendStartSelectedCell
, childContent
);
2400 if (mDragSelectingCells
)
2401 mAppendStartSelectedCell
= mStartSelectedCell
;
2403 mDragSelectingCells
= false;
2404 mStartSelectedCell
= nullptr;
2405 mEndSelectedCell
= nullptr;
2407 // Any other mouseup actions require that Ctrl or Cmd key is pressed
2408 // else stop table selection mode
2409 bool doMouseUpAction
= false;
2411 doMouseUpAction
= aMouseEvent
->IsMeta();
2413 doMouseUpAction
= aMouseEvent
->IsControl();
2415 if (!doMouseUpAction
)
2417 #ifdef DEBUG_TABLE_SELECTION
2418 printf("HandleTableSelection: Ending cell selection on mouseup: mAppendStartSelectedCell=%p\n",
2419 mAppendStartSelectedCell
.get());
2423 // Unselect a cell only if it wasn't
2424 // just selected on mousedown
2425 if( childContent
== mUnselectCellOnMouseUp
)
2427 // Scan ranges to find the cell to unselect (the selection range to remove)
2428 // XXXbz it's really weird that this lives outside the loop, so once we
2429 // find one we keep looking at it even if we find no more cells...
2430 nsINode
* previousCellParent
= nullptr;
2431 #ifdef DEBUG_TABLE_SELECTION
2432 printf("HandleTableSelection: Unselecting mUnselectCellOnMouseUp; rangeCount=%d\n", rangeCount
);
2434 for( int32_t i
= 0; i
< rangeCount
; i
++)
2436 // Strong reference, because sometimes we want to remove
2437 // this range, and then we might be the only owner.
2438 nsRefPtr
<nsRange
> range
= mDomSelections
[index
]->GetRangeAt(i
);
2439 if (!range
) return NS_ERROR_NULL_POINTER
;
2441 nsINode
* parent
= range
->GetStartParent();
2442 if (!parent
) return NS_ERROR_NULL_POINTER
;
2444 int32_t offset
= range
->StartOffset();
2445 // Be sure previous selection is a table cell
2446 nsIContent
* child
= parent
->GetChildAt(offset
);
2447 if (child
&& IsCell(child
))
2448 previousCellParent
= parent
;
2450 // We're done if we didn't find parent of a previously-selected cell
2451 if (!previousCellParent
) break;
2453 if (previousCellParent
== aParentContent
&& offset
== aContentOffset
)
2455 // Cell is already selected
2456 if (rangeCount
== 1)
2458 #ifdef DEBUG_TABLE_SELECTION
2459 printf("HandleTableSelection: Unselecting single selected cell\n");
2461 // This was the only cell selected.
2462 // Collapse to "normal" selection inside the cell
2463 mStartSelectedCell
= nullptr;
2464 mEndSelectedCell
= nullptr;
2465 mAppendStartSelectedCell
= nullptr;
2466 //TODO: We need a "Collapse to just before deepest child" routine
2467 // Even better, should we collapse to just after the LAST deepest child
2468 // (i.e., at the end of the cell's contents)?
2469 return mDomSelections
[index
]->Collapse(childContent
, 0);
2471 #ifdef DEBUG_TABLE_SELECTION
2472 printf("HandleTableSelection: Removing cell from multi-cell selection\n");
2474 // Unselecting the start of previous block
2475 // XXX What do we use now!
2476 if (childContent
== mAppendStartSelectedCell
)
2477 mAppendStartSelectedCell
= nullptr;
2479 // Deselect cell by removing its range from selection
2480 return mDomSelections
[index
]->RemoveRange(range
);
2483 mUnselectCellOnMouseUp
= nullptr;
2491 nsFrameSelection::SelectBlockOfCells(nsIContent
*aStartCell
, nsIContent
*aEndCell
)
2493 NS_ENSURE_TRUE(aStartCell
, NS_ERROR_NULL_POINTER
);
2494 NS_ENSURE_TRUE(aEndCell
, NS_ERROR_NULL_POINTER
);
2495 mEndSelectedCell
= aEndCell
;
2497 nsCOMPtr
<nsIContent
> startCell
;
2498 nsresult result
= NS_OK
;
2500 // If new end cell is in a different table, do nothing
2501 nsIContent
* table
= IsInSameTable(aStartCell
, aEndCell
);
2506 // Get starting and ending cells' location in the cellmap
2507 int32_t startRowIndex
, startColIndex
, endRowIndex
, endColIndex
;
2508 result
= GetCellIndexes(aStartCell
, startRowIndex
, startColIndex
);
2509 if(NS_FAILED(result
)) return result
;
2510 result
= GetCellIndexes(aEndCell
, endRowIndex
, endColIndex
);
2511 if(NS_FAILED(result
)) return result
;
2513 if (mDragSelectingCells
)
2515 // Drag selecting: remove selected cells outside of new block limits
2516 UnselectCells(table
, startRowIndex
, startColIndex
, endRowIndex
, endColIndex
,
2520 // Note that we select block in the direction of user's mouse dragging,
2521 // which means start cell may be after the end cell in either row or column
2522 return AddCellsToSelection(table
, startRowIndex
, startColIndex
,
2523 endRowIndex
, endColIndex
);
2527 nsFrameSelection::UnselectCells(nsIContent
*aTableContent
,
2528 int32_t aStartRowIndex
,
2529 int32_t aStartColumnIndex
,
2530 int32_t aEndRowIndex
,
2531 int32_t aEndColumnIndex
,
2532 bool aRemoveOutsideOfCellRange
)
2535 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
2536 if (!mDomSelections
[index
])
2537 return NS_ERROR_NULL_POINTER
;
2539 nsTableOuterFrame
* tableFrame
= do_QueryFrame(aTableContent
->GetPrimaryFrame());
2541 return NS_ERROR_FAILURE
;
2543 int32_t minRowIndex
= std::min(aStartRowIndex
, aEndRowIndex
);
2544 int32_t maxRowIndex
= std::max(aStartRowIndex
, aEndRowIndex
);
2545 int32_t minColIndex
= std::min(aStartColumnIndex
, aEndColumnIndex
);
2546 int32_t maxColIndex
= std::max(aStartColumnIndex
, aEndColumnIndex
);
2548 // Strong reference because we sometimes remove the range
2549 nsRefPtr
<nsRange
> range
= GetFirstCellRange();
2550 nsIContent
* cellNode
= GetFirstSelectedContent(range
);
2551 NS_PRECONDITION(!range
|| cellNode
, "Must have cellNode if had a range");
2553 int32_t curRowIndex
, curColIndex
;
2556 nsresult result
= GetCellIndexes(cellNode
, curRowIndex
, curColIndex
);
2557 if (NS_FAILED(result
))
2560 #ifdef DEBUG_TABLE_SELECTION
2562 printf("RemoveCellsToSelection -- range is null\n");
2566 if (aRemoveOutsideOfCellRange
) {
2567 if (curRowIndex
< minRowIndex
|| curRowIndex
> maxRowIndex
||
2568 curColIndex
< minColIndex
|| curColIndex
> maxColIndex
) {
2570 mDomSelections
[index
]->RemoveRange(range
);
2571 // Since we've removed the range, decrement pointer to next range
2572 mSelectedCellIndex
--;
2576 // Remove cell from selection if it belongs to the given cells range or
2577 // it is spanned onto the cells range.
2578 nsTableCellFrame
* cellFrame
=
2579 tableFrame
->GetCellFrameAt(curRowIndex
, curColIndex
);
2581 int32_t origRowIndex
, origColIndex
;
2582 cellFrame
->GetRowIndex(origRowIndex
);
2583 cellFrame
->GetColIndex(origColIndex
);
2584 uint32_t actualRowSpan
=
2585 tableFrame
->GetEffectiveRowSpanAt(origRowIndex
, origColIndex
);
2586 uint32_t actualColSpan
=
2587 tableFrame
->GetEffectiveColSpanAt(curRowIndex
, curColIndex
);
2588 if (origRowIndex
<= maxRowIndex
&& maxRowIndex
>= 0 &&
2589 origRowIndex
+ actualRowSpan
- 1 >= static_cast<uint32_t>(minRowIndex
) &&
2590 origColIndex
<= maxColIndex
&& maxColIndex
>= 0 &&
2591 origColIndex
+ actualColSpan
- 1 >= static_cast<uint32_t>(minColIndex
)) {
2593 mDomSelections
[index
]->RemoveRange(range
);
2594 // Since we've removed the range, decrement pointer to next range
2595 mSelectedCellIndex
--;
2600 range
= GetNextCellRange();
2601 cellNode
= GetFirstSelectedContent(range
);
2602 NS_PRECONDITION(!range
|| cellNode
, "Must have cellNode if had a range");
2609 nsFrameSelection::AddCellsToSelection(nsIContent
*aTableContent
,
2610 int32_t aStartRowIndex
,
2611 int32_t aStartColumnIndex
,
2612 int32_t aEndRowIndex
,
2613 int32_t aEndColumnIndex
)
2615 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
2616 if (!mDomSelections
[index
])
2617 return NS_ERROR_NULL_POINTER
;
2619 nsTableOuterFrame
* tableFrame
= do_QueryFrame(aTableContent
->GetPrimaryFrame());
2620 if (!tableFrame
) // Check that |table| is a table.
2621 return NS_ERROR_FAILURE
;
2623 nsresult result
= NS_OK
;
2624 int32_t row
= aStartRowIndex
;
2627 int32_t col
= aStartColumnIndex
;
2630 nsTableCellFrame
* cellFrame
= tableFrame
->GetCellFrameAt(row
, col
);
2632 // Skip cells that are spanned from previous locations or are already selected
2634 int32_t origRow
, origCol
;
2635 cellFrame
->GetRowIndex(origRow
);
2636 cellFrame
->GetColIndex(origCol
);
2637 if (origRow
== row
&& origCol
== col
&& !cellFrame
->IsSelected()) {
2638 result
= SelectCellElement(cellFrame
->GetContent());
2639 if (NS_FAILED(result
)) return result
;
2642 // Done when we reach end column
2643 if (col
== aEndColumnIndex
) break;
2645 if (aStartColumnIndex
< aEndColumnIndex
)
2650 if (row
== aEndRowIndex
) break;
2652 if (aStartRowIndex
< aEndRowIndex
)
2661 nsFrameSelection::RemoveCellsFromSelection(nsIContent
*aTable
,
2662 int32_t aStartRowIndex
,
2663 int32_t aStartColumnIndex
,
2664 int32_t aEndRowIndex
,
2665 int32_t aEndColumnIndex
)
2667 return UnselectCells(aTable
, aStartRowIndex
, aStartColumnIndex
,
2668 aEndRowIndex
, aEndColumnIndex
, false);
2672 nsFrameSelection::RestrictCellsToSelection(nsIContent
*aTable
,
2673 int32_t aStartRowIndex
,
2674 int32_t aStartColumnIndex
,
2675 int32_t aEndRowIndex
,
2676 int32_t aEndColumnIndex
)
2678 return UnselectCells(aTable
, aStartRowIndex
, aStartColumnIndex
,
2679 aEndRowIndex
, aEndColumnIndex
, true);
2683 nsFrameSelection::SelectRowOrColumn(nsIContent
*aCellContent
, uint32_t aTarget
)
2685 if (!aCellContent
) return NS_ERROR_NULL_POINTER
;
2687 nsIContent
* table
= GetParentTable(aCellContent
);
2688 if (!table
) return NS_ERROR_NULL_POINTER
;
2690 // Get table and cell layout interfaces to access
2691 // cell data based on cellmap location
2692 // Frames are not ref counted, so don't use an nsCOMPtr
2693 nsTableOuterFrame
* tableFrame
= do_QueryFrame(table
->GetPrimaryFrame());
2694 if (!tableFrame
) return NS_ERROR_FAILURE
;
2695 nsITableCellLayout
*cellLayout
= GetCellLayout(aCellContent
);
2696 if (!cellLayout
) return NS_ERROR_FAILURE
;
2698 // Get location of target cell:
2699 int32_t rowIndex
, colIndex
;
2700 nsresult result
= cellLayout
->GetCellIndexes(rowIndex
, colIndex
);
2701 if (NS_FAILED(result
)) return result
;
2703 // Be sure we start at proper beginning
2704 // (This allows us to select row or col given ANY cell!)
2705 if (aTarget
== nsISelectionPrivate::TABLESELECTION_ROW
)
2707 if (aTarget
== nsISelectionPrivate::TABLESELECTION_COLUMN
)
2710 nsCOMPtr
<nsIContent
> firstCell
, lastCell
;
2712 // Loop through all cells in column or row to find first and last
2713 nsCOMPtr
<nsIContent
> curCellContent
=
2714 tableFrame
->GetCellAt(rowIndex
, colIndex
);
2715 if (!curCellContent
)
2719 firstCell
= curCellContent
;
2721 lastCell
= curCellContent
.forget();
2723 // Move to next cell in cellmap, skipping spanned locations
2724 if (aTarget
== nsISelectionPrivate::TABLESELECTION_ROW
)
2725 colIndex
+= tableFrame
->GetEffectiveRowSpanAt(rowIndex
, colIndex
);
2727 rowIndex
+= tableFrame
->GetEffectiveRowSpanAt(rowIndex
, colIndex
);
2730 // Use SelectBlockOfCells:
2731 // This will replace existing selection,
2732 // but allow unselecting by dragging out of selected region
2733 if (firstCell
&& lastCell
)
2735 if (!mStartSelectedCell
)
2737 // We are starting a new block, so select the first cell
2738 result
= SelectCellElement(firstCell
);
2739 if (NS_FAILED(result
)) return result
;
2740 mStartSelectedCell
= firstCell
;
2742 nsCOMPtr
<nsIContent
> lastCellContent
= do_QueryInterface(lastCell
);
2743 result
= SelectBlockOfCells(mStartSelectedCell
, lastCellContent
);
2745 // This gets set to the cell at end of row/col,
2746 // but we need it to be the cell under cursor
2747 mEndSelectedCell
= aCellContent
;
2752 // This is a more efficient strategy that appends row to current selection,
2753 // but doesn't allow dragging OFF of an existing selection to unselect!
2755 // Loop through all cells in column or row
2756 result
= tableLayout
->GetCellDataAt(rowIndex
, colIndex
,
2757 getter_AddRefs(cellElement
),
2758 curRowIndex
, curColIndex
,
2760 actualRowSpan
, actualColSpan
,
2762 if (NS_FAILED(result
)) return result
;
2763 // We're done when cell is not found
2764 if (!cellElement
) break;
2767 // Check spans else we infinitely loop
2768 NS_ASSERTION(actualColSpan
, "actualColSpan is 0!");
2769 NS_ASSERTION(actualRowSpan
, "actualRowSpan is 0!");
2771 // Skip cells that are already selected or span from outside our region
2772 if (!isSelected
&& rowIndex
== curRowIndex
&& colIndex
== curColIndex
)
2774 result
= SelectCellElement(cellElement
);
2775 if (NS_FAILED(result
)) return result
;
2777 // Move to next row or column in cellmap, skipping spanned locations
2778 if (aTarget
== nsISelectionPrivate::TABLESELECTION_ROW
)
2779 colIndex
+= actualColSpan
;
2781 rowIndex
+= actualRowSpan
;
2783 while (cellElement
);
2790 nsFrameSelection::GetFirstCellNodeInRange(nsRange
*aRange
) const
2792 if (!aRange
) return nullptr;
2794 nsINode
* startParent
= aRange
->GetStartParent();
2798 int32_t offset
= aRange
->StartOffset();
2800 nsIContent
* childContent
= startParent
->GetChildAt(offset
);
2803 // Don't return node if not a cell
2804 if (!IsCell(childContent
))
2807 return childContent
;
2811 nsFrameSelection::GetFirstCellRange()
2813 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
2814 if (!mDomSelections
[index
])
2817 nsRange
* firstRange
= mDomSelections
[index
]->GetRangeAt(0);
2818 if (!GetFirstCellNodeInRange(firstRange
)) {
2822 // Setup for next cell
2823 mSelectedCellIndex
= 1;
2829 nsFrameSelection::GetNextCellRange()
2831 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
2832 if (!mDomSelections
[index
])
2835 nsRange
* range
= mDomSelections
[index
]->GetRangeAt(mSelectedCellIndex
);
2837 // Get first node in next range of selection - test if it's a cell
2838 if (!GetFirstCellNodeInRange(range
)) {
2842 // Setup for next cell
2843 mSelectedCellIndex
++;
2849 nsFrameSelection::GetCellIndexes(nsIContent
*aCell
,
2853 if (!aCell
) return NS_ERROR_NULL_POINTER
;
2855 aColIndex
=0; // initialize out params
2858 nsITableCellLayout
*cellLayoutObject
= GetCellLayout(aCell
);
2859 if (!cellLayoutObject
) return NS_ERROR_FAILURE
;
2860 return cellLayoutObject
->GetCellIndexes(aRowIndex
, aColIndex
);
2864 nsFrameSelection::IsInSameTable(nsIContent
*aContent1
,
2865 nsIContent
*aContent2
) const
2867 if (!aContent1
|| !aContent2
) return nullptr;
2869 nsIContent
* tableNode1
= GetParentTable(aContent1
);
2870 nsIContent
* tableNode2
= GetParentTable(aContent2
);
2872 // Must be in the same table. Note that we want to return false for
2873 // the test if both tables are null.
2874 return (tableNode1
== tableNode2
) ? tableNode1
: nullptr;
2878 nsFrameSelection::GetParentTable(nsIContent
*aCell
) const
2884 for (nsIContent
* parent
= aCell
->GetParent(); parent
;
2885 parent
= parent
->GetParent()) {
2886 if (parent
->Tag() == nsGkAtoms::table
&&
2896 nsFrameSelection::SelectCellElement(nsIContent
*aCellElement
)
2898 nsIContent
*parent
= aCellElement
->GetParent();
2901 int32_t offset
= parent
->IndexOf(aCellElement
);
2903 return CreateAndAddRange(parent
, offset
);
2907 Selection::getTableCellLocationFromRange(nsRange
* aRange
,
2908 int32_t* aSelectionType
,
2909 int32_t* aRow
, int32_t* aCol
)
2911 if (!aRange
|| !aSelectionType
|| !aRow
|| !aCol
)
2912 return NS_ERROR_NULL_POINTER
;
2914 *aSelectionType
= nsISelectionPrivate::TABLESELECTION_NONE
;
2918 // Must have access to frame selection to get cell info
2919 if (!mFrameSelection
) return NS_OK
;
2921 nsresult result
= GetTableSelectionType(aRange
, aSelectionType
);
2922 if (NS_FAILED(result
)) return result
;
2924 // Don't fail if range does not point to a single table cell,
2925 // let aSelectionType tell user if we don't have a cell
2926 if (*aSelectionType
!= nsISelectionPrivate::TABLESELECTION_CELL
)
2929 // Get the child content (the cell) pointed to by starting node of range
2930 // We do minimal checking since GetTableSelectionType assures
2931 // us that this really is a table cell
2932 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aRange
->GetStartParent());
2934 return NS_ERROR_FAILURE
;
2936 nsIContent
*child
= content
->GetChildAt(aRange
->StartOffset());
2938 return NS_ERROR_FAILURE
;
2940 //Note: This is a non-ref-counted pointer to the frame
2941 nsITableCellLayout
*cellLayout
= mFrameSelection
->GetCellLayout(child
);
2942 if (NS_FAILED(result
))
2945 return NS_ERROR_FAILURE
;
2947 return cellLayout
->GetCellIndexes(*aRow
, *aCol
);
2951 Selection::addTableCellRange(nsRange
* aRange
, bool* aDidAddRange
,
2954 if (!aDidAddRange
|| !aOutIndex
)
2955 return NS_ERROR_NULL_POINTER
;
2957 *aDidAddRange
= false;
2960 if (!mFrameSelection
)
2964 return NS_ERROR_NULL_POINTER
;
2968 // Get if we are adding a cell selection and the row, col of cell if we are
2969 int32_t newRow
, newCol
, tableMode
;
2970 result
= getTableCellLocationFromRange(aRange
, &tableMode
, &newRow
, &newCol
);
2971 if (NS_FAILED(result
)) return result
;
2973 // If not adding a cell range, we are done here
2974 if (tableMode
!= nsISelectionPrivate::TABLESELECTION_CELL
)
2976 mFrameSelection
->mSelectingTableCellMode
= tableMode
;
2977 // Don't fail if range isn't a selected cell, aDidAddRange tells caller if we didn't proceed
2981 // Set frame selection mode only if not already set to a table mode
2982 // so we don't lose the select row and column flags (not detected by getTableCellLocation)
2983 if (mFrameSelection
->mSelectingTableCellMode
== TABLESELECTION_NONE
)
2984 mFrameSelection
->mSelectingTableCellMode
= tableMode
;
2986 *aDidAddRange
= true;
2987 return AddItem(aRange
, aOutIndex
);
2990 //TODO: Figure out TABLESELECTION_COLUMN and TABLESELECTION_ALLCELLS
2992 Selection::GetTableSelectionType(nsIDOMRange
* aDOMRange
,
2993 int32_t* aTableSelectionType
)
2995 if (!aDOMRange
|| !aTableSelectionType
)
2996 return NS_ERROR_NULL_POINTER
;
2997 nsRange
* range
= static_cast<nsRange
*>(aDOMRange
);
2999 *aTableSelectionType
= nsISelectionPrivate::TABLESELECTION_NONE
;
3001 // Must have access to frame selection to get cell info
3002 if(!mFrameSelection
) return NS_OK
;
3004 nsINode
* startNode
= range
->GetStartParent();
3005 if (!startNode
) return NS_ERROR_FAILURE
;
3007 nsINode
* endNode
= range
->GetEndParent();
3008 if (!endNode
) return NS_ERROR_FAILURE
;
3010 // Not a single selected node
3011 if (startNode
!= endNode
) return NS_OK
;
3013 int32_t startOffset
= range
->StartOffset();
3014 int32_t endOffset
= range
->EndOffset();
3016 // Not a single selected node
3017 if ((endOffset
- startOffset
) != 1)
3020 nsIContent
* startContent
= static_cast<nsIContent
*>(startNode
);
3021 if (!(startNode
->IsElement() && startContent
->IsHTML())) {
3022 // Implies a check for being an element; if we ever make this work
3023 // for non-HTML, need to keep checking for elements.
3027 nsIAtom
*tag
= startContent
->Tag();
3029 if (tag
== nsGkAtoms::tr
)
3031 *aTableSelectionType
= nsISelectionPrivate::TABLESELECTION_CELL
;
3033 else //check to see if we are selecting a table or row (column and all cells not done yet)
3035 nsIContent
*child
= startNode
->GetChildAt(startOffset
);
3037 return NS_ERROR_FAILURE
;
3041 if (tag
== nsGkAtoms::table
)
3042 *aTableSelectionType
= nsISelectionPrivate::TABLESELECTION_TABLE
;
3043 else if (tag
== nsGkAtoms::tr
)
3044 *aTableSelectionType
= nsISelectionPrivate::TABLESELECTION_ROW
;
3051 nsFrameSelection::CreateAndAddRange(nsINode
*aParentNode
, int32_t aOffset
)
3053 if (!aParentNode
) return NS_ERROR_NULL_POINTER
;
3055 nsRefPtr
<nsRange
> range
= new nsRange(aParentNode
);
3057 // Set range around child at given offset
3058 nsresult result
= range
->SetStart(aParentNode
, aOffset
);
3059 if (NS_FAILED(result
)) return result
;
3060 result
= range
->SetEnd(aParentNode
, aOffset
+1);
3061 if (NS_FAILED(result
)) return result
;
3063 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
3064 if (!mDomSelections
[index
])
3065 return NS_ERROR_NULL_POINTER
;
3067 return mDomSelections
[index
]->AddRange(range
);
3070 // End of Table Selection
3073 nsFrameSelection::SetAncestorLimiter(nsIContent
*aLimiter
)
3075 if (mAncestorLimiter
!= aLimiter
) {
3076 mAncestorLimiter
= aLimiter
;
3078 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
3079 if (!mDomSelections
[index
])
3082 if (!IsValidSelectionPoint(this, mDomSelections
[index
]->GetFocusNode())) {
3083 ClearNormalSelection();
3084 if (mAncestorLimiter
) {
3085 PostReason(nsISelectionListener::NO_REASON
);
3086 TakeFocus(mAncestorLimiter
, 0, 0, CARET_ASSOCIATE_BEFORE
, false, false);
3092 //END nsFrameSelection methods
3095 //BEGIN nsISelection interface implementations
3100 nsFrameSelection::DeleteFromDocument()
3104 // If we're already collapsed, then we do nothing (bug 719503).
3106 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
3107 if (!mDomSelections
[index
])
3108 return NS_ERROR_NULL_POINTER
;
3110 mDomSelections
[index
]->GetIsCollapsed( &isCollapsed
);
3116 nsRefPtr
<Selection
> selection
= mDomSelections
[index
];
3117 for (int32_t rangeIdx
= 0; rangeIdx
< selection
->GetRangeCount(); ++rangeIdx
) {
3118 nsRefPtr
<nsRange
> range
= selection
->GetRangeAt(rangeIdx
);
3119 res
= range
->DeleteContents();
3124 // Collapse to the new location.
3125 // If we deleted one character, then we move back one element.
3126 // FIXME We don't know how to do this past frame boundaries yet.
3128 mDomSelections
[index
]->Collapse(mDomSelections
[index
]->GetAnchorNode(), mDomSelections
[index
]->AnchorOffset()-1);
3129 else if (mDomSelections
[index
]->AnchorOffset() > 0)
3130 mDomSelections
[index
]->Collapse(mDomSelections
[index
]->GetAnchorNode(), mDomSelections
[index
]->AnchorOffset());
3133 printf("Don't know how to set selection back past frame boundary\n");
3140 nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent
* aMouseEvent
)
3143 mDelayedMouseEventValid
= true;
3144 mDelayedMouseEventIsShift
= aMouseEvent
->IsShift();
3145 mDelayedMouseEventClickCount
= aMouseEvent
->clickCount
;
3147 mDelayedMouseEventValid
= false;
3152 nsFrameSelection::DisconnectFromPresShell()
3154 // Remove touch caret as selection listener
3155 nsRefPtr
<TouchCaret
> touchCaret
= mShell
->GetTouchCaret();
3157 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
3158 mDomSelections
[index
]->RemoveSelectionListener(touchCaret
);
3161 nsRefPtr
<SelectionCarets
> selectionCarets
= mShell
->GetSelectionCarets();
3162 if (selectionCarets
) {
3163 int8_t index
= GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL
);
3164 mDomSelections
[index
]->RemoveSelectionListener(selectionCarets
);
3167 StopAutoScrollTimer();
3168 for (int32_t i
= 0; i
< nsISelectionController::NUM_SELECTIONTYPES
; i
++) {
3169 mDomSelections
[i
]->Clear(nullptr);
3174 //END nsISelection interface implementations
3180 // mozilla::dom::Selection implementation
3182 // note: this can return a nil anchor node
3184 Selection::Selection()
3185 : mCachedOffsetForFrame(nullptr)
3186 , mDirection(eDirNext
)
3187 , mType(nsISelectionController::SELECTION_NORMAL
)
3188 , mApplyUserSelectStyle(false)
3192 Selection::Selection(nsFrameSelection
* aList
)
3193 : mFrameSelection(aList
)
3194 , mCachedOffsetForFrame(nullptr)
3195 , mDirection(eDirNext
)
3196 , mType(nsISelectionController::SELECTION_NORMAL
)
3197 , mApplyUserSelectStyle(false)
3201 Selection::~Selection()
3203 setAnchorFocusRange(-1);
3205 uint32_t count
= mRanges
.Length();
3206 for (uint32_t i
= 0; i
< count
; ++i
) {
3207 mRanges
[i
].mRange
->SetInSelection(false);
3210 if (mAutoScrollTimer
) {
3211 mAutoScrollTimer
->Stop();
3212 mAutoScrollTimer
= nullptr;
3215 mScrollEvent
.Revoke();
3217 if (mCachedOffsetForFrame
) {
3218 delete mCachedOffsetForFrame
;
3219 mCachedOffsetForFrame
= nullptr;
3224 Selection::GetParentObject() const
3226 nsIPresShell
* shell
= GetPresShell();
3228 return shell
->GetDocument();
3233 NS_IMPL_CYCLE_COLLECTION_CLASS(Selection
)
3235 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection
)
3236 // Unlink the selection listeners *before* we do RemoveAllRanges since
3237 // we don't want to notify the listeners during JS GC (they could be
3239 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners
)
3240 tmp
->RemoveAllRanges();
3241 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection
)
3242 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
3243 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3244 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection
)
3246 uint32_t i
, count
= tmp
->mRanges
.Length();
3247 for (i
= 0; i
< count
; ++i
) {
3248 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRanges
[i
].mRange
)
3251 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange
)
3252 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection
)
3253 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners
)
3254 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
3255 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3256 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Selection
)
3258 // QueryInterface implementation for Selection
3259 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Selection
)
3260 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
3261 NS_INTERFACE_MAP_ENTRY(nsISelection
)
3262 NS_INTERFACE_MAP_ENTRY(nsISelectionPrivate
)
3263 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
3264 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsISelection
)
3265 NS_INTERFACE_MAP_END
3267 NS_IMPL_CYCLE_COLLECTING_ADDREF(Selection
)
3268 NS_IMPL_CYCLE_COLLECTING_RELEASE(Selection
)
3272 Selection::GetAnchorNode(nsIDOMNode
** aAnchorNode
)
3274 nsINode
* anchorNode
= GetAnchorNode();
3276 return CallQueryInterface(anchorNode
, aAnchorNode
);
3279 *aAnchorNode
= nullptr;
3284 Selection::GetAnchorNode()
3286 if (!mAnchorFocusRange
)
3289 if (GetDirection() == eDirNext
) {
3290 return mAnchorFocusRange
->GetStartParent();
3293 return mAnchorFocusRange
->GetEndParent();
3297 Selection::GetAnchorOffset(int32_t* aAnchorOffset
)
3299 *aAnchorOffset
= static_cast<int32_t>(AnchorOffset());
3303 // note: this can return a nil focus node
3305 Selection::GetFocusNode(nsIDOMNode
** aFocusNode
)
3307 nsINode
* focusNode
= GetFocusNode();
3309 return CallQueryInterface(focusNode
, aFocusNode
);
3312 *aFocusNode
= nullptr;
3317 Selection::GetFocusNode()
3319 if (!mAnchorFocusRange
)
3322 if (GetDirection() == eDirNext
){
3323 return mAnchorFocusRange
->GetEndParent();
3326 return mAnchorFocusRange
->GetStartParent();
3330 Selection::GetFocusOffset(int32_t* aFocusOffset
)
3332 *aFocusOffset
= static_cast<int32_t>(FocusOffset());
3337 Selection::setAnchorFocusRange(int32_t indx
)
3339 if (indx
>= (int32_t)mRanges
.Length())
3341 if (indx
< 0) //release all
3343 mAnchorFocusRange
= nullptr;
3346 mAnchorFocusRange
= mRanges
[indx
].mRange
;
3351 Selection::AnchorOffset()
3353 if (!mAnchorFocusRange
)
3356 if (GetDirection() == eDirNext
){
3357 return mAnchorFocusRange
->StartOffset();
3360 return mAnchorFocusRange
->EndOffset();
3364 Selection::FocusOffset()
3366 if (!mAnchorFocusRange
)
3369 if (GetDirection() == eDirNext
){
3370 return mAnchorFocusRange
->EndOffset();
3373 return mAnchorFocusRange
->StartOffset();
3377 CompareToRangeStart(nsINode
* aCompareNode
, int32_t aCompareOffset
,
3378 nsRange
* aRange
, int32_t* aCmp
)
3380 nsINode
* start
= aRange
->GetStartParent();
3381 NS_ENSURE_STATE(aCompareNode
&& start
);
3382 // If the nodes that we're comparing are not in the same document,
3383 // assume that aCompareNode will fall at the end of the ranges.
3384 if (aCompareNode
->GetComposedDoc() != start
->GetComposedDoc() ||
3385 !start
->GetComposedDoc()) {
3388 *aCmp
= nsContentUtils::ComparePoints(aCompareNode
, aCompareOffset
,
3389 start
, aRange
->StartOffset());
3395 CompareToRangeEnd(nsINode
* aCompareNode
, int32_t aCompareOffset
,
3396 nsRange
* aRange
, int32_t* aCmp
)
3398 nsINode
* end
= aRange
->GetEndParent();
3399 NS_ENSURE_STATE(aCompareNode
&& end
);
3400 // If the nodes that we're comparing are not in the same document,
3401 // assume that aCompareNode will fall at the end of the ranges.
3402 if (aCompareNode
->GetComposedDoc() != end
->GetComposedDoc() ||
3403 !end
->GetComposedDoc()) {
3406 *aCmp
= nsContentUtils::ComparePoints(aCompareNode
, aCompareOffset
,
3407 end
, aRange
->EndOffset());
3412 // Selection::FindInsertionPoint
3414 // Binary searches the given sorted array of ranges for the insertion point
3415 // for the given node/offset. The given comparator is used, and the index
3416 // where the point should appear in the array is placed in *aInsertionPoint.
3418 // If there is an item in the array equal to the input point, we will return
3419 // the index of this item.
3422 Selection::FindInsertionPoint(
3423 nsTArray
<RangeData
>* aElementArray
,
3424 nsINode
* aPointNode
, int32_t aPointOffset
,
3425 nsresult (*aComparator
)(nsINode
*,int32_t,nsRange
*,int32_t*),
3429 int32_t beginSearch
= 0;
3430 int32_t endSearch
= aElementArray
->Length(); // one beyond what to check
3433 int32_t center
= endSearch
- 1; // Check last index, then binary search
3435 nsRange
* range
= (*aElementArray
)[center
].mRange
;
3438 nsresult rv
= aComparator(aPointNode
, aPointOffset
, range
, &cmp
);
3439 NS_ENSURE_SUCCESS(rv
, rv
);
3441 if (cmp
< 0) { // point < cur
3443 } else if (cmp
> 0) { // point > cur
3444 beginSearch
= center
+ 1;
3445 } else { // found match, done
3446 beginSearch
= center
;
3449 center
= (endSearch
- beginSearch
) / 2 + beginSearch
;
3450 } while (endSearch
- beginSearch
> 0);
3453 *aPoint
= beginSearch
;
3457 // Selection::SubtractRange
3459 // A helper function that subtracts aSubtract from aRange, and adds
3460 // 1 or 2 RangeData objects representing the remaining non-overlapping
3461 // difference to aOutput. It is assumed that the caller has checked that
3462 // aRange and aSubtract do indeed overlap
3465 Selection::SubtractRange(RangeData
* aRange
, nsRange
* aSubtract
,
3466 nsTArray
<RangeData
>* aOutput
)
3468 nsRange
* range
= aRange
->mRange
;
3470 // First we want to compare to the range start
3472 nsresult rv
= CompareToRangeStart(range
->GetStartParent(),
3473 range
->StartOffset(),
3475 NS_ENSURE_SUCCESS(rv
, rv
);
3477 // Also, make a comparison to the range end
3479 rv
= CompareToRangeEnd(range
->GetEndParent(),
3482 NS_ENSURE_SUCCESS(rv
, rv
);
3484 // If the existing range left overlaps the new range (aSubtract) then
3485 // cmp < 0, and cmp2 < 0
3486 // If it right overlaps the new range then cmp > 0 and cmp2 > 0
3487 // If it fully contains the new range, then cmp < 0 and cmp2 > 0
3490 // We need to add a new RangeData to the output, running from
3491 // the end of aSubtract to the end of range
3492 nsRefPtr
<nsRange
> postOverlap
= new nsRange(aSubtract
->GetEndParent());
3495 postOverlap
->SetStart(aSubtract
->GetEndParent(), aSubtract
->EndOffset());
3496 NS_ENSURE_SUCCESS(rv
, rv
);
3498 postOverlap
->SetEnd(range
->GetEndParent(), range
->EndOffset());
3499 NS_ENSURE_SUCCESS(rv
, rv
);
3500 if (!postOverlap
->Collapsed()) {
3501 if (!aOutput
->InsertElementAt(0, RangeData(postOverlap
)))
3502 return NS_ERROR_OUT_OF_MEMORY
;
3503 (*aOutput
)[0].mTextRangeStyle
= aRange
->mTextRangeStyle
;
3508 // We need to add a new RangeData to the output, running from
3509 // the start of the range to the start of aSubtract
3510 nsRefPtr
<nsRange
> preOverlap
= new nsRange(range
->GetStartParent());
3513 preOverlap
->SetStart(range
->GetStartParent(), range
->StartOffset());
3514 NS_ENSURE_SUCCESS(rv
, rv
);
3516 preOverlap
->SetEnd(aSubtract
->GetStartParent(), aSubtract
->StartOffset());
3517 NS_ENSURE_SUCCESS(rv
, rv
);
3519 if (!preOverlap
->Collapsed()) {
3520 if (!aOutput
->InsertElementAt(0, RangeData(preOverlap
)))
3521 return NS_ERROR_OUT_OF_MEMORY
;
3522 (*aOutput
)[0].mTextRangeStyle
= aRange
->mTextRangeStyle
;
3530 Selection::AddItem(nsRange
* aItem
, int32_t* aOutIndex
)
3533 return NS_ERROR_NULL_POINTER
;
3534 if (!aItem
->IsPositioned())
3535 return NS_ERROR_UNEXPECTED
;
3537 NS_ASSERTION(aOutIndex
, "aOutIndex can't be null");
3539 if (mApplyUserSelectStyle
) {
3540 nsAutoTArray
<nsRefPtr
<nsRange
>, 4> rangesToAdd
;
3541 aItem
->ExcludeNonSelectableNodes(&rangesToAdd
);
3542 for (size_t i
= 0; i
< rangesToAdd
.Length(); ++i
) {
3543 nsresult rv
= AddItemInternal(rangesToAdd
[i
], aOutIndex
);
3544 NS_ENSURE_SUCCESS(rv
, rv
);
3548 return AddItemInternal(aItem
, aOutIndex
);
3552 Selection::AddItemInternal(nsRange
* aItem
, int32_t* aOutIndex
)
3555 MOZ_ASSERT(aItem
->IsPositioned());
3556 MOZ_ASSERT(aOutIndex
);
3560 // a common case is that we have no ranges yet
3561 if (mRanges
.Length() == 0) {
3562 if (!mRanges
.AppendElement(RangeData(aItem
)))
3563 return NS_ERROR_OUT_OF_MEMORY
;
3564 aItem
->SetInSelection(true);
3570 int32_t startIndex
, endIndex
;
3571 nsresult rv
= GetIndicesForInterval(aItem
->GetStartParent(),
3572 aItem
->StartOffset(),
3573 aItem
->GetEndParent(),
3574 aItem
->EndOffset(), false,
3575 &startIndex
, &endIndex
);
3576 NS_ENSURE_SUCCESS(rv
, rv
);
3578 if (endIndex
== -1) {
3579 // All ranges start after the given range. We can insert our range at
3580 // position 0, knowing there are no overlaps (handled below)
3583 } else if (startIndex
== -1) {
3584 // All ranges end before the given range. We can insert our range at
3585 // the end of the array, knowing there are no overlaps (handled below)
3586 startIndex
= mRanges
.Length();
3587 endIndex
= startIndex
;
3590 // If the range is already contained in mRanges, silently succeed
3591 bool sameRange
= EqualsRangeAtPoint(aItem
->GetStartParent(),
3592 aItem
->StartOffset(),
3593 aItem
->GetEndParent(),
3594 aItem
->EndOffset(), startIndex
);
3596 *aOutIndex
= startIndex
;
3600 if (startIndex
== endIndex
) {
3601 // The new range doesn't overlap any existing ranges
3602 if (!mRanges
.InsertElementAt(startIndex
, RangeData(aItem
)))
3603 return NS_ERROR_OUT_OF_MEMORY
;
3604 aItem
->SetInSelection(true);
3605 *aOutIndex
= startIndex
;
3609 // We now know that at least 1 existing range overlaps with the range that
3610 // we are trying to add. In fact, the only ranges of interest are those at
3611 // the two end points, startIndex and endIndex - 1 (which may point to the
3612 // same range) as these may partially overlap the new range. Any ranges
3613 // between these indices are fully overlapped by the new range, and so can be
3615 nsTArray
<RangeData
> overlaps
;
3616 if (!overlaps
.InsertElementAt(0, mRanges
[startIndex
]))
3617 return NS_ERROR_OUT_OF_MEMORY
;
3619 if (endIndex
- 1 != startIndex
) {
3620 if (!overlaps
.InsertElementAt(1, mRanges
[endIndex
- 1]))
3621 return NS_ERROR_OUT_OF_MEMORY
;
3624 // Remove all the overlapping ranges
3625 for (int32_t i
= startIndex
; i
< endIndex
; ++i
) {
3626 mRanges
[i
].mRange
->SetInSelection(false);
3628 mRanges
.RemoveElementsAt(startIndex
, endIndex
- startIndex
);
3630 nsTArray
<RangeData
> temp
;
3631 for (int32_t i
= overlaps
.Length() - 1; i
>= 0; i
--) {
3632 nsresult rv
= SubtractRange(&overlaps
[i
], aItem
, &temp
);
3633 NS_ENSURE_SUCCESS(rv
, rv
);
3636 // Insert the new element into our "leftovers" array
3637 int32_t insertionPoint
;
3638 rv
= FindInsertionPoint(&temp
, aItem
->GetStartParent(),
3639 aItem
->StartOffset(), CompareToRangeStart
,
3641 NS_ENSURE_SUCCESS(rv
, rv
);
3643 if (!temp
.InsertElementAt(insertionPoint
, RangeData(aItem
)))
3644 return NS_ERROR_OUT_OF_MEMORY
;
3646 // Merge the leftovers back in to mRanges
3647 if (!mRanges
.InsertElementsAt(startIndex
, temp
))
3648 return NS_ERROR_OUT_OF_MEMORY
;
3650 for (uint32_t i
= 0; i
< temp
.Length(); ++i
) {
3651 temp
[i
].mRange
->SetInSelection(true);
3654 *aOutIndex
= startIndex
+ insertionPoint
;
3659 Selection::RemoveItem(nsRange
* aItem
)
3662 return NS_ERROR_NULL_POINTER
;
3664 // Find the range's index & remove it. We could use FindInsertionPoint to
3665 // get O(log n) time, but that requires many expensive DOM comparisons.
3666 // For even several thousand items, this is probably faster because the
3667 // comparisons are so fast.
3670 for (i
= 0; i
< mRanges
.Length(); i
++) {
3671 if (mRanges
[i
].mRange
== aItem
) {
3677 return NS_ERROR_INVALID_ARG
;
3679 mRanges
.RemoveElementAt(idx
);
3680 aItem
->SetInSelection(false);
3685 Selection::RemoveCollapsedRanges()
3688 while (i
< mRanges
.Length()) {
3689 if (mRanges
[i
].mRange
->Collapsed()) {
3690 nsresult rv
= RemoveItem(mRanges
[i
].mRange
);
3691 NS_ENSURE_SUCCESS(rv
, rv
);
3700 Selection::Clear(nsPresContext
* aPresContext
)
3702 setAnchorFocusRange(-1);
3704 for (uint32_t i
= 0; i
< mRanges
.Length(); ++i
) {
3705 mRanges
[i
].mRange
->SetInSelection(false);
3706 selectFrames(aPresContext
, mRanges
[i
].mRange
, false);
3710 // Reset direction so for more dependable table selection range handling
3711 SetDirection(eDirNext
);
3713 // If this was an ATTENTION selection, change it back to normal now
3714 if (mFrameSelection
&&
3715 mFrameSelection
->GetDisplaySelection() ==
3716 nsISelectionController::SELECTION_ATTENTION
) {
3717 mFrameSelection
->SetDisplaySelection(nsISelectionController::SELECTION_ON
);
3724 Selection::GetType(int16_t* aType
)
3726 NS_ENSURE_ARG_POINTER(aType
);
3732 // RangeMatches*Point
3734 // Compares the range beginning or ending point, and returns true if it
3735 // exactly matches the given DOM point.
3738 RangeMatchesBeginPoint(nsRange
* aRange
, nsINode
* aNode
, int32_t aOffset
)
3740 return aRange
->GetStartParent() == aNode
&& aRange
->StartOffset() == aOffset
;
3744 RangeMatchesEndPoint(nsRange
* aRange
, nsINode
* aNode
, int32_t aOffset
)
3746 return aRange
->GetEndParent() == aNode
&& aRange
->EndOffset() == aOffset
;
3749 // Selection::EqualsRangeAtPoint
3751 // Utility method for checking equivalence of two ranges.
3754 Selection::EqualsRangeAtPoint(
3755 nsINode
* aBeginNode
, int32_t aBeginOffset
,
3756 nsINode
* aEndNode
, int32_t aEndOffset
,
3757 int32_t aRangeIndex
)
3759 if (aRangeIndex
>=0 && aRangeIndex
< (int32_t) mRanges
.Length()) {
3760 nsRange
* range
= mRanges
[aRangeIndex
].mRange
;
3761 if (RangeMatchesBeginPoint(range
, aBeginNode
, aBeginOffset
) &&
3762 RangeMatchesEndPoint(range
, aEndNode
, aEndOffset
))
3768 // Selection::GetRangesForInterval
3770 // XPCOM wrapper for the nsTArray version
3773 Selection::GetRangesForInterval(nsIDOMNode
* aBeginNode
, int32_t aBeginOffset
,
3774 nsIDOMNode
* aEndNode
, int32_t aEndOffset
,
3775 bool aAllowAdjacent
,
3776 uint32_t* aResultCount
,
3777 nsIDOMRange
*** aResults
)
3779 if (!aBeginNode
|| ! aEndNode
|| ! aResultCount
|| ! aResults
)
3780 return NS_ERROR_NULL_POINTER
;
3783 *aResults
= nullptr;
3785 nsTArray
<nsRefPtr
<nsRange
>> results
;
3787 nsCOMPtr
<nsINode
> beginNode
= do_QueryInterface(aBeginNode
);
3788 nsCOMPtr
<nsINode
> endNode
= do_QueryInterface(aEndNode
);
3789 NS_ENSURE_TRUE(beginNode
&& endNode
, NS_ERROR_NULL_POINTER
);
3790 GetRangesForInterval(*beginNode
, aBeginOffset
, *endNode
, aEndOffset
,
3791 aAllowAdjacent
, results
, result
);
3792 if (result
.Failed()) {
3793 return result
.ErrorCode();
3795 *aResultCount
= results
.Length();
3796 if (*aResultCount
== 0) {
3800 *aResults
= static_cast<nsIDOMRange
**>
3801 (nsMemory::Alloc(sizeof(nsIDOMRange
*) * *aResultCount
));
3802 NS_ENSURE_TRUE(*aResults
, NS_ERROR_OUT_OF_MEMORY
);
3804 for (uint32_t i
= 0; i
< *aResultCount
; i
++) {
3805 (*aResults
)[i
] = results
[i
].forget().take();
3812 Selection::GetRangesForInterval(nsINode
& aBeginNode
, int32_t aBeginOffset
,
3813 nsINode
& aEndNode
, int32_t aEndOffset
,
3814 bool aAllowAdjacent
,
3815 nsTArray
<nsRefPtr
<nsRange
>>& aReturn
,
3816 mozilla::ErrorResult
& aRv
)
3818 nsTArray
<nsRange
*> results
;
3819 nsresult rv
= GetRangesForIntervalArray(&aBeginNode
, aBeginOffset
,
3820 &aEndNode
, aEndOffset
,
3821 aAllowAdjacent
, &results
);
3822 if (NS_FAILED(rv
)) {
3827 aReturn
.SetLength(results
.Length());
3828 for (uint32_t i
= 0; i
< results
.Length(); ++i
) {
3829 aReturn
[i
] = results
[i
]; // AddRefs
3833 // Selection::GetRangesForIntervalArray
3835 // Fills a nsTArray with the ranges overlapping the range specified by
3836 // the given endpoints. Ranges in the selection exactly adjacent to the
3837 // input range are not returned unless aAllowAdjacent is set.
3839 // For example, if the following ranges were in the selection
3840 // (assume everything is within the same node)
3842 // Start Offset: 0 2 7 9
3843 // End Offset: 2 5 9 10
3845 // and passed aBeginOffset of 2 and aEndOffset of 9, then with
3846 // aAllowAdjacent set, all the ranges should be returned. If
3847 // aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only
3848 // should be returned
3850 // Now that overlapping ranges are disallowed, there can be a maximum of
3851 // 2 adjacent ranges
3854 Selection::GetRangesForIntervalArray(nsINode
* aBeginNode
, int32_t aBeginOffset
,
3855 nsINode
* aEndNode
, int32_t aEndOffset
,
3856 bool aAllowAdjacent
,
3857 nsTArray
<nsRange
*>* aRanges
)
3860 int32_t startIndex
, endIndex
;
3861 nsresult res
= GetIndicesForInterval(aBeginNode
, aBeginOffset
,
3862 aEndNode
, aEndOffset
, aAllowAdjacent
,
3863 &startIndex
, &endIndex
);
3864 NS_ENSURE_SUCCESS(res
, res
);
3866 if (startIndex
== -1 || endIndex
== -1)
3869 for (int32_t i
= startIndex
; i
< endIndex
; i
++) {
3870 if (!aRanges
->AppendElement(mRanges
[i
].mRange
))
3871 return NS_ERROR_OUT_OF_MEMORY
;
3877 // Selection::GetIndicesForInterval
3879 // Works on the same principle as GetRangesForIntervalArray above, however
3880 // instead this returns the indices into mRanges between which the
3881 // overlapping ranges lie.
3884 Selection::GetIndicesForInterval(nsINode
* aBeginNode
, int32_t aBeginOffset
,
3885 nsINode
* aEndNode
, int32_t aEndOffset
,
3886 bool aAllowAdjacent
,
3887 int32_t* aStartIndex
, int32_t* aEndIndex
)
3893 aStartIndex
= &startIndex
;
3895 aEndIndex
= &endIndex
;
3900 if (mRanges
.Length() == 0)
3903 bool intervalIsCollapsed
= aBeginNode
== aEndNode
&&
3904 aBeginOffset
== aEndOffset
;
3906 // Ranges that end before the given interval and begin after the given
3907 // interval can be discarded
3908 int32_t endsBeforeIndex
;
3909 if (NS_FAILED(FindInsertionPoint(&mRanges
, aEndNode
, aEndOffset
,
3910 &CompareToRangeStart
,
3911 &endsBeforeIndex
))) {
3915 if (endsBeforeIndex
== 0) {
3916 nsRange
* endRange
= mRanges
[endsBeforeIndex
].mRange
;
3918 // If the interval is strictly before the range at index 0, we can optimize
3919 // by returning now - all ranges start after the given interval
3920 if (!RangeMatchesBeginPoint(endRange
, aEndNode
, aEndOffset
))
3923 // We now know that the start point of mRanges[0].mRange equals the end of
3924 // the interval. Thus, when aAllowadjacent is true, the caller is always
3925 // interested in this range. However, when excluding adjacencies, we must
3926 // remember to include the range when both it and the given interval are
3927 // collapsed to the same point
3928 if (!aAllowAdjacent
&& !(endRange
->Collapsed() && intervalIsCollapsed
))
3931 *aEndIndex
= endsBeforeIndex
;
3933 int32_t beginsAfterIndex
;
3934 if (NS_FAILED(FindInsertionPoint(&mRanges
, aBeginNode
, aBeginOffset
,
3936 &beginsAfterIndex
))) {
3939 if (beginsAfterIndex
== (int32_t) mRanges
.Length())
3940 return NS_OK
; // optimization: all ranges are strictly before us
3942 if (aAllowAdjacent
) {
3943 // At this point, one of the following holds:
3944 // endsBeforeIndex == mRanges.Length(),
3945 // endsBeforeIndex points to a range whose start point does not equal the
3946 // given interval's start point
3947 // endsBeforeIndex points to a range whose start point equals the given
3948 // interval's start point
3949 // In the final case, there can be two such ranges, a collapsed range, and
3950 // an adjacent range (they will appear in mRanges in that order). For this
3951 // final case, we need to increment endsBeforeIndex, until one of the
3952 // first two possibilites hold
3953 while (endsBeforeIndex
< (int32_t) mRanges
.Length()) {
3954 nsRange
* endRange
= mRanges
[endsBeforeIndex
].mRange
;
3955 if (!RangeMatchesBeginPoint(endRange
, aEndNode
, aEndOffset
))
3960 // Likewise, one of the following holds:
3961 // beginsAfterIndex == 0,
3962 // beginsAfterIndex points to a range whose end point does not equal
3963 // the given interval's end point
3964 // beginsOnOrAfter points to a range whose end point equals the given
3965 // interval's end point
3966 // In the final case, there can be two such ranges, an adjacent range, and
3967 // a collapsed range (they will appear in mRanges in that order). For this
3968 // final case, we only need to take action if both those ranges exist, and
3969 // we are pointing to the collapsed range - we need to point to the
3971 nsRange
* beginRange
= mRanges
[beginsAfterIndex
].mRange
;
3972 if (beginsAfterIndex
> 0 && beginRange
->Collapsed() &&
3973 RangeMatchesEndPoint(beginRange
, aBeginNode
, aBeginOffset
)) {
3974 beginRange
= mRanges
[beginsAfterIndex
- 1].mRange
;
3975 if (RangeMatchesEndPoint(beginRange
, aBeginNode
, aBeginOffset
))
3979 // See above for the possibilities at this point. The only case where we
3980 // need to take action is when the range at beginsAfterIndex ends on
3981 // the given interval's start point, but that range isn't collapsed (a
3982 // collapsed range should be included in the returned results).
3983 nsRange
* beginRange
= mRanges
[beginsAfterIndex
].mRange
;
3984 if (RangeMatchesEndPoint(beginRange
, aBeginNode
, aBeginOffset
) &&
3985 !beginRange
->Collapsed())
3988 // Again, see above for the meaning of endsBeforeIndex at this point.
3989 // In particular, endsBeforeIndex may point to a collaped range which
3990 // represents the point at the end of the interval - this range should be
3992 if (endsBeforeIndex
< (int32_t) mRanges
.Length()) {
3993 nsRange
* endRange
= mRanges
[endsBeforeIndex
].mRange
;
3994 if (RangeMatchesBeginPoint(endRange
, aEndNode
, aEndOffset
) &&
3995 endRange
->Collapsed())
4000 NS_ASSERTION(beginsAfterIndex
<= endsBeforeIndex
,
4001 "Is mRanges not ordered?");
4002 NS_ENSURE_STATE(beginsAfterIndex
<= endsBeforeIndex
);
4004 *aStartIndex
= beginsAfterIndex
;
4005 *aEndIndex
= endsBeforeIndex
;
4010 Selection::GetPrimaryFrameForAnchorNode(nsIFrame
** aReturnFrame
)
4013 return NS_ERROR_NULL_POINTER
;
4015 int32_t frameOffset
= 0;
4017 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(GetAnchorNode());
4018 if (content
&& mFrameSelection
)
4020 *aReturnFrame
= mFrameSelection
->
4021 GetFrameForNodeOffset(content
, AnchorOffset(),
4022 mFrameSelection
->GetHint(), &frameOffset
);
4026 return NS_ERROR_FAILURE
;
4030 Selection::GetPrimaryFrameForFocusNode(nsIFrame
** aReturnFrame
,
4031 int32_t* aOffsetUsed
,
4035 return NS_ERROR_NULL_POINTER
;
4037 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(GetFocusNode());
4038 if (!content
|| !mFrameSelection
)
4039 return NS_ERROR_FAILURE
;
4041 int32_t frameOffset
= 0;
4044 aOffsetUsed
= &frameOffset
;
4046 CaretAssociationHint hint
= mFrameSelection
->GetHint();
4049 nsBidiLevel caretBidiLevel
= mFrameSelection
->GetCaretBidiLevel();
4051 return nsCaret::GetCaretFrameForNodeOffset(mFrameSelection
,
4052 content
, FocusOffset(), hint
, caretBidiLevel
, aReturnFrame
, aOffsetUsed
);
4055 *aReturnFrame
= mFrameSelection
->
4056 GetFrameForNodeOffset(content
, FocusOffset(),
4059 return NS_ERROR_FAILURE
;
4064 //select all content children of aContent
4066 Selection::SelectAllFramesForContent(nsIContentIterator
* aInnerIter
,
4067 nsIContent
* aContent
,
4070 nsresult result
= aInnerIter
->Init(aContent
);
4072 if (NS_SUCCEEDED(result
))
4074 // First select frame of content passed in
4075 frame
= aContent
->GetPrimaryFrame();
4076 if (frame
&& frame
->GetType() == nsGkAtoms::textFrame
) {
4077 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
4078 textFrame
->SetSelectedRange(0, aContent
->GetText()->GetLength(), aSelected
, mType
);
4080 // Now iterated through the child frames and set them
4081 while (!aInnerIter
->IsDone()) {
4082 nsCOMPtr
<nsIContent
> innercontent
=
4083 do_QueryInterface(aInnerIter
->GetCurrentNode());
4085 frame
= innercontent
->GetPrimaryFrame();
4087 if (frame
->GetType() == nsGkAtoms::textFrame
) {
4088 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
4089 textFrame
->SetSelectedRange(0, innercontent
->GetText()->GetLength(), aSelected
, mType
);
4091 frame
->InvalidateFrameSubtree(); // frame continuations?
4101 return NS_ERROR_FAILURE
;
4105 * The idea of this helper method is to select or deselect "top to bottom",
4106 * traversing through the frames
4109 Selection::selectFrames(nsPresContext
* aPresContext
, nsRange
* aRange
,
4112 if (!mFrameSelection
|| !aPresContext
|| !aPresContext
->GetPresShell()) {
4118 if (mFrameSelection
->GetTableCellSelection()) {
4119 nsINode
* node
= aRange
->GetCommonAncestor();
4120 nsIFrame
* frame
= node
->IsContent() ? node
->AsContent()->GetPrimaryFrame()
4121 : aPresContext
->FrameManager()->GetRootFrame();
4123 frame
->InvalidateFrameSubtree();
4128 nsCOMPtr
<nsIContentIterator
> iter
= NS_NewContentSubtreeIterator();
4131 // Loop through the content iterator for each content node; for each text
4132 // node, call SetSelected on it:
4133 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aRange
->GetStartParent());
4135 // Don't warn, bug 1055722
4136 return NS_ERROR_UNEXPECTED
;
4139 // We must call first one explicitly
4140 if (content
->IsNodeOfType(nsINode::eTEXT
)) {
4141 nsIFrame
* frame
= content
->GetPrimaryFrame();
4142 // The frame could be an SVG text frame, in which case we'll ignore it.
4143 if (frame
&& frame
->GetType() == nsGkAtoms::textFrame
) {
4144 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
4145 uint32_t startOffset
= aRange
->StartOffset();
4147 if (aRange
->GetEndParent() == content
) {
4148 endOffset
= aRange
->EndOffset();
4150 endOffset
= content
->Length();
4152 textFrame
->SetSelectedRange(startOffset
, endOffset
, aSelect
, mType
);
4157 nsCOMPtr
<nsIContentIterator
> inneriter
= NS_NewContentIterator();
4158 for (iter
->First(); !iter
->IsDone(); iter
->Next()) {
4159 content
= do_QueryInterface(iter
->GetCurrentNode());
4160 SelectAllFramesForContent(inneriter
, content
, aSelect
);
4163 // We must now do the last one if it is not the same as the first
4164 if (aRange
->GetEndParent() != aRange
->GetStartParent()) {
4166 content
= do_QueryInterface(aRange
->GetEndParent(), &res
);
4167 NS_ENSURE_SUCCESS(res
, res
);
4168 NS_ENSURE_TRUE(content
, res
);
4170 if (content
->IsNodeOfType(nsINode::eTEXT
)) {
4171 nsIFrame
* frame
= content
->GetPrimaryFrame();
4172 // The frame could be an SVG text frame, in which case we'll ignore it.
4173 if (frame
&& frame
->GetType() == nsGkAtoms::textFrame
) {
4174 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
4175 textFrame
->SetSelectedRange(0, aRange
->EndOffset(), aSelect
, mType
);
4183 // Selection::LookUpSelection
4185 // This function is called when a node wants to know where the selection is
4188 // Usually, this is called when we already know there is a selection over
4189 // the node in question, and we only need to find the boundaries of it on
4190 // that node. This is when slowCheck is false--a strict test is not needed.
4191 // Other times, the caller has no idea, and wants us to test everything,
4192 // so we are supposed to determine whether there is a selection over the
4195 // A previous version of this code used this flag to do less work when
4196 // inclusion was already known (slowCheck=false). However, our tree
4197 // structure allows us to quickly determine ranges overlapping the node,
4198 // so we just ignore the slowCheck flag and do the full test every time.
4200 // PERFORMANCE: a common case is that we are doing a fast check with exactly
4201 // one range in the selection. In this case, this function is slower than
4202 // brute force because of the overhead of checking the tree. We can optimize
4203 // this case to make it faster by doing the same thing the previous version
4204 // of this function did in the case of 1 range. This would also mean that
4205 // the aSlowCheck flag would have meaning again.
4208 Selection::LookUpSelection(nsIContent
* aContent
, int32_t aContentOffset
,
4209 int32_t aContentLength
,
4210 SelectionDetails
** aReturnDetails
,
4211 SelectionType aType
, bool aSlowCheck
)
4214 if (!aContent
|| ! aReturnDetails
)
4215 return NS_ERROR_NULL_POINTER
;
4217 // it is common to have no ranges, to optimize that
4218 if (mRanges
.Length() == 0)
4221 nsTArray
<nsRange
*> overlappingRanges
;
4222 rv
= GetRangesForIntervalArray(aContent
, aContentOffset
,
4223 aContent
, aContentOffset
+ aContentLength
,
4225 &overlappingRanges
);
4226 NS_ENSURE_SUCCESS(rv
, rv
);
4227 if (overlappingRanges
.Length() == 0)
4230 for (uint32_t i
= 0; i
< overlappingRanges
.Length(); i
++) {
4231 nsRange
* range
= overlappingRanges
[i
];
4232 nsINode
* startNode
= range
->GetStartParent();
4233 nsINode
* endNode
= range
->GetEndParent();
4234 int32_t startOffset
= range
->StartOffset();
4235 int32_t endOffset
= range
->EndOffset();
4237 int32_t start
= -1, end
= -1;
4238 if (startNode
== aContent
&& endNode
== aContent
) {
4239 if (startOffset
< (aContentOffset
+ aContentLength
) &&
4240 endOffset
> aContentOffset
) {
4241 // this range is totally inside the requested content range
4242 start
= std::max(0, startOffset
- aContentOffset
);
4243 end
= std::min(aContentLength
, endOffset
- aContentOffset
);
4245 // otherwise, range is inside the requested node, but does not intersect
4246 // the requested content range, so ignore it
4247 } else if (startNode
== aContent
) {
4248 if (startOffset
< (aContentOffset
+ aContentLength
)) {
4249 // the beginning of the range is inside the requested node, but the
4250 // end is outside, select everything from there to the end
4251 start
= std::max(0, startOffset
- aContentOffset
);
4252 end
= aContentLength
;
4254 } else if (endNode
== aContent
) {
4255 if (endOffset
> aContentOffset
) {
4256 // the end of the range is inside the requested node, but the beginning
4257 // is outside, select everything from the beginning to there
4259 end
= std::min(aContentLength
, endOffset
- aContentOffset
);
4262 // this range does not begin or end in the requested node, but since
4263 // GetRangesForInterval returned this range, we know it overlaps.
4264 // Therefore, this node is enclosed in the range, and we select all
4267 end
= aContentLength
;
4270 continue; // the ranges do not overlap the input range
4272 SelectionDetails
* details
= new SelectionDetails
;
4274 details
->mNext
= *aReturnDetails
;
4275 details
->mStart
= start
;
4276 details
->mEnd
= end
;
4277 details
->mType
= aType
;
4278 RangeData
*rd
= FindRangeData(range
);
4280 details
->mTextRangeStyle
= rd
->mTextRangeStyle
;
4282 *aReturnDetails
= details
;
4288 Selection::Repaint(nsPresContext
* aPresContext
)
4290 int32_t arrCount
= (int32_t)mRanges
.Length();
4297 for (i
= 0; i
< arrCount
; i
++)
4299 nsresult rv
= selectFrames(aPresContext
, mRanges
[i
].mRange
, true);
4301 if (NS_FAILED(rv
)) {
4310 Selection::GetCanCacheFrameOffset(bool* aCanCacheFrameOffset
)
4312 NS_ENSURE_ARG_POINTER(aCanCacheFrameOffset
);
4314 if (mCachedOffsetForFrame
)
4315 *aCanCacheFrameOffset
= mCachedOffsetForFrame
->mCanCacheFrameOffset
;
4317 *aCanCacheFrameOffset
= false;
4323 Selection::SetCanCacheFrameOffset(bool aCanCacheFrameOffset
)
4325 if (!mCachedOffsetForFrame
) {
4326 mCachedOffsetForFrame
= new CachedOffsetForFrame
;
4329 mCachedOffsetForFrame
->mCanCacheFrameOffset
= aCanCacheFrameOffset
;
4331 // clean up cached frame when turn off cache
4333 if (!aCanCacheFrameOffset
) {
4334 mCachedOffsetForFrame
->mLastCaretFrame
= nullptr;
4341 Selection::GetCachedFrameOffset(nsIFrame
* aFrame
, int32_t inOffset
,
4344 if (!mCachedOffsetForFrame
) {
4345 mCachedOffsetForFrame
= new CachedOffsetForFrame
;
4348 nsresult rv
= NS_OK
;
4349 if (mCachedOffsetForFrame
->mCanCacheFrameOffset
&&
4350 mCachedOffsetForFrame
->mLastCaretFrame
&&
4351 (aFrame
== mCachedOffsetForFrame
->mLastCaretFrame
) &&
4352 (inOffset
== mCachedOffsetForFrame
->mLastContentOffset
))
4354 // get cached frame offset
4355 aPoint
= mCachedOffsetForFrame
->mCachedFrameOffset
;
4359 // Recalculate frame offset and cache it. Don't cache a frame offset if
4360 // GetPointFromOffset fails, though.
4361 rv
= aFrame
->GetPointFromOffset(inOffset
, &aPoint
);
4362 if (NS_SUCCEEDED(rv
) && mCachedOffsetForFrame
->mCanCacheFrameOffset
) {
4363 mCachedOffsetForFrame
->mCachedFrameOffset
= aPoint
;
4364 mCachedOffsetForFrame
->mLastCaretFrame
= aFrame
;
4365 mCachedOffsetForFrame
->mLastContentOffset
= inOffset
;
4373 Selection::GetAncestorLimiter(nsIContent
** aContent
)
4375 if (mFrameSelection
) {
4376 nsCOMPtr
<nsIContent
> c
= mFrameSelection
->GetAncestorLimiter();
4383 Selection::SetAncestorLimiter(nsIContent
* aContent
)
4385 if (mFrameSelection
)
4386 mFrameSelection
->SetAncestorLimiter(aContent
);
4391 Selection::FindRangeData(nsIDOMRange
* aRange
)
4393 NS_ENSURE_TRUE(aRange
, nullptr);
4394 for (uint32_t i
= 0; i
< mRanges
.Length(); i
++) {
4395 if (mRanges
[i
].mRange
== aRange
)
4402 Selection::SetTextRangeStyle(nsIDOMRange
* aRange
,
4403 const TextRangeStyle
& aTextRangeStyle
)
4405 NS_ENSURE_ARG_POINTER(aRange
);
4406 RangeData
*rd
= FindRangeData(aRange
);
4408 rd
->mTextRangeStyle
= aTextRangeStyle
;
4414 Selection::StartAutoScrollTimer(nsIFrame
* aFrame
, nsPoint
& aPoint
,
4417 NS_PRECONDITION(aFrame
, "Need a frame");
4420 if (!mFrameSelection
)
4421 return NS_OK
;//nothing to do
4423 if (!mAutoScrollTimer
)
4425 mAutoScrollTimer
= new nsAutoScrollTimer();
4427 result
= mAutoScrollTimer
->Init(mFrameSelection
, this);
4429 if (NS_FAILED(result
))
4433 result
= mAutoScrollTimer
->SetDelay(aDelay
);
4435 if (NS_FAILED(result
))
4438 return DoAutoScroll(aFrame
, aPoint
);
4442 Selection::StopAutoScrollTimer()
4444 if (mAutoScrollTimer
)
4445 return mAutoScrollTimer
->Stop();
4451 Selection::DoAutoScroll(nsIFrame
* aFrame
, nsPoint
& aPoint
)
4453 NS_PRECONDITION(aFrame
, "Need a frame");
4455 if (mAutoScrollTimer
)
4456 (void)mAutoScrollTimer
->Stop();
4458 nsPresContext
* presContext
= aFrame
->PresContext();
4459 nsRootPresContext
* rootPC
= presContext
->GetRootPresContext();
4462 nsIFrame
* rootmostFrame
= rootPC
->PresShell()->FrameManager()->GetRootFrame();
4463 // Get the point relative to the root most frame because the scroll we are
4464 // about to do will change the coordinates of aFrame.
4465 nsPoint globalPoint
= aPoint
+ aFrame
->GetOffsetToCrossDoc(rootmostFrame
);
4467 bool didScroll
= presContext
->PresShell()->ScrollFrameRectIntoView(
4469 nsRect(aPoint
, nsSize(0, 0)),
4470 nsIPresShell::ScrollAxis(),
4471 nsIPresShell::ScrollAxis(),
4475 // Start the AutoScroll timer if necessary.
4478 if (didScroll
&& mAutoScrollTimer
)
4480 nsPoint presContextPoint
= globalPoint
-
4481 presContext
->PresShell()->FrameManager()->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame
);
4482 mAutoScrollTimer
->Start(presContext
, presContextPoint
);
4489 /** RemoveAllRanges zeroes the selection
4492 Selection::RemoveAllRanges()
4495 RemoveAllRanges(result
);
4496 return result
.ErrorCode();
4500 Selection::RemoveAllRanges(ErrorResult
& aRv
)
4502 if (!mFrameSelection
)
4503 return; // nothing to do
4504 nsRefPtr
<nsPresContext
> presContext
= GetPresContext();
4505 nsresult result
= Clear(presContext
);
4506 if (NS_FAILED(result
)) {
4511 // Turn off signal for table selection
4512 mFrameSelection
->ClearTableCellSelection();
4514 result
= mFrameSelection
->NotifySelectionListeners(GetType());
4515 // Also need to notify the frames!
4516 // PresShell::CharacterDataChanged should do that on DocumentChanged
4517 if (NS_FAILED(result
)) {
4522 /** AddRange adds the specified range to the selection
4523 * @param aRange is the range to be added
4526 Selection::AddRange(nsIDOMRange
* aDOMRange
)
4529 return NS_ERROR_NULL_POINTER
;
4531 nsRange
* range
= static_cast<nsRange
*>(aDOMRange
);
4533 AddRange(*range
, result
);
4534 return result
.ErrorCode();
4538 Selection::AddRange(nsRange
& aRange
, ErrorResult
& aRv
)
4540 // This inserts a table cell range in proper document order
4541 // and returns NS_OK if range doesn't contain just one table cell
4544 nsresult result
= addTableCellRange(&aRange
, &didAddRange
, &rangeIndex
);
4545 if (NS_FAILED(result
)) {
4551 result
= AddItem(&aRange
, &rangeIndex
);
4552 if (NS_FAILED(result
)) {
4558 if (rangeIndex
< 0) {
4562 setAnchorFocusRange(rangeIndex
);
4564 // Make sure the caret appears on the next line, if at a newline
4565 if (mType
== nsISelectionController::SELECTION_NORMAL
)
4566 SetInterlinePosition(true);
4568 nsRefPtr
<nsPresContext
> presContext
= GetPresContext();
4569 selectFrames(presContext
, &aRange
, true);
4571 if (!mFrameSelection
)
4572 return;//nothing to do
4574 result
= mFrameSelection
->NotifySelectionListeners(GetType());
4575 if (NS_FAILED(result
)) {
4580 // Selection::RemoveRange
4582 // Removes the given range from the selection. The tricky part is updating
4583 // the flags on the frames that indicate whether they have a selection or
4584 // not. There could be several selection ranges on the frame, and clearing
4585 // the bit would cause the selection to not be drawn, even when there is
4586 // another range on the frame (bug 346185).
4588 // We therefore find any ranges that intersect the same nodes as the range
4589 // being removed, and cause them to set the selected bits back on their
4590 // selected frames after we've cleared the bit from ours.
4593 Selection::RemoveRange(nsIDOMRange
* aDOMRange
)
4596 return NS_ERROR_INVALID_ARG
;
4598 nsRange
* range
= static_cast<nsRange
*>(aDOMRange
);
4600 RemoveRange(*range
, result
);
4601 return result
.ErrorCode();
4605 Selection::RemoveRange(nsRange
& aRange
, ErrorResult
& aRv
)
4607 nsresult rv
= RemoveItem(&aRange
);
4608 if (NS_FAILED(rv
)) {
4613 nsINode
* beginNode
= aRange
.GetStartParent();
4614 nsINode
* endNode
= aRange
.GetEndParent();
4616 if (!beginNode
|| !endNode
) {
4617 // Detached range; nothing else to do here.
4621 // find out the length of the end node, so we can select all of it
4622 int32_t beginOffset
, endOffset
;
4623 if (endNode
->IsNodeOfType(nsINode::eTEXT
)) {
4624 // Get the length of the text. We can't just use the offset because
4625 // another range could be touching this text node but not intersect our
4628 endOffset
= static_cast<nsIContent
*>(endNode
)->TextLength();
4630 // For non-text nodes, the given offsets should be sufficient.
4631 beginOffset
= aRange
.StartOffset();
4632 endOffset
= aRange
.EndOffset();
4635 // clear the selected bit from the removed range's frames
4636 nsRefPtr
<nsPresContext
> presContext
= GetPresContext();
4637 selectFrames(presContext
, &aRange
, false);
4639 // add back the selected bit for each range touching our nodes
4640 nsTArray
<nsRange
*> affectedRanges
;
4641 rv
= GetRangesForIntervalArray(beginNode
, beginOffset
,
4643 true, &affectedRanges
);
4644 if (NS_FAILED(rv
)) {
4648 for (uint32_t i
= 0; i
< affectedRanges
.Length(); i
++) {
4649 selectFrames(presContext
, affectedRanges
[i
], true);
4652 int32_t cnt
= mRanges
.Length();
4653 if (&aRange
== mAnchorFocusRange
) {
4654 // Reset anchor to LAST range or clear it if there are no ranges.
4655 setAnchorFocusRange(cnt
- 1);
4657 // When the selection is user-created it makes sense to scroll the range
4658 // into view. The spell-check selection, however, is created and destroyed
4659 // in the background. We don't want to scroll in this case or the view
4660 // might appear to be moving randomly (bug 337871).
4661 if (mType
!= nsISelectionController::SELECTION_SPELLCHECK
&& cnt
> 0)
4662 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION
);
4665 if (!mFrameSelection
)
4666 return;//nothing to do
4667 rv
= mFrameSelection
->NotifySelectionListeners(GetType());
4668 if (NS_FAILED(rv
)) {
4676 * Collapse sets the whole selection to be one point.
4679 Selection::Collapse(nsIDOMNode
* aParentNode
, int32_t aOffset
)
4681 nsCOMPtr
<nsINode
> parentNode
= do_QueryInterface(aParentNode
);
4682 return Collapse(parentNode
, aOffset
);
4686 Selection::CollapseNative(nsINode
* aParentNode
, int32_t aOffset
)
4688 return Collapse(aParentNode
, aOffset
);
4692 Selection::Collapse(nsINode
* aParentNode
, int32_t aOffset
)
4695 return NS_ERROR_INVALID_ARG
;
4698 Collapse(*aParentNode
, static_cast<uint32_t>(aOffset
), result
);
4699 return result
.ErrorCode();
4703 Selection::Collapse(nsINode
& aParentNode
, uint32_t aOffset
, ErrorResult
& aRv
)
4705 if (!mFrameSelection
) {
4706 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
); // Can't do selection
4710 nsCOMPtr
<nsINode
> kungfuDeathGrip
= &aParentNode
;
4712 mFrameSelection
->InvalidateDesiredPos();
4713 if (!IsValidSelectionPoint(mFrameSelection
, &aParentNode
)) {
4714 aRv
.Throw(NS_ERROR_FAILURE
);
4719 nsRefPtr
<nsPresContext
> presContext
= GetPresContext();
4720 if (!presContext
|| presContext
->Document() != aParentNode
.OwnerDoc()) {
4721 aRv
.Throw(NS_ERROR_FAILURE
);
4725 // Delete all of the current ranges
4728 // Turn off signal for table selection
4729 mFrameSelection
->ClearTableCellSelection();
4731 nsRefPtr
<nsRange
> range
= new nsRange(&aParentNode
);
4732 result
= range
->SetEnd(&aParentNode
, aOffset
);
4733 if (NS_FAILED(result
)) {
4737 result
= range
->SetStart(&aParentNode
, aOffset
);
4738 if (NS_FAILED(result
)) {
4743 #ifdef DEBUG_SELECTION
4744 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(&aParentNode
);
4745 nsCOMPtr
<nsIDocument
> doc
= do_QueryInterface(&aParentNode
);
4746 printf ("Sel. Collapse to %p %s %d\n", &aParentNode
,
4747 content
? nsAtomCString(content
->Tag()).get()
4748 : (doc
? "DOCUMENT" : "???"),
4752 int32_t rangeIndex
= -1;
4753 result
= AddItem(range
, &rangeIndex
);
4754 if (NS_FAILED(result
)) {
4758 setAnchorFocusRange(0);
4759 selectFrames(presContext
, range
, true);
4760 result
= mFrameSelection
->NotifySelectionListeners(GetType());
4761 if (NS_FAILED(result
)) {
4767 * Sets the whole selection to be one point
4768 * at the start of the current selection
4771 Selection::CollapseToStart()
4774 CollapseToStart(result
);
4775 return result
.ErrorCode();
4779 Selection::CollapseToStart(ErrorResult
& aRv
)
4782 nsresult rv
= GetRangeCount(&cnt
);
4783 if (NS_FAILED(rv
) || cnt
<= 0) {
4784 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
4788 // Get the first range
4789 nsRange
* firstRange
= mRanges
[0].mRange
;
4791 aRv
.Throw(NS_ERROR_FAILURE
);
4795 if (mFrameSelection
) {
4796 int16_t reason
= mFrameSelection
->PopReason() | nsISelectionListener::COLLAPSETOSTART_REASON
;
4797 mFrameSelection
->PostReason(reason
);
4799 nsINode
* parent
= firstRange
->GetStartParent();
4801 aRv
.Throw(NS_ERROR_FAILURE
);
4804 Collapse(*parent
, firstRange
->StartOffset(), aRv
);
4808 * Sets the whole selection to be one point
4809 * at the end of the current selection
4812 Selection::CollapseToEnd()
4815 CollapseToEnd(result
);
4816 return result
.ErrorCode();
4820 Selection::CollapseToEnd(ErrorResult
& aRv
)
4823 nsresult rv
= GetRangeCount(&cnt
);
4824 if (NS_FAILED(rv
) || cnt
<= 0) {
4825 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
4829 // Get the last range
4830 nsRange
* lastRange
= mRanges
[cnt
- 1].mRange
;
4832 aRv
.Throw(NS_ERROR_FAILURE
);
4836 if (mFrameSelection
) {
4837 int16_t reason
= mFrameSelection
->PopReason() | nsISelectionListener::COLLAPSETOEND_REASON
;
4838 mFrameSelection
->PostReason(reason
);
4840 nsINode
* parent
= lastRange
->GetEndParent();
4842 aRv
.Throw(NS_ERROR_FAILURE
);
4845 Collapse(*parent
, lastRange
->EndOffset(), aRv
);
4849 * IsCollapsed -- is the whole selection just one point, or unset?
4852 Selection::IsCollapsed()
4854 uint32_t cnt
= mRanges
.Length();
4863 return mRanges
[0].mRange
->Collapsed();
4868 Selection::Collapsed()
4870 return IsCollapsed();
4874 Selection::GetIsCollapsed(bool* aIsCollapsed
)
4876 NS_ENSURE_TRUE(aIsCollapsed
, NS_ERROR_NULL_POINTER
);
4878 *aIsCollapsed
= IsCollapsed();
4883 Selection::GetRangeCount(int32_t* aRangeCount
)
4885 *aRangeCount
= (int32_t)RangeCount();
4891 Selection::GetRangeAt(int32_t aIndex
, nsIDOMRange
** aReturn
)
4894 *aReturn
= GetRangeAt(aIndex
, result
);
4895 NS_IF_ADDREF(*aReturn
);
4896 return result
.ErrorCode();
4900 Selection::GetRangeAt(uint32_t aIndex
, ErrorResult
& aRv
)
4902 nsRange
* range
= GetRangeAt(aIndex
);
4904 aRv
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
4912 Selection::GetRangeAt(int32_t aIndex
)
4914 RangeData
empty(nullptr);
4915 return mRanges
.SafeElementAt(aIndex
, empty
).mRange
;
4922 Selection::SetAnchorFocusToRange(nsRange
* aRange
)
4924 NS_ENSURE_STATE(mAnchorFocusRange
);
4926 nsresult res
= RemoveItem(mAnchorFocusRange
);
4930 int32_t aOutIndex
= -1;
4931 res
= AddItem(aRange
, &aOutIndex
);
4934 setAnchorFocusRange(aOutIndex
);
4940 Selection::ReplaceAnchorFocusRange(nsRange
* aRange
)
4942 NS_ENSURE_TRUE_VOID(mAnchorFocusRange
);
4943 nsRefPtr
<nsPresContext
> presContext
= GetPresContext();
4945 selectFrames(presContext
, mAnchorFocusRange
, false);
4946 SetAnchorFocusToRange(aRange
);
4947 selectFrames(presContext
, mAnchorFocusRange
, true);
4952 Notes which might come in handy for extend:
4954 We can tell the direction of the selection by asking for the anchors selection
4955 if the begin is less than the end then we know the selection is to the "right".
4956 else it is a backwards selection.
4961 if (a <= 1 && 1 <=2) a,1,2 or (a1,2)
4962 if (a < 2 && 1 > 2) a,2,1
4963 if (1 < a && a <2) 1,a,2
4964 if (a > 2 && 2 >1) 1,2,a
4965 if (2 < a && a <1) 2,a,1
4966 if (a > 1 && 1 >2) 2,1,a
4968 a 1 2 select from 1 to 2
4969 a 2 1 deselect from 2 to 1
4970 1 a 2 deselect from 1 to a select from a to 2
4971 1 2 a deselect from 1 to 2
4972 2 1 a = continue selection from 2 to 1
4977 * Extend extends the selection away from the anchor.
4978 * We don't need to know the direction, because we always change the focus.
4981 Selection::Extend(nsIDOMNode
* aParentNode
, int32_t aOffset
)
4983 nsCOMPtr
<nsINode
> parentNode
= do_QueryInterface(aParentNode
);
4984 return Extend(parentNode
, aOffset
);
4988 Selection::ExtendNative(nsINode
* aParentNode
, int32_t aOffset
)
4990 return Extend(aParentNode
, aOffset
);
4994 Selection::Extend(nsINode
* aParentNode
, int32_t aOffset
)
4997 return NS_ERROR_INVALID_ARG
;
5000 Extend(*aParentNode
, static_cast<uint32_t>(aOffset
), result
);
5001 return result
.ErrorCode();
5005 Selection::Extend(nsINode
& aParentNode
, uint32_t aOffset
, ErrorResult
& aRv
)
5007 // First, find the range containing the old focus point:
5008 if (!mAnchorFocusRange
) {
5009 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5013 if (!mFrameSelection
) {
5014 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
); // Can't do selection
5019 if (!IsValidSelectionPoint(mFrameSelection
, &aParentNode
)) {
5020 aRv
.Throw(NS_ERROR_FAILURE
);
5024 nsRefPtr
<nsPresContext
> presContext
= GetPresContext();
5025 if (!presContext
|| presContext
->Document() != aParentNode
.OwnerDoc()) {
5026 aRv
.Throw(NS_ERROR_FAILURE
);
5030 nsDirection dir
= GetDirection();
5032 // If aParentNode is inside a range in a multi-range selection we need
5033 // to remove the ranges that follows in the selection direction and
5034 // make that range the mAnchorFocusRange.
5035 if (mRanges
.Length() > 1) {
5036 for (size_t i
= 0; i
< mRanges
.Length(); ++i
) {
5037 nsRange
* range
= mRanges
[i
].mRange
;
5038 bool disconnected1
= false;
5039 bool disconnected2
= false;
5040 const bool isBeforeStart
=
5041 nsContentUtils::ComparePoints(range
->GetStartParent(),
5042 range
->StartOffset(),
5043 &aParentNode
, aOffset
,
5044 &disconnected1
) > 0;
5045 const bool isAfterEnd
=
5046 nsContentUtils::ComparePoints(range
->GetEndParent(),
5048 &aParentNode
, aOffset
,
5049 &disconnected2
) < 0;
5050 if (!isBeforeStart
&& !isAfterEnd
&& !disconnected1
&& !disconnected2
) {
5051 // aParentNode/aOffset is inside 'range'.
5052 mAnchorFocusRange
= range
;
5053 if (dir
== eDirNext
) {
5054 for (size_t j
= i
+ 1; j
< mRanges
.Length(); ++j
) {
5055 nsRange
* r
= mRanges
[j
].mRange
;
5056 r
->SetInSelection(false);
5057 selectFrames(presContext
, r
, false);
5059 mRanges
.TruncateLength(i
+ 1);
5061 for (size_t j
= 0; j
< i
; ++j
) {
5062 nsRange
* r
= mRanges
[j
].mRange
;
5063 r
->SetInSelection(false);
5064 selectFrames(presContext
, r
, false);
5066 mRanges
.RemoveElementsAt(0, i
);
5073 nsINode
* anchorNode
= GetAnchorNode();
5074 nsINode
* focusNode
= GetFocusNode();
5075 uint32_t anchorOffset
= AnchorOffset();
5076 uint32_t focusOffset
= FocusOffset();
5078 nsRefPtr
<nsRange
> range
= mAnchorFocusRange
->CloneRange();
5080 nsINode
* startNode
= range
->GetStartParent();
5081 nsINode
* endNode
= range
->GetEndParent();
5082 int32_t startOffset
= range
->StartOffset();
5083 int32_t endOffset
= range
->EndOffset();
5085 //compare anchor to old cursor.
5087 // We pass |disconnected| to the following ComparePoints calls in order
5088 // to avoid assertions. ComparePoints returns 1 in the disconnected case
5089 // and we can end up in various cases below, but it is assumed that in
5090 // any of the cases we end up, the nsRange implementation will collapse
5091 // the range to the new point because we can not make a valid range with
5092 // a disconnected point. This means that whatever range is currently
5093 // selected will be cleared.
5094 bool disconnected
= false;
5095 bool shouldClearRange
= false;
5096 int32_t result1
= nsContentUtils::ComparePoints(anchorNode
, anchorOffset
,
5097 focusNode
, focusOffset
,
5099 //compare old cursor to new cursor
5100 shouldClearRange
|= disconnected
;
5101 int32_t result2
= nsContentUtils::ComparePoints(focusNode
, focusOffset
,
5102 &aParentNode
, aOffset
,
5104 //compare anchor to new cursor
5105 shouldClearRange
|= disconnected
;
5106 int32_t result3
= nsContentUtils::ComparePoints(anchorNode
, anchorOffset
,
5107 &aParentNode
, aOffset
,
5110 // If the points are disconnected, the range will be collapsed below,
5111 // resulting in a range that selects nothing.
5112 if (shouldClearRange
) {
5113 // Repaint the current range with the selection removed.
5114 selectFrames(presContext
, range
, false);
5117 nsRefPtr
<nsRange
> difRange
= new nsRange(&aParentNode
);
5118 if ((result1
== 0 && result3
< 0) || (result1
<= 0 && result2
< 0)){//a1,2 a,1,2
5119 //select from 1 to 2 unless they are collapsed
5120 range
->SetEnd(aParentNode
, aOffset
, aRv
);
5125 res
= difRange
->SetEnd(range
->GetEndParent(), range
->EndOffset());
5126 nsresult tmp
= difRange
->SetStart(focusNode
, focusOffset
);
5127 if (NS_FAILED(tmp
)) {
5130 if (NS_FAILED(res
)) {
5134 selectFrames(presContext
, difRange
, true);
5135 res
= SetAnchorFocusToRange(range
);
5136 if (NS_FAILED(res
)) {
5141 else if (result1
== 0 && result3
> 0){//2, a1
5142 //select from 2 to 1a
5144 range
->SetStart(aParentNode
, aOffset
, aRv
);
5148 selectFrames(presContext
, range
, true);
5149 res
= SetAnchorFocusToRange(range
);
5150 if (NS_FAILED(res
)) {
5155 else if (result3
<= 0 && result2
>= 0) {//a,2,1 or a2,1 or a,21 or a21
5156 //deselect from 2 to 1
5157 res
= difRange
->SetEnd(focusNode
, focusOffset
);
5158 difRange
->SetStart(aParentNode
, aOffset
, aRv
);
5162 if (NS_FAILED(res
)) {
5167 range
->SetEnd(aParentNode
, aOffset
, aRv
);
5171 res
= SetAnchorFocusToRange(range
);
5172 if (NS_FAILED(res
)) {
5176 selectFrames(presContext
, difRange
, false); // deselect now
5177 difRange
->SetEnd(range
->GetEndParent(), range
->EndOffset());
5178 selectFrames(presContext
, difRange
, true); // must reselect last node maybe more
5180 else if (result1
>= 0 && result3
<= 0) {//1,a,2 or 1a,2 or 1,a2 or 1a2
5181 if (GetDirection() == eDirPrevious
){
5182 res
= range
->SetStart(endNode
, endOffset
);
5183 if (NS_FAILED(res
)) {
5189 range
->SetEnd(aParentNode
, aOffset
, aRv
);
5193 if (focusNode
!= anchorNode
|| focusOffset
!= anchorOffset
) {//if collapsed diff dont do anything
5194 res
= difRange
->SetStart(focusNode
, focusOffset
);
5195 nsresult tmp
= difRange
->SetEnd(anchorNode
, anchorOffset
);
5196 if (NS_FAILED(tmp
)) {
5199 if (NS_FAILED(res
)) {
5203 res
= SetAnchorFocusToRange(range
);
5204 if (NS_FAILED(res
)) {
5208 //deselect from 1 to a
5209 selectFrames(presContext
, difRange
, false);
5213 res
= SetAnchorFocusToRange(range
);
5214 if (NS_FAILED(res
)) {
5219 //select from a to 2
5220 selectFrames(presContext
, range
, true);
5222 else if (result2
<= 0 && result3
>= 0) {//1,2,a or 12,a or 1,2a or 12a
5223 //deselect from 1 to 2
5224 difRange
->SetEnd(aParentNode
, aOffset
, aRv
);
5225 res
= difRange
->SetStart(focusNode
, focusOffset
);
5229 if (NS_FAILED(res
)) {
5234 range
->SetStart(aParentNode
, aOffset
, aRv
);
5239 res
= SetAnchorFocusToRange(range
);
5240 if (NS_FAILED(res
)) {
5244 selectFrames(presContext
, difRange
, false);
5245 difRange
->SetStart(range
->GetStartParent(), range
->StartOffset());
5246 selectFrames(presContext
, difRange
, true);//must reselect last node
5248 else if (result3
>= 0 && result1
<= 0) {//2,a,1 or 2a,1 or 2,a1 or 2a1
5249 if (GetDirection() == eDirNext
){
5250 range
->SetEnd(startNode
, startOffset
);
5253 range
->SetStart(aParentNode
, aOffset
, aRv
);
5257 //deselect from a to 1
5258 if (focusNode
!= anchorNode
|| focusOffset
!= anchorOffset
) {//if collapsed diff dont do anything
5259 res
= difRange
->SetStart(anchorNode
, anchorOffset
);
5260 nsresult tmp
= difRange
->SetEnd(focusNode
, focusOffset
);
5261 if (NS_FAILED(tmp
)) {
5264 tmp
= SetAnchorFocusToRange(range
);
5265 if (NS_FAILED(tmp
)) {
5268 if (NS_FAILED(res
)) {
5272 selectFrames(presContext
, difRange
, false);
5276 res
= SetAnchorFocusToRange(range
);
5277 if (NS_FAILED(res
)) {
5282 //select from 2 to a
5283 selectFrames(presContext
, range
, true);
5285 else if (result2
>= 0 && result1
>= 0) {//2,1,a or 21,a or 2,1a or 21a
5286 //select from 2 to 1
5287 range
->SetStart(aParentNode
, aOffset
, aRv
);
5292 res
= difRange
->SetEnd(focusNode
, focusOffset
);
5293 nsresult tmp
= difRange
->SetStart(range
->GetStartParent(), range
->StartOffset());
5294 if (NS_FAILED(tmp
)) {
5297 if (NS_FAILED(res
)) {
5302 selectFrames(presContext
, difRange
, true);
5303 res
= SetAnchorFocusToRange(range
);
5304 if (NS_FAILED(res
)) {
5310 if (mRanges
.Length() > 1) {
5311 for (size_t i
= 0; i
< mRanges
.Length(); ++i
) {
5312 nsRange
* range
= mRanges
[i
].mRange
;
5313 MOZ_ASSERT(range
->IsInSelection());
5314 selectFrames(presContext
, range
, range
->IsInSelection());
5318 DEBUG_OUT_RANGE(range
);
5319 #ifdef DEBUG_SELECTION
5320 if (eDirNext
== mDirection
)
5321 printf(" direction = 1 LEFT TO RIGHT\n");
5323 printf(" direction = 0 RIGHT TO LEFT\n");
5326 #ifdef DEBUG_SELECTION
5327 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(&aParentNode
);
5329 printf ("Sel. Extend to %p %s %d\n", content
.get(),
5330 nsAtomCString(content
->Tag()).get(), aOffset
);
5332 res
= mFrameSelection
->NotifySelectionListeners(GetType());
5333 if (NS_FAILED(res
)) {
5339 Selection::SelectAllChildren(nsIDOMNode
* aParentNode
)
5342 nsCOMPtr
<nsINode
> node
= do_QueryInterface(aParentNode
);
5343 NS_ENSURE_TRUE(node
, NS_ERROR_INVALID_ARG
);
5344 SelectAllChildren(*node
, result
);
5345 return result
.ErrorCode();
5349 Selection::SelectAllChildren(nsINode
& aNode
, ErrorResult
& aRv
)
5351 if (mFrameSelection
)
5353 mFrameSelection
->PostReason(nsISelectionListener::SELECTALL_REASON
);
5355 Collapse(aNode
, 0, aRv
);
5360 if (mFrameSelection
)
5362 mFrameSelection
->PostReason(nsISelectionListener::SELECTALL_REASON
);
5364 Extend(aNode
, aNode
.GetChildCount(), aRv
);
5368 Selection::ContainsNode(nsIDOMNode
* aNode
, bool aAllowPartial
, bool* aYes
)
5371 return NS_ERROR_NULL_POINTER
;
5375 nsCOMPtr
<nsINode
> node
= do_QueryInterface(aNode
);
5377 return NS_ERROR_NULL_POINTER
;
5380 *aYes
= ContainsNode(*node
, aAllowPartial
, result
);
5381 return result
.ErrorCode();
5385 Selection::ContainsNode(nsINode
& aNode
, bool aAllowPartial
, ErrorResult
& aRv
)
5388 if (mRanges
.Length() == 0) {
5392 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp
5393 uint32_t nodeLength
;
5394 bool isData
= aNode
.IsNodeOfType(nsINode::eDATA_NODE
);
5396 nodeLength
= static_cast<nsIContent
&>(aNode
).TextLength();
5398 nodeLength
= aNode
.GetChildCount();
5401 nsTArray
<nsRange
*> overlappingRanges
;
5402 rv
= GetRangesForIntervalArray(&aNode
, 0, &aNode
, nodeLength
,
5403 false, &overlappingRanges
);
5404 if (NS_FAILED(rv
)) {
5408 if (overlappingRanges
.Length() == 0)
5409 return false; // no ranges overlap
5411 // if the caller said partial intersections are OK, we're done
5412 if (aAllowPartial
) {
5416 // text nodes always count as inside
5421 // The caller wants to know if the node is entirely within the given range,
5422 // so we have to check all intersecting ranges.
5423 for (uint32_t i
= 0; i
< overlappingRanges
.Length(); i
++) {
5424 bool nodeStartsBeforeRange
, nodeEndsAfterRange
;
5425 if (NS_SUCCEEDED(nsRange::CompareNodeToRange(&aNode
, overlappingRanges
[i
],
5426 &nodeStartsBeforeRange
,
5427 &nodeEndsAfterRange
))) {
5428 if (!nodeStartsBeforeRange
&& !nodeEndsAfterRange
) {
5438 Selection::GetPresContext() const
5440 nsIPresShell
*shell
= GetPresShell();
5445 return shell
->GetPresContext();
5449 Selection::GetPresShell() const
5451 if (!mFrameSelection
)
5452 return nullptr;//nothing to do
5454 return mFrameSelection
->GetShell();
5458 Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion
, nsRect
* aRect
)
5460 if (!mFrameSelection
)
5461 return nullptr; // nothing to do
5463 NS_ENSURE_TRUE(aRect
, nullptr);
5465 aRect
->SetRect(0, 0, 0, 0);
5468 case nsISelectionController::SELECTION_ANCHOR_REGION
:
5469 case nsISelectionController::SELECTION_FOCUS_REGION
:
5470 return GetSelectionEndPointGeometry(aRegion
, aRect
);
5472 case nsISelectionController::SELECTION_WHOLE_SELECTION
:
5478 NS_ASSERTION(aRegion
== nsISelectionController::SELECTION_WHOLE_SELECTION
,
5479 "should only be SELECTION_WHOLE_SELECTION here");
5482 nsIFrame
* anchorFrame
= GetSelectionEndPointGeometry(
5483 nsISelectionController::SELECTION_ANCHOR_REGION
, &anchorRect
);
5488 nsIFrame
* focusFrame
= GetSelectionEndPointGeometry(
5489 nsISelectionController::SELECTION_FOCUS_REGION
, &focusRect
);
5493 NS_ASSERTION(anchorFrame
->PresContext() == focusFrame
->PresContext(),
5494 "points of selection in different documents?");
5495 // make focusRect relative to anchorFrame
5496 focusRect
+= focusFrame
->GetOffsetTo(anchorFrame
);
5498 aRect
->UnionRectEdges(anchorRect
, focusRect
);
5503 Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion
, nsRect
* aRect
)
5505 if (!mFrameSelection
)
5506 return nullptr; // nothing to do
5508 NS_ENSURE_TRUE(aRect
, nullptr);
5510 aRect
->SetRect(0, 0, 0, 0);
5512 nsINode
*node
= nullptr;
5513 uint32_t nodeOffset
= 0;
5514 nsIFrame
*frame
= nullptr;
5517 case nsISelectionController::SELECTION_ANCHOR_REGION
:
5518 node
= GetAnchorNode();
5519 nodeOffset
= AnchorOffset();
5521 case nsISelectionController::SELECTION_FOCUS_REGION
:
5522 node
= GetFocusNode();
5523 nodeOffset
= FocusOffset();
5532 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(node
);
5533 NS_ENSURE_TRUE(content
.get(), nullptr);
5534 int32_t frameOffset
= 0;
5535 frame
= mFrameSelection
->GetFrameForNodeOffset(content
, nodeOffset
,
5536 mFrameSelection
->GetHint(),
5541 // Figure out what node type we have, then get the
5542 // appropriate rect for it's nodeOffset.
5543 bool isText
= node
->IsNodeOfType(nsINode::eTEXT
);
5547 nsIFrame
* childFrame
= nullptr;
5550 frame
->GetChildFrameContainingOffset(nodeOffset
,
5551 mFrameSelection
->GetHint(),
5552 &frameOffset
, &childFrame
);
5560 // Get the x coordinate of the offset into the text frame.
5561 rv
= GetCachedFrameOffset(frame
, nodeOffset
, pt
);
5566 // Return the rect relative to the frame, with zero width.
5569 } else if (mFrameSelection
->GetHint() == CARET_ASSOCIATE_BEFORE
) {
5570 // It's the frame's right edge we're interested in.
5571 aRect
->x
= frame
->GetRect().width
;
5573 aRect
->height
= frame
->GetRect().height
;
5579 Selection::ScrollSelectionIntoViewEvent::Run()
5582 return NS_OK
; // event revoked
5584 int32_t flags
= Selection::SCROLL_DO_FLUSH
|
5585 Selection::SCROLL_SYNCHRONOUS
;
5587 mSelection
->mScrollEvent
.Forget();
5588 mSelection
->ScrollIntoView(mRegion
, mVerticalScroll
,
5589 mHorizontalScroll
, mFlags
| flags
);
5594 Selection::PostScrollSelectionIntoViewEvent(
5595 SelectionRegion aRegion
,
5597 nsIPresShell::ScrollAxis aVertical
,
5598 nsIPresShell::ScrollAxis aHorizontal
)
5600 // If we've already posted an event, revoke it and place a new one at the
5601 // end of the queue to make sure that any new pending reflow events are
5602 // processed before we scroll. This will insure that we scroll to the
5603 // correct place on screen.
5604 mScrollEvent
.Revoke();
5606 nsRefPtr
<ScrollSelectionIntoViewEvent
> ev
=
5607 new ScrollSelectionIntoViewEvent(this, aRegion
, aVertical
, aHorizontal
,
5609 nsresult rv
= NS_DispatchToCurrentThread(ev
);
5610 NS_ENSURE_SUCCESS(rv
, rv
);
5617 Selection::ScrollIntoView(SelectionRegion aRegion
, bool aIsSynchronous
,
5618 int16_t aVPercent
, int16_t aHPercent
)
5621 ScrollIntoView(aRegion
, aIsSynchronous
, aVPercent
, aHPercent
, result
);
5622 if (result
.Failed()) {
5623 return result
.ErrorCode();
5629 Selection::ScrollIntoView(int16_t aRegion
, bool aIsSynchronous
,
5630 int16_t aVPercent
, int16_t aHPercent
,
5633 nsresult rv
= ScrollIntoViewInternal(aRegion
, aIsSynchronous
,
5634 nsIPresShell::ScrollAxis(aVPercent
),
5635 nsIPresShell::ScrollAxis(aHPercent
));
5636 if (NS_FAILED(rv
)) {
5642 Selection::ScrollIntoViewInternal(SelectionRegion aRegion
, bool aIsSynchronous
,
5643 nsIPresShell::ScrollAxis aVertical
,
5644 nsIPresShell::ScrollAxis aHorizontal
)
5646 return ScrollIntoView(aRegion
, aVertical
, aHorizontal
,
5647 aIsSynchronous
? Selection::SCROLL_SYNCHRONOUS
: 0);
5651 Selection::ScrollIntoView(SelectionRegion aRegion
,
5652 nsIPresShell::ScrollAxis aVertical
,
5653 nsIPresShell::ScrollAxis aHorizontal
,
5656 if (!mFrameSelection
)
5657 return NS_OK
;//nothing to do
5659 nsCOMPtr
<nsIPresShell
> presShell
= mFrameSelection
->GetShell();
5663 if (mFrameSelection
->GetBatching())
5666 if (!(aFlags
& Selection::SCROLL_SYNCHRONOUS
))
5667 return PostScrollSelectionIntoViewEvent(aRegion
, aFlags
,
5668 aVertical
, aHorizontal
);
5670 // Now that text frame character offsets are always valid (though not
5671 // necessarily correct), the worst that will happen if we don't flush here
5672 // is that some callers might scroll to the wrong place. Those should
5673 // either manually flush if they're in a safe position for it or use the
5674 // async version of this method.
5675 if (aFlags
& Selection::SCROLL_DO_FLUSH
) {
5676 presShell
->FlushPendingNotifications(Flush_Layout
);
5678 // Reget the presshell, since it might have been Destroy'ed.
5679 presShell
= mFrameSelection
? mFrameSelection
->GetShell() : nullptr;
5685 // Scroll the selection region into view.
5689 nsIFrame
* frame
= GetSelectionAnchorGeometry(aRegion
, &rect
);
5691 return NS_ERROR_FAILURE
;
5693 // Scroll vertically to get the caret into view, but only if the container
5694 // is perceived to be scrollable in that direction (i.e. there is a visible
5695 // vertical scrollbar or the scroll range is at least one device pixel)
5696 aVertical
.mOnlyIfPerceivedScrollableDirection
= true;
5699 if (aFlags
& Selection::SCROLL_FIRST_ANCESTOR_ONLY
) {
5700 flags
|= nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY
;
5702 if (aFlags
& Selection::SCROLL_OVERFLOW_HIDDEN
) {
5703 flags
|= nsIPresShell::SCROLL_OVERFLOW_HIDDEN
;
5706 presShell
->ScrollFrameRectIntoView(frame
, rect
, aVertical
, aHorizontal
,
5712 Selection::AddSelectionListener(nsISelectionListener
* aNewListener
)
5715 return NS_ERROR_NULL_POINTER
;
5717 AddSelectionListener(aNewListener
, result
);
5718 if (result
.Failed()) {
5719 return result
.ErrorCode();
5725 Selection::AddSelectionListener(nsISelectionListener
* aNewListener
,
5728 bool result
= mSelectionListeners
.AppendObject(aNewListener
); // AddRefs
5730 aRv
.Throw(NS_ERROR_FAILURE
);
5735 Selection::RemoveSelectionListener(nsISelectionListener
* aListenerToRemove
)
5737 if (!aListenerToRemove
)
5738 return NS_ERROR_NULL_POINTER
;
5740 RemoveSelectionListener(aListenerToRemove
, result
);
5741 if (result
.Failed()) {
5742 return result
.ErrorCode();
5748 Selection::RemoveSelectionListener(nsISelectionListener
* aListenerToRemove
,
5751 bool result
= mSelectionListeners
.RemoveObject(aListenerToRemove
); // Releases
5753 aRv
.Throw(NS_ERROR_FAILURE
);
5758 Selection::NotifySelectionListeners()
5760 if (!mFrameSelection
)
5761 return NS_OK
;//nothing to do
5763 if (mFrameSelection
->GetBatching()) {
5764 mFrameSelection
->SetDirty();
5767 nsCOMArray
<nsISelectionListener
> selectionListeners(mSelectionListeners
);
5768 int32_t cnt
= selectionListeners
.Count();
5769 if (cnt
!= mSelectionListeners
.Count()) {
5770 return NS_ERROR_OUT_OF_MEMORY
; // nsCOMArray is fallible
5773 nsCOMPtr
<nsIDOMDocument
> domdoc
;
5774 nsIPresShell
* ps
= GetPresShell();
5776 domdoc
= do_QueryInterface(ps
->GetDocument());
5779 short reason
= mFrameSelection
->PopReason();
5780 for (int32_t i
= 0; i
< cnt
; i
++) {
5781 selectionListeners
[i
]->NotifySelectionChanged(domdoc
, this, reason
);
5787 Selection::StartBatchChanges()
5789 if (mFrameSelection
)
5790 mFrameSelection
->StartBatchChanges();
5798 Selection::EndBatchChanges()
5800 if (mFrameSelection
)
5801 mFrameSelection
->EndBatchChanges();
5809 Selection::DeleteFromDocument()
5812 DeleteFromDocument(result
);
5813 return result
.ErrorCode();
5817 Selection::DeleteFromDocument(ErrorResult
& aRv
)
5819 if (!mFrameSelection
)
5820 return;//nothing to do
5821 nsresult rv
= mFrameSelection
->DeleteFromDocument();
5822 if (NS_FAILED(rv
)) {
5828 Selection::Modify(const nsAString
& aAlter
, const nsAString
& aDirection
,
5829 const nsAString
& aGranularity
)
5832 Modify(aAlter
, aDirection
, aGranularity
, result
);
5833 return result
.ErrorCode();
5837 Selection::Modify(const nsAString
& aAlter
, const nsAString
& aDirection
,
5838 const nsAString
& aGranularity
, ErrorResult
& aRv
)
5840 // Silently exit if there's no selection or no focus node.
5841 if (!mFrameSelection
|| !GetAnchorFocusRange() || !GetFocusNode()) {
5845 if (!aAlter
.LowerCaseEqualsLiteral("move") &&
5846 !aAlter
.LowerCaseEqualsLiteral("extend")) {
5847 aRv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
5851 if (!aDirection
.LowerCaseEqualsLiteral("forward") &&
5852 !aDirection
.LowerCaseEqualsLiteral("backward") &&
5853 !aDirection
.LowerCaseEqualsLiteral("left") &&
5854 !aDirection
.LowerCaseEqualsLiteral("right")) {
5855 aRv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
5859 // Line moves are always visual.
5860 bool visual
= aDirection
.LowerCaseEqualsLiteral("left") ||
5861 aDirection
.LowerCaseEqualsLiteral("right") ||
5862 aGranularity
.LowerCaseEqualsLiteral("line");
5864 bool forward
= aDirection
.LowerCaseEqualsLiteral("forward") ||
5865 aDirection
.LowerCaseEqualsLiteral("right");
5867 bool extend
= aAlter
.LowerCaseEqualsLiteral("extend");
5869 nsSelectionAmount amount
;
5870 if (aGranularity
.LowerCaseEqualsLiteral("character")) {
5871 amount
= eSelectCluster
;
5872 } else if (aGranularity
.LowerCaseEqualsLiteral("word")) {
5873 amount
= eSelectWordNoSpace
;
5874 } else if (aGranularity
.LowerCaseEqualsLiteral("line")) {
5875 amount
= eSelectLine
;
5876 } else if (aGranularity
.LowerCaseEqualsLiteral("lineboundary")) {
5877 amount
= forward
? eSelectEndLine
: eSelectBeginLine
;
5878 } else if (aGranularity
.LowerCaseEqualsLiteral("sentence") ||
5879 aGranularity
.LowerCaseEqualsLiteral("sentenceboundary") ||
5880 aGranularity
.LowerCaseEqualsLiteral("paragraph") ||
5881 aGranularity
.LowerCaseEqualsLiteral("paragraphboundary") ||
5882 aGranularity
.LowerCaseEqualsLiteral("documentboundary")) {
5883 aRv
.Throw(NS_ERROR_NOT_IMPLEMENTED
);
5886 aRv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
5890 // If the anchor doesn't equal the focus and we try to move without first
5891 // collapsing the selection, MoveCaret will collapse the selection and quit.
5892 // To avoid this, we need to collapse the selection first.
5893 nsresult rv
= NS_OK
;
5895 nsINode
* focusNode
= GetFocusNode();
5896 // We should have checked earlier that there was a focus node.
5898 aRv
.Throw(NS_ERROR_UNEXPECTED
);
5901 uint32_t focusOffset
= FocusOffset();
5902 Collapse(focusNode
, focusOffset
);
5905 // If the paragraph direction of the focused frame is right-to-left,
5906 // we may have to swap the direction of movement.
5909 rv
= GetPrimaryFrameForFocusNode(&frame
, &offset
, visual
);
5910 if (NS_SUCCEEDED(rv
) && frame
) {
5911 nsBidiDirection paraDir
= nsBidiPresUtils::ParagraphDirection(frame
);
5913 if (paraDir
== NSBIDI_RTL
&& visual
) {
5914 if (amount
== eSelectBeginLine
) {
5915 amount
= eSelectEndLine
;
5917 } else if (amount
== eSelectEndLine
) {
5918 amount
= eSelectBeginLine
;
5924 // MoveCaret will return an error if it can't move in the specified
5925 // direction, but we just ignore this error unless it's a line move, in which
5926 // case we call nsISelectionController::CompleteMove to move the cursor to
5927 // the beginning/end of the line.
5928 rv
= mFrameSelection
->MoveCaret(forward
? eDirNext
: eDirPrevious
,
5930 visual
? nsFrameSelection::eVisual
5931 : nsFrameSelection::eLogical
);
5933 if (aGranularity
.LowerCaseEqualsLiteral("line") && NS_FAILED(rv
)) {
5934 nsCOMPtr
<nsISelectionController
> shell
=
5935 do_QueryInterface(mFrameSelection
->GetShell());
5938 shell
->CompleteMove(forward
, extend
);
5942 /** SelectionLanguageChange modifies the cursor Bidi level after a change in keyboard direction
5943 * @param aLangRTL is true if the new language is right-to-left or false if the new language is left-to-right
5946 Selection::SelectionLanguageChange(bool aLangRTL
)
5948 if (!mFrameSelection
)
5949 return NS_ERROR_NOT_INITIALIZED
; // Can't do selection
5951 // if the direction of the language hasn't changed, nothing to do
5952 nsBidiLevel kbdBidiLevel
= aLangRTL
? NSBIDI_RTL
: NSBIDI_LTR
;
5953 if (kbdBidiLevel
== mFrameSelection
->mKbdBidiLevel
) {
5957 mFrameSelection
->mKbdBidiLevel
= kbdBidiLevel
;
5960 nsIFrame
*focusFrame
= 0;
5962 result
= GetPrimaryFrameForFocusNode(&focusFrame
, nullptr, false);
5963 if (NS_FAILED(result
)) {
5967 return NS_ERROR_FAILURE
;
5970 int32_t frameStart
, frameEnd
;
5971 focusFrame
->GetOffsets(frameStart
, frameEnd
);
5972 nsRefPtr
<nsPresContext
> context
= GetPresContext();
5973 nsBidiLevel levelBefore
, levelAfter
;
5975 return NS_ERROR_FAILURE
;
5978 nsBidiLevel level
= NS_GET_EMBEDDING_LEVEL(focusFrame
);
5979 int32_t focusOffset
= static_cast<int32_t>(FocusOffset());
5980 if ((focusOffset
!= frameStart
) && (focusOffset
!= frameEnd
))
5981 // the cursor is not at a frame boundary, so the level of both the characters (logically) before and after the cursor
5982 // is equal to the frame level
5983 levelBefore
= levelAfter
= level
;
5985 // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find the level of the characters
5986 // before and after the cursor
5987 nsCOMPtr
<nsIContent
> focusContent
= do_QueryInterface(GetFocusNode());
5988 nsPrevNextBidiLevels levels
= mFrameSelection
->
5989 GetPrevNextBidiLevels(focusContent
, focusOffset
, false);
5991 levelBefore
= levels
.mLevelBefore
;
5992 levelAfter
= levels
.mLevelAfter
;
5995 if (IS_SAME_DIRECTION(levelBefore
, levelAfter
)) {
5996 // if cursor is between two characters with the same orientation, changing the keyboard language
5997 // must toggle the cursor level between the level of the character with the lowest level
5998 // (if the new language corresponds to the orientation of that character) and this level plus 1
5999 // (if the new language corresponds to the opposite orientation)
6000 if ((level
!= levelBefore
) && (level
!= levelAfter
))
6001 level
= std::min(levelBefore
, levelAfter
);
6002 if (IS_SAME_DIRECTION(level
, kbdBidiLevel
))
6003 mFrameSelection
->SetCaretBidiLevel(level
);
6005 mFrameSelection
->SetCaretBidiLevel(level
+ 1);
6008 // if cursor is between characters with opposite orientations, changing the keyboard language must change
6009 // the cursor level to that of the adjacent character with the orientation corresponding to the new language.
6010 if (IS_SAME_DIRECTION(levelBefore
, kbdBidiLevel
))
6011 mFrameSelection
->SetCaretBidiLevel(levelBefore
);
6013 mFrameSelection
->SetCaretBidiLevel(levelAfter
);
6016 // The caret might have moved, so invalidate the desired position
6017 // for future usages of up-arrow or down-arrow
6018 mFrameSelection
->InvalidateDesiredPos();
6023 NS_IMETHODIMP_(nsDirection
)
6024 Selection::GetSelectionDirection() {
6028 NS_IMETHODIMP_(void)
6029 Selection::SetSelectionDirection(nsDirection aDirection
) {
6030 mDirection
= aDirection
;
6034 Selection::WrapObject(JSContext
* aCx
)
6036 return mozilla::dom::SelectionBinding::Wrap(aCx
, this);
6039 // nsAutoCopyListener
6041 nsAutoCopyListener
* nsAutoCopyListener::sInstance
= nullptr;
6043 NS_IMPL_ISUPPORTS(nsAutoCopyListener
, nsISelectionListener
)
6047 * On every selection change, we copy to the clipboard anew, creating a
6048 * HTML buffer, a transferable, an nsISupportsString and
6049 * a huge mess every time. This is basically what nsPresShell::DoCopy does
6050 * to move the selection into the clipboard for Edit->Copy.
6052 * What we should do, to make our end of the deal faster:
6053 * Create a singleton transferable with our own magic converter. When selection
6054 * changes (use a quick cache to detect ``real'' changes), we put the new
6055 * nsISelection in the transferable. Our magic converter will take care of
6056 * transferable->whatever-other-format when the time comes to actually
6057 * hand over the clipboard contents.
6060 * - which X clipboard should we populate?
6061 * - should we use a different one than Edit->Copy, so that inadvertant
6062 * selections (or simple clicks, which currently cause a selection
6063 * notification, regardless of if they're in the document which currently has
6064 * selection!) don't lose the contents of the ``application''? Or should we
6065 * just put some intelligence in the ``is this a real selection?'' code to
6066 * protect our selection against clicks in other documents that don't create
6068 * - maybe we should just never clear the X clipboard? That would make this
6069 * problem just go away, which is very tempting.
6073 nsAutoCopyListener::NotifySelectionChanged(nsIDOMDocument
*aDoc
,
6074 nsISelection
*aSel
, int16_t aReason
)
6076 if (!(aReason
& nsISelectionListener::MOUSEUP_REASON
||
6077 aReason
& nsISelectionListener::SELECTALL_REASON
||
6078 aReason
& nsISelectionListener::KEYPRESS_REASON
))
6079 return NS_OK
; //dont care if we are still dragging
6082 if (!aDoc
|| !aSel
||
6083 NS_FAILED(aSel
->GetIsCollapsed(&collapsed
)) || collapsed
) {
6084 #ifdef DEBUG_CLIPBOARD
6085 fprintf(stderr
, "CLIPBOARD: no selection/collapsed selection\n");
6087 /* clear X clipboard? */
6091 nsCOMPtr
<nsIDocument
> doc
= do_QueryInterface(aDoc
);
6092 NS_ENSURE_TRUE(doc
, NS_ERROR_FAILURE
);
6094 // call the copy code
6095 return nsCopySupport::HTMLCopy(aSel
, doc
, nsIClipboard::kSelectionClipboard
);