Bumping manifests a=b2g-bump
[gecko.git] / layout / generic / nsSelection.cpp
blobb8b0629b13136e08cc3a373b5f9435f2490ad192
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/. */
7 /*
8 * Implementation of selection: nsISelection,nsISelectionPrivate and nsFrameSelection
9 */
11 #include "mozilla/dom/Selection.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/EventStates.h"
16 #include "nsCOMPtr.h"
17 #include "nsString.h"
18 #include "nsFrameSelection.h"
19 #include "nsISelectionListener.h"
20 #include "nsContentCID.h"
21 #include "nsIContent.h"
22 #include "nsIDOMNode.h"
23 #include "nsRange.h"
24 #include "nsCOMArray.h"
25 #include "nsITableCellLayout.h"
26 #include "nsTArray.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"
34 #include <algorithm>
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"
53 #include "nsCaret.h"
54 #include "TouchCaret.h"
55 #include "SelectionCarets.h"
57 #include "mozilla/MouseEvents.h"
58 #include "mozilla/TextEvents.h"
60 #include "nsITimer.h"
61 #include "nsFrameManager.h"
62 // notifications
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"
74 #include "nsError.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);
88 // returns the parent
89 static nsINode* ParentOffset(nsINode *aNode, int32_t *aChildOffset);
90 static nsINode* GetCellParent(nsINode *aDomNode);
92 #ifdef PRINT_RANGE
93 static void printRange(nsRange *aDomRange);
94 #define DEBUG_OUT_RANGE(x) printRange(x)
95 #else
96 #define DEBUG_OUT_RANGE(x)
97 #endif // PRINT_RANGE
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,
110 nsPoint aDesiredPos,
111 bool aJumpLines,
112 bool aScrollViewStop,
113 bool aIsKeyboardSelect,
114 bool aVisual,
115 EWordMovementType aWordMovementType)
116 : mAmount(aAmount)
117 , mDirection(aDirection)
118 , mStartOffset(aStartOffset)
119 , mDesiredPos(aDesiredPos)
120 , mWordMovementType(aWordMovementType)
121 , mJumpLines(aJumpLines)
122 , mScrollViewStop(aScrollViewStop)
123 , mIsKeyboardSelect(aIsKeyboardSelect)
124 , mVisual(aVisual)
125 , mResultContent()
126 , mResultFrame(nullptr)
127 , mContentOffset(0)
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
148 public:
150 NS_DECL_ISUPPORTS
152 nsAutoScrollTimer()
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)
160 mPoint = 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();
168 if (!mTimer)
170 nsresult result;
171 mTimer = do_CreateInstance("@mozilla.org/timer;1", &result);
173 if (NS_FAILED(result))
174 return result;
177 return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
180 nsresult Stop()
182 if (mTimer)
184 mTimer->Cancel();
185 mTimer = 0;
188 mContent = nullptr;
189 return NS_OK;
192 nsresult Init(nsFrameSelection* aFrameSelection, Selection* aSelection)
194 mFrameSelection = aFrameSelection;
195 mSelection = aSelection;
196 return NS_OK;
199 nsresult SetDelay(uint32_t aDelay)
201 mDelay = aDelay;
202 return NS_OK;
205 NS_IMETHOD Notify(nsITimer *timer) MOZ_OVERRIDE
207 if (mSelection && mPresContext)
209 nsWeakFrame frame =
210 mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nullptr;
211 if (!frame)
212 return NS_OK;
213 mContent = nullptr;
215 nsPoint pt = mPoint -
216 frame->GetOffsetTo(mPresContext->PresShell()->FrameManager()->GetRootFrame());
217 mFrameSelection->HandleDrag(frame, pt);
218 if (!frame.IsAlive())
219 return NS_OK;
221 NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?");
222 mSelection->DoAutoScroll(frame, pt);
224 return NS_OK;
227 protected:
228 virtual ~nsAutoScrollTimer()
230 if (mTimer) {
231 mTimer->Cancel();
235 private:
236 nsFrameSelection *mFrameSelection;
237 Selection* mSelection;
238 nsPresContext *mPresContext;
239 // relative to mPresContext's root frame
240 nsPoint mPoint;
241 nsCOMPtr<nsITimer> mTimer;
242 nsCOMPtr<nsIContent> mContent;
243 uint32_t mDelay;
246 NS_IMPL_ISUPPORTS(nsAutoScrollTimer, nsITimerCallback)
248 nsresult NS_NewDomSelection(nsISelection **aDomSelection)
250 Selection* rlist = new Selection;
251 *aDomSelection = (nsISelection *)rlist;
252 NS_ADDREF(rlist);
253 return NS_OK;
256 static int8_t
257 GetIndexFromSelectionType(SelectionType aType)
259 switch (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;
270 default:
271 return -1; break;
273 /* NOTREACHED */
274 return 0;
277 static SelectionType
278 GetSelectionTypeFromIndex(int8_t aIndex)
280 switch (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;
291 default:
292 return nsISelectionController::SELECTION_NORMAL; break;
294 /* NOTREACHED */
295 return 0;
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
309 or composer)
311 bool
312 IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode)
314 if (!aFrameSel || !aNode)
315 return false;
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()
332 int32_t i;
333 for (i = 0;i<nsISelectionController::NUM_SELECTIONTYPES;i++){
334 mDomSelections[i] = new Selection(this);
335 mDomSelections[i]->SetType(GetSelectionTypeFromIndex(i));
337 mBatching = 0;
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();
356 if (autoCopy) {
357 int8_t index =
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)
383 int32_t i;
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;
406 int32_t i;
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
426 nsresult
427 nsFrameSelection::FetchDesiredPos(nsPoint &aDesiredPos)
429 if (!mShell) {
430 NS_ERROR("fetch desired position failed");
431 return NS_ERROR_FAILURE;
433 if (mDesiredPosSet) {
434 aDesiredPos = mDesiredPos;
435 return NS_OK;
438 nsRefPtr<nsCaret> caret = mShell->GetCaret();
439 if (!caret) {
440 return NS_ERROR_NULL_POINTER;
443 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
444 caret->SetSelection(mDomSelections[index]);
446 nsRect coord;
447 nsIFrame* caretFrame = caret->GetGeometry(&coord);
448 if (!caretFrame) {
449 return NS_ERROR_FAILURE;
451 nsPoint viewOffset(0, 0);
452 nsView* view = nullptr;
453 caretFrame->GetOffsetFromView(viewOffset, &view);
454 if (view) {
455 coord += viewOffset;
457 aDesiredPos = coord.TopLeft();
458 return NS_OK;
461 void
462 nsFrameSelection::InvalidateDesiredPos() // do not listen to mDesiredPos;
463 // you must get another.
465 mDesiredPosSet = false;
468 void
469 nsFrameSelection::SetDesiredPos(nsPoint aPos)
471 mDesiredPos = aPos;
472 mDesiredPosSet = true;
475 nsresult
476 nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(nsIFrame *aFrame,
477 nsPoint& aPoint,
478 nsIFrame **aRetFrame,
479 nsPoint& aRetPoint)
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;
497 *aRetFrame = aFrame;
498 aRetPoint = aPoint;
501 // Get the frame and content for the selection's anchor point!
504 nsresult result;
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))
515 return result;
517 if (!anchorNode)
518 return NS_OK;
520 result = mDomSelections[index]->GetAnchorOffset(&anchorOffset);
522 if (NS_FAILED(result))
523 return result;
525 nsCOMPtr<nsIContent> anchorContent = do_QueryInterface(anchorNode);
527 if (!anchorContent)
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();
544 if (content)
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)
556 return NS_OK;
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);
580 return NS_OK;
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
585 // root.
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
592 // frame.
595 *aRetFrame = anchorRoot->GetPrimaryFrame();
597 if (!*aRetFrame)
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);
607 return NS_OK;
610 void
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();
622 return;
625 nsBidiLevel
626 nsFrameSelection::GetCaretBidiLevel() const
628 return mCaretBidiLevel;
631 void
632 nsFrameSelection::UndefineCaretBidiLevel()
634 mCaretBidiLevel |= BIDI_LEVEL_UNDEFINED;
637 #ifdef PRINT_RANGE
638 void printRange(nsRange *aDomRange)
640 if (!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 */
657 static
658 nsIAtom *GetTag(nsINode *aNode)
660 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
661 if (!content)
663 NS_NOTREACHED("bad node passed to GetTag()");
664 return nullptr;
667 return content->Tag();
670 // Returns the parent
671 nsINode*
672 ParentOffset(nsINode *aNode, int32_t *aChildOffset)
674 if (!aNode || !aChildOffset)
675 return nullptr;
677 nsIContent* parent = aNode->GetParent();
678 if (parent)
680 *aChildOffset = parent->IndexOf(aNode);
682 return parent;
685 return nullptr;
688 static nsINode*
689 GetCellParent(nsINode *aDomNode)
691 if (!aDomNode)
692 return nullptr;
693 nsINode* current = aDomNode;
694 // Start with current node and look for a table cell
695 while (current)
697 nsIAtom* tag = GetTag(current);
698 if (tag == nsGkAtoms::td || tag == nsGkAtoms::th)
699 return current;
700 current = current->GetParent();
702 return nullptr;
705 void
706 nsFrameSelection::Init(nsIPresShell *aShell, nsIContent *aLimiter)
708 mShell = aShell;
709 mDragState = false;
710 mDesiredPosSet = false;
711 mLimiter = aLimiter;
712 mCaretMovementStyle =
713 Preferences::GetInt("bidi.edit.caret_movement_style", 2);
714 // Set touch caret as selection listener
715 nsRefPtr<TouchCaret> touchCaret = mShell->GetTouchCaret();
716 if (touchCaret) {
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);
733 nsresult
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
746 // positioning.
747 mShell->FlushPendingNotifications(Flush_Layout);
749 if (!mShell) {
750 return NS_OK;
753 nsPresContext *context = mShell->GetPresContext();
754 if (!context)
755 return NS_ERROR_FAILURE;
757 bool isCollapsed;
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];
762 if (!sel)
763 return NS_ERROR_NULL_POINTER;
765 int32_t scrollFlags = 0;
766 nsINode* focusNode = sel->GetFocusNode();
767 if (focusNode &&
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)) {
779 return result;
781 if (aAmount == eSelectLine) {
782 result = FetchDesiredPos(desiredPos);
783 if (NS_FAILED(result)) {
784 return result;
786 SetDesiredPos(desiredPos);
789 int32_t caretStyle = Preferences::GetInt("layout.selection.caret_style", 0);
790 if (caretStyle == 0
791 #ifdef XP_WIN
792 && aAmount != eSelectLine
793 #endif
795 // Put caret at the selection edge in the |aDirection| direction.
796 caretStyle = 2;
799 if (!isCollapsed && !aContinueSelection && caretStyle == 2 &&
800 aAmount <= eSelectLine) {
801 switch (aDirection) {
802 case eDirPrevious:
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);
814 return NS_OK;
817 case eDirNext:
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);
829 return NS_OK;
834 nsIFrame *frame;
835 int32_t offsetused = 0;
836 result = sel->GetPrimaryFrameForFocusNode(&frame, &offsetused,
837 visualMovement);
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
850 switch (aAmount){
851 case eSelectCharacter:
852 case eSelectCluster:
853 case eSelectWord:
854 case eSelectWordNoSpace:
855 InvalidateDesiredPos();
856 pos.mAmount = aAmount;
857 pos.mDirection = (visualMovement && paraDir == NSBIDI_RTL)
858 ? nsDirection(1 - aDirection) : aDirection;
859 break;
860 case eSelectLine:
861 pos.mAmount = aAmount;
862 pos.mDirection = aDirection;
863 break;
864 case eSelectBeginLine:
865 case eSelectEndLine:
866 InvalidateDesiredPos();
867 pos.mAmount = aAmount;
868 pos.mDirection = (visualMovement && paraDir == NSBIDI_RTL)
869 ? nsDirection(1 - aDirection) : aDirection;
870 break;
871 default:
872 return NS_ERROR_FAILURE;
874 PostReason(nsISelectionListener::KEYPRESS_REASON);
875 if (NS_SUCCEEDED(result = frame->PeekOffset(&pos)) && pos.mResultContent)
877 nsIFrame *theFrame;
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;
891 else
892 tHint = CARET_ASSOCIATE_AFTER;
893 } else {
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().
896 tHint = pos.mAttach;
897 theFrame = GetFrameForNodeOffset(pos.mResultContent, pos.mContentOffset,
898 tHint, &currentOffset);
899 if (!theFrame)
900 return NS_ERROR_FAILURE;
902 theFrame->GetOffsets(frameStart, frameEnd);
905 if (context->BidiEnabled())
907 switch (aAmount) {
908 case eSelectBeginLine:
909 case eSelectEndLine:
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));
917 break;
919 default:
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));
927 else {
928 BidiLevelFromMove(mShell, pos.mResultContent, pos.mContentOffset,
929 aAmount, tHint);
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.
943 if (!isBRFrame) {
944 mHint = CARET_ASSOCIATE_BEFORE; // We're now at the end of the frame to the left.
946 result = NS_OK;
948 if (NS_SUCCEEDED(result))
950 result = mDomSelections[index]->
951 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
952 nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
953 scrollFlags);
956 return result;
959 //END nsFrameSelection methods
962 //BEGIN nsFrameSelection methods
964 NS_IMETHODIMP
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;
972 if (!shell) {
973 aReturn.Truncate();
974 return NS_OK;
976 shell->FlushPendingNotifications(Flush_Style);
978 return ToStringWithFormat("text/plain",
979 nsIDocumentEncoder::SkipInvisibleContent,
980 0, aReturn);
983 void
984 Selection::Stringify(nsAString& aResult)
986 // Eat the error code
987 ToString(aResult);
990 NS_IMETHODIMP
991 Selection::ToStringWithFormat(const char* aFormatType, uint32_t aFlags,
992 int32_t aWrapCol, nsAString& aReturn)
994 ErrorResult result;
995 NS_ConvertUTF8toUTF16 format(aFormatType);
996 ToStringWithFormat(format, aFlags, aWrapCol, aReturn, result);
997 if (result.Failed()) {
998 return result.ErrorCode();
1000 return NS_OK;
1003 void
1004 Selection::ToStringWithFormat(const nsAString& aFormatType, uint32_t aFlags,
1005 int32_t aWrapCol, nsAString& aReturn,
1006 ErrorResult& aRv)
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)) {
1014 aRv.Throw(rv);
1015 return;
1018 nsIPresShell* shell = GetPresShell();
1019 if (!shell) {
1020 aRv.Throw(NS_ERROR_FAILURE);
1021 return;
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)) {
1035 aRv.Throw(rv);
1036 return;
1039 encoder->SetSelection(this);
1040 if (aWrapCol != 0)
1041 encoder->SetWrapColumn(aWrapCol);
1043 rv = encoder->EncodeToString(aReturn);
1044 if (NS_FAILED(rv)) {
1045 aRv.Throw(rv);
1049 NS_IMETHODIMP
1050 Selection::SetInterlinePosition(bool aHintRight)
1052 ErrorResult result;
1053 SetInterlinePosition(aHintRight, result);
1054 if (result.Failed()) {
1055 return result.ErrorCode();
1057 return NS_OK;
1060 void
1061 Selection::SetInterlinePosition(bool aHintRight, ErrorResult& aRv)
1063 if (!mFrameSelection) {
1064 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
1065 return;
1067 mFrameSelection->SetHint(aHintRight ? CARET_ASSOCIATE_AFTER : CARET_ASSOCIATE_BEFORE);
1070 NS_IMETHODIMP
1071 Selection::GetInterlinePosition(bool* aHintRight)
1073 ErrorResult result;
1074 *aHintRight = GetInterlinePosition(result);
1075 if (result.Failed()) {
1076 return result.ErrorCode();
1078 return NS_OK;
1081 bool
1082 Selection::GetInterlinePosition(ErrorResult& aRv)
1084 if (!mFrameSelection) {
1085 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
1086 return false;
1088 return mFrameSelection->GetHint() == CARET_ASSOCIATE_AFTER;
1091 Nullable<int16_t>
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);
1103 void
1104 Selection::SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel, mozilla::ErrorResult& aRv)
1106 if (!mFrameSelection) {
1107 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
1108 return;
1110 if (aCaretBidiLevel.IsNull()) {
1111 mFrameSelection->UndefineCaretBidiLevel();
1112 } else {
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, &currentOffset);
1142 if (!currentFrame)
1143 return levels;
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;
1153 else {
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));
1158 return levels;
1161 nsIFrame *newFrame;
1162 int32_t offset;
1163 bool jumpedLine;
1164 nsresult rv = currentFrame->GetFrameFromDirection(direction, false,
1165 aJumpLines, true,
1166 &newFrame, &offset, &jumpedLine);
1167 if (NS_FAILED(rv))
1168 newFrame = nullptr;
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.
1176 if (!aJumpLines) {
1177 if (currentFrame->GetType() == nsGkAtoms::brFrame) {
1178 currentFrame = nullptr;
1179 currentLevel = baseLevel;
1181 if (newFrame && newFrame->GetType() == nsGkAtoms::brFrame) {
1182 newFrame = nullptr;
1183 newLevel = baseLevel;
1187 if (direction == eDirNext)
1188 levels.SetData(currentFrame, newFrame, currentLevel, newLevel);
1189 else
1190 levels.SetData(newFrame, currentFrame, newLevel, currentLevel);
1192 return levels;
1195 nsresult
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;
1206 nsresult result;
1207 nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID,&result));
1208 if (NS_FAILED(result))
1209 return result;
1211 result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
1212 mShell->GetPresContext(), aFrameIn,
1213 eLeaf,
1214 false, // aVisual
1215 false, // aLockInScrollView
1216 false // aFollowOOFs
1218 if (NS_FAILED(result))
1219 return result;
1221 do {
1222 *aFrameOut = foundFrame;
1223 if (aDirection == eDirNext)
1224 frameTraversal->Next();
1225 else
1226 frameTraversal->Prev();
1228 foundFrame = frameTraversal->CurrentItem();
1229 if (!foundFrame)
1230 return NS_ERROR_FAILURE;
1231 foundLevel = NS_GET_EMBEDDING_LEVEL(foundFrame);
1233 } while (foundLevel > aBidiLevel);
1235 return NS_OK;
1239 nsresult
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();
1252 return NS_OK;
1255 mMaintainRange = nullptr;
1256 return NS_OK;
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,
1277 nsIContent* aNode,
1278 uint32_t aContentOffset,
1279 nsSelectionAmount aAmount,
1280 CaretAssociateHint aHint)
1282 switch (aAmount) {
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:
1288 case eSelectWord:
1289 case eSelectWordNoSpace:
1290 case eSelectBeginLine:
1291 case eSelectEndLine:
1292 case eSelectNoAmount:
1294 nsPrevNextBidiLevels levels = GetPrevNextBidiLevels(aNode, aContentOffset,
1295 aHint, false);
1297 SetCaretBidiLevel(aHint == CARET_ASSOCIATE_BEFORE ?
1298 levels.mLevelBefore : levels.mLevelAfter);
1299 break;
1302 // Up and Down: the new cursor Bidi level is the smaller of the two surrounding characters
1303 case eSelectLine:
1304 case eSelectParagraph:
1305 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame, &secondFrame, &firstLevel, &secondLevel);
1306 aPresShell->SetCaretBidiLevel(std::min(firstLevel, secondLevel));
1307 break;
1310 default:
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);
1328 if (!clickInFrame)
1329 return;
1331 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(clickInFrame));
1335 bool
1336 nsFrameSelection::AdjustForMaintainedSelection(nsIContent *aContent,
1337 int32_t aOffset)
1339 if (!mMaintainRange)
1340 return false;
1342 if (!aContent) {
1343 return false;
1346 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1347 if (!mDomSelections[index])
1348 return false;
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,
1357 aContent, aOffset);
1358 int32_t relToEnd =
1359 nsContentUtils::ComparePoints(rangeEndNode, rangeEndOffset,
1360 aContent, aOffset);
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) ||
1365 (relToStart > 0 &&
1366 mDomSelections[index]->GetDirection() == eDirNext) ||
1367 (relToEnd < 0 &&
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.
1373 return true;
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);
1379 return false;
1383 nsresult
1384 nsFrameSelection::HandleClick(nsIContent* aNewFocus,
1385 uint32_t aContentOffset,
1386 uint32_t aContentEndOffset,
1387 bool aContinueSelection,
1388 bool aMultipleSelection,
1389 CaretAssociateHint aHint)
1391 if (!aNewFocus)
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);
1416 return NS_OK;
1419 void
1420 nsFrameSelection::HandleDrag(nsIFrame *aFrame, nsPoint aPoint)
1422 if (!aFrame || !mShell)
1423 return;
1425 nsresult result;
1426 nsIFrame *newFrame = 0;
1427 nsPoint newPoint;
1429 result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame, newPoint);
1430 if (NS_FAILED(result))
1431 return;
1432 if (!newFrame)
1433 return;
1435 nsIFrame::ContentOffsets offsets =
1436 newFrame->GetContentOffsetsFromPoint(newPoint);
1437 if (!offsets.content)
1438 return;
1440 if (newFrame->IsSelected() &&
1441 AdjustForMaintainedSelection(offsets.content, offsets.offset))
1442 return;
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;
1459 int32_t offset;
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,
1468 false, false);
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);
1488 nsresult
1489 nsFrameSelection::StartAutoScrollTimer(nsIFrame *aFrame,
1490 nsPoint aPoint,
1491 uint32_t aDelay)
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);
1500 void
1501 nsFrameSelection::StopAutoScrollTimer()
1503 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1504 if (!mDomSelections[index])
1505 return;
1507 mDomSelections[index]->StopAutoScrollTimer();
1511 hard to go from nodes to frames, easy the other way!
1513 nsresult
1514 nsFrameSelection::TakeFocus(nsIContent* aNewFocus,
1515 uint32_t aContentOffset,
1516 uint32_t aContentEndOffset,
1517 CaretAssociateHint aHint,
1518 bool aContinueSelection,
1519 bool aMultipleSelection)
1521 if (!aNewFocus)
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;
1535 mHint = aHint;
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;
1550 mBatching = 1;
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;
1564 } else {
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
1588 if (mCellParent)
1589 printf(" * TakeFocus - Collapsing into new cell\n");
1590 #endif
1593 else {
1594 // Now update the range list:
1595 if (aContinueSelection && aNewFocus)
1597 int32_t offset;
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");
1603 #endif
1604 WidgetMouseEvent event(false, 0, nullptr, WidgetMouseEvent::eReal);
1606 // Start selecting in the cell we were in before
1607 nsINode* parent = ParentOffset(mCellParent, &offset);
1608 if (parent)
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;
1618 if (parent)
1620 mCellParent = cellparent;
1621 // Continue selection into next cell
1622 HandleTableSelection(parent, offset,
1623 nsISelectionPrivate::TABLESELECTION_CELL, &event);
1626 else
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
1634 else
1635 mDomSelections[index]->Extend(aNewFocus, aContentOffset);
1640 // Don't notify selection listeners if batching is on:
1641 if (GetBatching())
1642 return NS_OK;
1643 return NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL);
1647 SelectionDetails*
1648 nsFrameSelection::LookUpSelection(nsIContent *aContent,
1649 int32_t aContentOffset,
1650 int32_t aContentLength,
1651 bool aSlowCheck) const
1653 if (!aContent || !mShell)
1654 return nullptr;
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);
1665 return details;
1668 void
1669 nsFrameSelection::SetDragState(bool aState)
1671 if (mDragState == aState)
1672 return;
1674 mDragState = aState;
1676 if (!mDragState)
1678 mDragSelectingCells = false;
1679 PostReason(nsISelectionListener::MOUSEUP_REASON);
1680 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL); //notify that reason is mouse up please.
1684 Selection*
1685 nsFrameSelection::GetSelection(SelectionType aType) const
1687 int8_t index = GetIndexFromSelectionType(aType);
1688 if (index < 0)
1689 return nullptr;
1691 return mDomSelections[index];
1694 nsresult
1695 nsFrameSelection::ScrollSelectionIntoView(SelectionType aType,
1696 SelectionRegion aRegion,
1697 int16_t aFlags) const
1699 int8_t index = GetIndexFromSelectionType(aType);
1700 if (index < 0)
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,
1724 verticalScroll,
1725 nsIPresShell::ScrollAxis(),
1726 flags);
1729 nsresult
1730 nsFrameSelection::RepaintSelection(SelectionType aType) const
1732 int8_t index = GetIndexFromSelectionType(aType);
1733 if (index < 0)
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());
1741 nsIFrame*
1742 nsFrameSelection::GetFrameForNodeOffset(nsIContent* aNode,
1743 int32_t aOffset,
1744 CaretAssociateHint aHint,
1745 int32_t* aReturnOffset) const
1747 if (!aNode || !aReturnOffset || !mShell)
1748 return nullptr;
1750 if (aOffset < 0)
1751 return nullptr;
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)
1764 if (aOffset > 0)
1765 childIndex = aOffset - 1;
1766 else
1767 childIndex = aOffset;
1769 else
1771 NS_ASSERTION(aHint == CARET_ASSOCIATE_AFTER, "unknown direction");
1772 if (aOffset >= numChildren)
1774 if (numChildren > 0)
1775 childIndex = numChildren - 1;
1776 else
1777 childIndex = 0;
1779 else
1780 childIndex = aOffset;
1783 if (childIndex > 0 || numChildren > 0) {
1784 nsCOMPtr<nsIContent> childNode = theNode->GetChildAt(childIndex);
1786 if (!childNode)
1787 return nullptr;
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);
1806 } else {
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);
1812 if (textNode)
1814 if (theNode->GetPrimaryFrame())
1816 if (aOffset > childIndex)
1818 uint32_t textLength = 0;
1820 nsresult rv = textNode->GetLength(&textLength);
1821 if (NS_FAILED(rv))
1822 return nullptr;
1824 *aReturnOffset = (int32_t)textLength;
1826 else
1827 *aReturnOffset = 0;
1828 } else {
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);
1835 if (!newChildNode)
1836 return nullptr;
1838 theNode = newChildNode;
1839 int32_t newOffset =
1840 aHint == CARET_ASSOCIATE_BEFORE ? theNode->GetChildCount() : 0;
1841 return GetFrameForNodeOffset(theNode, newOffset, aHint, aReturnOffset);
1842 } else {
1843 // newChildIndex is illegal which means we're at first or last
1844 // child. Just use original node to get the frame.
1845 theNode = aNode;
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);
1857 if (shadowRoot) {
1858 theNode = shadowRoot->GetHost();
1861 nsIFrame* returnFrame = theNode->GetPrimaryFrame();
1862 if (!returnFrame)
1863 return nullptr;
1865 // find the child frame containing the offset we want
1866 returnFrame->GetChildFrameContainingOffset(*aReturnOffset, aHint == CARET_ASSOCIATE_AFTER,
1867 &aOffset, &returnFrame);
1868 return returnFrame;
1871 void
1872 nsFrameSelection::CommonPageMove(bool aForward,
1873 bool aExtend,
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();
1882 if (!scrolledFrame)
1883 return;
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);
1888 if (!domSel) {
1889 return;
1892 nsRect caretPos;
1893 nsIFrame* caretFrame = nsCaret::GetGeometry(domSel, &caretPos);
1894 if (!caretFrame)
1895 return;
1897 //need to adjust caret jump by percentage scroll
1898 nsSize scrollDelta = aScrollableFrame->GetPageScrollAmount();
1900 if (aForward)
1901 caretPos.y += scrollDelta.height;
1902 else
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)
1915 return;
1917 // scroll one page
1918 aScrollableFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
1919 nsIScrollableFrame::PAGES,
1920 nsIScrollableFrame::SMOOTH);
1922 // place the caret
1923 HandleClick(offsets.content, offsets.offset,
1924 offsets.offset, aExtend, false, CARET_ASSOCIATE_AFTER);
1927 nsresult
1928 nsFrameSelection::PhysicalMove(int16_t aDirection, int16_t aAmount,
1929 bool aExtend)
1931 NS_ENSURE_STATE(mShell);
1932 // Flush out layout, since we need it to be up to date to do caret
1933 // positioning.
1934 mShell->FlushPendingNotifications(Flush_Layout);
1936 if (!mShell) {
1937 return NS_OK;
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();
1946 if (!context) {
1947 return NS_ERROR_FAILURE;
1950 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1951 nsRefPtr<Selection> sel = mDomSelections[index];
1952 if (!sel) {
1953 return NS_ERROR_NULL_POINTER;
1956 // Map the abstract movement amounts (0-1) to direction-specific
1957 // selection units.
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 }
1988 WritingMode wm;
1989 nsIFrame *frame = nullptr;
1990 int32_t offsetused = 0;
1991 if (NS_SUCCEEDED(sel->GetPrimaryFrameForFocusNode(&frame, &offsetused,
1992 true))) {
1993 if (frame) {
1994 wm = frame->GetWritingMode();
1998 const PhysicalToLogicalMapping& mapping =
1999 wm.IsVertical()
2000 ? wm.IsVerticalLR() ? verticalLR[aDirection] : verticalRL[aDirection]
2001 : horizontal[aDirection];
2003 nsresult rv = MoveCaret(mapping.direction, aExtend, mapping.amounts[aAmount],
2004 eVisual);
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],
2010 eVisual);
2014 return rv;
2017 nsresult
2018 nsFrameSelection::CharacterMove(bool aForward, bool aExtend)
2020 return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectCluster,
2021 eUsePrefStyle);
2024 nsresult
2025 nsFrameSelection::CharacterExtendForDelete()
2027 return MoveCaret(eDirNext, true, eSelectCluster, eLogical);
2030 nsresult
2031 nsFrameSelection::CharacterExtendForBackspace()
2033 return MoveCaret(eDirPrevious, true, eSelectCharacter, eLogical);
2036 nsresult
2037 nsFrameSelection::WordMove(bool aForward, bool aExtend)
2039 return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectWord,
2040 eUsePrefStyle);
2043 nsresult
2044 nsFrameSelection::WordExtendForDelete(bool aForward)
2046 return MoveCaret(aForward ? eDirNext : eDirPrevious, true, eSelectWord,
2047 eLogical);
2050 nsresult
2051 nsFrameSelection::LineMove(bool aForward, bool aExtend)
2053 return MoveCaret(aForward ? eDirNext : eDirPrevious, aExtend, eSelectLine,
2054 eUsePrefStyle);
2057 nsresult
2058 nsFrameSelection::IntraLineMove(bool aForward, bool aExtend)
2060 if (aForward) {
2061 return MoveCaret(eDirNext, aExtend, eSelectEndLine, eLogical);
2062 } else {
2063 return MoveCaret(eDirPrevious, aExtend, eSelectBeginLine, eLogical);
2067 nsresult
2068 nsFrameSelection::SelectAll()
2070 nsCOMPtr<nsIContent> rootContent;
2071 if (mLimiter)
2073 rootContent = mLimiter;//addrefit
2075 else if (mAncestorLimiter) {
2076 rootContent = mAncestorLimiter;
2078 else
2080 NS_ENSURE_STATE(mShell);
2081 nsIDocument *doc = mShell->GetDocument();
2082 if (!doc)
2083 return NS_ERROR_FAILURE;
2084 rootContent = doc->GetRootElement();
2085 if (!rootContent)
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
2095 void
2096 nsFrameSelection::StartBatchChanges()
2098 mBatching++;
2101 void
2102 nsFrameSelection::EndBatchChanges()
2104 mBatching--;
2105 NS_ASSERTION(mBatching >=0,"Bad mBatching");
2106 if (mBatching == 0 && mChangesDuringBatching){
2107 mChangesDuringBatching = false;
2108 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL);
2113 nsresult
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());
2133 nsITableCellLayout*
2134 nsFrameSelection::GetCellLayout(nsIContent *aCellContent) const
2136 NS_ENSURE_TRUE(mShell, nullptr);
2137 nsITableCellLayout *cellLayoutObject =
2138 do_QueryFrame(aCellContent->GetPrimaryFrame());
2139 return cellLayoutObject;
2142 nsresult
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();
2152 static nsIContent*
2153 GetFirstSelectedContent(nsRange* aRange)
2155 if (!aRange) {
2156 return nullptr;
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
2168 nsresult
2169 nsFrameSelection::HandleTableSelection(nsINode* aParentContent,
2170 int32_t aContentOffset,
2171 int32_t aTarget,
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,
2180 // just do nothing
2181 return NS_OK;
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
2190 // selected cell
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)
2209 return NS_OK;
2211 #ifdef DEBUG_TABLE_SELECTION
2212 printf(" mStartSelectedCell = %p, mEndSelectedCell = %p, childContent = %p \n",
2213 mStartSelectedCell.get(), mEndSelectedCell.get(), childContent);
2214 #endif
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);
2234 #endif
2235 if ((mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW && startRowIndex == curRowIndex) ||
2236 (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN && startColIndex == curColIndex))
2237 return NS_OK;
2239 #ifdef DEBUG_TABLE_SELECTION
2240 printf(" Dragged into a new column or row\n");
2241 #endif
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");
2249 #endif
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;
2270 else
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
2282 return NS_OK;
2284 else
2286 // Not dragging -- mouse event is down or up
2287 if (mDragState)
2289 #ifdef DEBUG_TABLE_SELECTION
2290 printf("HandleTableSelection: Mouse down event\n");
2291 #endif
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();
2311 else
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;
2323 if (isSelected)
2325 // Remember this cell to (possibly) unselect it on mouseup
2326 mUnselectCellOnMouseUp = childContent;
2327 #ifdef DEBUG_TABLE_SELECTION
2328 printf("HandleTableSelection: Saving mUnselectCellOnMouseUp\n");
2329 #endif
2331 else
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);
2346 return NS_OK;
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);
2365 #endif
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);
2380 else
2382 #ifdef DEBUG_TABLE_SELECTION
2383 printf("HandleTableSelection: Mouse UP event. mDragSelectingCells=%d, mStartSelectedCell=%p\n",
2384 mDragSelectingCells, mStartSelectedCell.get());
2385 #endif
2386 // First check if we are extending a block selection
2387 int32_t rangeCount;
2388 result = mDomSelections[index]->GetRangeCount(&rangeCount);
2389 if (NS_FAILED(result))
2390 return 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;
2410 #ifdef XP_MACOSX
2411 doMouseUpAction = aMouseEvent->IsMeta();
2412 #else
2413 doMouseUpAction = aMouseEvent->IsControl();
2414 #endif
2415 if (!doMouseUpAction)
2417 #ifdef DEBUG_TABLE_SELECTION
2418 printf("HandleTableSelection: Ending cell selection on mouseup: mAppendStartSelectedCell=%p\n",
2419 mAppendStartSelectedCell.get());
2420 #endif
2421 return NS_OK;
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);
2433 #endif
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");
2460 #endif
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");
2473 #endif
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;
2487 return result;
2490 nsresult
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);
2502 if (!table) {
2503 return NS_OK;
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,
2517 true);
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);
2526 nsresult
2527 nsFrameSelection::UnselectCells(nsIContent *aTableContent,
2528 int32_t aStartRowIndex,
2529 int32_t aStartColumnIndex,
2530 int32_t aEndRowIndex,
2531 int32_t aEndColumnIndex,
2532 bool aRemoveOutsideOfCellRange)
2534 int8_t index =
2535 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2536 if (!mDomSelections[index])
2537 return NS_ERROR_NULL_POINTER;
2539 nsTableOuterFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame());
2540 if (!tableFrame)
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;
2554 while (cellNode)
2556 nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex);
2557 if (NS_FAILED(result))
2558 return result;
2560 #ifdef DEBUG_TABLE_SELECTION
2561 if (!range)
2562 printf("RemoveCellsToSelection -- range is null\n");
2563 #endif
2565 if (range) {
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--;
2575 } else {
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");
2605 return NS_OK;
2608 nsresult
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;
2625 while(true)
2627 int32_t col = aStartColumnIndex;
2628 while(true)
2630 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col);
2632 // Skip cells that are spanned from previous locations or are already selected
2633 if (cellFrame) {
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)
2646 col ++;
2647 else
2648 col--;
2650 if (row == aEndRowIndex) break;
2652 if (aStartRowIndex < aEndRowIndex)
2653 row++;
2654 else
2655 row--;
2657 return result;
2660 nsresult
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);
2671 nsresult
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);
2682 nsresult
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)
2706 colIndex = 0;
2707 if (aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN)
2708 rowIndex = 0;
2710 nsCOMPtr<nsIContent> firstCell, lastCell;
2711 while (true) {
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)
2716 break;
2718 if (!firstCell)
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);
2726 else
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;
2748 return result;
2751 #if 0
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!
2754 do {
2755 // Loop through all cells in column or row
2756 result = tableLayout->GetCellDataAt(rowIndex, colIndex,
2757 getter_AddRefs(cellElement),
2758 curRowIndex, curColIndex,
2759 rowSpan, colSpan,
2760 actualRowSpan, actualColSpan,
2761 isSelected);
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;
2780 else
2781 rowIndex += actualRowSpan;
2783 while (cellElement);
2784 #endif
2786 return NS_OK;
2789 nsIContent*
2790 nsFrameSelection::GetFirstCellNodeInRange(nsRange *aRange) const
2792 if (!aRange) return nullptr;
2794 nsINode* startParent = aRange->GetStartParent();
2795 if (!startParent)
2796 return nullptr;
2798 int32_t offset = aRange->StartOffset();
2800 nsIContent* childContent = startParent->GetChildAt(offset);
2801 if (!childContent)
2802 return nullptr;
2803 // Don't return node if not a cell
2804 if (!IsCell(childContent))
2805 return nullptr;
2807 return childContent;
2810 nsRange*
2811 nsFrameSelection::GetFirstCellRange()
2813 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2814 if (!mDomSelections[index])
2815 return nullptr;
2817 nsRange* firstRange = mDomSelections[index]->GetRangeAt(0);
2818 if (!GetFirstCellNodeInRange(firstRange)) {
2819 return nullptr;
2822 // Setup for next cell
2823 mSelectedCellIndex = 1;
2825 return firstRange;
2828 nsRange*
2829 nsFrameSelection::GetNextCellRange()
2831 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2832 if (!mDomSelections[index])
2833 return nullptr;
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)) {
2839 return nullptr;
2842 // Setup for next cell
2843 mSelectedCellIndex++;
2845 return range;
2848 nsresult
2849 nsFrameSelection::GetCellIndexes(nsIContent *aCell,
2850 int32_t &aRowIndex,
2851 int32_t &aColIndex)
2853 if (!aCell) return NS_ERROR_NULL_POINTER;
2855 aColIndex=0; // initialize out params
2856 aRowIndex=0;
2858 nsITableCellLayout *cellLayoutObject = GetCellLayout(aCell);
2859 if (!cellLayoutObject) return NS_ERROR_FAILURE;
2860 return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex);
2863 nsIContent*
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;
2877 nsIContent*
2878 nsFrameSelection::GetParentTable(nsIContent *aCell) const
2880 if (!aCell) {
2881 return nullptr;
2884 for (nsIContent* parent = aCell->GetParent(); parent;
2885 parent = parent->GetParent()) {
2886 if (parent->Tag() == nsGkAtoms::table &&
2887 parent->IsHTML()) {
2888 return parent;
2892 return nullptr;
2895 nsresult
2896 nsFrameSelection::SelectCellElement(nsIContent *aCellElement)
2898 nsIContent *parent = aCellElement->GetParent();
2900 // Get child offset
2901 int32_t offset = parent->IndexOf(aCellElement);
2903 return CreateAndAddRange(parent, offset);
2906 nsresult
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;
2915 *aRow = 0;
2916 *aCol = 0;
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)
2927 return NS_OK;
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());
2933 if (!content)
2934 return NS_ERROR_FAILURE;
2936 nsIContent *child = content->GetChildAt(aRange->StartOffset());
2937 if (!child)
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))
2943 return result;
2944 if (!cellLayout)
2945 return NS_ERROR_FAILURE;
2947 return cellLayout->GetCellIndexes(*aRow, *aCol);
2950 nsresult
2951 Selection::addTableCellRange(nsRange* aRange, bool* aDidAddRange,
2952 int32_t* aOutIndex)
2954 if (!aDidAddRange || !aOutIndex)
2955 return NS_ERROR_NULL_POINTER;
2957 *aDidAddRange = false;
2958 *aOutIndex = -1;
2960 if (!mFrameSelection)
2961 return NS_OK;
2963 if (!aRange)
2964 return NS_ERROR_NULL_POINTER;
2966 nsresult result;
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
2978 return NS_OK;
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
2991 nsresult
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)
3018 return NS_OK;
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.
3024 return NS_OK;
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);
3036 if (!child)
3037 return NS_ERROR_FAILURE;
3039 tag = child->Tag();
3041 if (tag == nsGkAtoms::table)
3042 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_TABLE;
3043 else if (tag == nsGkAtoms::tr)
3044 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_ROW;
3047 return NS_OK;
3050 nsresult
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
3072 void
3073 nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter)
3075 if (mAncestorLimiter != aLimiter) {
3076 mAncestorLimiter = aLimiter;
3077 int8_t index =
3078 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3079 if (!mDomSelections[index])
3080 return;
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
3099 nsresult
3100 nsFrameSelection::DeleteFromDocument()
3102 nsresult res;
3104 // If we're already collapsed, then we do nothing (bug 719503).
3105 bool isCollapsed;
3106 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3107 if (!mDomSelections[index])
3108 return NS_ERROR_NULL_POINTER;
3110 mDomSelections[index]->GetIsCollapsed( &isCollapsed);
3111 if (isCollapsed)
3113 return NS_OK;
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();
3120 if (NS_FAILED(res))
3121 return res;
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.
3127 if (isCollapsed)
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());
3131 #ifdef DEBUG
3132 else
3133 printf("Don't know how to set selection back past frame boundary\n");
3134 #endif
3136 return NS_OK;
3139 void
3140 nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent* aMouseEvent)
3142 if (aMouseEvent) {
3143 mDelayedMouseEventValid = true;
3144 mDelayedMouseEventIsShift = aMouseEvent->IsShift();
3145 mDelayedMouseEventClickCount = aMouseEvent->clickCount;
3146 } else {
3147 mDelayedMouseEventValid = false;
3151 void
3152 nsFrameSelection::DisconnectFromPresShell()
3154 // Remove touch caret as selection listener
3155 nsRefPtr<TouchCaret> touchCaret = mShell->GetTouchCaret();
3156 if (touchCaret) {
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);
3171 mShell = nullptr;
3174 //END nsISelection interface implementations
3176 #if 0
3177 #pragma mark -
3178 #endif
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;
3223 nsIDocument*
3224 Selection::GetParentObject() const
3226 nsIPresShell* shell = GetPresShell();
3227 if (shell) {
3228 return shell->GetDocument();
3230 return nullptr;
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
3238 // in JS!).
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)
3271 NS_IMETHODIMP
3272 Selection::GetAnchorNode(nsIDOMNode** aAnchorNode)
3274 nsINode* anchorNode = GetAnchorNode();
3275 if (anchorNode) {
3276 return CallQueryInterface(anchorNode, aAnchorNode);
3279 *aAnchorNode = nullptr;
3280 return NS_OK;
3283 nsINode*
3284 Selection::GetAnchorNode()
3286 if (!mAnchorFocusRange)
3287 return nullptr;
3289 if (GetDirection() == eDirNext) {
3290 return mAnchorFocusRange->GetStartParent();
3293 return mAnchorFocusRange->GetEndParent();
3296 NS_IMETHODIMP
3297 Selection::GetAnchorOffset(int32_t* aAnchorOffset)
3299 *aAnchorOffset = static_cast<int32_t>(AnchorOffset());
3300 return NS_OK;
3303 // note: this can return a nil focus node
3304 NS_IMETHODIMP
3305 Selection::GetFocusNode(nsIDOMNode** aFocusNode)
3307 nsINode* focusNode = GetFocusNode();
3308 if (focusNode) {
3309 return CallQueryInterface(focusNode, aFocusNode);
3312 *aFocusNode = nullptr;
3313 return NS_OK;
3316 nsINode*
3317 Selection::GetFocusNode()
3319 if (!mAnchorFocusRange)
3320 return nullptr;
3322 if (GetDirection() == eDirNext){
3323 return mAnchorFocusRange->GetEndParent();
3326 return mAnchorFocusRange->GetStartParent();
3329 NS_IMETHODIMP
3330 Selection::GetFocusOffset(int32_t* aFocusOffset)
3332 *aFocusOffset = static_cast<int32_t>(FocusOffset());
3333 return NS_OK;
3336 void
3337 Selection::setAnchorFocusRange(int32_t indx)
3339 if (indx >= (int32_t)mRanges.Length())
3340 return;
3341 if (indx < 0) //release all
3343 mAnchorFocusRange = nullptr;
3345 else{
3346 mAnchorFocusRange = mRanges[indx].mRange;
3350 uint32_t
3351 Selection::AnchorOffset()
3353 if (!mAnchorFocusRange)
3354 return 0;
3356 if (GetDirection() == eDirNext){
3357 return mAnchorFocusRange->StartOffset();
3360 return mAnchorFocusRange->EndOffset();
3363 uint32_t
3364 Selection::FocusOffset()
3366 if (!mAnchorFocusRange)
3367 return 0;
3369 if (GetDirection() == eDirNext){
3370 return mAnchorFocusRange->EndOffset();
3373 return mAnchorFocusRange->StartOffset();
3376 static nsresult
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()) {
3386 *aCmp = 1;
3387 } else {
3388 *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset,
3389 start, aRange->StartOffset());
3391 return NS_OK;
3394 static nsresult
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()) {
3404 *aCmp = 1;
3405 } else {
3406 *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset,
3407 end, aRange->EndOffset());
3409 return NS_OK;
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.
3421 nsresult
3422 Selection::FindInsertionPoint(
3423 nsTArray<RangeData>* aElementArray,
3424 nsINode* aPointNode, int32_t aPointOffset,
3425 nsresult (*aComparator)(nsINode*,int32_t,nsRange*,int32_t*),
3426 int32_t* aPoint)
3428 *aPoint = 0;
3429 int32_t beginSearch = 0;
3430 int32_t endSearch = aElementArray->Length(); // one beyond what to check
3432 if (endSearch) {
3433 int32_t center = endSearch - 1; // Check last index, then binary search
3434 do {
3435 nsRange* range = (*aElementArray)[center].mRange;
3437 int32_t cmp;
3438 nsresult rv = aComparator(aPointNode, aPointOffset, range, &cmp);
3439 NS_ENSURE_SUCCESS(rv, rv);
3441 if (cmp < 0) { // point < cur
3442 endSearch = center;
3443 } else if (cmp > 0) { // point > cur
3444 beginSearch = center + 1;
3445 } else { // found match, done
3446 beginSearch = center;
3447 break;
3449 center = (endSearch - beginSearch) / 2 + beginSearch;
3450 } while (endSearch - beginSearch > 0);
3453 *aPoint = beginSearch;
3454 return NS_OK;
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
3464 nsresult
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
3471 int32_t cmp;
3472 nsresult rv = CompareToRangeStart(range->GetStartParent(),
3473 range->StartOffset(),
3474 aSubtract, &cmp);
3475 NS_ENSURE_SUCCESS(rv, rv);
3477 // Also, make a comparison to the range end
3478 int32_t cmp2;
3479 rv = CompareToRangeEnd(range->GetEndParent(),
3480 range->EndOffset(),
3481 aSubtract, &cmp2);
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
3489 if (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());
3494 rv =
3495 postOverlap->SetStart(aSubtract->GetEndParent(), aSubtract->EndOffset());
3496 NS_ENSURE_SUCCESS(rv, rv);
3497 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;
3507 if (cmp < 0) {
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());
3512 nsresult rv =
3513 preOverlap->SetStart(range->GetStartParent(), range->StartOffset());
3514 NS_ENSURE_SUCCESS(rv, rv);
3515 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;
3526 return NS_OK;
3529 nsresult
3530 Selection::AddItem(nsRange* aItem, int32_t* aOutIndex)
3532 if (!aItem)
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);
3546 return NS_OK;
3548 return AddItemInternal(aItem, aOutIndex);
3551 nsresult
3552 Selection::AddItemInternal(nsRange* aItem, int32_t* aOutIndex)
3554 MOZ_ASSERT(aItem);
3555 MOZ_ASSERT(aItem->IsPositioned());
3556 MOZ_ASSERT(aOutIndex);
3558 *aOutIndex = -1;
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);
3566 *aOutIndex = 0;
3567 return NS_OK;
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)
3581 startIndex = 0;
3582 endIndex = 0;
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);
3595 if (sameRange) {
3596 *aOutIndex = startIndex;
3597 return NS_OK;
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;
3606 return NS_OK;
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
3614 // removed
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,
3640 &insertionPoint);
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;
3655 return NS_OK;
3658 nsresult
3659 Selection::RemoveItem(nsRange* aItem)
3661 if (!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.
3668 int32_t idx = -1;
3669 uint32_t i;
3670 for (i = 0; i < mRanges.Length(); i ++) {
3671 if (mRanges[i].mRange == aItem) {
3672 idx = (int32_t)i;
3673 break;
3676 if (idx < 0)
3677 return NS_ERROR_INVALID_ARG;
3679 mRanges.RemoveElementAt(idx);
3680 aItem->SetInSelection(false);
3681 return NS_OK;
3684 nsresult
3685 Selection::RemoveCollapsedRanges()
3687 uint32_t i = 0;
3688 while (i < mRanges.Length()) {
3689 if (mRanges[i].mRange->Collapsed()) {
3690 nsresult rv = RemoveItem(mRanges[i].mRange);
3691 NS_ENSURE_SUCCESS(rv, rv);
3692 } else {
3693 ++i;
3696 return NS_OK;
3699 nsresult
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);
3708 mRanges.Clear();
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);
3720 return NS_OK;
3723 NS_IMETHODIMP
3724 Selection::GetType(int16_t* aType)
3726 NS_ENSURE_ARG_POINTER(aType);
3727 *aType = Type();
3729 return NS_OK;
3732 // RangeMatches*Point
3734 // Compares the range beginning or ending point, and returns true if it
3735 // exactly matches the given DOM point.
3737 static inline bool
3738 RangeMatchesBeginPoint(nsRange* aRange, nsINode* aNode, int32_t aOffset)
3740 return aRange->GetStartParent() == aNode && aRange->StartOffset() == aOffset;
3743 static inline bool
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.
3753 bool
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))
3763 return true;
3765 return false;
3768 // Selection::GetRangesForInterval
3770 // XPCOM wrapper for the nsTArray version
3772 NS_IMETHODIMP
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;
3782 *aResultCount = 0;
3783 *aResults = nullptr;
3785 nsTArray<nsRefPtr<nsRange>> results;
3786 ErrorResult result;
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) {
3797 return NS_OK;
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();
3807 return NS_OK;
3811 void
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)) {
3823 aRv.Throw(rv);
3824 return;
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
3853 nsresult
3854 Selection::GetRangesForIntervalArray(nsINode* aBeginNode, int32_t aBeginOffset,
3855 nsINode* aEndNode, int32_t aEndOffset,
3856 bool aAllowAdjacent,
3857 nsTArray<nsRange*>* aRanges)
3859 aRanges->Clear();
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)
3867 return NS_OK;
3869 for (int32_t i = startIndex; i < endIndex; i++) {
3870 if (!aRanges->AppendElement(mRanges[i].mRange))
3871 return NS_ERROR_OUT_OF_MEMORY;
3874 return NS_OK;
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.
3883 nsresult
3884 Selection::GetIndicesForInterval(nsINode* aBeginNode, int32_t aBeginOffset,
3885 nsINode* aEndNode, int32_t aEndOffset,
3886 bool aAllowAdjacent,
3887 int32_t* aStartIndex, int32_t* aEndIndex)
3889 int32_t startIndex;
3890 int32_t endIndex;
3892 if (!aStartIndex)
3893 aStartIndex = &startIndex;
3894 if (!aEndIndex)
3895 aEndIndex = &endIndex;
3897 *aStartIndex = -1;
3898 *aEndIndex = -1;
3900 if (mRanges.Length() == 0)
3901 return NS_OK;
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))) {
3912 return NS_OK;
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))
3921 return NS_OK;
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))
3929 return NS_OK;
3931 *aEndIndex = endsBeforeIndex;
3933 int32_t beginsAfterIndex;
3934 if (NS_FAILED(FindInsertionPoint(&mRanges, aBeginNode, aBeginOffset,
3935 &CompareToRangeEnd,
3936 &beginsAfterIndex))) {
3937 return NS_OK;
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))
3956 break;
3957 endsBeforeIndex++;
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
3970 // adjacent range
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))
3976 beginsAfterIndex--;
3978 } else {
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())
3986 beginsAfterIndex++;
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
3991 // included
3992 if (endsBeforeIndex < (int32_t) mRanges.Length()) {
3993 nsRange* endRange = mRanges[endsBeforeIndex].mRange;
3994 if (RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset) &&
3995 endRange->Collapsed())
3996 endsBeforeIndex++;
4000 NS_ASSERTION(beginsAfterIndex <= endsBeforeIndex,
4001 "Is mRanges not ordered?");
4002 NS_ENSURE_STATE(beginsAfterIndex <= endsBeforeIndex);
4004 *aStartIndex = beginsAfterIndex;
4005 *aEndIndex = endsBeforeIndex;
4006 return NS_OK;
4009 NS_IMETHODIMP
4010 Selection::GetPrimaryFrameForAnchorNode(nsIFrame** aReturnFrame)
4012 if (!aReturnFrame)
4013 return NS_ERROR_NULL_POINTER;
4015 int32_t frameOffset = 0;
4016 *aReturnFrame = 0;
4017 nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode());
4018 if (content && mFrameSelection)
4020 *aReturnFrame = mFrameSelection->
4021 GetFrameForNodeOffset(content, AnchorOffset(),
4022 mFrameSelection->GetHint(), &frameOffset);
4023 if (*aReturnFrame)
4024 return NS_OK;
4026 return NS_ERROR_FAILURE;
4029 NS_IMETHODIMP
4030 Selection::GetPrimaryFrameForFocusNode(nsIFrame** aReturnFrame,
4031 int32_t* aOffsetUsed,
4032 bool aVisual)
4034 if (!aReturnFrame)
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;
4042 *aReturnFrame = 0;
4043 if (!aOffsetUsed)
4044 aOffsetUsed = &frameOffset;
4046 CaretAssociationHint hint = mFrameSelection->GetHint();
4048 if (aVisual) {
4049 nsBidiLevel caretBidiLevel = mFrameSelection->GetCaretBidiLevel();
4051 return nsCaret::GetCaretFrameForNodeOffset(mFrameSelection,
4052 content, FocusOffset(), hint, caretBidiLevel, aReturnFrame, aOffsetUsed);
4055 *aReturnFrame = mFrameSelection->
4056 GetFrameForNodeOffset(content, FocusOffset(),
4057 hint, aOffsetUsed);
4058 if (!*aReturnFrame)
4059 return NS_ERROR_FAILURE;
4061 return NS_OK;
4064 //select all content children of aContent
4065 nsresult
4066 Selection::SelectAllFramesForContent(nsIContentIterator* aInnerIter,
4067 nsIContent* aContent,
4068 bool aSelected)
4070 nsresult result = aInnerIter->Init(aContent);
4071 nsIFrame *frame;
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();
4086 if (frame) {
4087 if (frame->GetType() == nsGkAtoms::textFrame) {
4088 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
4089 textFrame->SetSelectedRange(0, innercontent->GetText()->GetLength(), aSelected, mType);
4090 } else {
4091 frame->InvalidateFrameSubtree(); // frame continuations?
4095 aInnerIter->Next();
4098 return NS_OK;
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
4108 nsresult
4109 Selection::selectFrames(nsPresContext* aPresContext, nsRange* aRange,
4110 bool aSelect)
4112 if (!mFrameSelection || !aPresContext || !aPresContext->GetPresShell()) {
4113 // nothing to do
4114 return NS_OK;
4116 MOZ_ASSERT(aRange);
4118 if (mFrameSelection->GetTableCellSelection()) {
4119 nsINode* node = aRange->GetCommonAncestor();
4120 nsIFrame* frame = node->IsContent() ? node->AsContent()->GetPrimaryFrame()
4121 : aPresContext->FrameManager()->GetRootFrame();
4122 if (frame) {
4123 frame->InvalidateFrameSubtree();
4125 return NS_OK;
4128 nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
4129 iter->Init(aRange);
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());
4134 if (!content) {
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();
4146 uint32_t endOffset;
4147 if (aRange->GetEndParent() == content) {
4148 endOffset = aRange->EndOffset();
4149 } else {
4150 endOffset = content->Length();
4152 textFrame->SetSelectedRange(startOffset, endOffset, aSelect, mType);
4156 iter->First();
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()) {
4165 nsresult res;
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);
4179 return NS_OK;
4183 // Selection::LookUpSelection
4185 // This function is called when a node wants to know where the selection is
4186 // over itself.
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
4193 // node at all.
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.
4207 NS_IMETHODIMP
4208 Selection::LookUpSelection(nsIContent* aContent, int32_t aContentOffset,
4209 int32_t aContentLength,
4210 SelectionDetails** aReturnDetails,
4211 SelectionType aType, bool aSlowCheck)
4213 nsresult rv;
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)
4219 return NS_OK;
4221 nsTArray<nsRange*> overlappingRanges;
4222 rv = GetRangesForIntervalArray(aContent, aContentOffset,
4223 aContent, aContentOffset + aContentLength,
4224 false,
4225 &overlappingRanges);
4226 NS_ENSURE_SUCCESS(rv, rv);
4227 if (overlappingRanges.Length() == 0)
4228 return NS_OK;
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
4258 start = 0;
4259 end = std::min(aContentLength, endOffset - aContentOffset);
4261 } else {
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
4265 // of it.
4266 start = 0;
4267 end = aContentLength;
4269 if (start < 0)
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);
4279 if (rd) {
4280 details->mTextRangeStyle = rd->mTextRangeStyle;
4282 *aReturnDetails = details;
4284 return NS_OK;
4287 NS_IMETHODIMP
4288 Selection::Repaint(nsPresContext* aPresContext)
4290 int32_t arrCount = (int32_t)mRanges.Length();
4292 if (arrCount < 1)
4293 return NS_OK;
4295 int32_t i;
4297 for (i = 0; i < arrCount; i++)
4299 nsresult rv = selectFrames(aPresContext, mRanges[i].mRange, true);
4301 if (NS_FAILED(rv)) {
4302 return rv;
4306 return NS_OK;
4309 NS_IMETHODIMP
4310 Selection::GetCanCacheFrameOffset(bool* aCanCacheFrameOffset)
4312 NS_ENSURE_ARG_POINTER(aCanCacheFrameOffset);
4314 if (mCachedOffsetForFrame)
4315 *aCanCacheFrameOffset = mCachedOffsetForFrame->mCanCacheFrameOffset;
4316 else
4317 *aCanCacheFrameOffset = false;
4319 return NS_OK;
4322 NS_IMETHODIMP
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
4332 // fix bug 207936
4333 if (!aCanCacheFrameOffset) {
4334 mCachedOffsetForFrame->mLastCaretFrame = nullptr;
4337 return NS_OK;
4340 NS_IMETHODIMP
4341 Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
4342 nsPoint& aPoint)
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;
4357 else
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;
4369 return rv;
4372 NS_IMETHODIMP
4373 Selection::GetAncestorLimiter(nsIContent** aContent)
4375 if (mFrameSelection) {
4376 nsCOMPtr<nsIContent> c = mFrameSelection->GetAncestorLimiter();
4377 c.forget(aContent);
4379 return NS_OK;
4382 NS_IMETHODIMP
4383 Selection::SetAncestorLimiter(nsIContent* aContent)
4385 if (mFrameSelection)
4386 mFrameSelection->SetAncestorLimiter(aContent);
4387 return NS_OK;
4390 RangeData*
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)
4396 return &mRanges[i];
4398 return nullptr;
4401 NS_IMETHODIMP
4402 Selection::SetTextRangeStyle(nsIDOMRange* aRange,
4403 const TextRangeStyle& aTextRangeStyle)
4405 NS_ENSURE_ARG_POINTER(aRange);
4406 RangeData *rd = FindRangeData(aRange);
4407 if (rd) {
4408 rd->mTextRangeStyle = aTextRangeStyle;
4410 return NS_OK;
4413 nsresult
4414 Selection::StartAutoScrollTimer(nsIFrame* aFrame, nsPoint& aPoint,
4415 uint32_t aDelay)
4417 NS_PRECONDITION(aFrame, "Need a frame");
4419 nsresult result;
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))
4430 return result;
4433 result = mAutoScrollTimer->SetDelay(aDelay);
4435 if (NS_FAILED(result))
4436 return result;
4438 return DoAutoScroll(aFrame, aPoint);
4441 nsresult
4442 Selection::StopAutoScrollTimer()
4444 if (mAutoScrollTimer)
4445 return mAutoScrollTimer->Stop();
4447 return NS_OK;
4450 nsresult
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();
4460 if (!rootPC)
4461 return NS_OK;
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(
4468 aFrame,
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);
4485 return NS_OK;
4489 /** RemoveAllRanges zeroes the selection
4491 NS_IMETHODIMP
4492 Selection::RemoveAllRanges()
4494 ErrorResult result;
4495 RemoveAllRanges(result);
4496 return result.ErrorCode();
4499 void
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)) {
4507 aRv.Throw(result);
4508 return;
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)) {
4518 aRv.Throw(result);
4522 /** AddRange adds the specified range to the selection
4523 * @param aRange is the range to be added
4525 NS_IMETHODIMP
4526 Selection::AddRange(nsIDOMRange* aDOMRange)
4528 if (!aDOMRange) {
4529 return NS_ERROR_NULL_POINTER;
4531 nsRange* range = static_cast<nsRange*>(aDOMRange);
4532 ErrorResult result;
4533 AddRange(*range, result);
4534 return result.ErrorCode();
4537 void
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
4542 bool didAddRange;
4543 int32_t rangeIndex;
4544 nsresult result = addTableCellRange(&aRange, &didAddRange, &rangeIndex);
4545 if (NS_FAILED(result)) {
4546 aRv.Throw(result);
4547 return;
4550 if (!didAddRange) {
4551 result = AddItem(&aRange, &rangeIndex);
4552 if (NS_FAILED(result)) {
4553 aRv.Throw(result);
4554 return;
4558 if (rangeIndex < 0) {
4559 return;
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)) {
4576 aRv.Throw(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.
4592 nsresult
4593 Selection::RemoveRange(nsIDOMRange* aDOMRange)
4595 if (!aDOMRange) {
4596 return NS_ERROR_INVALID_ARG;
4598 nsRange* range = static_cast<nsRange*>(aDOMRange);
4599 ErrorResult result;
4600 RemoveRange(*range, result);
4601 return result.ErrorCode();
4604 void
4605 Selection::RemoveRange(nsRange& aRange, ErrorResult& aRv)
4607 nsresult rv = RemoveItem(&aRange);
4608 if (NS_FAILED(rv)) {
4609 aRv.Throw(rv);
4610 return;
4613 nsINode* beginNode = aRange.GetStartParent();
4614 nsINode* endNode = aRange.GetEndParent();
4616 if (!beginNode || !endNode) {
4617 // Detached range; nothing else to do here.
4618 return;
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
4626 // range.
4627 beginOffset = 0;
4628 endOffset = static_cast<nsIContent*>(endNode)->TextLength();
4629 } else {
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,
4642 endNode, endOffset,
4643 true, &affectedRanges);
4644 if (NS_FAILED(rv)) {
4645 aRv.Throw(rv);
4646 return;
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)) {
4669 aRv.Throw(rv);
4676 * Collapse sets the whole selection to be one point.
4678 NS_IMETHODIMP
4679 Selection::Collapse(nsIDOMNode* aParentNode, int32_t aOffset)
4681 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
4682 return Collapse(parentNode, aOffset);
4685 NS_IMETHODIMP
4686 Selection::CollapseNative(nsINode* aParentNode, int32_t aOffset)
4688 return Collapse(aParentNode, aOffset);
4691 nsresult
4692 Selection::Collapse(nsINode* aParentNode, int32_t aOffset)
4694 if (!aParentNode)
4695 return NS_ERROR_INVALID_ARG;
4697 ErrorResult result;
4698 Collapse(*aParentNode, static_cast<uint32_t>(aOffset), result);
4699 return result.ErrorCode();
4702 void
4703 Selection::Collapse(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv)
4705 if (!mFrameSelection) {
4706 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
4707 return;
4710 nsCOMPtr<nsINode> kungfuDeathGrip = &aParentNode;
4712 mFrameSelection->InvalidateDesiredPos();
4713 if (!IsValidSelectionPoint(mFrameSelection, &aParentNode)) {
4714 aRv.Throw(NS_ERROR_FAILURE);
4715 return;
4717 nsresult result;
4719 nsRefPtr<nsPresContext> presContext = GetPresContext();
4720 if (!presContext || presContext->Document() != aParentNode.OwnerDoc()) {
4721 aRv.Throw(NS_ERROR_FAILURE);
4722 return;
4725 // Delete all of the current ranges
4726 Clear(presContext);
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)) {
4734 aRv.Throw(result);
4735 return;
4737 result = range->SetStart(&aParentNode, aOffset);
4738 if (NS_FAILED(result)) {
4739 aRv.Throw(result);
4740 return;
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" : "???"),
4749 aOffset);
4750 #endif
4752 int32_t rangeIndex = -1;
4753 result = AddItem(range, &rangeIndex);
4754 if (NS_FAILED(result)) {
4755 aRv.Throw(result);
4756 return;
4758 setAnchorFocusRange(0);
4759 selectFrames(presContext, range, true);
4760 result = mFrameSelection->NotifySelectionListeners(GetType());
4761 if (NS_FAILED(result)) {
4762 aRv.Throw(result);
4767 * Sets the whole selection to be one point
4768 * at the start of the current selection
4770 NS_IMETHODIMP
4771 Selection::CollapseToStart()
4773 ErrorResult result;
4774 CollapseToStart(result);
4775 return result.ErrorCode();
4778 void
4779 Selection::CollapseToStart(ErrorResult& aRv)
4781 int32_t cnt;
4782 nsresult rv = GetRangeCount(&cnt);
4783 if (NS_FAILED(rv) || cnt <= 0) {
4784 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
4785 return;
4788 // Get the first range
4789 nsRange* firstRange = mRanges[0].mRange;
4790 if (!firstRange) {
4791 aRv.Throw(NS_ERROR_FAILURE);
4792 return;
4795 if (mFrameSelection) {
4796 int16_t reason = mFrameSelection->PopReason() | nsISelectionListener::COLLAPSETOSTART_REASON;
4797 mFrameSelection->PostReason(reason);
4799 nsINode* parent = firstRange->GetStartParent();
4800 if (!parent) {
4801 aRv.Throw(NS_ERROR_FAILURE);
4802 return;
4804 Collapse(*parent, firstRange->StartOffset(), aRv);
4808 * Sets the whole selection to be one point
4809 * at the end of the current selection
4811 NS_IMETHODIMP
4812 Selection::CollapseToEnd()
4814 ErrorResult result;
4815 CollapseToEnd(result);
4816 return result.ErrorCode();
4819 void
4820 Selection::CollapseToEnd(ErrorResult& aRv)
4822 int32_t cnt;
4823 nsresult rv = GetRangeCount(&cnt);
4824 if (NS_FAILED(rv) || cnt <= 0) {
4825 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
4826 return;
4829 // Get the last range
4830 nsRange* lastRange = mRanges[cnt - 1].mRange;
4831 if (!lastRange) {
4832 aRv.Throw(NS_ERROR_FAILURE);
4833 return;
4836 if (mFrameSelection) {
4837 int16_t reason = mFrameSelection->PopReason() | nsISelectionListener::COLLAPSETOEND_REASON;
4838 mFrameSelection->PostReason(reason);
4840 nsINode* parent = lastRange->GetEndParent();
4841 if (!parent) {
4842 aRv.Throw(NS_ERROR_FAILURE);
4843 return;
4845 Collapse(*parent, lastRange->EndOffset(), aRv);
4849 * IsCollapsed -- is the whole selection just one point, or unset?
4851 bool
4852 Selection::IsCollapsed()
4854 uint32_t cnt = mRanges.Length();
4855 if (cnt == 0) {
4856 return true;
4859 if (cnt != 1) {
4860 return false;
4863 return mRanges[0].mRange->Collapsed();
4866 /* virtual */
4867 bool
4868 Selection::Collapsed()
4870 return IsCollapsed();
4873 NS_IMETHODIMP
4874 Selection::GetIsCollapsed(bool* aIsCollapsed)
4876 NS_ENSURE_TRUE(aIsCollapsed, NS_ERROR_NULL_POINTER);
4878 *aIsCollapsed = IsCollapsed();
4879 return NS_OK;
4882 NS_IMETHODIMP
4883 Selection::GetRangeCount(int32_t* aRangeCount)
4885 *aRangeCount = (int32_t)RangeCount();
4887 return NS_OK;
4890 NS_IMETHODIMP
4891 Selection::GetRangeAt(int32_t aIndex, nsIDOMRange** aReturn)
4893 ErrorResult result;
4894 *aReturn = GetRangeAt(aIndex, result);
4895 NS_IF_ADDREF(*aReturn);
4896 return result.ErrorCode();
4899 nsRange*
4900 Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv)
4902 nsRange* range = GetRangeAt(aIndex);
4903 if (!range) {
4904 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
4905 return nullptr;
4908 return range;
4911 nsRange*
4912 Selection::GetRangeAt(int32_t aIndex)
4914 RangeData empty(nullptr);
4915 return mRanges.SafeElementAt(aIndex, empty).mRange;
4919 utility function
4921 nsresult
4922 Selection::SetAnchorFocusToRange(nsRange* aRange)
4924 NS_ENSURE_STATE(mAnchorFocusRange);
4926 nsresult res = RemoveItem(mAnchorFocusRange);
4927 if (NS_FAILED(res))
4928 return res;
4930 int32_t aOutIndex = -1;
4931 res = AddItem(aRange, &aOutIndex);
4932 if (NS_FAILED(res))
4933 return res;
4934 setAnchorFocusRange(aOutIndex);
4936 return NS_OK;
4939 void
4940 Selection::ReplaceAnchorFocusRange(nsRange* aRange)
4942 NS_ENSURE_TRUE_VOID(mAnchorFocusRange);
4943 nsRefPtr<nsPresContext> presContext = GetPresContext();
4944 if (presContext) {
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.
4957 a = anchor
4958 1 = old cursor
4959 2 = new cursor
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
4967 then execute
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.
4980 NS_IMETHODIMP
4981 Selection::Extend(nsIDOMNode* aParentNode, int32_t aOffset)
4983 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
4984 return Extend(parentNode, aOffset);
4987 NS_IMETHODIMP
4988 Selection::ExtendNative(nsINode* aParentNode, int32_t aOffset)
4990 return Extend(aParentNode, aOffset);
4993 nsresult
4994 Selection::Extend(nsINode* aParentNode, int32_t aOffset)
4996 if (!aParentNode)
4997 return NS_ERROR_INVALID_ARG;
4999 ErrorResult result;
5000 Extend(*aParentNode, static_cast<uint32_t>(aOffset), result);
5001 return result.ErrorCode();
5004 void
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);
5010 return;
5013 if (!mFrameSelection) {
5014 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
5015 return;
5018 nsresult res;
5019 if (!IsValidSelectionPoint(mFrameSelection, &aParentNode)) {
5020 aRv.Throw(NS_ERROR_FAILURE);
5021 return;
5024 nsRefPtr<nsPresContext> presContext = GetPresContext();
5025 if (!presContext || presContext->Document() != aParentNode.OwnerDoc()) {
5026 aRv.Throw(NS_ERROR_FAILURE);
5027 return;
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(),
5047 range->EndOffset(),
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);
5060 } else {
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);
5068 break;
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,
5098 &disconnected);
5099 //compare old cursor to new cursor
5100 shouldClearRange |= disconnected;
5101 int32_t result2 = nsContentUtils::ComparePoints(focusNode, focusOffset,
5102 &aParentNode, aOffset,
5103 &disconnected);
5104 //compare anchor to new cursor
5105 shouldClearRange |= disconnected;
5106 int32_t result3 = nsContentUtils::ComparePoints(anchorNode, anchorOffset,
5107 &aParentNode, aOffset,
5108 &disconnected);
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);
5121 if (aRv.Failed()) {
5122 return;
5124 dir = eDirNext;
5125 res = difRange->SetEnd(range->GetEndParent(), range->EndOffset());
5126 nsresult tmp = difRange->SetStart(focusNode, focusOffset);
5127 if (NS_FAILED(tmp)) {
5128 res = tmp;
5130 if (NS_FAILED(res)) {
5131 aRv.Throw(res);
5132 return;
5134 selectFrames(presContext, difRange , true);
5135 res = SetAnchorFocusToRange(range);
5136 if (NS_FAILED(res)) {
5137 aRv.Throw(res);
5138 return;
5141 else if (result1 == 0 && result3 > 0){//2, a1
5142 //select from 2 to 1a
5143 dir = eDirPrevious;
5144 range->SetStart(aParentNode, aOffset, aRv);
5145 if (aRv.Failed()) {
5146 return;
5148 selectFrames(presContext, range, true);
5149 res = SetAnchorFocusToRange(range);
5150 if (NS_FAILED(res)) {
5151 aRv.Throw(res);
5152 return;
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);
5159 if (aRv.Failed()) {
5160 return;
5162 if (NS_FAILED(res)) {
5163 aRv.Throw(res);
5164 return;
5167 range->SetEnd(aParentNode, aOffset, aRv);
5168 if (aRv.Failed()) {
5169 return;
5171 res = SetAnchorFocusToRange(range);
5172 if (NS_FAILED(res)) {
5173 aRv.Throw(res);
5174 return;
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)) {
5184 aRv.Throw(res);
5185 return;
5188 dir = eDirNext;
5189 range->SetEnd(aParentNode, aOffset, aRv);
5190 if (aRv.Failed()) {
5191 return;
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)) {
5197 res = tmp;
5199 if (NS_FAILED(res)) {
5200 aRv.Throw(res);
5201 return;
5203 res = SetAnchorFocusToRange(range);
5204 if (NS_FAILED(res)) {
5205 aRv.Throw(res);
5206 return;
5208 //deselect from 1 to a
5209 selectFrames(presContext, difRange , false);
5211 else
5213 res = SetAnchorFocusToRange(range);
5214 if (NS_FAILED(res)) {
5215 aRv.Throw(res);
5216 return;
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);
5226 if (aRv.Failed()) {
5227 return;
5229 if (NS_FAILED(res)) {
5230 aRv.Throw(res);
5231 return;
5233 dir = eDirPrevious;
5234 range->SetStart(aParentNode, aOffset, aRv);
5235 if (aRv.Failed()) {
5236 return;
5239 res = SetAnchorFocusToRange(range);
5240 if (NS_FAILED(res)) {
5241 aRv.Throw(res);
5242 return;
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);
5252 dir = eDirPrevious;
5253 range->SetStart(aParentNode, aOffset, aRv);
5254 if (aRv.Failed()) {
5255 return;
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)) {
5262 res = tmp;
5264 tmp = SetAnchorFocusToRange(range);
5265 if (NS_FAILED(tmp)) {
5266 res = tmp;
5268 if (NS_FAILED(res)) {
5269 aRv.Throw(res);
5270 return;
5272 selectFrames(presContext, difRange, false);
5274 else
5276 res = SetAnchorFocusToRange(range);
5277 if (NS_FAILED(res)) {
5278 aRv.Throw(res);
5279 return;
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);
5288 if (aRv.Failed()) {
5289 return;
5291 dir = eDirPrevious;
5292 res = difRange->SetEnd(focusNode, focusOffset);
5293 nsresult tmp = difRange->SetStart(range->GetStartParent(), range->StartOffset());
5294 if (NS_FAILED(tmp)) {
5295 res = tmp;
5297 if (NS_FAILED(res)) {
5298 aRv.Throw(res);
5299 return;
5302 selectFrames(presContext, difRange, true);
5303 res = SetAnchorFocusToRange(range);
5304 if (NS_FAILED(res)) {
5305 aRv.Throw(res);
5306 return;
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");
5322 else
5323 printf(" direction = 0 RIGHT TO LEFT\n");
5324 #endif
5325 SetDirection(dir);
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);
5331 #endif
5332 res = mFrameSelection->NotifySelectionListeners(GetType());
5333 if (NS_FAILED(res)) {
5334 aRv.Throw(res);
5338 NS_IMETHODIMP
5339 Selection::SelectAllChildren(nsIDOMNode* aParentNode)
5341 ErrorResult result;
5342 nsCOMPtr<nsINode> node = do_QueryInterface(aParentNode);
5343 NS_ENSURE_TRUE(node, NS_ERROR_INVALID_ARG);
5344 SelectAllChildren(*node, result);
5345 return result.ErrorCode();
5348 void
5349 Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv)
5351 if (mFrameSelection)
5353 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON);
5355 Collapse(aNode, 0, aRv);
5356 if (aRv.Failed()) {
5357 return;
5360 if (mFrameSelection)
5362 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON);
5364 Extend(aNode, aNode.GetChildCount(), aRv);
5367 NS_IMETHODIMP
5368 Selection::ContainsNode(nsIDOMNode* aNode, bool aAllowPartial, bool* aYes)
5370 if (!aYes) {
5371 return NS_ERROR_NULL_POINTER;
5373 *aYes = false;
5375 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
5376 if (!node) {
5377 return NS_ERROR_NULL_POINTER;
5379 ErrorResult result;
5380 *aYes = ContainsNode(*node, aAllowPartial, result);
5381 return result.ErrorCode();
5384 bool
5385 Selection::ContainsNode(nsINode& aNode, bool aAllowPartial, ErrorResult& aRv)
5387 nsresult rv;
5388 if (mRanges.Length() == 0) {
5389 return false;
5392 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp
5393 uint32_t nodeLength;
5394 bool isData = aNode.IsNodeOfType(nsINode::eDATA_NODE);
5395 if (isData) {
5396 nodeLength = static_cast<nsIContent&>(aNode).TextLength();
5397 } else {
5398 nodeLength = aNode.GetChildCount();
5401 nsTArray<nsRange*> overlappingRanges;
5402 rv = GetRangesForIntervalArray(&aNode, 0, &aNode, nodeLength,
5403 false, &overlappingRanges);
5404 if (NS_FAILED(rv)) {
5405 aRv.Throw(rv);
5406 return false;
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) {
5413 return true;
5416 // text nodes always count as inside
5417 if (isData) {
5418 return true;
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) {
5429 return true;
5433 return false;
5437 nsPresContext*
5438 Selection::GetPresContext() const
5440 nsIPresShell *shell = GetPresShell();
5441 if (!shell) {
5442 return nullptr;
5445 return shell->GetPresContext();
5448 nsIPresShell*
5449 Selection::GetPresShell() const
5451 if (!mFrameSelection)
5452 return nullptr;//nothing to do
5454 return mFrameSelection->GetShell();
5457 nsIFrame *
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);
5467 switch (aRegion) {
5468 case nsISelectionController::SELECTION_ANCHOR_REGION:
5469 case nsISelectionController::SELECTION_FOCUS_REGION:
5470 return GetSelectionEndPointGeometry(aRegion, aRect);
5471 break;
5472 case nsISelectionController::SELECTION_WHOLE_SELECTION:
5473 break;
5474 default:
5475 return nullptr;
5478 NS_ASSERTION(aRegion == nsISelectionController::SELECTION_WHOLE_SELECTION,
5479 "should only be SELECTION_WHOLE_SELECTION here");
5481 nsRect anchorRect;
5482 nsIFrame* anchorFrame = GetSelectionEndPointGeometry(
5483 nsISelectionController::SELECTION_ANCHOR_REGION, &anchorRect);
5484 if (!anchorFrame)
5485 return nullptr;
5487 nsRect focusRect;
5488 nsIFrame* focusFrame = GetSelectionEndPointGeometry(
5489 nsISelectionController::SELECTION_FOCUS_REGION, &focusRect);
5490 if (!focusFrame)
5491 return nullptr;
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);
5499 return anchorFrame;
5502 nsIFrame *
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;
5516 switch (aRegion) {
5517 case nsISelectionController::SELECTION_ANCHOR_REGION:
5518 node = GetAnchorNode();
5519 nodeOffset = AnchorOffset();
5520 break;
5521 case nsISelectionController::SELECTION_FOCUS_REGION:
5522 node = GetFocusNode();
5523 nodeOffset = FocusOffset();
5524 break;
5525 default:
5526 return nullptr;
5529 if (!node)
5530 return nullptr;
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(),
5537 &frameOffset);
5538 if (!frame)
5539 return nullptr;
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);
5545 nsPoint pt(0, 0);
5546 if (isText) {
5547 nsIFrame* childFrame = nullptr;
5548 frameOffset = 0;
5549 nsresult rv =
5550 frame->GetChildFrameContainingOffset(nodeOffset,
5551 mFrameSelection->GetHint(),
5552 &frameOffset, &childFrame);
5553 if (NS_FAILED(rv))
5554 return nullptr;
5555 if (!childFrame)
5556 return nullptr;
5558 frame = childFrame;
5560 // Get the x coordinate of the offset into the text frame.
5561 rv = GetCachedFrameOffset(frame, nodeOffset, pt);
5562 if (NS_FAILED(rv))
5563 return nullptr;
5566 // Return the rect relative to the frame, with zero width.
5567 if (isText) {
5568 aRect->x = pt.x;
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;
5575 return frame;
5578 NS_IMETHODIMP
5579 Selection::ScrollSelectionIntoViewEvent::Run()
5581 if (!mSelection)
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);
5590 return NS_OK;
5593 nsresult
5594 Selection::PostScrollSelectionIntoViewEvent(
5595 SelectionRegion aRegion,
5596 int32_t aFlags,
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,
5608 aFlags);
5609 nsresult rv = NS_DispatchToCurrentThread(ev);
5610 NS_ENSURE_SUCCESS(rv, rv);
5612 mScrollEvent = ev;
5613 return NS_OK;
5616 NS_IMETHODIMP
5617 Selection::ScrollIntoView(SelectionRegion aRegion, bool aIsSynchronous,
5618 int16_t aVPercent, int16_t aHPercent)
5620 ErrorResult result;
5621 ScrollIntoView(aRegion, aIsSynchronous, aVPercent, aHPercent, result);
5622 if (result.Failed()) {
5623 return result.ErrorCode();
5625 return NS_OK;
5628 void
5629 Selection::ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
5630 int16_t aVPercent, int16_t aHPercent,
5631 ErrorResult& aRv)
5633 nsresult rv = ScrollIntoViewInternal(aRegion, aIsSynchronous,
5634 nsIPresShell::ScrollAxis(aVPercent),
5635 nsIPresShell::ScrollAxis(aHPercent));
5636 if (NS_FAILED(rv)) {
5637 aRv.Throw(rv);
5641 NS_IMETHODIMP
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);
5650 nsresult
5651 Selection::ScrollIntoView(SelectionRegion aRegion,
5652 nsIPresShell::ScrollAxis aVertical,
5653 nsIPresShell::ScrollAxis aHorizontal,
5654 int32_t aFlags)
5656 if (!mFrameSelection)
5657 return NS_OK;//nothing to do
5659 nsCOMPtr<nsIPresShell> presShell = mFrameSelection->GetShell();
5660 if (!presShell)
5661 return NS_OK;
5663 if (mFrameSelection->GetBatching())
5664 return NS_OK;
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;
5680 if (!presShell)
5681 return NS_OK;
5685 // Scroll the selection region into view.
5688 nsRect rect;
5689 nsIFrame* frame = GetSelectionAnchorGeometry(aRegion, &rect);
5690 if (!frame)
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;
5698 uint32_t flags = 0;
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,
5707 flags);
5708 return NS_OK;
5711 NS_IMETHODIMP
5712 Selection::AddSelectionListener(nsISelectionListener* aNewListener)
5714 if (!aNewListener)
5715 return NS_ERROR_NULL_POINTER;
5716 ErrorResult result;
5717 AddSelectionListener(aNewListener, result);
5718 if (result.Failed()) {
5719 return result.ErrorCode();
5721 return NS_OK;
5724 void
5725 Selection::AddSelectionListener(nsISelectionListener* aNewListener,
5726 ErrorResult& aRv)
5728 bool result = mSelectionListeners.AppendObject(aNewListener); // AddRefs
5729 if (!result) {
5730 aRv.Throw(NS_ERROR_FAILURE);
5734 NS_IMETHODIMP
5735 Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove)
5737 if (!aListenerToRemove)
5738 return NS_ERROR_NULL_POINTER;
5739 ErrorResult result;
5740 RemoveSelectionListener(aListenerToRemove, result);
5741 if (result.Failed()) {
5742 return result.ErrorCode();
5744 return NS_OK;
5747 void
5748 Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove,
5749 ErrorResult& aRv)
5751 bool result = mSelectionListeners.RemoveObject(aListenerToRemove); // Releases
5752 if (!result) {
5753 aRv.Throw(NS_ERROR_FAILURE);
5757 nsresult
5758 Selection::NotifySelectionListeners()
5760 if (!mFrameSelection)
5761 return NS_OK;//nothing to do
5763 if (mFrameSelection->GetBatching()) {
5764 mFrameSelection->SetDirty();
5765 return NS_OK;
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();
5775 if (ps) {
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);
5783 return NS_OK;
5786 NS_IMETHODIMP
5787 Selection::StartBatchChanges()
5789 if (mFrameSelection)
5790 mFrameSelection->StartBatchChanges();
5792 return NS_OK;
5797 NS_IMETHODIMP
5798 Selection::EndBatchChanges()
5800 if (mFrameSelection)
5801 mFrameSelection->EndBatchChanges();
5803 return NS_OK;
5808 NS_IMETHODIMP
5809 Selection::DeleteFromDocument()
5811 ErrorResult result;
5812 DeleteFromDocument(result);
5813 return result.ErrorCode();
5816 void
5817 Selection::DeleteFromDocument(ErrorResult& aRv)
5819 if (!mFrameSelection)
5820 return;//nothing to do
5821 nsresult rv = mFrameSelection->DeleteFromDocument();
5822 if (NS_FAILED(rv)) {
5823 aRv.Throw(rv);
5827 NS_IMETHODIMP
5828 Selection::Modify(const nsAString& aAlter, const nsAString& aDirection,
5829 const nsAString& aGranularity)
5831 ErrorResult result;
5832 Modify(aAlter, aDirection, aGranularity, result);
5833 return result.ErrorCode();
5836 void
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()) {
5842 return;
5845 if (!aAlter.LowerCaseEqualsLiteral("move") &&
5846 !aAlter.LowerCaseEqualsLiteral("extend")) {
5847 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
5848 return;
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);
5856 return;
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);
5884 return;
5885 } else {
5886 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
5887 return;
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;
5894 if (!extend) {
5895 nsINode* focusNode = GetFocusNode();
5896 // We should have checked earlier that there was a focus node.
5897 if (!focusNode) {
5898 aRv.Throw(NS_ERROR_UNEXPECTED);
5899 return;
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.
5907 nsIFrame *frame;
5908 int32_t offset;
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;
5916 forward = !forward;
5917 } else if (amount == eSelectEndLine) {
5918 amount = eSelectBeginLine;
5919 forward = !forward;
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,
5929 extend, amount,
5930 visual ? nsFrameSelection::eVisual
5931 : nsFrameSelection::eLogical);
5933 if (aGranularity.LowerCaseEqualsLiteral("line") && NS_FAILED(rv)) {
5934 nsCOMPtr<nsISelectionController> shell =
5935 do_QueryInterface(mFrameSelection->GetShell());
5936 if (!shell)
5937 return;
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
5945 NS_IMETHODIMP
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) {
5954 return NS_OK;
5957 mFrameSelection->mKbdBidiLevel = kbdBidiLevel;
5959 nsresult result;
5960 nsIFrame *focusFrame = 0;
5962 result = GetPrimaryFrameForFocusNode(&focusFrame, nullptr, false);
5963 if (NS_FAILED(result)) {
5964 return result;
5966 if (!focusFrame) {
5967 return NS_ERROR_FAILURE;
5970 int32_t frameStart, frameEnd;
5971 focusFrame->GetOffsets(frameStart, frameEnd);
5972 nsRefPtr<nsPresContext> context = GetPresContext();
5973 nsBidiLevel levelBefore, levelAfter;
5974 if (!context) {
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;
5984 else {
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);
6004 else
6005 mFrameSelection->SetCaretBidiLevel(level + 1);
6007 else {
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);
6012 else
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();
6020 return NS_OK;
6023 NS_IMETHODIMP_(nsDirection)
6024 Selection::GetSelectionDirection() {
6025 return mDirection;
6028 NS_IMETHODIMP_(void)
6029 Selection::SetSelectionDirection(nsDirection aDirection) {
6030 mDirection = aDirection;
6033 JSObject*
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)
6046 * What we do now:
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.
6059 * Other issues:
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
6067 * selections?
6068 * - maybe we should just never clear the X clipboard? That would make this
6069 * problem just go away, which is very tempting.
6072 NS_IMETHODIMP
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
6081 bool collapsed;
6082 if (!aDoc || !aSel ||
6083 NS_FAILED(aSel->GetIsCollapsed(&collapsed)) || collapsed) {
6084 #ifdef DEBUG_CLIPBOARD
6085 fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n");
6086 #endif
6087 /* clear X clipboard? */
6088 return NS_OK;
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);