Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 "nsIDOMKeyEvent.h"
26 #include "nsITableCellLayout.h"
27 #include "nsTArray.h"
28 #include "nsTableOuterFrame.h"
29 #include "nsTableCellFrame.h"
30 #include "nsIScrollableFrame.h"
31 #include "nsCCUncollectableMarker.h"
32 #include "nsIContentIterator.h"
33 #include "nsIDocumentEncoder.h"
34 #include "nsTextFragment.h"
35 #include <algorithm>
37 #include "nsGkAtoms.h"
38 #include "nsIFrameTraversal.h"
39 #include "nsLayoutUtils.h"
40 #include "nsLayoutCID.h"
41 #include "nsBidiPresUtils.h"
42 static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
43 #include "nsTextFrame.h"
45 #include "nsIDOMText.h"
47 #include "nsContentUtils.h"
48 #include "nsThreadUtils.h"
49 #include "mozilla/Preferences.h"
50 #include "nsDOMClassInfoID.h"
52 //included for desired x position;
53 #include "nsPresContext.h"
54 #include "nsIPresShell.h"
55 #include "nsCaret.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 struct CachedOffsetForFrame {
108 CachedOffsetForFrame()
109 : mCachedFrameOffset(0, 0) // nsPoint ctor
110 , mLastCaretFrame(nullptr)
111 , mLastContentOffset(0)
112 , mCanCacheFrameOffset(false)
113 {}
115 nsPoint mCachedFrameOffset; // cached frame offset
116 nsIFrame* mLastCaretFrame; // store the frame the caret was last drawn in.
117 int32_t mLastContentOffset; // store last content offset
118 bool mCanCacheFrameOffset; // cached frame offset is valid?
119 };
121 // Stack-class to turn on/off selection batching for table selection
122 class MOZ_STACK_CLASS nsSelectionBatcher MOZ_FINAL
123 {
124 private:
125 nsCOMPtr<nsISelectionPrivate> mSelection;
126 public:
127 nsSelectionBatcher(nsISelectionPrivate *aSelection) : mSelection(aSelection)
128 {
129 if (mSelection) mSelection->StartBatchChanges();
130 }
131 ~nsSelectionBatcher()
132 {
133 if (mSelection) mSelection->EndBatchChanges();
134 }
135 };
137 class nsAutoScrollTimer : public nsITimerCallback
138 {
139 public:
141 NS_DECL_ISUPPORTS
143 nsAutoScrollTimer()
144 : mFrameSelection(0), mSelection(0), mPresContext(0), mPoint(0,0), mDelay(30)
145 {
146 }
148 virtual ~nsAutoScrollTimer()
149 {
150 if (mTimer)
151 mTimer->Cancel();
152 }
154 // aPoint is relative to aPresContext's root frame
155 nsresult Start(nsPresContext *aPresContext, nsPoint &aPoint)
156 {
157 mPoint = aPoint;
159 // Store the presentation context. The timer will be
160 // stopped by the selection if the prescontext is destroyed.
161 mPresContext = aPresContext;
163 mContent = nsIPresShell::GetCapturingContent();
165 if (!mTimer)
166 {
167 nsresult result;
168 mTimer = do_CreateInstance("@mozilla.org/timer;1", &result);
170 if (NS_FAILED(result))
171 return result;
172 }
174 return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
175 }
177 nsresult Stop()
178 {
179 if (mTimer)
180 {
181 mTimer->Cancel();
182 mTimer = 0;
183 }
185 mContent = nullptr;
186 return NS_OK;
187 }
189 nsresult Init(nsFrameSelection* aFrameSelection, Selection* aSelection)
190 {
191 mFrameSelection = aFrameSelection;
192 mSelection = aSelection;
193 return NS_OK;
194 }
196 nsresult SetDelay(uint32_t aDelay)
197 {
198 mDelay = aDelay;
199 return NS_OK;
200 }
202 NS_IMETHOD Notify(nsITimer *timer) MOZ_OVERRIDE
203 {
204 if (mSelection && mPresContext)
205 {
206 nsWeakFrame frame =
207 mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nullptr;
208 if (!frame)
209 return NS_OK;
210 mContent = nullptr;
212 nsPoint pt = mPoint -
213 frame->GetOffsetTo(mPresContext->PresShell()->FrameManager()->GetRootFrame());
214 mFrameSelection->HandleDrag(frame, pt);
215 if (!frame.IsAlive())
216 return NS_OK;
218 NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?");
219 mSelection->DoAutoScroll(frame, pt);
220 }
221 return NS_OK;
222 }
223 private:
224 nsFrameSelection *mFrameSelection;
225 Selection* mSelection;
226 nsPresContext *mPresContext;
227 // relative to mPresContext's root frame
228 nsPoint mPoint;
229 nsCOMPtr<nsITimer> mTimer;
230 nsCOMPtr<nsIContent> mContent;
231 uint32_t mDelay;
232 };
234 NS_IMPL_ISUPPORTS(nsAutoScrollTimer, nsITimerCallback)
236 nsresult NS_NewDomSelection(nsISelection **aDomSelection)
237 {
238 Selection* rlist = new Selection;
239 *aDomSelection = (nsISelection *)rlist;
240 NS_ADDREF(rlist);
241 return NS_OK;
242 }
244 static int8_t
245 GetIndexFromSelectionType(SelectionType aType)
246 {
247 switch (aType)
248 {
249 case nsISelectionController::SELECTION_NORMAL: return 0; break;
250 case nsISelectionController::SELECTION_SPELLCHECK: return 1; break;
251 case nsISelectionController::SELECTION_IME_RAWINPUT: return 2; break;
252 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: return 3; break;
253 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: return 4; break;
254 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: return 5; break;
255 case nsISelectionController::SELECTION_ACCESSIBILITY: return 6; break;
256 case nsISelectionController::SELECTION_FIND: return 7; break;
257 case nsISelectionController::SELECTION_URLSECONDARY: return 8; break;
258 default:
259 return -1; break;
260 }
261 /* NOTREACHED */
262 return 0;
263 }
265 static SelectionType
266 GetSelectionTypeFromIndex(int8_t aIndex)
267 {
268 switch (aIndex)
269 {
270 case 0: return nsISelectionController::SELECTION_NORMAL; break;
271 case 1: return nsISelectionController::SELECTION_SPELLCHECK; break;
272 case 2: return nsISelectionController::SELECTION_IME_RAWINPUT; break;
273 case 3: return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT; break;
274 case 4: return nsISelectionController::SELECTION_IME_CONVERTEDTEXT; break;
275 case 5: return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; break;
276 case 6: return nsISelectionController::SELECTION_ACCESSIBILITY; break;
277 case 7: return nsISelectionController::SELECTION_FIND; break;
278 case 8: return nsISelectionController::SELECTION_URLSECONDARY; break;
279 default:
280 return nsISelectionController::SELECTION_NORMAL; break;
281 }
282 /* NOTREACHED */
283 return 0;
284 }
286 /*
287 The limiter is used specifically for the text areas and textfields
288 In that case it is the DIV tag that is anonymously created for the text
289 areas/fields. Text nodes and BR nodes fall beneath it. In the case of a
290 BR node the limiter will be the parent and the offset will point before or
291 after the BR node. In the case of the text node the parent content is
292 the text node itself and the offset will be the exact character position.
293 The offset is not important to check for validity. Simply look at the
294 passed in content. If it equals the limiter then the selection point is valid.
295 If its parent it the limiter then the point is also valid. In the case of
296 NO limiter all points are valid since you are in a topmost iframe. (browser
297 or composer)
298 */
299 bool
300 IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode)
301 {
302 if (!aFrameSel || !aNode)
303 return false;
305 nsIContent *limiter = aFrameSel->GetLimiter();
306 if (limiter && limiter != aNode && limiter != aNode->GetParent()) {
307 //if newfocus == the limiter. that's ok. but if not there and not parent bad
308 return false; //not in the right content. tLimiter said so
309 }
311 limiter = aFrameSel->GetAncestorLimiter();
312 return !limiter || nsContentUtils::ContentIsDescendantOf(aNode, limiter);
313 }
316 ////////////BEGIN nsFrameSelection methods
318 nsFrameSelection::nsFrameSelection()
319 {
320 int32_t i;
321 for (i = 0;i<nsISelectionController::NUM_SELECTIONTYPES;i++){
322 mDomSelections[i] = new Selection(this);
323 mDomSelections[i]->SetType(GetSelectionTypeFromIndex(i));
324 }
325 mBatching = 0;
326 mChangesDuringBatching = false;
327 mNotifyFrames = true;
329 mMouseDoubleDownState = false;
331 mHint = HINTLEFT;
332 mCaretBidiLevel = BIDI_LEVEL_UNDEFINED;
333 mDragSelectingCells = false;
334 mSelectingTableCellMode = 0;
335 mSelectedCellIndex = 0;
337 // Check to see if the autocopy pref is enabled
338 // and add the autocopy listener if it is
339 if (Preferences::GetBool("clipboard.autocopy")) {
340 nsAutoCopyListener *autoCopy = nsAutoCopyListener::GetInstance();
342 if (autoCopy) {
343 int8_t index =
344 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
345 if (mDomSelections[index]) {
346 autoCopy->Listen(mDomSelections[index]);
347 }
348 }
349 }
351 mDisplaySelection = nsISelectionController::SELECTION_OFF;
352 mSelectionChangeReason = nsISelectionListener::NO_REASON;
354 mDelayedMouseEventValid = false;
355 // These values are not used since they are only valid when
356 // mDelayedMouseEventValid is true, and setting mDelayedMouseEventValid
357 //alwaysoverrides these values.
358 mDelayedMouseEventIsShift = false;
359 mDelayedMouseEventClickCount = 0;
360 }
363 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection)
365 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection)
366 int32_t i;
367 for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) {
368 tmp->mDomSelections[i] = nullptr;
369 }
371 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCellParent)
372 tmp->mSelectingTableCellMode = 0;
373 tmp->mDragSelectingCells = false;
374 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartSelectedCell)
375 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSelectedCell)
376 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAppendStartSelectedCell)
377 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnselectCellOnMouseUp)
378 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainRange)
379 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiter)
380 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAncestorLimiter)
381 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
382 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection)
383 if (tmp->mShell && tmp->mShell->GetDocument() &&
384 nsCCUncollectableMarker::InGeneration(cb,
385 tmp->mShell->GetDocument()->
386 GetMarkedCCGeneration())) {
387 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
388 }
389 int32_t i;
390 for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) {
391 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections[i])
392 }
394 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCellParent)
395 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartSelectedCell)
396 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSelectedCell)
397 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAppendStartSelectedCell)
398 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnselectCellOnMouseUp)
399 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainRange)
400 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiter)
401 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAncestorLimiter)
402 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
404 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsFrameSelection, AddRef)
405 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsFrameSelection, Release)
408 nsresult
409 nsFrameSelection::FetchDesiredX(nscoord &aDesiredX) //the x position requested by the Key Handling for up down
410 {
411 if (!mShell)
412 {
413 NS_ERROR("fetch desired X failed");
414 return NS_ERROR_FAILURE;
415 }
416 if (mDesiredXSet)
417 {
418 aDesiredX = mDesiredX;
419 return NS_OK;
420 }
422 nsRefPtr<nsCaret> caret = mShell->GetCaret();
423 if (!caret)
424 return NS_ERROR_NULL_POINTER;
426 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
427 nsresult result = caret->SetCaretDOMSelection(mDomSelections[index]);
428 if (NS_FAILED(result))
429 return result;
431 nsRect coord;
432 nsIFrame* caretFrame = caret->GetGeometry(mDomSelections[index], &coord);
433 if (!caretFrame)
434 return NS_ERROR_FAILURE;
435 nsPoint viewOffset(0, 0);
436 nsView* view = nullptr;
437 caretFrame->GetOffsetFromView(viewOffset, &view);
438 if (view)
439 coord.x += viewOffset.x;
441 aDesiredX = coord.x;
442 return NS_OK;
443 }
447 void
448 nsFrameSelection::InvalidateDesiredX() //do not listen to mDesiredX you must get another.
449 {
450 mDesiredXSet = false;
451 }
455 void
456 nsFrameSelection::SetDesiredX(nscoord aX) //set the mDesiredX
457 {
458 mDesiredX = aX;
459 mDesiredXSet = true;
460 }
462 nsresult
463 nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(nsIFrame *aFrame,
464 nsPoint& aPoint,
465 nsIFrame **aRetFrame,
466 nsPoint& aRetPoint)
467 {
468 //
469 // The whole point of this method is to return a frame and point that
470 // that lie within the same valid subtree as the anchor node's frame,
471 // for use with the method GetContentAndOffsetsFromPoint().
472 //
473 // A valid subtree is defined to be one where all the content nodes in
474 // the tree have a valid parent-child relationship.
475 //
476 // If the anchor frame and aFrame are in the same subtree, aFrame will
477 // be returned in aRetFrame. If they are in different subtrees, we
478 // return the frame for the root of the subtree.
479 //
481 if (!aFrame || !aRetFrame)
482 return NS_ERROR_NULL_POINTER;
484 *aRetFrame = aFrame;
485 aRetPoint = aPoint;
487 //
488 // Get the frame and content for the selection's anchor point!
489 //
491 nsresult result;
492 nsCOMPtr<nsIDOMNode> anchorNode;
493 int32_t anchorOffset = 0;
495 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
496 if (!mDomSelections[index])
497 return NS_ERROR_NULL_POINTER;
499 result = mDomSelections[index]->GetAnchorNode(getter_AddRefs(anchorNode));
501 if (NS_FAILED(result))
502 return result;
504 if (!anchorNode)
505 return NS_OK;
507 result = mDomSelections[index]->GetAnchorOffset(&anchorOffset);
509 if (NS_FAILED(result))
510 return result;
512 nsCOMPtr<nsIContent> anchorContent = do_QueryInterface(anchorNode);
514 if (!anchorContent)
515 return NS_ERROR_FAILURE;
517 //
518 // Now find the root of the subtree containing the anchor's content.
519 //
521 NS_ENSURE_STATE(mShell);
522 nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(mShell);
523 NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED);
525 //
526 // Now find the root of the subtree containing aFrame's content.
527 //
529 nsIContent* content = aFrame->GetContent();
531 if (content)
532 {
533 nsIContent* contentRoot = content->GetSelectionRootContent(mShell);
534 NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED);
536 if (anchorRoot == contentRoot)
537 {
538 // If the aFrame's content isn't the capturing content, it should be
539 // a descendant. At this time, we can return simply.
540 nsIContent* capturedContent = nsIPresShell::GetCapturingContent();
541 if (capturedContent != content)
542 {
543 return NS_OK;
544 }
546 // Find the frame under the mouse cursor with the root frame.
547 // At this time, don't use the anchor's frame because it may not have
548 // fixed positioned frames.
549 nsIFrame* rootFrame = mShell->FrameManager()->GetRootFrame();
550 nsPoint ptInRoot = aPoint + aFrame->GetOffsetTo(rootFrame);
551 nsIFrame* cursorFrame =
552 nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot);
554 // If the mouse cursor in on a frame which is descendant of same
555 // selection root, we can expand the selection to the frame.
556 if (cursorFrame && cursorFrame->PresContext()->PresShell() == mShell)
557 {
558 nsIContent* cursorContent = cursorFrame->GetContent();
559 NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE);
560 nsIContent* cursorContentRoot =
561 cursorContent->GetSelectionRootContent(mShell);
562 NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED);
563 if (cursorContentRoot == anchorRoot)
564 {
565 *aRetFrame = cursorFrame;
566 aRetPoint = aPoint + aFrame->GetOffsetTo(cursorFrame);
567 return NS_OK;
568 }
569 }
570 // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
571 // cursor is out of the window), we should use the frame of the anchor
572 // root.
573 }
574 }
576 //
577 // When we can't find a frame which is under the mouse cursor and has a same
578 // selection root as the anchor node's, we should return the selection root
579 // frame.
580 //
582 *aRetFrame = anchorRoot->GetPrimaryFrame();
584 if (!*aRetFrame)
585 return NS_ERROR_FAILURE;
587 //
588 // Now make sure that aRetPoint is converted to the same coordinate
589 // system used by aRetFrame.
590 //
592 aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame);
594 return NS_OK;
595 }
597 void
598 nsFrameSelection::SetCaretBidiLevel(uint8_t aLevel)
599 {
600 // If the current level is undefined, we have just inserted new text.
601 // In this case, we don't want to reset the keyboard language
602 mCaretBidiLevel = aLevel;
603 return;
604 }
606 uint8_t
607 nsFrameSelection::GetCaretBidiLevel() const
608 {
609 return mCaretBidiLevel;
610 }
612 void
613 nsFrameSelection::UndefineCaretBidiLevel()
614 {
615 mCaretBidiLevel |= BIDI_LEVEL_UNDEFINED;
616 }
618 #ifdef PRINT_RANGE
619 void printRange(nsRange *aDomRange)
620 {
621 if (!aDomRange)
622 {
623 printf("NULL nsIDOMRange\n");
624 }
625 nsINode* startNode = aDomRange->GetStartParent();
626 nsINode* endNode = aDomRange->GetEndParent();
627 int32_t startOffset = aDomRange->StartOffset();
628 int32_t endOffset = aDomRange->EndOffset();
630 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
631 (unsigned long)aDomRange,
632 (unsigned long)startNode, (long)startOffset,
633 (unsigned long)endNode, (long)endOffset);
635 }
636 #endif /* PRINT_RANGE */
638 static
639 nsIAtom *GetTag(nsINode *aNode)
640 {
641 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
642 if (!content)
643 {
644 NS_NOTREACHED("bad node passed to GetTag()");
645 return nullptr;
646 }
648 return content->Tag();
649 }
651 // Returns the parent
652 nsINode*
653 ParentOffset(nsINode *aNode, int32_t *aChildOffset)
654 {
655 if (!aNode || !aChildOffset)
656 return nullptr;
658 nsIContent* parent = aNode->GetParent();
659 if (parent)
660 {
661 *aChildOffset = parent->IndexOf(aNode);
663 return parent;
664 }
666 return nullptr;
667 }
669 static nsINode*
670 GetCellParent(nsINode *aDomNode)
671 {
672 if (!aDomNode)
673 return nullptr;
674 nsINode* current = aDomNode;
675 // Start with current node and look for a table cell
676 while (current)
677 {
678 nsIAtom* tag = GetTag(current);
679 if (tag == nsGkAtoms::td || tag == nsGkAtoms::th)
680 return current;
681 current = current->GetParent();
682 }
683 return nullptr;
684 }
687 void
688 nsFrameSelection::Init(nsIPresShell *aShell, nsIContent *aLimiter)
689 {
690 mShell = aShell;
691 mMouseDownState = false;
692 mDesiredXSet = false;
693 mLimiter = aLimiter;
694 mCaretMovementStyle =
695 Preferences::GetInt("bidi.edit.caret_movement_style", 2);
696 }
698 nsresult
699 nsFrameSelection::MoveCaret(uint32_t aKeycode,
700 bool aContinueSelection,
701 nsSelectionAmount aAmount)
702 {
703 bool visualMovement =
704 (aKeycode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE ||
705 aKeycode == nsIDOMKeyEvent::DOM_VK_DELETE ||
706 aKeycode == nsIDOMKeyEvent::DOM_VK_HOME ||
707 aKeycode == nsIDOMKeyEvent::DOM_VK_END) ?
708 false : // Delete operations and home/end are always logical
709 mCaretMovementStyle == 1 ||
710 (mCaretMovementStyle == 2 && !aContinueSelection);
712 return MoveCaret(aKeycode, aContinueSelection, aAmount, visualMovement);
713 }
715 nsresult
716 nsFrameSelection::MoveCaret(uint32_t aKeycode,
717 bool aContinueSelection,
718 nsSelectionAmount aAmount,
719 bool aVisualMovement)
720 {
721 NS_ENSURE_STATE(mShell);
722 // Flush out layout, since we need it to be up to date to do caret
723 // positioning.
724 mShell->FlushPendingNotifications(Flush_Layout);
726 if (!mShell) {
727 return NS_OK;
728 }
730 nsPresContext *context = mShell->GetPresContext();
731 if (!context)
732 return NS_ERROR_FAILURE;
734 bool isCollapsed;
735 nscoord desiredX = 0; //we must keep this around and revalidate it when its just UP/DOWN
737 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
738 nsRefPtr<Selection> sel = mDomSelections[index];
739 if (!sel)
740 return NS_ERROR_NULL_POINTER;
742 int32_t scrollFlags = 0;
743 nsINode* focusNode = sel->GetFocusNode();
744 if (focusNode &&
745 (focusNode->IsEditable() ||
746 (focusNode->IsElement() &&
747 focusNode->AsElement()->State().
748 HasState(NS_EVENT_STATE_MOZ_READWRITE)))) {
749 // If caret moves in editor, it should cause scrolling even if it's in
750 // overflow: hidden;.
751 scrollFlags |= Selection::SCROLL_OVERFLOW_HIDDEN;
752 }
754 nsresult result = sel->GetIsCollapsed(&isCollapsed);
755 if (NS_FAILED(result))
756 return result;
757 if (aKeycode == nsIDOMKeyEvent::DOM_VK_UP ||
758 aKeycode == nsIDOMKeyEvent::DOM_VK_DOWN)
759 {
760 result = FetchDesiredX(desiredX);
761 if (NS_FAILED(result))
762 return result;
763 SetDesiredX(desiredX);
764 }
766 int32_t caretStyle = Preferences::GetInt("layout.selection.caret_style", 0);
767 if (caretStyle == 0
768 #ifdef XP_WIN
769 && aKeycode != nsIDOMKeyEvent::DOM_VK_UP
770 && aKeycode != nsIDOMKeyEvent::DOM_VK_DOWN
771 #endif
772 ) {
773 // Put caret at the selection edge in the |aKeycode| direction.
774 caretStyle = 2;
775 }
777 if (!isCollapsed && !aContinueSelection && caretStyle == 2) {
778 switch (aKeycode){
779 case nsIDOMKeyEvent::DOM_VK_LEFT :
780 case nsIDOMKeyEvent::DOM_VK_UP :
781 {
782 const nsRange* anchorFocusRange = sel->GetAnchorFocusRange();
783 if (anchorFocusRange) {
784 PostReason(nsISelectionListener::COLLAPSETOSTART_REASON);
785 sel->Collapse(anchorFocusRange->GetStartParent(),
786 anchorFocusRange->StartOffset());
787 }
788 mHint = HINTRIGHT;
789 sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
790 nsIPresShell::ScrollAxis(),
791 nsIPresShell::ScrollAxis(), scrollFlags);
792 return NS_OK;
793 }
795 case nsIDOMKeyEvent::DOM_VK_RIGHT :
796 case nsIDOMKeyEvent::DOM_VK_DOWN :
797 {
798 const nsRange* anchorFocusRange = sel->GetAnchorFocusRange();
799 if (anchorFocusRange) {
800 PostReason(nsISelectionListener::COLLAPSETOEND_REASON);
801 sel->Collapse(anchorFocusRange->GetEndParent(),
802 anchorFocusRange->EndOffset());
803 }
804 mHint = HINTLEFT;
805 sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
806 nsIPresShell::ScrollAxis(),
807 nsIPresShell::ScrollAxis(), scrollFlags);
808 return NS_OK;
809 }
810 }
811 }
813 nsIFrame *frame;
814 int32_t offsetused = 0;
815 result = sel->GetPrimaryFrameForFocusNode(&frame, &offsetused,
816 aVisualMovement);
818 if (NS_FAILED(result) || !frame)
819 return NS_FAILED(result) ? result : NS_ERROR_FAILURE;
821 //set data using mLimiter to stop on scroll views. If we have a limiter then we stop peeking
822 //when we hit scrollable views. If no limiter then just let it go ahead
823 nsPeekOffsetStruct pos(aAmount, eDirPrevious, offsetused, desiredX,
824 true, mLimiter != nullptr, true, aVisualMovement);
826 nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(frame);
828 HINT tHint(mHint); //temporary variable so we dont set mHint until it is necessary
829 switch (aKeycode){
830 case nsIDOMKeyEvent::DOM_VK_RIGHT :
831 InvalidateDesiredX();
832 pos.mDirection = (baseLevel & 1) ? eDirPrevious : eDirNext;
833 break;
834 case nsIDOMKeyEvent::DOM_VK_LEFT :
835 InvalidateDesiredX();
836 pos.mDirection = (baseLevel & 1) ? eDirNext : eDirPrevious;
837 break;
838 case nsIDOMKeyEvent::DOM_VK_DELETE :
839 InvalidateDesiredX();
840 pos.mDirection = eDirNext;
841 break;
842 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE :
843 InvalidateDesiredX();
844 pos.mDirection = eDirPrevious;
845 break;
846 case nsIDOMKeyEvent::DOM_VK_DOWN :
847 pos.mAmount = eSelectLine;
848 pos.mDirection = eDirNext;
849 break;
850 case nsIDOMKeyEvent::DOM_VK_UP :
851 pos.mAmount = eSelectLine;
852 pos.mDirection = eDirPrevious;
853 break;
854 case nsIDOMKeyEvent::DOM_VK_HOME :
855 InvalidateDesiredX();
856 pos.mAmount = eSelectBeginLine;
857 break;
858 case nsIDOMKeyEvent::DOM_VK_END :
859 InvalidateDesiredX();
860 pos.mAmount = eSelectEndLine;
861 break;
862 default :return NS_ERROR_FAILURE;
863 }
864 PostReason(nsISelectionListener::KEYPRESS_REASON);
865 if (NS_SUCCEEDED(result = frame->PeekOffset(&pos)) && pos.mResultContent)
866 {
867 nsIFrame *theFrame;
868 int32_t currentOffset, frameStart, frameEnd;
870 if (aAmount >= eSelectCharacter && aAmount <= eSelectWord)
871 {
872 // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does not set pos.mAttachForward,
873 // so determine the hint here based on the result frame and offset:
874 // If we're at the end of a text frame, set the hint to HINTLEFT to indicate that we
875 // want the caret displayed at the end of this frame, not at the beginning of the next one.
876 theFrame = pos.mResultFrame;
877 theFrame->GetOffsets(frameStart, frameEnd);
878 currentOffset = pos.mContentOffset;
879 if (frameEnd == currentOffset && !(frameStart == 0 && frameEnd == 0))
880 tHint = HINTLEFT;
881 else
882 tHint = HINTRIGHT;
883 } else {
884 // For up/down and home/end, pos.mResultFrame might not be set correctly, or not at all.
885 // In these cases, get the frame based on the content and hint returned by PeekOffset().
886 tHint = (HINT)pos.mAttachForward;
887 theFrame = GetFrameForNodeOffset(pos.mResultContent, pos.mContentOffset,
888 tHint, ¤tOffset);
889 if (!theFrame)
890 return NS_ERROR_FAILURE;
892 theFrame->GetOffsets(frameStart, frameEnd);
893 }
895 if (context->BidiEnabled())
896 {
897 switch (aKeycode) {
898 case nsIDOMKeyEvent::DOM_VK_HOME:
899 case nsIDOMKeyEvent::DOM_VK_END:
900 // set the caret Bidi level to the paragraph embedding level
901 SetCaretBidiLevel(NS_GET_BASE_LEVEL(theFrame));
902 break;
904 default:
905 // If the current position is not a frame boundary, it's enough just to take the Bidi level of the current frame
906 if ((pos.mContentOffset != frameStart && pos.mContentOffset != frameEnd)
907 || (eSelectLine == aAmount))
908 {
909 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame));
910 }
911 else
912 BidiLevelFromMove(mShell, pos.mResultContent, pos.mContentOffset, aKeycode, tHint);
913 }
914 }
915 result = TakeFocus(pos.mResultContent, pos.mContentOffset, pos.mContentOffset,
916 tHint, aContinueSelection, false);
917 } else if (aKeycode == nsIDOMKeyEvent::DOM_VK_RIGHT && !aContinueSelection) {
918 // Collapse selection if PeekOffset failed, we either
919 // 1. bumped into the BRFrame, bug 207623
920 // 2. had select-all in a text input (DIV range), bug 352759.
921 bool isBRFrame = frame->GetType() == nsGkAtoms::brFrame;
922 sel->Collapse(sel->GetFocusNode(), sel->FocusOffset());
923 // Note: 'frame' might be dead here.
924 if (!isBRFrame) {
925 mHint = HINTLEFT; // We're now at the end of the frame to the left.
926 }
927 result = NS_OK;
928 }
929 if (NS_SUCCEEDED(result))
930 {
931 result = mDomSelections[index]->
932 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
933 nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
934 scrollFlags);
935 }
937 return result;
938 }
940 //END nsFrameSelection methods
943 //BEGIN nsFrameSelection methods
945 NS_IMETHODIMP
946 Selection::ToString(nsAString& aReturn)
947 {
948 // We need Flush_Style here to make sure frames have been created for
949 // the selected content. Use mFrameSelection->GetShell() which returns
950 // null if the Selection has been disconnected (the shell is Destroyed).
951 nsCOMPtr<nsIPresShell> shell =
952 mFrameSelection ? mFrameSelection->GetShell() : nullptr;
953 if (!shell) {
954 aReturn.Truncate();
955 return NS_OK;
956 }
957 shell->FlushPendingNotifications(Flush_Style);
959 return ToStringWithFormat("text/plain",
960 nsIDocumentEncoder::SkipInvisibleContent,
961 0, aReturn);
962 }
964 void
965 Selection::Stringify(nsAString& aResult)
966 {
967 // Eat the error code
968 ToString(aResult);
969 }
971 NS_IMETHODIMP
972 Selection::ToStringWithFormat(const char* aFormatType, uint32_t aFlags,
973 int32_t aWrapCol, nsAString& aReturn)
974 {
975 ErrorResult result;
976 NS_ConvertUTF8toUTF16 format(aFormatType);
977 ToStringWithFormat(format, aFlags, aWrapCol, aReturn, result);
978 if (result.Failed()) {
979 return result.ErrorCode();
980 }
981 return NS_OK;
982 }
984 void
985 Selection::ToStringWithFormat(const nsAString& aFormatType, uint32_t aFlags,
986 int32_t aWrapCol, nsAString& aReturn,
987 ErrorResult& aRv)
988 {
989 nsresult rv = NS_OK;
990 NS_ConvertUTF8toUTF16 formatType( NS_DOC_ENCODER_CONTRACTID_BASE );
991 formatType.Append(aFormatType);
992 nsCOMPtr<nsIDocumentEncoder> encoder =
993 do_CreateInstance(NS_ConvertUTF16toUTF8(formatType).get(), &rv);
994 if (NS_FAILED(rv)) {
995 aRv.Throw(rv);
996 return;
997 }
999 nsIPresShell* shell = GetPresShell();
1000 if (!shell) {
1001 aRv.Throw(NS_ERROR_FAILURE);
1002 return;
1003 }
1005 nsIDocument *doc = shell->GetDocument();
1007 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
1008 NS_ASSERTION(domDoc, "Need a document");
1010 // Flags should always include OutputSelectionOnly if we're coming from here:
1011 aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
1012 nsAutoString readstring;
1013 readstring.Assign(aFormatType);
1014 rv = encoder->Init(domDoc, readstring, aFlags);
1015 if (NS_FAILED(rv)) {
1016 aRv.Throw(rv);
1017 return;
1018 }
1020 encoder->SetSelection(this);
1021 if (aWrapCol != 0)
1022 encoder->SetWrapColumn(aWrapCol);
1024 rv = encoder->EncodeToString(aReturn);
1025 if (NS_FAILED(rv)) {
1026 aRv.Throw(rv);
1027 }
1028 }
1030 NS_IMETHODIMP
1031 Selection::SetInterlinePosition(bool aHintRight)
1032 {
1033 ErrorResult result;
1034 SetInterlinePosition(aHintRight, result);
1035 if (result.Failed()) {
1036 return result.ErrorCode();
1037 }
1038 return NS_OK;
1039 }
1041 void
1042 Selection::SetInterlinePosition(bool aHintRight, ErrorResult& aRv)
1043 {
1044 if (!mFrameSelection) {
1045 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
1046 return;
1047 }
1048 nsFrameSelection::HINT hint;
1049 if (aHintRight)
1050 hint = nsFrameSelection::HINTRIGHT;
1051 else
1052 hint = nsFrameSelection::HINTLEFT;
1053 mFrameSelection->SetHint(hint);
1054 }
1056 NS_IMETHODIMP
1057 Selection::GetInterlinePosition(bool* aHintRight)
1058 {
1059 ErrorResult result;
1060 *aHintRight = GetInterlinePosition(result);
1061 if (result.Failed()) {
1062 return result.ErrorCode();
1063 }
1064 return NS_OK;
1065 }
1067 bool
1068 Selection::GetInterlinePosition(ErrorResult& aRv)
1069 {
1070 if (!mFrameSelection) {
1071 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
1072 return false;
1073 }
1074 return (mFrameSelection->GetHint() == nsFrameSelection::HINTRIGHT);
1075 }
1077 nsPrevNextBidiLevels
1078 nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode,
1079 uint32_t aContentOffset,
1080 bool aJumpLines) const
1081 {
1082 return GetPrevNextBidiLevels(aNode, aContentOffset, mHint, aJumpLines);
1083 }
1085 nsPrevNextBidiLevels
1086 nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode,
1087 uint32_t aContentOffset,
1088 HINT aHint,
1089 bool aJumpLines) const
1090 {
1091 // Get the level of the frames on each side
1092 nsIFrame *currentFrame;
1093 int32_t currentOffset;
1094 int32_t frameStart, frameEnd;
1095 nsDirection direction;
1097 nsPrevNextBidiLevels levels;
1098 levels.SetData(nullptr, nullptr, 0, 0);
1100 currentFrame = GetFrameForNodeOffset(aNode, aContentOffset,
1101 aHint, ¤tOffset);
1102 if (!currentFrame)
1103 return levels;
1105 currentFrame->GetOffsets(frameStart, frameEnd);
1107 if (0 == frameStart && 0 == frameEnd)
1108 direction = eDirPrevious;
1109 else if (frameStart == currentOffset)
1110 direction = eDirPrevious;
1111 else if (frameEnd == currentOffset)
1112 direction = eDirNext;
1113 else {
1114 // we are neither at the beginning nor at the end of the frame, so we have no worries
1115 levels.SetData(currentFrame, currentFrame,
1116 NS_GET_EMBEDDING_LEVEL(currentFrame),
1117 NS_GET_EMBEDDING_LEVEL(currentFrame));
1118 return levels;
1119 }
1121 nsIFrame *newFrame;
1122 int32_t offset;
1123 bool jumpedLine;
1124 nsresult rv = currentFrame->GetFrameFromDirection(direction, false,
1125 aJumpLines, true,
1126 &newFrame, &offset, &jumpedLine);
1127 if (NS_FAILED(rv))
1128 newFrame = nullptr;
1130 uint8_t baseLevel = NS_GET_BASE_LEVEL(currentFrame);
1131 uint8_t currentLevel = NS_GET_EMBEDDING_LEVEL(currentFrame);
1132 uint8_t newLevel = newFrame ? NS_GET_EMBEDDING_LEVEL(newFrame) : baseLevel;
1134 // If not jumping lines, disregard br frames, since they might be positioned incorrectly.
1135 // XXX This could be removed once bug 339786 is fixed.
1136 if (!aJumpLines) {
1137 if (currentFrame->GetType() == nsGkAtoms::brFrame) {
1138 currentFrame = nullptr;
1139 currentLevel = baseLevel;
1140 }
1141 if (newFrame && newFrame->GetType() == nsGkAtoms::brFrame) {
1142 newFrame = nullptr;
1143 newLevel = baseLevel;
1144 }
1145 }
1147 if (direction == eDirNext)
1148 levels.SetData(currentFrame, newFrame, currentLevel, newLevel);
1149 else
1150 levels.SetData(newFrame, currentFrame, newLevel, currentLevel);
1152 return levels;
1153 }
1155 nsresult
1156 nsFrameSelection::GetFrameFromLevel(nsIFrame *aFrameIn,
1157 nsDirection aDirection,
1158 uint8_t aBidiLevel,
1159 nsIFrame **aFrameOut) const
1160 {
1161 NS_ENSURE_STATE(mShell);
1162 uint8_t foundLevel = 0;
1163 nsIFrame *foundFrame = aFrameIn;
1165 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
1166 nsresult result;
1167 nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID,&result));
1168 if (NS_FAILED(result))
1169 return result;
1171 result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
1172 mShell->GetPresContext(), aFrameIn,
1173 eLeaf,
1174 false, // aVisual
1175 false, // aLockInScrollView
1176 false // aFollowOOFs
1177 );
1178 if (NS_FAILED(result))
1179 return result;
1181 do {
1182 *aFrameOut = foundFrame;
1183 if (aDirection == eDirNext)
1184 frameTraversal->Next();
1185 else
1186 frameTraversal->Prev();
1188 foundFrame = frameTraversal->CurrentItem();
1189 if (!foundFrame)
1190 return NS_ERROR_FAILURE;
1191 foundLevel = NS_GET_EMBEDDING_LEVEL(foundFrame);
1193 } while (foundLevel > aBidiLevel);
1195 return NS_OK;
1196 }
1199 nsresult
1200 nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount)
1201 {
1202 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1203 if (!mDomSelections[index])
1204 return NS_ERROR_NULL_POINTER;
1206 mMaintainedAmount = aAmount;
1208 const nsRange* anchorFocusRange =
1209 mDomSelections[index]->GetAnchorFocusRange();
1210 if (anchorFocusRange) {
1211 mMaintainRange = anchorFocusRange->CloneRange();
1212 return NS_OK;
1213 }
1215 mMaintainRange = nullptr;
1216 return NS_OK;
1217 }
1220 /** After moving the caret, its Bidi level is set according to the following rules:
1221 *
1222 * After moving over a character with left/right arrow, set to the Bidi level of the last moved over character.
1223 * After Home and End, set to the paragraph embedding level.
1224 * After up/down arrow, PageUp/Down, set to the lower level of the 2 surrounding characters.
1225 * After mouse click, set to the level of the current frame.
1226 *
1227 * The following two methods use GetPrevNextBidiLevels to determine the new Bidi level.
1228 * BidiLevelFromMove is called when the caret is moved in response to a keyboard event
1229 *
1230 * @param aPresShell is the presentation shell
1231 * @param aNode is the content node
1232 * @param aContentOffset is the new caret position, as an offset into aNode
1233 * @param aKeycode is the keyboard event that moved the caret to the new position
1234 * @param aHint is the hint indicating in what logical direction the caret moved
1235 */
1236 void nsFrameSelection::BidiLevelFromMove(nsIPresShell* aPresShell,
1237 nsIContent *aNode,
1238 uint32_t aContentOffset,
1239 uint32_t aKeycode,
1240 HINT aHint)
1241 {
1242 switch (aKeycode) {
1244 // Right and Left: the new cursor Bidi level is the level of the character moved over
1245 case nsIDOMKeyEvent::DOM_VK_RIGHT:
1246 case nsIDOMKeyEvent::DOM_VK_LEFT:
1247 {
1248 nsPrevNextBidiLevels levels = GetPrevNextBidiLevels(aNode, aContentOffset,
1249 aHint, false);
1251 if (HINTLEFT == aHint)
1252 SetCaretBidiLevel(levels.mLevelBefore);
1253 else
1254 SetCaretBidiLevel(levels.mLevelAfter);
1255 break;
1256 }
1257 /*
1258 // Up and Down: the new cursor Bidi level is the smaller of the two surrounding characters
1259 case nsIDOMKeyEvent::DOM_VK_UP:
1260 case nsIDOMKeyEvent::DOM_VK_DOWN:
1261 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame, &secondFrame, &firstLevel, &secondLevel);
1262 aPresShell->SetCaretBidiLevel(std::min(firstLevel, secondLevel));
1263 break;
1264 */
1266 default:
1267 UndefineCaretBidiLevel();
1268 }
1269 }
1271 /**
1272 * BidiLevelFromClick is called when the caret is repositioned by clicking the mouse
1273 *
1274 * @param aNode is the content node
1275 * @param aContentOffset is the new caret position, as an offset into aNode
1276 */
1277 void nsFrameSelection::BidiLevelFromClick(nsIContent *aNode,
1278 uint32_t aContentOffset)
1279 {
1280 nsIFrame* clickInFrame=nullptr;
1281 int32_t OffsetNotUsed;
1283 clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, mHint, &OffsetNotUsed);
1284 if (!clickInFrame)
1285 return;
1287 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(clickInFrame));
1288 }
1291 bool
1292 nsFrameSelection::AdjustForMaintainedSelection(nsIContent *aContent,
1293 int32_t aOffset)
1294 {
1295 if (!mMaintainRange)
1296 return false;
1298 if (!aContent) {
1299 return false;
1300 }
1302 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1303 if (!mDomSelections[index])
1304 return false;
1306 nsINode* rangeStartNode = mMaintainRange->GetStartParent();
1307 nsINode* rangeEndNode = mMaintainRange->GetEndParent();
1308 int32_t rangeStartOffset = mMaintainRange->StartOffset();
1309 int32_t rangeEndOffset = mMaintainRange->EndOffset();
1311 int32_t relToStart =
1312 nsContentUtils::ComparePoints(rangeStartNode, rangeStartOffset,
1313 aContent, aOffset);
1314 int32_t relToEnd =
1315 nsContentUtils::ComparePoints(rangeEndNode, rangeEndOffset,
1316 aContent, aOffset);
1318 // If aContent/aOffset is inside the maintained selection, or if it is on the
1319 // "anchor" side of the maintained selection, we need to do something.
1320 if ((relToStart < 0 && relToEnd > 0) ||
1321 (relToStart > 0 &&
1322 mDomSelections[index]->GetDirection() == eDirNext) ||
1323 (relToEnd < 0 &&
1324 mDomSelections[index]->GetDirection() == eDirPrevious)) {
1325 // Set the current range to the maintained range.
1326 mDomSelections[index]->ReplaceAnchorFocusRange(mMaintainRange);
1327 if (relToStart < 0 && relToEnd > 0) {
1328 // We're inside the maintained selection, just keep it selected.
1329 return true;
1330 }
1331 // Reverse the direction of the selection so that the anchor will be on the
1332 // far side of the maintained selection, relative to aContent/aOffset.
1333 mDomSelections[index]->SetDirection(relToStart > 0 ? eDirPrevious : eDirNext);
1334 }
1335 return false;
1336 }
1339 nsresult
1340 nsFrameSelection::HandleClick(nsIContent *aNewFocus,
1341 uint32_t aContentOffset,
1342 uint32_t aContentEndOffset,
1343 bool aContinueSelection,
1344 bool aMultipleSelection,
1345 bool aHint)
1346 {
1347 if (!aNewFocus)
1348 return NS_ERROR_INVALID_ARG;
1350 InvalidateDesiredX();
1352 if (!aContinueSelection) {
1353 mMaintainRange = nullptr;
1354 if (!IsValidSelectionPoint(this, aNewFocus)) {
1355 mAncestorLimiter = nullptr;
1356 }
1357 }
1359 // Don't take focus when dragging off of a table
1360 if (!mDragSelectingCells)
1361 {
1362 BidiLevelFromClick(aNewFocus, aContentOffset);
1363 PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON);
1364 if (aContinueSelection &&
1365 AdjustForMaintainedSelection(aNewFocus, aContentOffset))
1366 return NS_OK; //shift clicked to maintained selection. rejected.
1368 return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, HINT(aHint),
1369 aContinueSelection, aMultipleSelection);
1370 }
1372 return NS_OK;
1373 }
1375 void
1376 nsFrameSelection::HandleDrag(nsIFrame *aFrame, nsPoint aPoint)
1377 {
1378 if (!aFrame || !mShell)
1379 return;
1381 nsresult result;
1382 nsIFrame *newFrame = 0;
1383 nsPoint newPoint;
1385 result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame, newPoint);
1386 if (NS_FAILED(result))
1387 return;
1388 if (!newFrame)
1389 return;
1391 nsIFrame::ContentOffsets offsets =
1392 newFrame->GetContentOffsetsFromPoint(newPoint);
1393 if (!offsets.content)
1394 return;
1396 if (newFrame->IsSelected() &&
1397 AdjustForMaintainedSelection(offsets.content, offsets.offset))
1398 return;
1400 // Adjust offsets according to maintained amount
1401 if (mMaintainRange &&
1402 mMaintainedAmount != eSelectNoAmount) {
1404 nsINode* rangenode = mMaintainRange->GetStartParent();
1405 int32_t rangeOffset = mMaintainRange->StartOffset();
1406 int32_t relativePosition =
1407 nsContentUtils::ComparePoints(rangenode, rangeOffset,
1408 offsets.content, offsets.offset);
1410 nsDirection direction = relativePosition > 0 ? eDirPrevious : eDirNext;
1411 nsSelectionAmount amount = mMaintainedAmount;
1412 if (amount == eSelectBeginLine && direction == eDirNext)
1413 amount = eSelectEndLine;
1415 int32_t offset;
1416 nsIFrame* frame = GetFrameForNodeOffset(offsets.content, offsets.offset, HINTRIGHT, &offset);
1418 if (frame && amount == eSelectWord && direction == eDirPrevious) {
1419 // To avoid selecting the previous word when at start of word,
1420 // first move one character forward.
1421 nsPeekOffsetStruct charPos(eSelectCharacter, eDirNext, offset, 0,
1422 false, mLimiter != nullptr, false, false);
1423 if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) {
1424 frame = charPos.mResultFrame;
1425 offset = charPos.mContentOffset;
1426 }
1427 }
1429 nsPeekOffsetStruct pos(amount, direction, offset, 0,
1430 false, mLimiter != nullptr, false, false);
1432 if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) {
1433 offsets.content = pos.mResultContent;
1434 offsets.offset = pos.mContentOffset;
1435 }
1436 }
1438 HandleClick(offsets.content, offsets.offset, offsets.offset,
1439 true, false, offsets.associateWithNext);
1440 }
1442 nsresult
1443 nsFrameSelection::StartAutoScrollTimer(nsIFrame *aFrame,
1444 nsPoint aPoint,
1445 uint32_t aDelay)
1446 {
1447 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1448 if (!mDomSelections[index])
1449 return NS_ERROR_NULL_POINTER;
1451 return mDomSelections[index]->StartAutoScrollTimer(aFrame, aPoint, aDelay);
1452 }
1454 void
1455 nsFrameSelection::StopAutoScrollTimer()
1456 {
1457 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1458 if (!mDomSelections[index])
1459 return;
1461 mDomSelections[index]->StopAutoScrollTimer();
1462 }
1464 /**
1465 hard to go from nodes to frames, easy the other way!
1466 */
1467 nsresult
1468 nsFrameSelection::TakeFocus(nsIContent *aNewFocus,
1469 uint32_t aContentOffset,
1470 uint32_t aContentEndOffset,
1471 HINT aHint,
1472 bool aContinueSelection,
1473 bool aMultipleSelection)
1474 {
1475 if (!aNewFocus)
1476 return NS_ERROR_NULL_POINTER;
1478 NS_ENSURE_STATE(mShell);
1480 if (!IsValidSelectionPoint(this,aNewFocus))
1481 return NS_ERROR_FAILURE;
1483 // Clear all table selection data
1484 mSelectingTableCellMode = 0;
1485 mDragSelectingCells = false;
1486 mStartSelectedCell = nullptr;
1487 mEndSelectedCell = nullptr;
1488 mAppendStartSelectedCell = nullptr;
1489 mHint = aHint;
1491 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1492 if (!mDomSelections[index])
1493 return NS_ERROR_NULL_POINTER;
1495 //traverse through document and unselect crap here
1496 if (!aContinueSelection) {//single click? setting cursor down
1497 uint32_t batching = mBatching;//hack to use the collapse code.
1498 bool changes = mChangesDuringBatching;
1499 mBatching = 1;
1501 if (aMultipleSelection) {
1502 // Remove existing collapsed ranges as there's no point in having
1503 // non-anchor/focus collapsed ranges.
1504 mDomSelections[index]->RemoveCollapsedRanges();
1506 nsRefPtr<nsRange> newRange = new nsRange(aNewFocus);
1508 newRange->SetStart(aNewFocus, aContentOffset);
1509 newRange->SetEnd(aNewFocus, aContentOffset);
1510 mDomSelections[index]->AddRange(newRange);
1511 mBatching = batching;
1512 mChangesDuringBatching = changes;
1513 }
1514 else
1515 {
1516 bool oldDesiredXSet = mDesiredXSet; //need to keep old desired X if it was set.
1517 mDomSelections[index]->Collapse(aNewFocus, aContentOffset);
1518 mDesiredXSet = oldDesiredXSet; //now reset desired X back.
1519 mBatching = batching;
1520 mChangesDuringBatching = changes;
1521 }
1522 if (aContentEndOffset != aContentOffset)
1523 mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);
1525 //find out if we are inside a table. if so, find out which one and which cell
1526 //once we do that, the next time we get a takefocus, check the parent tree.
1527 //if we are no longer inside same table ,cell then switch to table selection mode.
1528 // BUT only do this in an editor
1530 NS_ENSURE_STATE(mShell);
1531 int16_t displaySelection = mShell->GetSelectionFlags();
1533 // Editor has DISPLAY_ALL selection type
1534 if (displaySelection == nsISelectionDisplay::DISPLAY_ALL)
1535 {
1536 mCellParent = GetCellParent(aNewFocus);
1537 #ifdef DEBUG_TABLE_SELECTION
1538 if (mCellParent)
1539 printf(" * TakeFocus - Collapsing into new cell\n");
1540 #endif
1541 }
1542 }
1543 else {
1544 // Now update the range list:
1545 if (aContinueSelection && aNewFocus)
1546 {
1547 int32_t offset;
1548 nsINode *cellparent = GetCellParent(aNewFocus);
1549 if (mCellParent && cellparent && cellparent != mCellParent) //switch to cell selection mode
1550 {
1551 #ifdef DEBUG_TABLE_SELECTION
1552 printf(" * TakeFocus - moving into new cell\n");
1553 #endif
1554 WidgetMouseEvent event(false, 0, nullptr, WidgetMouseEvent::eReal);
1556 // Start selecting in the cell we were in before
1557 nsINode* parent = ParentOffset(mCellParent, &offset);
1558 if (parent)
1559 HandleTableSelection(parent, offset,
1560 nsISelectionPrivate::TABLESELECTION_CELL, &event);
1562 // Find the parent of this new cell and extend selection to it
1563 parent = ParentOffset(cellparent, &offset);
1565 // XXXX We need to REALLY get the current key shift state
1566 // (we'd need to add event listener -- let's not bother for now)
1567 event.modifiers &= ~MODIFIER_SHIFT; //aContinueSelection;
1568 if (parent)
1569 {
1570 mCellParent = cellparent;
1571 // Continue selection into next cell
1572 HandleTableSelection(parent, offset,
1573 nsISelectionPrivate::TABLESELECTION_CELL, &event);
1574 }
1575 }
1576 else
1577 {
1578 // XXXX Problem: Shift+click in browser is appending text selection to selected table!!!
1579 // is this the place to erase seleced cells ?????
1580 if (mDomSelections[index]->GetDirection() == eDirNext && aContentEndOffset > aContentOffset) //didn't go far enough
1581 {
1582 mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);//this will only redraw the diff
1583 }
1584 else
1585 mDomSelections[index]->Extend(aNewFocus, aContentOffset);
1586 }
1587 }
1588 }
1590 // Don't notify selection listeners if batching is on:
1591 if (GetBatching())
1592 return NS_OK;
1593 return NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL);
1594 }
1597 SelectionDetails*
1598 nsFrameSelection::LookUpSelection(nsIContent *aContent,
1599 int32_t aContentOffset,
1600 int32_t aContentLength,
1601 bool aSlowCheck) const
1602 {
1603 if (!aContent || !mShell)
1604 return nullptr;
1606 SelectionDetails* details = nullptr;
1608 for (int32_t j = 0; j < nsISelectionController::NUM_SELECTIONTYPES; j++) {
1609 if (mDomSelections[j]) {
1610 mDomSelections[j]->LookUpSelection(aContent, aContentOffset,
1611 aContentLength, &details, (SelectionType)(1<<j), aSlowCheck);
1612 }
1613 }
1615 return details;
1616 }
1618 void
1619 nsFrameSelection::SetMouseDownState(bool aState)
1620 {
1621 if (mMouseDownState == aState)
1622 return;
1624 mMouseDownState = aState;
1626 if (!mMouseDownState)
1627 {
1628 mDragSelectingCells = false;
1629 PostReason(nsISelectionListener::MOUSEUP_REASON);
1630 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL); //notify that reason is mouse up please.
1631 }
1632 }
1634 Selection*
1635 nsFrameSelection::GetSelection(SelectionType aType) const
1636 {
1637 int8_t index = GetIndexFromSelectionType(aType);
1638 if (index < 0)
1639 return nullptr;
1641 return mDomSelections[index];
1642 }
1644 nsresult
1645 nsFrameSelection::ScrollSelectionIntoView(SelectionType aType,
1646 SelectionRegion aRegion,
1647 int16_t aFlags) const
1648 {
1649 int8_t index = GetIndexFromSelectionType(aType);
1650 if (index < 0)
1651 return NS_ERROR_INVALID_ARG;
1653 if (!mDomSelections[index])
1654 return NS_ERROR_NULL_POINTER;
1656 nsIPresShell::ScrollAxis verticalScroll = nsIPresShell::ScrollAxis();
1657 int32_t flags = Selection::SCROLL_DO_FLUSH;
1658 if (aFlags & nsISelectionController::SCROLL_SYNCHRONOUS) {
1659 flags |= Selection::SCROLL_SYNCHRONOUS;
1660 } else if (aFlags & nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY) {
1661 flags |= Selection::SCROLL_FIRST_ANCESTOR_ONLY;
1662 }
1663 if (aFlags & nsISelectionController::SCROLL_OVERFLOW_HIDDEN) {
1664 flags |= Selection::SCROLL_OVERFLOW_HIDDEN;
1665 }
1666 if (aFlags & nsISelectionController::SCROLL_CENTER_VERTICALLY) {
1667 verticalScroll = nsIPresShell::ScrollAxis(
1668 nsIPresShell::SCROLL_CENTER, nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE);
1669 }
1671 // After ScrollSelectionIntoView(), the pending notifications might be
1672 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1673 return mDomSelections[index]->ScrollIntoView(aRegion,
1674 verticalScroll,
1675 nsIPresShell::ScrollAxis(),
1676 flags);
1677 }
1679 nsresult
1680 nsFrameSelection::RepaintSelection(SelectionType aType) const
1681 {
1682 int8_t index = GetIndexFromSelectionType(aType);
1683 if (index < 0)
1684 return NS_ERROR_INVALID_ARG;
1685 if (!mDomSelections[index])
1686 return NS_ERROR_NULL_POINTER;
1687 NS_ENSURE_STATE(mShell);
1688 return mDomSelections[index]->Repaint(mShell->GetPresContext());
1689 }
1691 nsIFrame*
1692 nsFrameSelection::GetFrameForNodeOffset(nsIContent *aNode,
1693 int32_t aOffset,
1694 HINT aHint,
1695 int32_t *aReturnOffset) const
1696 {
1697 if (!aNode || !aReturnOffset || !mShell)
1698 return nullptr;
1700 if (aOffset < 0)
1701 return nullptr;
1703 *aReturnOffset = aOffset;
1705 nsCOMPtr<nsIContent> theNode = aNode;
1707 if (aNode->IsElement())
1708 {
1709 int32_t childIndex = 0;
1710 int32_t numChildren = theNode->GetChildCount();
1712 if (aHint == HINTLEFT)
1713 {
1714 if (aOffset > 0)
1715 childIndex = aOffset - 1;
1716 else
1717 childIndex = aOffset;
1718 }
1719 else // HINTRIGHT
1720 {
1721 if (aOffset >= numChildren)
1722 {
1723 if (numChildren > 0)
1724 childIndex = numChildren - 1;
1725 else
1726 childIndex = 0;
1727 }
1728 else
1729 childIndex = aOffset;
1730 }
1732 if (childIndex > 0 || numChildren > 0) {
1733 nsCOMPtr<nsIContent> childNode = theNode->GetChildAt(childIndex);
1735 if (!childNode)
1736 return nullptr;
1738 theNode = childNode;
1739 }
1741 #ifdef DONT_DO_THIS_YET
1742 // XXX: We can't use this code yet because the hinting
1743 // can cause us to attach to the wrong line frame.
1745 // Now that we have the child node, check if it too
1746 // can contain children. If so, call this method again!
1748 if (theNode->IsElement())
1749 {
1750 int32_t newOffset = 0;
1752 if (aOffset > childIndex)
1753 {
1754 numChildren = theNode->GetChildCount();
1756 newOffset = numChildren;
1757 }
1759 return GetFrameForNodeOffset(theNode, newOffset, aHint, aReturnOffset);
1760 }
1761 else
1762 #endif // DONT_DO_THIS_YET
1763 {
1764 // Check to see if theNode is a text node. If it is, translate
1765 // aOffset into an offset into the text node.
1767 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(theNode);
1769 if (textNode)
1770 {
1771 if (theNode->GetPrimaryFrame())
1772 {
1773 if (aOffset > childIndex)
1774 {
1775 uint32_t textLength = 0;
1777 nsresult rv = textNode->GetLength(&textLength);
1778 if (NS_FAILED(rv))
1779 return nullptr;
1781 *aReturnOffset = (int32_t)textLength;
1782 }
1783 else
1784 *aReturnOffset = 0;
1785 }
1786 else
1787 {
1788 // If we're at a collapsed whitespace content node (which
1789 // does not have a primary frame), just use the original node
1790 // to get the frame on which we should put the caret.
1791 theNode = aNode;
1792 }
1793 }
1794 }
1795 }
1797 // If the node is a ShadowRoot, the frame needs to be adjusted,
1798 // because a ShadowRoot does not get a frame. Its children are rendered
1799 // as children of the host.
1800 mozilla::dom::ShadowRoot* shadowRoot =
1801 mozilla::dom::ShadowRoot::FromNode(theNode);
1802 if (shadowRoot) {
1803 theNode = shadowRoot->GetHost();
1804 }
1806 nsIFrame* returnFrame = theNode->GetPrimaryFrame();
1807 if (!returnFrame)
1808 return nullptr;
1810 // find the child frame containing the offset we want
1811 returnFrame->GetChildFrameContainingOffset(*aReturnOffset, aHint == HINTRIGHT,
1812 &aOffset, &returnFrame);
1813 return returnFrame;
1814 }
1816 void
1817 nsFrameSelection::CommonPageMove(bool aForward,
1818 bool aExtend,
1819 nsIScrollableFrame* aScrollableFrame)
1820 {
1821 // expected behavior for PageMove is to scroll AND move the caret
1822 // and remain relative position of the caret in view. see Bug 4302.
1824 //get the frame from the scrollable view
1826 nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame();
1827 if (!scrolledFrame)
1828 return;
1830 // find out where the caret is.
1831 // we should know mDesiredX value of nsFrameSelection, but I havent seen that behavior in other windows applications yet.
1832 nsISelection* domSel = GetSelection(nsISelectionController::SELECTION_NORMAL);
1833 if (!domSel)
1834 return;
1836 nsRefPtr<nsCaret> caret = mShell->GetCaret();
1838 nsRect caretPos;
1839 nsIFrame* caretFrame = caret->GetGeometry(domSel, &caretPos);
1840 if (!caretFrame)
1841 return;
1843 //need to adjust caret jump by percentage scroll
1844 nsSize scrollDelta = aScrollableFrame->GetPageScrollAmount();
1846 if (aForward)
1847 caretPos.y += scrollDelta.height;
1848 else
1849 caretPos.y -= scrollDelta.height;
1851 caretPos += caretFrame->GetOffsetTo(scrolledFrame);
1853 // get a content at desired location
1854 nsPoint desiredPoint;
1855 desiredPoint.x = caretPos.x;
1856 desiredPoint.y = caretPos.y + caretPos.height/2;
1857 nsIFrame::ContentOffsets offsets =
1858 scrolledFrame->GetContentOffsetsFromPoint(desiredPoint);
1860 if (!offsets.content)
1861 return;
1863 // scroll one page
1864 aScrollableFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
1865 nsIScrollableFrame::PAGES,
1866 nsIScrollableFrame::SMOOTH);
1868 // place the caret
1869 HandleClick(offsets.content, offsets.offset,
1870 offsets.offset, aExtend, false, true);
1871 }
1873 nsresult
1874 nsFrameSelection::CharacterMove(bool aForward, bool aExtend)
1875 {
1876 if (aForward)
1877 return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT, aExtend, eSelectCluster);
1878 else
1879 return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT, aExtend, eSelectCluster);
1880 }
1882 nsresult
1883 nsFrameSelection::CharacterExtendForDelete()
1884 {
1885 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, true, eSelectCluster);
1886 }
1888 nsresult
1889 nsFrameSelection::CharacterExtendForBackspace()
1890 {
1891 return MoveCaret(nsIDOMKeyEvent::DOM_VK_BACK_SPACE, true, eSelectCharacter);
1892 }
1894 nsresult
1895 nsFrameSelection::WordMove(bool aForward, bool aExtend)
1896 {
1897 if (aForward)
1898 return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT,aExtend,eSelectWord);
1899 else
1900 return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT,aExtend,eSelectWord);
1901 }
1903 nsresult
1904 nsFrameSelection::WordExtendForDelete(bool aForward)
1905 {
1906 if (aForward)
1907 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, true, eSelectWord);
1908 else
1909 return MoveCaret(nsIDOMKeyEvent::DOM_VK_BACK_SPACE, true, eSelectWord);
1910 }
1912 nsresult
1913 nsFrameSelection::LineMove(bool aForward, bool aExtend)
1914 {
1915 if (aForward)
1916 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DOWN,aExtend,eSelectLine);
1917 else
1918 return MoveCaret(nsIDOMKeyEvent::DOM_VK_UP,aExtend,eSelectLine);
1919 }
1921 nsresult
1922 nsFrameSelection::IntraLineMove(bool aForward, bool aExtend)
1923 {
1924 if (aForward)
1925 return MoveCaret(nsIDOMKeyEvent::DOM_VK_END,aExtend,eSelectLine);
1926 else
1927 return MoveCaret(nsIDOMKeyEvent::DOM_VK_HOME,aExtend,eSelectLine);
1928 }
1930 nsresult
1931 nsFrameSelection::SelectAll()
1932 {
1933 nsCOMPtr<nsIContent> rootContent;
1934 if (mLimiter)
1935 {
1936 rootContent = mLimiter;//addrefit
1937 }
1938 else if (mAncestorLimiter) {
1939 rootContent = mAncestorLimiter;
1940 }
1941 else
1942 {
1943 NS_ENSURE_STATE(mShell);
1944 nsIDocument *doc = mShell->GetDocument();
1945 if (!doc)
1946 return NS_ERROR_FAILURE;
1947 rootContent = doc->GetRootElement();
1948 if (!rootContent)
1949 return NS_ERROR_FAILURE;
1950 }
1951 int32_t numChildren = rootContent->GetChildCount();
1952 PostReason(nsISelectionListener::NO_REASON);
1953 return TakeFocus(rootContent, 0, numChildren, HINTLEFT, false, false);
1954 }
1956 //////////END FRAMESELECTION
1958 void
1959 nsFrameSelection::StartBatchChanges()
1960 {
1961 mBatching++;
1962 }
1964 void
1965 nsFrameSelection::EndBatchChanges()
1966 {
1967 mBatching--;
1968 NS_ASSERTION(mBatching >=0,"Bad mBatching");
1969 if (mBatching == 0 && mChangesDuringBatching){
1970 mChangesDuringBatching = false;
1971 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL);
1972 }
1973 }
1976 nsresult
1977 nsFrameSelection::NotifySelectionListeners(SelectionType aType)
1978 {
1979 int8_t index = GetIndexFromSelectionType(aType);
1980 if (index >=0 && mDomSelections[index])
1981 {
1982 return mDomSelections[index]->NotifySelectionListeners();
1983 }
1984 return NS_ERROR_FAILURE;
1985 }
1987 // Start of Table Selection methods
1989 static bool IsCell(nsIContent *aContent)
1990 {
1991 return ((aContent->Tag() == nsGkAtoms::td ||
1992 aContent->Tag() == nsGkAtoms::th) &&
1993 aContent->IsHTML());
1994 }
1996 nsITableCellLayout*
1997 nsFrameSelection::GetCellLayout(nsIContent *aCellContent) const
1998 {
1999 NS_ENSURE_TRUE(mShell, nullptr);
2000 nsITableCellLayout *cellLayoutObject =
2001 do_QueryFrame(aCellContent->GetPrimaryFrame());
2002 return cellLayoutObject;
2003 }
2005 nsresult
2006 nsFrameSelection::ClearNormalSelection()
2007 {
2008 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2009 if (!mDomSelections[index])
2010 return NS_ERROR_NULL_POINTER;
2012 return mDomSelections[index]->RemoveAllRanges();
2013 }
2015 static nsIContent*
2016 GetFirstSelectedContent(nsRange* aRange)
2017 {
2018 if (!aRange) {
2019 return nullptr;
2020 }
2022 NS_PRECONDITION(aRange->GetStartParent(), "Must have start parent!");
2023 NS_PRECONDITION(aRange->GetStartParent()->IsElement(),
2024 "Unexpected parent");
2026 return aRange->GetStartParent()->GetChildAt(aRange->StartOffset());
2027 }
2029 // Table selection support.
2030 // TODO: Separate table methods into a separate nsITableSelection interface
2031 nsresult
2032 nsFrameSelection::HandleTableSelection(nsINode* aParentContent,
2033 int32_t aContentOffset,
2034 int32_t aTarget,
2035 WidgetMouseEvent* aMouseEvent)
2036 {
2037 NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER);
2038 NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER);
2040 if (mMouseDownState && mDragSelectingCells && (aTarget & nsISelectionPrivate::TABLESELECTION_TABLE))
2041 {
2042 // We were selecting cells and user drags mouse in table border or inbetween cells,
2043 // just do nothing
2044 return NS_OK;
2045 }
2047 nsresult result = NS_OK;
2049 nsIContent *childContent = aParentContent->GetChildAt(aContentOffset);
2051 // When doing table selection, always set the direction to next so
2052 // we can be sure that anchorNode's offset always points to the
2053 // selected cell
2054 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2055 if (!mDomSelections[index])
2056 return NS_ERROR_NULL_POINTER;
2058 mDomSelections[index]->SetDirection(eDirNext);
2060 // Stack-class to wrap all table selection changes in
2061 // BeginBatchChanges() / EndBatchChanges()
2062 nsSelectionBatcher selectionBatcher(mDomSelections[index]);
2064 int32_t startRowIndex, startColIndex, curRowIndex, curColIndex;
2065 if (mMouseDownState && mDragSelectingCells)
2066 {
2067 // We are drag-selecting
2068 if (aTarget != nsISelectionPrivate::TABLESELECTION_TABLE)
2069 {
2070 // If dragging in the same cell as last event, do nothing
2071 if (mEndSelectedCell == childContent)
2072 return NS_OK;
2074 #ifdef DEBUG_TABLE_SELECTION
2075 printf(" mStartSelectedCell = %x, mEndSelectedCell = %x, childContent = %x \n", mStartSelectedCell, mEndSelectedCell, childContent);
2076 #endif
2077 // aTarget can be any "cell mode",
2078 // so we can easily drag-select rows and columns
2079 // Once we are in row or column mode,
2080 // we can drift into any cell to stay in that mode
2081 // even if aTarget = TABLESELECTION_CELL
2083 if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW ||
2084 mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN)
2085 {
2086 if (mEndSelectedCell)
2087 {
2088 // Also check if cell is in same row/col
2089 result = GetCellIndexes(mEndSelectedCell, startRowIndex, startColIndex);
2090 if (NS_FAILED(result)) return result;
2091 result = GetCellIndexes(childContent, curRowIndex, curColIndex);
2092 if (NS_FAILED(result)) return result;
2094 #ifdef DEBUG_TABLE_SELECTION
2095 printf(" curRowIndex = %d, startRowIndex = %d, curColIndex = %d, startColIndex = %d\n", curRowIndex, startRowIndex, curColIndex, startColIndex);
2096 #endif
2097 if ((mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW && startRowIndex == curRowIndex) ||
2098 (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN && startColIndex == curColIndex))
2099 return NS_OK;
2100 }
2101 #ifdef DEBUG_TABLE_SELECTION
2102 printf(" Dragged into a new column or row\n");
2103 #endif
2104 // Continue dragging row or column selection
2105 return SelectRowOrColumn(childContent, mSelectingTableCellMode);
2106 }
2107 else if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_CELL)
2108 {
2109 #ifdef DEBUG_TABLE_SELECTION
2110 printf("HandleTableSelection: Dragged into a new cell\n");
2111 #endif
2112 // Trick for quick selection of rows and columns
2113 // Hold down shift, then start selecting in one direction
2114 // If next cell dragged into is in same row, select entire row,
2115 // if next cell is in same column, select entire column
2116 if (mStartSelectedCell && aMouseEvent->IsShift())
2117 {
2118 result = GetCellIndexes(mStartSelectedCell, startRowIndex, startColIndex);
2119 if (NS_FAILED(result)) return result;
2120 result = GetCellIndexes(childContent, curRowIndex, curColIndex);
2121 if (NS_FAILED(result)) return result;
2123 if (startRowIndex == curRowIndex ||
2124 startColIndex == curColIndex)
2125 {
2126 // Force new selection block
2127 mStartSelectedCell = nullptr;
2128 mDomSelections[index]->RemoveAllRanges();
2130 if (startRowIndex == curRowIndex)
2131 mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_ROW;
2132 else
2133 mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_COLUMN;
2135 return SelectRowOrColumn(childContent, mSelectingTableCellMode);
2136 }
2137 }
2139 // Reselect block of cells to new end location
2140 return SelectBlockOfCells(mStartSelectedCell, childContent);
2141 }
2142 }
2143 // Do nothing if dragging in table, but outside a cell
2144 return NS_OK;
2145 }
2146 else
2147 {
2148 // Not dragging -- mouse event is down or up
2149 if (mMouseDownState)
2150 {
2151 #ifdef DEBUG_TABLE_SELECTION
2152 printf("HandleTableSelection: Mouse down event\n");
2153 #endif
2154 // Clear cell we stored in mouse-down
2155 mUnselectCellOnMouseUp = nullptr;
2157 if (aTarget == nsISelectionPrivate::TABLESELECTION_CELL)
2158 {
2159 bool isSelected = false;
2161 // Check if we have other selected cells
2162 nsIContent* previousCellNode =
2163 GetFirstSelectedContent(GetFirstCellRange());
2164 if (previousCellNode)
2165 {
2166 // We have at least 1 other selected cell
2168 // Check if new cell is already selected
2169 nsIFrame *cellFrame = childContent->GetPrimaryFrame();
2170 if (!cellFrame) return NS_ERROR_NULL_POINTER;
2171 isSelected = cellFrame->IsSelected();
2172 }
2173 else
2174 {
2175 // No cells selected -- remove non-cell selection
2176 mDomSelections[index]->RemoveAllRanges();
2177 }
2178 mDragSelectingCells = true; // Signal to start drag-cell-selection
2179 mSelectingTableCellMode = aTarget;
2180 // Set start for new drag-selection block (not appended)
2181 mStartSelectedCell = childContent;
2182 // The initial block end is same as the start
2183 mEndSelectedCell = childContent;
2185 if (isSelected)
2186 {
2187 // Remember this cell to (possibly) unselect it on mouseup
2188 mUnselectCellOnMouseUp = childContent;
2189 #ifdef DEBUG_TABLE_SELECTION
2190 printf("HandleTableSelection: Saving mUnselectCellOnMouseUp\n");
2191 #endif
2192 }
2193 else
2194 {
2195 // Select an unselected cell
2196 // but first remove existing selection if not in same table
2197 if (previousCellNode &&
2198 !IsInSameTable(previousCellNode, childContent))
2199 {
2200 mDomSelections[index]->RemoveAllRanges();
2201 // Reset selection mode that is cleared in RemoveAllRanges
2202 mSelectingTableCellMode = aTarget;
2203 }
2205 return SelectCellElement(childContent);
2206 }
2208 return NS_OK;
2209 }
2210 else if (aTarget == nsISelectionPrivate::TABLESELECTION_TABLE)
2211 {
2212 //TODO: We currently select entire table when clicked between cells,
2213 // should we restrict to only around border?
2214 // *** How do we get location data for cell and click?
2215 mDragSelectingCells = false;
2216 mStartSelectedCell = nullptr;
2217 mEndSelectedCell = nullptr;
2219 // Remove existing selection and select the table
2220 mDomSelections[index]->RemoveAllRanges();
2221 return CreateAndAddRange(aParentContent, aContentOffset);
2222 }
2223 else if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW || aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN)
2224 {
2225 #ifdef DEBUG_TABLE_SELECTION
2226 printf("aTarget == %d\n", aTarget);
2227 #endif
2229 // Start drag-selecting mode so multiple rows/cols can be selected
2230 // Note: Currently, nsFrame::GetDataForTableSelection
2231 // will never call us for row or column selection on mouse down
2232 mDragSelectingCells = true;
2234 // Force new selection block
2235 mStartSelectedCell = nullptr;
2236 mDomSelections[index]->RemoveAllRanges();
2237 // Always do this AFTER RemoveAllRanges
2238 mSelectingTableCellMode = aTarget;
2239 return SelectRowOrColumn(childContent, aTarget);
2240 }
2241 }
2242 else
2243 {
2244 #ifdef DEBUG_TABLE_SELECTION
2245 printf("HandleTableSelection: Mouse UP event. mDragSelectingCells=%d, mStartSelectedCell=%d\n", mDragSelectingCells, mStartSelectedCell);
2246 #endif
2247 // First check if we are extending a block selection
2248 int32_t rangeCount;
2249 result = mDomSelections[index]->GetRangeCount(&rangeCount);
2250 if (NS_FAILED(result))
2251 return result;
2253 if (rangeCount > 0 && aMouseEvent->IsShift() &&
2254 mAppendStartSelectedCell && mAppendStartSelectedCell != childContent)
2255 {
2256 // Shift key is down: append a block selection
2257 mDragSelectingCells = false;
2258 return SelectBlockOfCells(mAppendStartSelectedCell, childContent);
2259 }
2261 if (mDragSelectingCells)
2262 mAppendStartSelectedCell = mStartSelectedCell;
2264 mDragSelectingCells = false;
2265 mStartSelectedCell = nullptr;
2266 mEndSelectedCell = nullptr;
2268 // Any other mouseup actions require that Ctrl or Cmd key is pressed
2269 // else stop table selection mode
2270 bool doMouseUpAction = false;
2271 #ifdef XP_MACOSX
2272 doMouseUpAction = aMouseEvent->IsMeta();
2273 #else
2274 doMouseUpAction = aMouseEvent->IsControl();
2275 #endif
2276 if (!doMouseUpAction)
2277 {
2278 #ifdef DEBUG_TABLE_SELECTION
2279 printf("HandleTableSelection: Ending cell selection on mouseup: mAppendStartSelectedCell=%d\n", mAppendStartSelectedCell);
2280 #endif
2281 return NS_OK;
2282 }
2283 // Unselect a cell only if it wasn't
2284 // just selected on mousedown
2285 if( childContent == mUnselectCellOnMouseUp)
2286 {
2287 // Scan ranges to find the cell to unselect (the selection range to remove)
2288 // XXXbz it's really weird that this lives outside the loop, so once we
2289 // find one we keep looking at it even if we find no more cells...
2290 nsINode* previousCellParent = nullptr;
2291 #ifdef DEBUG_TABLE_SELECTION
2292 printf("HandleTableSelection: Unselecting mUnselectCellOnMouseUp; rangeCount=%d\n", rangeCount);
2293 #endif
2294 for( int32_t i = 0; i < rangeCount; i++)
2295 {
2296 // Strong reference, because sometimes we want to remove
2297 // this range, and then we might be the only owner.
2298 nsRefPtr<nsRange> range = mDomSelections[index]->GetRangeAt(i);
2299 if (!range) return NS_ERROR_NULL_POINTER;
2301 nsINode* parent = range->GetStartParent();
2302 if (!parent) return NS_ERROR_NULL_POINTER;
2304 int32_t offset = range->StartOffset();
2305 // Be sure previous selection is a table cell
2306 nsIContent* child = parent->GetChildAt(offset);
2307 if (child && IsCell(child))
2308 previousCellParent = parent;
2310 // We're done if we didn't find parent of a previously-selected cell
2311 if (!previousCellParent) break;
2313 if (previousCellParent == aParentContent && offset == aContentOffset)
2314 {
2315 // Cell is already selected
2316 if (rangeCount == 1)
2317 {
2318 #ifdef DEBUG_TABLE_SELECTION
2319 printf("HandleTableSelection: Unselecting single selected cell\n");
2320 #endif
2321 // This was the only cell selected.
2322 // Collapse to "normal" selection inside the cell
2323 mStartSelectedCell = nullptr;
2324 mEndSelectedCell = nullptr;
2325 mAppendStartSelectedCell = nullptr;
2326 //TODO: We need a "Collapse to just before deepest child" routine
2327 // Even better, should we collapse to just after the LAST deepest child
2328 // (i.e., at the end of the cell's contents)?
2329 return mDomSelections[index]->Collapse(childContent, 0);
2330 }
2331 #ifdef DEBUG_TABLE_SELECTION
2332 printf("HandleTableSelection: Removing cell from multi-cell selection\n");
2333 #endif
2334 // Unselecting the start of previous block
2335 // XXX What do we use now!
2336 if (childContent == mAppendStartSelectedCell)
2337 mAppendStartSelectedCell = nullptr;
2339 // Deselect cell by removing its range from selection
2340 return mDomSelections[index]->RemoveRange(range);
2341 }
2342 }
2343 mUnselectCellOnMouseUp = nullptr;
2344 }
2345 }
2346 }
2347 return result;
2348 }
2350 nsresult
2351 nsFrameSelection::SelectBlockOfCells(nsIContent *aStartCell, nsIContent *aEndCell)
2352 {
2353 NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER);
2354 NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER);
2355 mEndSelectedCell = aEndCell;
2357 nsCOMPtr<nsIContent> startCell;
2358 nsresult result = NS_OK;
2360 // If new end cell is in a different table, do nothing
2361 nsIContent* table = IsInSameTable(aStartCell, aEndCell);
2362 if (!table) {
2363 return NS_OK;
2364 }
2366 // Get starting and ending cells' location in the cellmap
2367 int32_t startRowIndex, startColIndex, endRowIndex, endColIndex;
2368 result = GetCellIndexes(aStartCell, startRowIndex, startColIndex);
2369 if(NS_FAILED(result)) return result;
2370 result = GetCellIndexes(aEndCell, endRowIndex, endColIndex);
2371 if(NS_FAILED(result)) return result;
2373 if (mDragSelectingCells)
2374 {
2375 // Drag selecting: remove selected cells outside of new block limits
2376 UnselectCells(table, startRowIndex, startColIndex, endRowIndex, endColIndex,
2377 true);
2378 }
2380 // Note that we select block in the direction of user's mouse dragging,
2381 // which means start cell may be after the end cell in either row or column
2382 return AddCellsToSelection(table, startRowIndex, startColIndex,
2383 endRowIndex, endColIndex);
2384 }
2386 nsresult
2387 nsFrameSelection::UnselectCells(nsIContent *aTableContent,
2388 int32_t aStartRowIndex,
2389 int32_t aStartColumnIndex,
2390 int32_t aEndRowIndex,
2391 int32_t aEndColumnIndex,
2392 bool aRemoveOutsideOfCellRange)
2393 {
2394 int8_t index =
2395 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2396 if (!mDomSelections[index])
2397 return NS_ERROR_NULL_POINTER;
2399 nsTableOuterFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame());
2400 if (!tableFrame)
2401 return NS_ERROR_FAILURE;
2403 int32_t minRowIndex = std::min(aStartRowIndex, aEndRowIndex);
2404 int32_t maxRowIndex = std::max(aStartRowIndex, aEndRowIndex);
2405 int32_t minColIndex = std::min(aStartColumnIndex, aEndColumnIndex);
2406 int32_t maxColIndex = std::max(aStartColumnIndex, aEndColumnIndex);
2408 // Strong reference because we sometimes remove the range
2409 nsRefPtr<nsRange> range = GetFirstCellRange();
2410 nsIContent* cellNode = GetFirstSelectedContent(range);
2411 NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range");
2413 int32_t curRowIndex, curColIndex;
2414 while (cellNode)
2415 {
2416 nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex);
2417 if (NS_FAILED(result))
2418 return result;
2420 #ifdef DEBUG_TABLE_SELECTION
2421 if (!range)
2422 printf("RemoveCellsToSelection -- range is null\n");
2423 #endif
2425 if (range) {
2426 if (aRemoveOutsideOfCellRange) {
2427 if (curRowIndex < minRowIndex || curRowIndex > maxRowIndex ||
2428 curColIndex < minColIndex || curColIndex > maxColIndex) {
2430 mDomSelections[index]->RemoveRange(range);
2431 // Since we've removed the range, decrement pointer to next range
2432 mSelectedCellIndex--;
2433 }
2435 } else {
2436 // Remove cell from selection if it belongs to the given cells range or
2437 // it is spanned onto the cells range.
2438 nsTableCellFrame* cellFrame =
2439 tableFrame->GetCellFrameAt(curRowIndex, curColIndex);
2441 int32_t origRowIndex, origColIndex;
2442 cellFrame->GetRowIndex(origRowIndex);
2443 cellFrame->GetColIndex(origColIndex);
2444 uint32_t actualRowSpan =
2445 tableFrame->GetEffectiveRowSpanAt(origRowIndex, origColIndex);
2446 uint32_t actualColSpan =
2447 tableFrame->GetEffectiveColSpanAt(curRowIndex, curColIndex);
2448 if (origRowIndex <= maxRowIndex && maxRowIndex >= 0 &&
2449 origRowIndex + actualRowSpan - 1 >= static_cast<uint32_t>(minRowIndex) &&
2450 origColIndex <= maxColIndex && maxColIndex >= 0 &&
2451 origColIndex + actualColSpan - 1 >= static_cast<uint32_t>(minColIndex)) {
2453 mDomSelections[index]->RemoveRange(range);
2454 // Since we've removed the range, decrement pointer to next range
2455 mSelectedCellIndex--;
2456 }
2457 }
2458 }
2460 range = GetNextCellRange();
2461 cellNode = GetFirstSelectedContent(range);
2462 NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range");
2463 }
2465 return NS_OK;
2466 }
2468 nsresult
2469 nsFrameSelection::AddCellsToSelection(nsIContent *aTableContent,
2470 int32_t aStartRowIndex,
2471 int32_t aStartColumnIndex,
2472 int32_t aEndRowIndex,
2473 int32_t aEndColumnIndex)
2474 {
2475 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2476 if (!mDomSelections[index])
2477 return NS_ERROR_NULL_POINTER;
2479 nsTableOuterFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame());
2480 if (!tableFrame) // Check that |table| is a table.
2481 return NS_ERROR_FAILURE;
2483 nsresult result = NS_OK;
2484 int32_t row = aStartRowIndex;
2485 while(true)
2486 {
2487 int32_t col = aStartColumnIndex;
2488 while(true)
2489 {
2490 nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col);
2492 // Skip cells that are spanned from previous locations or are already selected
2493 if (cellFrame) {
2494 int32_t origRow, origCol;
2495 cellFrame->GetRowIndex(origRow);
2496 cellFrame->GetColIndex(origCol);
2497 if (origRow == row && origCol == col && !cellFrame->IsSelected()) {
2498 result = SelectCellElement(cellFrame->GetContent());
2499 if (NS_FAILED(result)) return result;
2500 }
2501 }
2502 // Done when we reach end column
2503 if (col == aEndColumnIndex) break;
2505 if (aStartColumnIndex < aEndColumnIndex)
2506 col ++;
2507 else
2508 col--;
2509 };
2510 if (row == aEndRowIndex) break;
2512 if (aStartRowIndex < aEndRowIndex)
2513 row++;
2514 else
2515 row--;
2516 };
2517 return result;
2518 }
2520 nsresult
2521 nsFrameSelection::RemoveCellsFromSelection(nsIContent *aTable,
2522 int32_t aStartRowIndex,
2523 int32_t aStartColumnIndex,
2524 int32_t aEndRowIndex,
2525 int32_t aEndColumnIndex)
2526 {
2527 return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex,
2528 aEndRowIndex, aEndColumnIndex, false);
2529 }
2531 nsresult
2532 nsFrameSelection::RestrictCellsToSelection(nsIContent *aTable,
2533 int32_t aStartRowIndex,
2534 int32_t aStartColumnIndex,
2535 int32_t aEndRowIndex,
2536 int32_t aEndColumnIndex)
2537 {
2538 return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex,
2539 aEndRowIndex, aEndColumnIndex, true);
2540 }
2542 nsresult
2543 nsFrameSelection::SelectRowOrColumn(nsIContent *aCellContent, uint32_t aTarget)
2544 {
2545 if (!aCellContent) return NS_ERROR_NULL_POINTER;
2547 nsIContent* table = GetParentTable(aCellContent);
2548 if (!table) return NS_ERROR_NULL_POINTER;
2550 // Get table and cell layout interfaces to access
2551 // cell data based on cellmap location
2552 // Frames are not ref counted, so don't use an nsCOMPtr
2553 nsTableOuterFrame* tableFrame = do_QueryFrame(table->GetPrimaryFrame());
2554 if (!tableFrame) return NS_ERROR_FAILURE;
2555 nsITableCellLayout *cellLayout = GetCellLayout(aCellContent);
2556 if (!cellLayout) return NS_ERROR_FAILURE;
2558 // Get location of target cell:
2559 int32_t rowIndex, colIndex;
2560 nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex);
2561 if (NS_FAILED(result)) return result;
2563 // Be sure we start at proper beginning
2564 // (This allows us to select row or col given ANY cell!)
2565 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW)
2566 colIndex = 0;
2567 if (aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN)
2568 rowIndex = 0;
2570 nsCOMPtr<nsIContent> firstCell, lastCell;
2571 while (true) {
2572 // Loop through all cells in column or row to find first and last
2573 nsCOMPtr<nsIContent> curCellContent =
2574 tableFrame->GetCellAt(rowIndex, colIndex);
2575 if (!curCellContent)
2576 break;
2578 if (!firstCell)
2579 firstCell = curCellContent;
2581 lastCell = curCellContent.forget();
2583 // Move to next cell in cellmap, skipping spanned locations
2584 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW)
2585 colIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
2586 else
2587 rowIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex);
2588 }
2590 // Use SelectBlockOfCells:
2591 // This will replace existing selection,
2592 // but allow unselecting by dragging out of selected region
2593 if (firstCell && lastCell)
2594 {
2595 if (!mStartSelectedCell)
2596 {
2597 // We are starting a new block, so select the first cell
2598 result = SelectCellElement(firstCell);
2599 if (NS_FAILED(result)) return result;
2600 mStartSelectedCell = firstCell;
2601 }
2602 nsCOMPtr<nsIContent> lastCellContent = do_QueryInterface(lastCell);
2603 result = SelectBlockOfCells(mStartSelectedCell, lastCellContent);
2605 // This gets set to the cell at end of row/col,
2606 // but we need it to be the cell under cursor
2607 mEndSelectedCell = aCellContent;
2608 return result;
2609 }
2611 #if 0
2612 // This is a more efficient strategy that appends row to current selection,
2613 // but doesn't allow dragging OFF of an existing selection to unselect!
2614 do {
2615 // Loop through all cells in column or row
2616 result = tableLayout->GetCellDataAt(rowIndex, colIndex,
2617 getter_AddRefs(cellElement),
2618 curRowIndex, curColIndex,
2619 rowSpan, colSpan,
2620 actualRowSpan, actualColSpan,
2621 isSelected);
2622 if (NS_FAILED(result)) return result;
2623 // We're done when cell is not found
2624 if (!cellElement) break;
2627 // Check spans else we infinitely loop
2628 NS_ASSERTION(actualColSpan, "actualColSpan is 0!");
2629 NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!");
2631 // Skip cells that are already selected or span from outside our region
2632 if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex)
2633 {
2634 result = SelectCellElement(cellElement);
2635 if (NS_FAILED(result)) return result;
2636 }
2637 // Move to next row or column in cellmap, skipping spanned locations
2638 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW)
2639 colIndex += actualColSpan;
2640 else
2641 rowIndex += actualRowSpan;
2642 }
2643 while (cellElement);
2644 #endif
2646 return NS_OK;
2647 }
2649 nsIContent*
2650 nsFrameSelection::GetFirstCellNodeInRange(nsRange *aRange) const
2651 {
2652 if (!aRange) return nullptr;
2654 nsINode* startParent = aRange->GetStartParent();
2655 if (!startParent)
2656 return nullptr;
2658 int32_t offset = aRange->StartOffset();
2660 nsIContent* childContent = startParent->GetChildAt(offset);
2661 if (!childContent)
2662 return nullptr;
2663 // Don't return node if not a cell
2664 if (!IsCell(childContent))
2665 return nullptr;
2667 return childContent;
2668 }
2670 nsRange*
2671 nsFrameSelection::GetFirstCellRange()
2672 {
2673 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2674 if (!mDomSelections[index])
2675 return nullptr;
2677 nsRange* firstRange = mDomSelections[index]->GetRangeAt(0);
2678 if (!GetFirstCellNodeInRange(firstRange)) {
2679 return nullptr;
2680 }
2682 // Setup for next cell
2683 mSelectedCellIndex = 1;
2685 return firstRange;
2686 }
2688 nsRange*
2689 nsFrameSelection::GetNextCellRange()
2690 {
2691 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2692 if (!mDomSelections[index])
2693 return nullptr;
2695 nsRange* range = mDomSelections[index]->GetRangeAt(mSelectedCellIndex);
2697 // Get first node in next range of selection - test if it's a cell
2698 if (!GetFirstCellNodeInRange(range)) {
2699 return nullptr;
2700 }
2702 // Setup for next cell
2703 mSelectedCellIndex++;
2705 return range;
2706 }
2708 nsresult
2709 nsFrameSelection::GetCellIndexes(nsIContent *aCell,
2710 int32_t &aRowIndex,
2711 int32_t &aColIndex)
2712 {
2713 if (!aCell) return NS_ERROR_NULL_POINTER;
2715 aColIndex=0; // initialize out params
2716 aRowIndex=0;
2718 nsITableCellLayout *cellLayoutObject = GetCellLayout(aCell);
2719 if (!cellLayoutObject) return NS_ERROR_FAILURE;
2720 return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex);
2721 }
2723 nsIContent*
2724 nsFrameSelection::IsInSameTable(nsIContent *aContent1,
2725 nsIContent *aContent2) const
2726 {
2727 if (!aContent1 || !aContent2) return nullptr;
2729 nsIContent* tableNode1 = GetParentTable(aContent1);
2730 nsIContent* tableNode2 = GetParentTable(aContent2);
2732 // Must be in the same table. Note that we want to return false for
2733 // the test if both tables are null.
2734 return (tableNode1 == tableNode2) ? tableNode1 : nullptr;
2735 }
2737 nsIContent*
2738 nsFrameSelection::GetParentTable(nsIContent *aCell) const
2739 {
2740 if (!aCell) {
2741 return nullptr;
2742 }
2744 for (nsIContent* parent = aCell->GetParent(); parent;
2745 parent = parent->GetParent()) {
2746 if (parent->Tag() == nsGkAtoms::table &&
2747 parent->IsHTML()) {
2748 return parent;
2749 }
2750 }
2752 return nullptr;
2753 }
2755 nsresult
2756 nsFrameSelection::SelectCellElement(nsIContent *aCellElement)
2757 {
2758 nsIContent *parent = aCellElement->GetParent();
2760 // Get child offset
2761 int32_t offset = parent->IndexOf(aCellElement);
2763 return CreateAndAddRange(parent, offset);
2764 }
2766 nsresult
2767 Selection::getTableCellLocationFromRange(nsRange* aRange,
2768 int32_t* aSelectionType,
2769 int32_t* aRow, int32_t* aCol)
2770 {
2771 if (!aRange || !aSelectionType || !aRow || !aCol)
2772 return NS_ERROR_NULL_POINTER;
2774 *aSelectionType = nsISelectionPrivate::TABLESELECTION_NONE;
2775 *aRow = 0;
2776 *aCol = 0;
2778 // Must have access to frame selection to get cell info
2779 if (!mFrameSelection) return NS_OK;
2781 nsresult result = GetTableSelectionType(aRange, aSelectionType);
2782 if (NS_FAILED(result)) return result;
2784 // Don't fail if range does not point to a single table cell,
2785 // let aSelectionType tell user if we don't have a cell
2786 if (*aSelectionType != nsISelectionPrivate::TABLESELECTION_CELL)
2787 return NS_OK;
2789 // Get the child content (the cell) pointed to by starting node of range
2790 // We do minimal checking since GetTableSelectionType assures
2791 // us that this really is a table cell
2792 nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetStartParent());
2793 if (!content)
2794 return NS_ERROR_FAILURE;
2796 nsIContent *child = content->GetChildAt(aRange->StartOffset());
2797 if (!child)
2798 return NS_ERROR_FAILURE;
2800 //Note: This is a non-ref-counted pointer to the frame
2801 nsITableCellLayout *cellLayout = mFrameSelection->GetCellLayout(child);
2802 if (NS_FAILED(result))
2803 return result;
2804 if (!cellLayout)
2805 return NS_ERROR_FAILURE;
2807 return cellLayout->GetCellIndexes(*aRow, *aCol);
2808 }
2810 nsresult
2811 Selection::addTableCellRange(nsRange* aRange, bool* aDidAddRange,
2812 int32_t* aOutIndex)
2813 {
2814 if (!aDidAddRange || !aOutIndex)
2815 return NS_ERROR_NULL_POINTER;
2817 *aDidAddRange = false;
2818 *aOutIndex = -1;
2820 if (!mFrameSelection)
2821 return NS_OK;
2823 if (!aRange)
2824 return NS_ERROR_NULL_POINTER;
2826 nsresult result;
2828 // Get if we are adding a cell selection and the row, col of cell if we are
2829 int32_t newRow, newCol, tableMode;
2830 result = getTableCellLocationFromRange(aRange, &tableMode, &newRow, &newCol);
2831 if (NS_FAILED(result)) return result;
2833 // If not adding a cell range, we are done here
2834 if (tableMode != nsISelectionPrivate::TABLESELECTION_CELL)
2835 {
2836 mFrameSelection->mSelectingTableCellMode = tableMode;
2837 // Don't fail if range isn't a selected cell, aDidAddRange tells caller if we didn't proceed
2838 return NS_OK;
2839 }
2841 // Set frame selection mode only if not already set to a table mode
2842 // so we don't lose the select row and column flags (not detected by getTableCellLocation)
2843 if (mFrameSelection->mSelectingTableCellMode == TABLESELECTION_NONE)
2844 mFrameSelection->mSelectingTableCellMode = tableMode;
2846 *aDidAddRange = true;
2847 return AddItem(aRange, aOutIndex);
2848 }
2850 //TODO: Figure out TABLESELECTION_COLUMN and TABLESELECTION_ALLCELLS
2851 nsresult
2852 Selection::GetTableSelectionType(nsIDOMRange* aDOMRange,
2853 int32_t* aTableSelectionType)
2854 {
2855 if (!aDOMRange || !aTableSelectionType)
2856 return NS_ERROR_NULL_POINTER;
2857 nsRange* range = static_cast<nsRange*>(aDOMRange);
2859 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_NONE;
2861 // Must have access to frame selection to get cell info
2862 if(!mFrameSelection) return NS_OK;
2864 nsINode* startNode = range->GetStartParent();
2865 if (!startNode) return NS_ERROR_FAILURE;
2867 nsINode* endNode = range->GetEndParent();
2868 if (!endNode) return NS_ERROR_FAILURE;
2870 // Not a single selected node
2871 if (startNode != endNode) return NS_OK;
2873 int32_t startOffset = range->StartOffset();
2874 int32_t endOffset = range->EndOffset();
2876 // Not a single selected node
2877 if ((endOffset - startOffset) != 1)
2878 return NS_OK;
2880 nsIContent* startContent = static_cast<nsIContent*>(startNode);
2881 if (!(startNode->IsElement() && startContent->IsHTML())) {
2882 // Implies a check for being an element; if we ever make this work
2883 // for non-HTML, need to keep checking for elements.
2884 return NS_OK;
2885 }
2887 nsIAtom *tag = startContent->Tag();
2889 if (tag == nsGkAtoms::tr)
2890 {
2891 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_CELL;
2892 }
2893 else //check to see if we are selecting a table or row (column and all cells not done yet)
2894 {
2895 nsIContent *child = startNode->GetChildAt(startOffset);
2896 if (!child)
2897 return NS_ERROR_FAILURE;
2899 tag = child->Tag();
2901 if (tag == nsGkAtoms::table)
2902 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_TABLE;
2903 else if (tag == nsGkAtoms::tr)
2904 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_ROW;
2905 }
2907 return NS_OK;
2908 }
2910 nsresult
2911 nsFrameSelection::CreateAndAddRange(nsINode *aParentNode, int32_t aOffset)
2912 {
2913 if (!aParentNode) return NS_ERROR_NULL_POINTER;
2915 nsRefPtr<nsRange> range = new nsRange(aParentNode);
2917 // Set range around child at given offset
2918 nsresult result = range->SetStart(aParentNode, aOffset);
2919 if (NS_FAILED(result)) return result;
2920 result = range->SetEnd(aParentNode, aOffset+1);
2921 if (NS_FAILED(result)) return result;
2923 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2924 if (!mDomSelections[index])
2925 return NS_ERROR_NULL_POINTER;
2927 return mDomSelections[index]->AddRange(range);
2928 }
2930 // End of Table Selection
2932 void
2933 nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter)
2934 {
2935 if (mAncestorLimiter != aLimiter) {
2936 mAncestorLimiter = aLimiter;
2937 int8_t index =
2938 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2939 if (!mDomSelections[index])
2940 return;
2942 if (!IsValidSelectionPoint(this, mDomSelections[index]->GetFocusNode())) {
2943 ClearNormalSelection();
2944 if (mAncestorLimiter) {
2945 PostReason(nsISelectionListener::NO_REASON);
2946 TakeFocus(mAncestorLimiter, 0, 0, HINTLEFT, false, false);
2947 }
2948 }
2949 }
2950 }
2952 //END nsFrameSelection methods
2955 //BEGIN nsISelection interface implementations
2959 nsresult
2960 nsFrameSelection::DeleteFromDocument()
2961 {
2962 nsresult res;
2964 // If we're already collapsed, then we do nothing (bug 719503).
2965 bool isCollapsed;
2966 int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2967 if (!mDomSelections[index])
2968 return NS_ERROR_NULL_POINTER;
2970 mDomSelections[index]->GetIsCollapsed( &isCollapsed);
2971 if (isCollapsed)
2972 {
2973 return NS_OK;
2974 }
2976 nsRefPtr<Selection> selection = mDomSelections[index];
2977 for (int32_t rangeIdx = 0; rangeIdx < selection->GetRangeCount(); ++rangeIdx) {
2978 nsRefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
2979 res = range->DeleteContents();
2980 if (NS_FAILED(res))
2981 return res;
2982 }
2984 // Collapse to the new location.
2985 // If we deleted one character, then we move back one element.
2986 // FIXME We don't know how to do this past frame boundaries yet.
2987 if (isCollapsed)
2988 mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset()-1);
2989 else if (mDomSelections[index]->AnchorOffset() > 0)
2990 mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset());
2991 #ifdef DEBUG
2992 else
2993 printf("Don't know how to set selection back past frame boundary\n");
2994 #endif
2996 return NS_OK;
2997 }
2999 void
3000 nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent* aMouseEvent)
3001 {
3002 if (aMouseEvent) {
3003 mDelayedMouseEventValid = true;
3004 mDelayedMouseEventIsShift = aMouseEvent->IsShift();
3005 mDelayedMouseEventClickCount = aMouseEvent->clickCount;
3006 } else {
3007 mDelayedMouseEventValid = false;
3008 }
3009 }
3011 void
3012 nsFrameSelection::DisconnectFromPresShell()
3013 {
3014 StopAutoScrollTimer();
3015 for (int32_t i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; i++) {
3016 mDomSelections[i]->Clear(nullptr);
3017 }
3018 mShell = nullptr;
3019 }
3021 //END nsISelection interface implementations
3023 #if 0
3024 #pragma mark -
3025 #endif
3027 // mozilla::dom::Selection implementation
3029 // note: this can return a nil anchor node
3031 Selection::Selection()
3032 : mCachedOffsetForFrame(nullptr)
3033 , mDirection(eDirNext)
3034 , mType(nsISelectionController::SELECTION_NORMAL)
3035 {
3036 SetIsDOMBinding();
3037 }
3039 Selection::Selection(nsFrameSelection* aList)
3040 : mFrameSelection(aList)
3041 , mCachedOffsetForFrame(nullptr)
3042 , mDirection(eDirNext)
3043 , mType(nsISelectionController::SELECTION_NORMAL)
3044 {
3045 SetIsDOMBinding();
3046 }
3048 Selection::~Selection()
3049 {
3050 setAnchorFocusRange(-1);
3052 uint32_t count = mRanges.Length();
3053 for (uint32_t i = 0; i < count; ++i) {
3054 mRanges[i].mRange->SetInSelection(false);
3055 }
3057 if (mAutoScrollTimer) {
3058 mAutoScrollTimer->Stop();
3059 mAutoScrollTimer = nullptr;
3060 }
3062 mScrollEvent.Revoke();
3064 if (mCachedOffsetForFrame) {
3065 delete mCachedOffsetForFrame;
3066 mCachedOffsetForFrame = nullptr;
3067 }
3068 }
3070 nsIDocument*
3071 Selection::GetParentObject() const
3072 {
3073 nsIPresShell* shell = GetPresShell();
3074 if (shell) {
3075 return shell->GetDocument();
3076 }
3077 return nullptr;
3078 }
3080 NS_IMPL_CYCLE_COLLECTION_CLASS(Selection)
3082 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection)
3083 // Unlink the selection listeners *before* we do RemoveAllRanges since
3084 // we don't want to notify the listeners during JS GC (they could be
3085 // in JS!).
3086 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners)
3087 tmp->RemoveAllRanges();
3088 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection)
3089 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
3090 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3091 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection)
3092 {
3093 uint32_t i, count = tmp->mRanges.Length();
3094 for (i = 0; i < count; ++i) {
3095 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRanges[i].mRange)
3096 }
3097 }
3098 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange)
3099 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection)
3100 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners)
3101 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
3102 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3103 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Selection)
3105 DOMCI_DATA(Selection, Selection)
3107 // QueryInterface implementation for Selection
3108 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Selection)
3109 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
3110 NS_INTERFACE_MAP_ENTRY(nsISelection)
3111 NS_INTERFACE_MAP_ENTRY(nsISelectionPrivate)
3112 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
3113 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelection)
3114 NS_INTERFACE_MAP_END
3116 NS_IMPL_CYCLE_COLLECTING_ADDREF(Selection)
3117 NS_IMPL_CYCLE_COLLECTING_RELEASE(Selection)
3120 NS_IMETHODIMP
3121 Selection::GetAnchorNode(nsIDOMNode** aAnchorNode)
3122 {
3123 nsINode* anchorNode = GetAnchorNode();
3124 if (anchorNode) {
3125 return CallQueryInterface(anchorNode, aAnchorNode);
3126 }
3128 *aAnchorNode = nullptr;
3129 return NS_OK;
3130 }
3132 nsINode*
3133 Selection::GetAnchorNode()
3134 {
3135 if (!mAnchorFocusRange)
3136 return nullptr;
3138 if (GetDirection() == eDirNext) {
3139 return mAnchorFocusRange->GetStartParent();
3140 }
3142 return mAnchorFocusRange->GetEndParent();
3143 }
3145 NS_IMETHODIMP
3146 Selection::GetAnchorOffset(int32_t* aAnchorOffset)
3147 {
3148 *aAnchorOffset = static_cast<int32_t>(AnchorOffset());
3149 return NS_OK;
3150 }
3152 // note: this can return a nil focus node
3153 NS_IMETHODIMP
3154 Selection::GetFocusNode(nsIDOMNode** aFocusNode)
3155 {
3156 nsINode* focusNode = GetFocusNode();
3157 if (focusNode) {
3158 return CallQueryInterface(focusNode, aFocusNode);
3159 }
3161 *aFocusNode = nullptr;
3162 return NS_OK;
3163 }
3165 nsINode*
3166 Selection::GetFocusNode()
3167 {
3168 if (!mAnchorFocusRange)
3169 return nullptr;
3171 if (GetDirection() == eDirNext){
3172 return mAnchorFocusRange->GetEndParent();
3173 }
3175 return mAnchorFocusRange->GetStartParent();
3176 }
3178 NS_IMETHODIMP
3179 Selection::GetFocusOffset(int32_t* aFocusOffset)
3180 {
3181 *aFocusOffset = static_cast<int32_t>(FocusOffset());
3182 return NS_OK;
3183 }
3185 void
3186 Selection::setAnchorFocusRange(int32_t indx)
3187 {
3188 if (indx >= (int32_t)mRanges.Length())
3189 return;
3190 if (indx < 0) //release all
3191 {
3192 mAnchorFocusRange = nullptr;
3193 }
3194 else{
3195 mAnchorFocusRange = mRanges[indx].mRange;
3196 }
3197 }
3199 uint32_t
3200 Selection::AnchorOffset()
3201 {
3202 if (!mAnchorFocusRange)
3203 return 0;
3205 if (GetDirection() == eDirNext){
3206 return mAnchorFocusRange->StartOffset();
3207 }
3209 return mAnchorFocusRange->EndOffset();
3210 }
3212 uint32_t
3213 Selection::FocusOffset()
3214 {
3215 if (!mAnchorFocusRange)
3216 return 0;
3218 if (GetDirection() == eDirNext){
3219 return mAnchorFocusRange->EndOffset();
3220 }
3222 return mAnchorFocusRange->StartOffset();
3223 }
3225 static nsresult
3226 CompareToRangeStart(nsINode* aCompareNode, int32_t aCompareOffset,
3227 nsRange* aRange, int32_t* aCmp)
3228 {
3229 nsINode* start = aRange->GetStartParent();
3230 NS_ENSURE_STATE(aCompareNode && start);
3231 // If the nodes that we're comparing are not in the same document,
3232 // assume that aCompareNode will fall at the end of the ranges.
3233 if (aCompareNode->GetCurrentDoc() != start->GetCurrentDoc() ||
3234 !start->GetCurrentDoc()) {
3235 *aCmp = 1;
3236 } else {
3237 *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset,
3238 start, aRange->StartOffset());
3239 }
3240 return NS_OK;
3241 }
3243 static nsresult
3244 CompareToRangeEnd(nsINode* aCompareNode, int32_t aCompareOffset,
3245 nsRange* aRange, int32_t* aCmp)
3246 {
3247 nsINode* end = aRange->GetEndParent();
3248 NS_ENSURE_STATE(aCompareNode && end);
3249 // If the nodes that we're comparing are not in the same document,
3250 // assume that aCompareNode will fall at the end of the ranges.
3251 if (aCompareNode->GetCurrentDoc() != end->GetCurrentDoc() ||
3252 !end->GetCurrentDoc()) {
3253 *aCmp = 1;
3254 } else {
3255 *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset,
3256 end, aRange->EndOffset());
3257 }
3258 return NS_OK;
3259 }
3261 // Selection::FindInsertionPoint
3262 //
3263 // Binary searches the given sorted array of ranges for the insertion point
3264 // for the given node/offset. The given comparator is used, and the index
3265 // where the point should appear in the array is placed in *aInsertionPoint.
3266 //
3267 // If there is an item in the array equal to the input point, we will return
3268 // the index of this item.
3270 nsresult
3271 Selection::FindInsertionPoint(
3272 nsTArray<RangeData>* aElementArray,
3273 nsINode* aPointNode, int32_t aPointOffset,
3274 nsresult (*aComparator)(nsINode*,int32_t,nsRange*,int32_t*),
3275 int32_t* aPoint)
3276 {
3277 *aPoint = 0;
3278 int32_t beginSearch = 0;
3279 int32_t endSearch = aElementArray->Length(); // one beyond what to check
3281 if (endSearch) {
3282 int32_t center = endSearch - 1; // Check last index, then binary search
3283 do {
3284 nsRange* range = (*aElementArray)[center].mRange;
3286 int32_t cmp;
3287 nsresult rv = aComparator(aPointNode, aPointOffset, range, &cmp);
3288 NS_ENSURE_SUCCESS(rv, rv);
3290 if (cmp < 0) { // point < cur
3291 endSearch = center;
3292 } else if (cmp > 0) { // point > cur
3293 beginSearch = center + 1;
3294 } else { // found match, done
3295 beginSearch = center;
3296 break;
3297 }
3298 center = (endSearch - beginSearch) / 2 + beginSearch;
3299 } while (endSearch - beginSearch > 0);
3300 }
3302 *aPoint = beginSearch;
3303 return NS_OK;
3304 }
3306 // Selection::SubtractRange
3307 //
3308 // A helper function that subtracts aSubtract from aRange, and adds
3309 // 1 or 2 RangeData objects representing the remaining non-overlapping
3310 // difference to aOutput. It is assumed that the caller has checked that
3311 // aRange and aSubtract do indeed overlap
3313 nsresult
3314 Selection::SubtractRange(RangeData* aRange, nsRange* aSubtract,
3315 nsTArray<RangeData>* aOutput)
3316 {
3317 nsRange* range = aRange->mRange;
3319 // First we want to compare to the range start
3320 int32_t cmp;
3321 nsresult rv = CompareToRangeStart(range->GetStartParent(),
3322 range->StartOffset(),
3323 aSubtract, &cmp);
3324 NS_ENSURE_SUCCESS(rv, rv);
3326 // Also, make a comparison to the range end
3327 int32_t cmp2;
3328 rv = CompareToRangeEnd(range->GetEndParent(),
3329 range->EndOffset(),
3330 aSubtract, &cmp2);
3331 NS_ENSURE_SUCCESS(rv, rv);
3333 // If the existing range left overlaps the new range (aSubtract) then
3334 // cmp < 0, and cmp2 < 0
3335 // If it right overlaps the new range then cmp > 0 and cmp2 > 0
3336 // If it fully contains the new range, then cmp < 0 and cmp2 > 0
3338 if (cmp2 > 0) {
3339 // We need to add a new RangeData to the output, running from
3340 // the end of aSubtract to the end of range
3341 nsRefPtr<nsRange> postOverlap = new nsRange(aSubtract->GetEndParent());
3343 rv =
3344 postOverlap->SetStart(aSubtract->GetEndParent(), aSubtract->EndOffset());
3345 NS_ENSURE_SUCCESS(rv, rv);
3346 rv =
3347 postOverlap->SetEnd(range->GetEndParent(), range->EndOffset());
3348 NS_ENSURE_SUCCESS(rv, rv);
3349 if (!postOverlap->Collapsed()) {
3350 if (!aOutput->InsertElementAt(0, RangeData(postOverlap)))
3351 return NS_ERROR_OUT_OF_MEMORY;
3352 (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
3353 }
3354 }
3356 if (cmp < 0) {
3357 // We need to add a new RangeData to the output, running from
3358 // the start of the range to the start of aSubtract
3359 nsRefPtr<nsRange> preOverlap = new nsRange(range->GetStartParent());
3361 nsresult rv =
3362 preOverlap->SetStart(range->GetStartParent(), range->StartOffset());
3363 NS_ENSURE_SUCCESS(rv, rv);
3364 rv =
3365 preOverlap->SetEnd(aSubtract->GetStartParent(), aSubtract->StartOffset());
3366 NS_ENSURE_SUCCESS(rv, rv);
3368 if (!preOverlap->Collapsed()) {
3369 if (!aOutput->InsertElementAt(0, RangeData(preOverlap)))
3370 return NS_ERROR_OUT_OF_MEMORY;
3371 (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
3372 }
3373 }
3375 return NS_OK;
3376 }
3378 nsresult
3379 Selection::AddItem(nsRange* aItem, int32_t* aOutIndex)
3380 {
3381 if (!aItem)
3382 return NS_ERROR_NULL_POINTER;
3383 if (!aItem->IsPositioned())
3384 return NS_ERROR_UNEXPECTED;
3386 NS_ASSERTION(aOutIndex, "aOutIndex can't be null");
3388 *aOutIndex = -1;
3390 // a common case is that we have no ranges yet
3391 if (mRanges.Length() == 0) {
3392 if (!mRanges.AppendElement(RangeData(aItem)))
3393 return NS_ERROR_OUT_OF_MEMORY;
3394 aItem->SetInSelection(true);
3396 *aOutIndex = 0;
3397 return NS_OK;
3398 }
3400 int32_t startIndex, endIndex;
3401 nsresult rv = GetIndicesForInterval(aItem->GetStartParent(),
3402 aItem->StartOffset(),
3403 aItem->GetEndParent(),
3404 aItem->EndOffset(), false,
3405 &startIndex, &endIndex);
3406 NS_ENSURE_SUCCESS(rv, rv);
3408 if (endIndex == -1) {
3409 // All ranges start after the given range. We can insert our range at
3410 // position 0, knowing there are no overlaps (handled below)
3411 startIndex = 0;
3412 endIndex = 0;
3413 } else if (startIndex == -1) {
3414 // All ranges end before the given range. We can insert our range at
3415 // the end of the array, knowing there are no overlaps (handled below)
3416 startIndex = mRanges.Length();
3417 endIndex = startIndex;
3418 }
3420 // If the range is already contained in mRanges, silently succeed
3421 bool sameRange = EqualsRangeAtPoint(aItem->GetStartParent(),
3422 aItem->StartOffset(),
3423 aItem->GetEndParent(),
3424 aItem->EndOffset(), startIndex);
3425 if (sameRange) {
3426 *aOutIndex = startIndex;
3427 return NS_OK;
3428 }
3430 if (startIndex == endIndex) {
3431 // The new range doesn't overlap any existing ranges
3432 if (!mRanges.InsertElementAt(startIndex, RangeData(aItem)))
3433 return NS_ERROR_OUT_OF_MEMORY;
3434 aItem->SetInSelection(true);
3435 *aOutIndex = startIndex;
3436 return NS_OK;
3437 }
3439 // We now know that at least 1 existing range overlaps with the range that
3440 // we are trying to add. In fact, the only ranges of interest are those at
3441 // the two end points, startIndex and endIndex - 1 (which may point to the
3442 // same range) as these may partially overlap the new range. Any ranges
3443 // between these indices are fully overlapped by the new range, and so can be
3444 // removed
3445 nsTArray<RangeData> overlaps;
3446 if (!overlaps.InsertElementAt(0, mRanges[startIndex]))
3447 return NS_ERROR_OUT_OF_MEMORY;
3449 if (endIndex - 1 != startIndex) {
3450 if (!overlaps.InsertElementAt(1, mRanges[endIndex - 1]))
3451 return NS_ERROR_OUT_OF_MEMORY;
3452 }
3454 // Remove all the overlapping ranges
3455 for (int32_t i = startIndex; i < endIndex; ++i) {
3456 mRanges[i].mRange->SetInSelection(false);
3457 }
3458 mRanges.RemoveElementsAt(startIndex, endIndex - startIndex);
3460 nsTArray<RangeData> temp;
3461 for (int32_t i = overlaps.Length() - 1; i >= 0; i--) {
3462 nsresult rv = SubtractRange(&overlaps[i], aItem, &temp);
3463 NS_ENSURE_SUCCESS(rv, rv);
3464 }
3466 // Insert the new element into our "leftovers" array
3467 int32_t insertionPoint;
3468 rv = FindInsertionPoint(&temp, aItem->GetStartParent(),
3469 aItem->StartOffset(), CompareToRangeStart,
3470 &insertionPoint);
3471 NS_ENSURE_SUCCESS(rv, rv);
3473 if (!temp.InsertElementAt(insertionPoint, RangeData(aItem)))
3474 return NS_ERROR_OUT_OF_MEMORY;
3476 // Merge the leftovers back in to mRanges
3477 if (!mRanges.InsertElementsAt(startIndex, temp))
3478 return NS_ERROR_OUT_OF_MEMORY;
3480 for (uint32_t i = 0; i < temp.Length(); ++i) {
3481 temp[i].mRange->SetInSelection(true);
3482 }
3484 *aOutIndex = startIndex + insertionPoint;
3485 return NS_OK;
3486 }
3488 nsresult
3489 Selection::RemoveItem(nsRange* aItem)
3490 {
3491 if (!aItem)
3492 return NS_ERROR_NULL_POINTER;
3494 // Find the range's index & remove it. We could use FindInsertionPoint to
3495 // get O(log n) time, but that requires many expensive DOM comparisons.
3496 // For even several thousand items, this is probably faster because the
3497 // comparisons are so fast.
3498 int32_t idx = -1;
3499 uint32_t i;
3500 for (i = 0; i < mRanges.Length(); i ++) {
3501 if (mRanges[i].mRange == aItem) {
3502 idx = (int32_t)i;
3503 break;
3504 }
3505 }
3506 if (idx < 0)
3507 return NS_ERROR_INVALID_ARG;
3509 mRanges.RemoveElementAt(idx);
3510 aItem->SetInSelection(false);
3511 return NS_OK;
3512 }
3514 nsresult
3515 Selection::RemoveCollapsedRanges()
3516 {
3517 uint32_t i = 0;
3518 while (i < mRanges.Length()) {
3519 if (mRanges[i].mRange->Collapsed()) {
3520 nsresult rv = RemoveItem(mRanges[i].mRange);
3521 NS_ENSURE_SUCCESS(rv, rv);
3522 } else {
3523 ++i;
3524 }
3525 }
3526 return NS_OK;
3527 }
3529 nsresult
3530 Selection::Clear(nsPresContext* aPresContext)
3531 {
3532 setAnchorFocusRange(-1);
3534 for (uint32_t i = 0; i < mRanges.Length(); ++i) {
3535 mRanges[i].mRange->SetInSelection(false);
3536 selectFrames(aPresContext, mRanges[i].mRange, false);
3537 }
3538 mRanges.Clear();
3540 // Reset direction so for more dependable table selection range handling
3541 SetDirection(eDirNext);
3543 // If this was an ATTENTION selection, change it back to normal now
3544 if (mFrameSelection &&
3545 mFrameSelection->GetDisplaySelection() ==
3546 nsISelectionController::SELECTION_ATTENTION) {
3547 mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON);
3548 }
3550 return NS_OK;
3551 }
3553 NS_IMETHODIMP
3554 Selection::GetType(int16_t* aType)
3555 {
3556 NS_ENSURE_ARG_POINTER(aType);
3557 *aType = Type();
3559 return NS_OK;
3560 }
3562 // RangeMatches*Point
3563 //
3564 // Compares the range beginning or ending point, and returns true if it
3565 // exactly matches the given DOM point.
3567 static inline bool
3568 RangeMatchesBeginPoint(nsRange* aRange, nsINode* aNode, int32_t aOffset)
3569 {
3570 return aRange->GetStartParent() == aNode && aRange->StartOffset() == aOffset;
3571 }
3573 static inline bool
3574 RangeMatchesEndPoint(nsRange* aRange, nsINode* aNode, int32_t aOffset)
3575 {
3576 return aRange->GetEndParent() == aNode && aRange->EndOffset() == aOffset;
3577 }
3579 // Selection::EqualsRangeAtPoint
3580 //
3581 // Utility method for checking equivalence of two ranges.
3583 bool
3584 Selection::EqualsRangeAtPoint(
3585 nsINode* aBeginNode, int32_t aBeginOffset,
3586 nsINode* aEndNode, int32_t aEndOffset,
3587 int32_t aRangeIndex)
3588 {
3589 if (aRangeIndex >=0 && aRangeIndex < (int32_t) mRanges.Length()) {
3590 nsRange* range = mRanges[aRangeIndex].mRange;
3591 if (RangeMatchesBeginPoint(range, aBeginNode, aBeginOffset) &&
3592 RangeMatchesEndPoint(range, aEndNode, aEndOffset))
3593 return true;
3594 }
3595 return false;
3596 }
3598 // Selection::GetRangesForInterval
3599 //
3600 // XPCOM wrapper for the nsTArray version
3602 NS_IMETHODIMP
3603 Selection::GetRangesForInterval(nsIDOMNode* aBeginNode, int32_t aBeginOffset,
3604 nsIDOMNode* aEndNode, int32_t aEndOffset,
3605 bool aAllowAdjacent,
3606 uint32_t* aResultCount,
3607 nsIDOMRange*** aResults)
3608 {
3609 if (!aBeginNode || ! aEndNode || ! aResultCount || ! aResults)
3610 return NS_ERROR_NULL_POINTER;
3612 *aResultCount = 0;
3613 *aResults = nullptr;
3615 nsTArray<nsRefPtr<nsRange>> results;
3616 ErrorResult result;
3617 nsCOMPtr<nsINode> beginNode = do_QueryInterface(aBeginNode);
3618 nsCOMPtr<nsINode> endNode = do_QueryInterface(aEndNode);
3619 NS_ENSURE_TRUE(beginNode && endNode, NS_ERROR_NULL_POINTER);
3620 GetRangesForInterval(*beginNode, aBeginOffset, *endNode, aEndOffset,
3621 aAllowAdjacent, results, result);
3622 if (result.Failed()) {
3623 return result.ErrorCode();
3624 }
3625 *aResultCount = results.Length();
3626 if (*aResultCount == 0) {
3627 return NS_OK;
3628 }
3630 *aResults = static_cast<nsIDOMRange**>
3631 (nsMemory::Alloc(sizeof(nsIDOMRange*) * *aResultCount));
3632 NS_ENSURE_TRUE(*aResults, NS_ERROR_OUT_OF_MEMORY);
3634 for (uint32_t i = 0; i < *aResultCount; i++) {
3635 (*aResults)[i] = results[i].forget().take();
3636 }
3637 return NS_OK;
3638 }
3641 void
3642 Selection::GetRangesForInterval(nsINode& aBeginNode, int32_t aBeginOffset,
3643 nsINode& aEndNode, int32_t aEndOffset,
3644 bool aAllowAdjacent,
3645 nsTArray<nsRefPtr<nsRange>>& aReturn,
3646 mozilla::ErrorResult& aRv)
3647 {
3648 nsTArray<nsRange*> results;
3649 nsresult rv = GetRangesForIntervalArray(&aBeginNode, aBeginOffset,
3650 &aEndNode, aEndOffset,
3651 aAllowAdjacent, &results);
3652 if (NS_FAILED(rv)) {
3653 aRv.Throw(rv);
3654 return;
3655 }
3657 aReturn.SetLength(results.Length());
3658 for (uint32_t i = 0; i < results.Length(); ++i) {
3659 aReturn[i] = results[i]; // AddRefs
3660 }
3661 }
3663 // Selection::GetRangesForIntervalArray
3664 //
3665 // Fills a nsTArray with the ranges overlapping the range specified by
3666 // the given endpoints. Ranges in the selection exactly adjacent to the
3667 // input range are not returned unless aAllowAdjacent is set.
3668 //
3669 // For example, if the following ranges were in the selection
3670 // (assume everything is within the same node)
3671 //
3672 // Start Offset: 0 2 7 9
3673 // End Offset: 2 5 9 10
3674 //
3675 // and passed aBeginOffset of 2 and aEndOffset of 9, then with
3676 // aAllowAdjacent set, all the ranges should be returned. If
3677 // aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only
3678 // should be returned
3679 //
3680 // Now that overlapping ranges are disallowed, there can be a maximum of
3681 // 2 adjacent ranges
3683 nsresult
3684 Selection::GetRangesForIntervalArray(nsINode* aBeginNode, int32_t aBeginOffset,
3685 nsINode* aEndNode, int32_t aEndOffset,
3686 bool aAllowAdjacent,
3687 nsTArray<nsRange*>* aRanges)
3688 {
3689 aRanges->Clear();
3690 int32_t startIndex, endIndex;
3691 nsresult res = GetIndicesForInterval(aBeginNode, aBeginOffset,
3692 aEndNode, aEndOffset, aAllowAdjacent,
3693 &startIndex, &endIndex);
3694 NS_ENSURE_SUCCESS(res, res);
3696 if (startIndex == -1 || endIndex == -1)
3697 return NS_OK;
3699 for (int32_t i = startIndex; i < endIndex; i++) {
3700 if (!aRanges->AppendElement(mRanges[i].mRange))
3701 return NS_ERROR_OUT_OF_MEMORY;
3702 }
3704 return NS_OK;
3705 }
3707 // Selection::GetIndicesForInterval
3708 //
3709 // Works on the same principle as GetRangesForIntervalArray above, however
3710 // instead this returns the indices into mRanges between which the
3711 // overlapping ranges lie.
3713 nsresult
3714 Selection::GetIndicesForInterval(nsINode* aBeginNode, int32_t aBeginOffset,
3715 nsINode* aEndNode, int32_t aEndOffset,
3716 bool aAllowAdjacent,
3717 int32_t* aStartIndex, int32_t* aEndIndex)
3718 {
3719 int32_t startIndex;
3720 int32_t endIndex;
3722 if (!aStartIndex)
3723 aStartIndex = &startIndex;
3724 if (!aEndIndex)
3725 aEndIndex = &endIndex;
3727 *aStartIndex = -1;
3728 *aEndIndex = -1;
3730 if (mRanges.Length() == 0)
3731 return NS_OK;
3733 bool intervalIsCollapsed = aBeginNode == aEndNode &&
3734 aBeginOffset == aEndOffset;
3736 // Ranges that end before the given interval and begin after the given
3737 // interval can be discarded
3738 int32_t endsBeforeIndex;
3739 if (NS_FAILED(FindInsertionPoint(&mRanges, aEndNode, aEndOffset,
3740 &CompareToRangeStart,
3741 &endsBeforeIndex))) {
3742 return NS_OK;
3743 }
3745 if (endsBeforeIndex == 0) {
3746 nsRange* endRange = mRanges[endsBeforeIndex].mRange;
3748 // If the interval is strictly before the range at index 0, we can optimize
3749 // by returning now - all ranges start after the given interval
3750 if (!RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset))
3751 return NS_OK;
3753 // We now know that the start point of mRanges[0].mRange equals the end of
3754 // the interval. Thus, when aAllowadjacent is true, the caller is always
3755 // interested in this range. However, when excluding adjacencies, we must
3756 // remember to include the range when both it and the given interval are
3757 // collapsed to the same point
3758 if (!aAllowAdjacent && !(endRange->Collapsed() && intervalIsCollapsed))
3759 return NS_OK;
3760 }
3761 *aEndIndex = endsBeforeIndex;
3763 int32_t beginsAfterIndex;
3764 if (NS_FAILED(FindInsertionPoint(&mRanges, aBeginNode, aBeginOffset,
3765 &CompareToRangeEnd,
3766 &beginsAfterIndex))) {
3767 return NS_OK;
3768 }
3769 if (beginsAfterIndex == (int32_t) mRanges.Length())
3770 return NS_OK; // optimization: all ranges are strictly before us
3772 if (aAllowAdjacent) {
3773 // At this point, one of the following holds:
3774 // endsBeforeIndex == mRanges.Length(),
3775 // endsBeforeIndex points to a range whose start point does not equal the
3776 // given interval's start point
3777 // endsBeforeIndex points to a range whose start point equals the given
3778 // interval's start point
3779 // In the final case, there can be two such ranges, a collapsed range, and
3780 // an adjacent range (they will appear in mRanges in that order). For this
3781 // final case, we need to increment endsBeforeIndex, until one of the
3782 // first two possibilites hold
3783 while (endsBeforeIndex < (int32_t) mRanges.Length()) {
3784 nsRange* endRange = mRanges[endsBeforeIndex].mRange;
3785 if (!RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset))
3786 break;
3787 endsBeforeIndex++;
3788 }
3790 // Likewise, one of the following holds:
3791 // beginsAfterIndex == 0,
3792 // beginsAfterIndex points to a range whose end point does not equal
3793 // the given interval's end point
3794 // beginsOnOrAfter points to a range whose end point equals the given
3795 // interval's end point
3796 // In the final case, there can be two such ranges, an adjacent range, and
3797 // a collapsed range (they will appear in mRanges in that order). For this
3798 // final case, we only need to take action if both those ranges exist, and
3799 // we are pointing to the collapsed range - we need to point to the
3800 // adjacent range
3801 nsRange* beginRange = mRanges[beginsAfterIndex].mRange;
3802 if (beginsAfterIndex > 0 && beginRange->Collapsed() &&
3803 RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset)) {
3804 beginRange = mRanges[beginsAfterIndex - 1].mRange;
3805 if (RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset))
3806 beginsAfterIndex--;
3807 }
3808 } else {
3809 // See above for the possibilities at this point. The only case where we
3810 // need to take action is when the range at beginsAfterIndex ends on
3811 // the given interval's start point, but that range isn't collapsed (a
3812 // collapsed range should be included in the returned results).
3813 nsRange* beginRange = mRanges[beginsAfterIndex].mRange;
3814 if (RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset) &&
3815 !beginRange->Collapsed())
3816 beginsAfterIndex++;
3818 // Again, see above for the meaning of endsBeforeIndex at this point.
3819 // In particular, endsBeforeIndex may point to a collaped range which
3820 // represents the point at the end of the interval - this range should be
3821 // included
3822 if (endsBeforeIndex < (int32_t) mRanges.Length()) {
3823 nsRange* endRange = mRanges[endsBeforeIndex].mRange;
3824 if (RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset) &&
3825 endRange->Collapsed())
3826 endsBeforeIndex++;
3827 }
3828 }
3830 NS_ASSERTION(beginsAfterIndex <= endsBeforeIndex,
3831 "Is mRanges not ordered?");
3832 NS_ENSURE_STATE(beginsAfterIndex <= endsBeforeIndex);
3834 *aStartIndex = beginsAfterIndex;
3835 *aEndIndex = endsBeforeIndex;
3836 return NS_OK;
3837 }
3839 NS_IMETHODIMP
3840 Selection::GetPrimaryFrameForAnchorNode(nsIFrame** aReturnFrame)
3841 {
3842 if (!aReturnFrame)
3843 return NS_ERROR_NULL_POINTER;
3845 int32_t frameOffset = 0;
3846 *aReturnFrame = 0;
3847 nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode());
3848 if (content && mFrameSelection)
3849 {
3850 *aReturnFrame = mFrameSelection->
3851 GetFrameForNodeOffset(content, AnchorOffset(),
3852 mFrameSelection->GetHint(), &frameOffset);
3853 if (*aReturnFrame)
3854 return NS_OK;
3855 }
3856 return NS_ERROR_FAILURE;
3857 }
3859 NS_IMETHODIMP
3860 Selection::GetPrimaryFrameForFocusNode(nsIFrame** aReturnFrame,
3861 int32_t* aOffsetUsed,
3862 bool aVisual)
3863 {
3864 if (!aReturnFrame)
3865 return NS_ERROR_NULL_POINTER;
3867 nsCOMPtr<nsIContent> content = do_QueryInterface(GetFocusNode());
3868 if (!content || !mFrameSelection)
3869 return NS_ERROR_FAILURE;
3871 int32_t frameOffset = 0;
3872 *aReturnFrame = 0;
3873 if (!aOffsetUsed)
3874 aOffsetUsed = &frameOffset;
3876 nsFrameSelection::HINT hint = mFrameSelection->GetHint();
3878 if (aVisual) {
3879 nsIPresShell *presShell = mFrameSelection->GetShell();
3880 if (!presShell)
3881 return NS_ERROR_FAILURE;
3883 nsRefPtr<nsCaret> caret = presShell->GetCaret();
3884 if (!caret)
3885 return NS_ERROR_FAILURE;
3887 uint8_t caretBidiLevel = mFrameSelection->GetCaretBidiLevel();
3889 return caret->GetCaretFrameForNodeOffset(content, FocusOffset(),
3890 hint, caretBidiLevel, aReturnFrame, aOffsetUsed);
3891 }
3893 *aReturnFrame = mFrameSelection->
3894 GetFrameForNodeOffset(content, FocusOffset(),
3895 hint, aOffsetUsed);
3896 if (!*aReturnFrame)
3897 return NS_ERROR_FAILURE;
3899 return NS_OK;
3900 }
3902 //select all content children of aContent
3903 nsresult
3904 Selection::SelectAllFramesForContent(nsIContentIterator* aInnerIter,
3905 nsIContent* aContent,
3906 bool aSelected)
3907 {
3908 nsresult result = aInnerIter->Init(aContent);
3909 nsIFrame *frame;
3910 if (NS_SUCCEEDED(result))
3911 {
3912 // First select frame of content passed in
3913 frame = aContent->GetPrimaryFrame();
3914 if (frame && frame->GetType() == nsGkAtoms::textFrame) {
3915 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
3916 textFrame->SetSelectedRange(0, aContent->GetText()->GetLength(), aSelected, mType);
3917 }
3918 // Now iterated through the child frames and set them
3919 while (!aInnerIter->IsDone()) {
3920 nsCOMPtr<nsIContent> innercontent =
3921 do_QueryInterface(aInnerIter->GetCurrentNode());
3923 frame = innercontent->GetPrimaryFrame();
3924 if (frame) {
3925 if (frame->GetType() == nsGkAtoms::textFrame) {
3926 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
3927 textFrame->SetSelectedRange(0, innercontent->GetText()->GetLength(), aSelected, mType);
3928 } else {
3929 frame->InvalidateFrameSubtree(); // frame continuations?
3930 }
3931 }
3933 aInnerIter->Next();
3934 }
3936 return NS_OK;
3937 }
3939 return NS_ERROR_FAILURE;
3940 }
3942 /**
3943 * The idea of this helper method is to select or deselect "top to bottom",
3944 * traversing through the frames
3945 */
3946 nsresult
3947 Selection::selectFrames(nsPresContext* aPresContext, nsRange* aRange,
3948 bool aSelect)
3949 {
3950 if (!mFrameSelection || !aPresContext || !aPresContext->GetPresShell()) {
3951 // nothing to do
3952 return NS_OK;
3953 }
3954 MOZ_ASSERT(aRange);
3956 if (mFrameSelection->GetTableCellSelection()) {
3957 nsINode* node = aRange->GetCommonAncestor();
3958 nsIFrame* frame = node->IsContent() ? node->AsContent()->GetPrimaryFrame()
3959 : aPresContext->FrameManager()->GetRootFrame();
3960 if (frame) {
3961 frame->InvalidateFrameSubtree();
3962 }
3963 return NS_OK;
3964 }
3966 nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
3967 iter->Init(aRange);
3969 // Loop through the content iterator for each content node; for each text
3970 // node, call SetSelected on it:
3971 nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetStartParent());
3972 NS_ENSURE_STATE(content);
3974 // We must call first one explicitly
3975 if (content->IsNodeOfType(nsINode::eTEXT)) {
3976 nsIFrame* frame = content->GetPrimaryFrame();
3977 // The frame could be an SVG text frame, in which case we'll ignore it.
3978 if (frame && frame->GetType() == nsGkAtoms::textFrame) {
3979 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
3980 uint32_t startOffset = aRange->StartOffset();
3981 uint32_t endOffset;
3982 if (aRange->GetEndParent() == content) {
3983 endOffset = aRange->EndOffset();
3984 } else {
3985 endOffset = content->Length();
3986 }
3987 textFrame->SetSelectedRange(startOffset, endOffset, aSelect, mType);
3988 }
3989 }
3991 iter->First();
3992 nsCOMPtr<nsIContentIterator> inneriter = NS_NewContentIterator();
3993 for (iter->First(); !iter->IsDone(); iter->Next()) {
3994 content = do_QueryInterface(iter->GetCurrentNode());
3995 SelectAllFramesForContent(inneriter, content, aSelect);
3996 }
3998 // We must now do the last one if it is not the same as the first
3999 if (aRange->GetEndParent() != aRange->GetStartParent()) {
4000 nsresult res;
4001 content = do_QueryInterface(aRange->GetEndParent(), &res);
4002 NS_ENSURE_SUCCESS(res, res);
4003 NS_ENSURE_TRUE(content, res);
4005 if (content->IsNodeOfType(nsINode::eTEXT)) {
4006 nsIFrame* frame = content->GetPrimaryFrame();
4007 // The frame could be an SVG text frame, in which case we'll ignore it.
4008 if (frame && frame->GetType() == nsGkAtoms::textFrame) {
4009 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
4010 textFrame->SetSelectedRange(0, aRange->EndOffset(), aSelect, mType);
4011 }
4012 }
4013 }
4014 return NS_OK;
4015 }
4018 // Selection::LookUpSelection
4019 //
4020 // This function is called when a node wants to know where the selection is
4021 // over itself.
4022 //
4023 // Usually, this is called when we already know there is a selection over
4024 // the node in question, and we only need to find the boundaries of it on
4025 // that node. This is when slowCheck is false--a strict test is not needed.
4026 // Other times, the caller has no idea, and wants us to test everything,
4027 // so we are supposed to determine whether there is a selection over the
4028 // node at all.
4029 //
4030 // A previous version of this code used this flag to do less work when
4031 // inclusion was already known (slowCheck=false). However, our tree
4032 // structure allows us to quickly determine ranges overlapping the node,
4033 // so we just ignore the slowCheck flag and do the full test every time.
4034 //
4035 // PERFORMANCE: a common case is that we are doing a fast check with exactly
4036 // one range in the selection. In this case, this function is slower than
4037 // brute force because of the overhead of checking the tree. We can optimize
4038 // this case to make it faster by doing the same thing the previous version
4039 // of this function did in the case of 1 range. This would also mean that
4040 // the aSlowCheck flag would have meaning again.
4042 NS_IMETHODIMP
4043 Selection::LookUpSelection(nsIContent* aContent, int32_t aContentOffset,
4044 int32_t aContentLength,
4045 SelectionDetails** aReturnDetails,
4046 SelectionType aType, bool aSlowCheck)
4047 {
4048 nsresult rv;
4049 if (!aContent || ! aReturnDetails)
4050 return NS_ERROR_NULL_POINTER;
4052 // it is common to have no ranges, to optimize that
4053 if (mRanges.Length() == 0)
4054 return NS_OK;
4056 nsTArray<nsRange*> overlappingRanges;
4057 rv = GetRangesForIntervalArray(aContent, aContentOffset,
4058 aContent, aContentOffset + aContentLength,
4059 false,
4060 &overlappingRanges);
4061 NS_ENSURE_SUCCESS(rv, rv);
4062 if (overlappingRanges.Length() == 0)
4063 return NS_OK;
4065 for (uint32_t i = 0; i < overlappingRanges.Length(); i++) {
4066 nsRange* range = overlappingRanges[i];
4067 nsINode* startNode = range->GetStartParent();
4068 nsINode* endNode = range->GetEndParent();
4069 int32_t startOffset = range->StartOffset();
4070 int32_t endOffset = range->EndOffset();
4072 int32_t start = -1, end = -1;
4073 if (startNode == aContent && endNode == aContent) {
4074 if (startOffset < (aContentOffset + aContentLength) &&
4075 endOffset > aContentOffset) {
4076 // this range is totally inside the requested content range
4077 start = std::max(0, startOffset - aContentOffset);
4078 end = std::min(aContentLength, endOffset - aContentOffset);
4079 }
4080 // otherwise, range is inside the requested node, but does not intersect
4081 // the requested content range, so ignore it
4082 } else if (startNode == aContent) {
4083 if (startOffset < (aContentOffset + aContentLength)) {
4084 // the beginning of the range is inside the requested node, but the
4085 // end is outside, select everything from there to the end
4086 start = std::max(0, startOffset - aContentOffset);
4087 end = aContentLength;
4088 }
4089 } else if (endNode == aContent) {
4090 if (endOffset > aContentOffset) {
4091 // the end of the range is inside the requested node, but the beginning
4092 // is outside, select everything from the beginning to there
4093 start = 0;
4094 end = std::min(aContentLength, endOffset - aContentOffset);
4095 }
4096 } else {
4097 // this range does not begin or end in the requested node, but since
4098 // GetRangesForInterval returned this range, we know it overlaps.
4099 // Therefore, this node is enclosed in the range, and we select all
4100 // of it.
4101 start = 0;
4102 end = aContentLength;
4103 }
4104 if (start < 0)
4105 continue; // the ranges do not overlap the input range
4107 SelectionDetails* details = new SelectionDetails;
4109 details->mNext = *aReturnDetails;
4110 details->mStart = start;
4111 details->mEnd = end;
4112 details->mType = aType;
4113 RangeData *rd = FindRangeData(range);
4114 if (rd) {
4115 details->mTextRangeStyle = rd->mTextRangeStyle;
4116 }
4117 *aReturnDetails = details;
4118 }
4119 return NS_OK;
4120 }
4122 NS_IMETHODIMP
4123 Selection::Repaint(nsPresContext* aPresContext)
4124 {
4125 int32_t arrCount = (int32_t)mRanges.Length();
4127 if (arrCount < 1)
4128 return NS_OK;
4130 int32_t i;
4132 for (i = 0; i < arrCount; i++)
4133 {
4134 nsresult rv = selectFrames(aPresContext, mRanges[i].mRange, true);
4136 if (NS_FAILED(rv)) {
4137 return rv;
4138 }
4139 }
4141 return NS_OK;
4142 }
4144 NS_IMETHODIMP
4145 Selection::GetCanCacheFrameOffset(bool* aCanCacheFrameOffset)
4146 {
4147 NS_ENSURE_ARG_POINTER(aCanCacheFrameOffset);
4149 if (mCachedOffsetForFrame)
4150 *aCanCacheFrameOffset = mCachedOffsetForFrame->mCanCacheFrameOffset;
4151 else
4152 *aCanCacheFrameOffset = false;
4154 return NS_OK;
4155 }
4157 NS_IMETHODIMP
4158 Selection::SetCanCacheFrameOffset(bool aCanCacheFrameOffset)
4159 {
4160 if (!mCachedOffsetForFrame) {
4161 mCachedOffsetForFrame = new CachedOffsetForFrame;
4162 }
4164 mCachedOffsetForFrame->mCanCacheFrameOffset = aCanCacheFrameOffset;
4166 // clean up cached frame when turn off cache
4167 // fix bug 207936
4168 if (!aCanCacheFrameOffset) {
4169 mCachedOffsetForFrame->mLastCaretFrame = nullptr;
4170 }
4172 return NS_OK;
4173 }
4175 NS_IMETHODIMP
4176 Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
4177 nsPoint& aPoint)
4178 {
4179 if (!mCachedOffsetForFrame) {
4180 mCachedOffsetForFrame = new CachedOffsetForFrame;
4181 }
4183 nsresult rv = NS_OK;
4184 if (mCachedOffsetForFrame->mCanCacheFrameOffset &&
4185 mCachedOffsetForFrame->mLastCaretFrame &&
4186 (aFrame == mCachedOffsetForFrame->mLastCaretFrame) &&
4187 (inOffset == mCachedOffsetForFrame->mLastContentOffset))
4188 {
4189 // get cached frame offset
4190 aPoint = mCachedOffsetForFrame->mCachedFrameOffset;
4191 }
4192 else
4193 {
4194 // Recalculate frame offset and cache it. Don't cache a frame offset if
4195 // GetPointFromOffset fails, though.
4196 rv = aFrame->GetPointFromOffset(inOffset, &aPoint);
4197 if (NS_SUCCEEDED(rv) && mCachedOffsetForFrame->mCanCacheFrameOffset) {
4198 mCachedOffsetForFrame->mCachedFrameOffset = aPoint;
4199 mCachedOffsetForFrame->mLastCaretFrame = aFrame;
4200 mCachedOffsetForFrame->mLastContentOffset = inOffset;
4201 }
4202 }
4204 return rv;
4205 }
4207 NS_IMETHODIMP
4208 Selection::SetAncestorLimiter(nsIContent* aContent)
4209 {
4210 if (mFrameSelection)
4211 mFrameSelection->SetAncestorLimiter(aContent);
4212 return NS_OK;
4213 }
4215 RangeData*
4216 Selection::FindRangeData(nsIDOMRange* aRange)
4217 {
4218 NS_ENSURE_TRUE(aRange, nullptr);
4219 for (uint32_t i = 0; i < mRanges.Length(); i++) {
4220 if (mRanges[i].mRange == aRange)
4221 return &mRanges[i];
4222 }
4223 return nullptr;
4224 }
4226 NS_IMETHODIMP
4227 Selection::SetTextRangeStyle(nsIDOMRange* aRange,
4228 const TextRangeStyle& aTextRangeStyle)
4229 {
4230 NS_ENSURE_ARG_POINTER(aRange);
4231 RangeData *rd = FindRangeData(aRange);
4232 if (rd) {
4233 rd->mTextRangeStyle = aTextRangeStyle;
4234 }
4235 return NS_OK;
4236 }
4238 nsresult
4239 Selection::StartAutoScrollTimer(nsIFrame* aFrame, nsPoint& aPoint,
4240 uint32_t aDelay)
4241 {
4242 NS_PRECONDITION(aFrame, "Need a frame");
4244 nsresult result;
4245 if (!mFrameSelection)
4246 return NS_OK;//nothing to do
4248 if (!mAutoScrollTimer)
4249 {
4250 mAutoScrollTimer = new nsAutoScrollTimer();
4252 result = mAutoScrollTimer->Init(mFrameSelection, this);
4254 if (NS_FAILED(result))
4255 return result;
4256 }
4258 result = mAutoScrollTimer->SetDelay(aDelay);
4260 if (NS_FAILED(result))
4261 return result;
4263 return DoAutoScroll(aFrame, aPoint);
4264 }
4266 nsresult
4267 Selection::StopAutoScrollTimer()
4268 {
4269 if (mAutoScrollTimer)
4270 return mAutoScrollTimer->Stop();
4272 return NS_OK;
4273 }
4275 nsresult
4276 Selection::DoAutoScroll(nsIFrame* aFrame, nsPoint& aPoint)
4277 {
4278 NS_PRECONDITION(aFrame, "Need a frame");
4280 if (mAutoScrollTimer)
4281 (void)mAutoScrollTimer->Stop();
4283 nsPresContext* presContext = aFrame->PresContext();
4284 nsRootPresContext* rootPC = presContext->GetRootPresContext();
4285 if (!rootPC)
4286 return NS_OK;
4287 nsIFrame* rootmostFrame = rootPC->PresShell()->FrameManager()->GetRootFrame();
4288 // Get the point relative to the root most frame because the scroll we are
4289 // about to do will change the coordinates of aFrame.
4290 nsPoint globalPoint = aPoint + aFrame->GetOffsetToCrossDoc(rootmostFrame);
4292 bool didScroll = presContext->PresShell()->ScrollFrameRectIntoView(
4293 aFrame,
4294 nsRect(aPoint, nsSize(0, 0)),
4295 nsIPresShell::ScrollAxis(),
4296 nsIPresShell::ScrollAxis(),
4297 0);
4299 //
4300 // Start the AutoScroll timer if necessary.
4301 //
4303 if (didScroll && mAutoScrollTimer)
4304 {
4305 nsPoint presContextPoint = globalPoint -
4306 presContext->PresShell()->FrameManager()->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame);
4307 mAutoScrollTimer->Start(presContext, presContextPoint);
4308 }
4310 return NS_OK;
4311 }
4314 /** RemoveAllRanges zeroes the selection
4315 */
4316 NS_IMETHODIMP
4317 Selection::RemoveAllRanges()
4318 {
4319 ErrorResult result;
4320 RemoveAllRanges(result);
4321 return result.ErrorCode();
4322 }
4324 void
4325 Selection::RemoveAllRanges(ErrorResult& aRv)
4326 {
4327 if (!mFrameSelection)
4328 return; // nothing to do
4329 nsRefPtr<nsPresContext> presContext = GetPresContext();
4330 nsresult result = Clear(presContext);
4331 if (NS_FAILED(result)) {
4332 aRv.Throw(result);
4333 return;
4334 }
4336 // Turn off signal for table selection
4337 mFrameSelection->ClearTableCellSelection();
4339 result = mFrameSelection->NotifySelectionListeners(GetType());
4340 // Also need to notify the frames!
4341 // PresShell::CharacterDataChanged should do that on DocumentChanged
4342 if (NS_FAILED(result)) {
4343 aRv.Throw(result);
4344 }
4345 }
4347 /** AddRange adds the specified range to the selection
4348 * @param aRange is the range to be added
4349 */
4350 NS_IMETHODIMP
4351 Selection::AddRange(nsIDOMRange* aDOMRange)
4352 {
4353 if (!aDOMRange) {
4354 return NS_ERROR_NULL_POINTER;
4355 }
4356 nsRange* range = static_cast<nsRange*>(aDOMRange);
4357 ErrorResult result;
4358 AddRange(*range, result);
4359 return result.ErrorCode();
4360 }
4362 void
4363 Selection::AddRange(nsRange& aRange, ErrorResult& aRv)
4364 {
4365 // This inserts a table cell range in proper document order
4366 // and returns NS_OK if range doesn't contain just one table cell
4367 bool didAddRange;
4368 int32_t rangeIndex;
4369 nsresult result = addTableCellRange(&aRange, &didAddRange, &rangeIndex);
4370 if (NS_FAILED(result)) {
4371 aRv.Throw(result);
4372 return;
4373 }
4375 if (!didAddRange)
4376 {
4377 result = AddItem(&aRange, &rangeIndex);
4378 if (NS_FAILED(result)) {
4379 aRv.Throw(result);
4380 return;
4381 }
4382 }
4384 NS_ASSERTION(rangeIndex >= 0, "Range index not returned");
4385 setAnchorFocusRange(rangeIndex);
4387 // Make sure the caret appears on the next line, if at a newline
4388 if (mType == nsISelectionController::SELECTION_NORMAL)
4389 SetInterlinePosition(true);
4391 nsRefPtr<nsPresContext> presContext = GetPresContext();
4392 selectFrames(presContext, &aRange, true);
4394 if (!mFrameSelection)
4395 return;//nothing to do
4397 result = mFrameSelection->NotifySelectionListeners(GetType());
4398 if (NS_FAILED(result)) {
4399 aRv.Throw(result);
4400 }
4401 }
4403 // Selection::RemoveRange
4404 //
4405 // Removes the given range from the selection. The tricky part is updating
4406 // the flags on the frames that indicate whether they have a selection or
4407 // not. There could be several selection ranges on the frame, and clearing
4408 // the bit would cause the selection to not be drawn, even when there is
4409 // another range on the frame (bug 346185).
4410 //
4411 // We therefore find any ranges that intersect the same nodes as the range
4412 // being removed, and cause them to set the selected bits back on their
4413 // selected frames after we've cleared the bit from ours.
4415 nsresult
4416 Selection::RemoveRange(nsIDOMRange* aDOMRange)
4417 {
4418 if (!aDOMRange) {
4419 return NS_ERROR_INVALID_ARG;
4420 }
4421 nsRange* range = static_cast<nsRange*>(aDOMRange);
4422 ErrorResult result;
4423 RemoveRange(*range, result);
4424 return result.ErrorCode();
4425 }
4427 void
4428 Selection::RemoveRange(nsRange& aRange, ErrorResult& aRv)
4429 {
4430 nsresult rv = RemoveItem(&aRange);
4431 if (NS_FAILED(rv)) {
4432 aRv.Throw(rv);
4433 return;
4434 }
4436 nsINode* beginNode = aRange.GetStartParent();
4437 nsINode* endNode = aRange.GetEndParent();
4439 if (!beginNode || !endNode) {
4440 // Detached range; nothing else to do here.
4441 return;
4442 }
4444 // find out the length of the end node, so we can select all of it
4445 int32_t beginOffset, endOffset;
4446 if (endNode->IsNodeOfType(nsINode::eTEXT)) {
4447 // Get the length of the text. We can't just use the offset because
4448 // another range could be touching this text node but not intersect our
4449 // range.
4450 beginOffset = 0;
4451 endOffset = static_cast<nsIContent*>(endNode)->TextLength();
4452 } else {
4453 // For non-text nodes, the given offsets should be sufficient.
4454 beginOffset = aRange.StartOffset();
4455 endOffset = aRange.EndOffset();
4456 }
4458 // clear the selected bit from the removed range's frames
4459 nsRefPtr<nsPresContext> presContext = GetPresContext();
4460 selectFrames(presContext, &aRange, false);
4462 // add back the selected bit for each range touching our nodes
4463 nsTArray<nsRange*> affectedRanges;
4464 rv = GetRangesForIntervalArray(beginNode, beginOffset,
4465 endNode, endOffset,
4466 true, &affectedRanges);
4467 if (NS_FAILED(rv)) {
4468 aRv.Throw(rv);
4469 return;
4470 }
4471 for (uint32_t i = 0; i < affectedRanges.Length(); i++) {
4472 selectFrames(presContext, affectedRanges[i], true);
4473 }
4475 int32_t cnt = mRanges.Length();
4476 if (&aRange == mAnchorFocusRange) {
4477 // Reset anchor to LAST range or clear it if there are no ranges.
4478 setAnchorFocusRange(cnt - 1);
4480 // When the selection is user-created it makes sense to scroll the range
4481 // into view. The spell-check selection, however, is created and destroyed
4482 // in the background. We don't want to scroll in this case or the view
4483 // might appear to be moving randomly (bug 337871).
4484 if (mType != nsISelectionController::SELECTION_SPELLCHECK && cnt > 0)
4485 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION);
4486 }
4488 if (!mFrameSelection)
4489 return;//nothing to do
4490 rv = mFrameSelection->NotifySelectionListeners(GetType());
4491 if (NS_FAILED(rv)) {
4492 aRv.Throw(rv);
4493 }
4494 }
4498 /*
4499 * Collapse sets the whole selection to be one point.
4500 */
4501 NS_IMETHODIMP
4502 Selection::Collapse(nsIDOMNode* aParentNode, int32_t aOffset)
4503 {
4504 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
4505 return Collapse(parentNode, aOffset);
4506 }
4508 NS_IMETHODIMP
4509 Selection::CollapseNative(nsINode* aParentNode, int32_t aOffset)
4510 {
4511 return Collapse(aParentNode, aOffset);
4512 }
4514 nsresult
4515 Selection::Collapse(nsINode* aParentNode, int32_t aOffset)
4516 {
4517 if (!aParentNode)
4518 return NS_ERROR_INVALID_ARG;
4520 ErrorResult result;
4521 Collapse(*aParentNode, static_cast<uint32_t>(aOffset), result);
4522 return result.ErrorCode();
4523 }
4525 void
4526 Selection::Collapse(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv)
4527 {
4528 if (!mFrameSelection) {
4529 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
4530 return;
4531 }
4533 nsCOMPtr<nsINode> kungfuDeathGrip = &aParentNode;
4535 mFrameSelection->InvalidateDesiredX();
4536 if (!IsValidSelectionPoint(mFrameSelection, &aParentNode)) {
4537 aRv.Throw(NS_ERROR_FAILURE);
4538 return;
4539 }
4540 nsresult result;
4542 nsRefPtr<nsPresContext> presContext = GetPresContext();
4543 if (!presContext || presContext->Document() != aParentNode.OwnerDoc()) {
4544 aRv.Throw(NS_ERROR_FAILURE);
4545 return;
4546 }
4548 // Delete all of the current ranges
4549 Clear(presContext);
4551 // Turn off signal for table selection
4552 mFrameSelection->ClearTableCellSelection();
4554 nsRefPtr<nsRange> range = new nsRange(&aParentNode);
4555 result = range->SetEnd(&aParentNode, aOffset);
4556 if (NS_FAILED(result)) {
4557 aRv.Throw(result);
4558 return;
4559 }
4560 result = range->SetStart(&aParentNode, aOffset);
4561 if (NS_FAILED(result)) {
4562 aRv.Throw(result);
4563 return;
4564 }
4566 #ifdef DEBUG_SELECTION
4567 nsCOMPtr<nsIContent> content = do_QueryInterface(&aParentNode);
4568 nsCOMPtr<nsIDocument> doc = do_QueryInterface(&aParentNode);
4569 printf ("Sel. Collapse to %p %s %d\n", &aParentNode,
4570 content ? nsAtomCString(content->Tag()).get()
4571 : (doc ? "DOCUMENT" : "???"),
4572 aOffset);
4573 #endif
4575 int32_t rangeIndex = -1;
4576 result = AddItem(range, &rangeIndex);
4577 if (NS_FAILED(result)) {
4578 aRv.Throw(result);
4579 return;
4580 }
4581 setAnchorFocusRange(0);
4582 selectFrames(presContext, range, true);
4583 result = mFrameSelection->NotifySelectionListeners(GetType());
4584 if (NS_FAILED(result)) {
4585 aRv.Throw(result);
4586 }
4587 }
4589 /*
4590 * Sets the whole selection to be one point
4591 * at the start of the current selection
4592 */
4593 NS_IMETHODIMP
4594 Selection::CollapseToStart()
4595 {
4596 ErrorResult result;
4597 CollapseToStart(result);
4598 return result.ErrorCode();
4599 }
4601 void
4602 Selection::CollapseToStart(ErrorResult& aRv)
4603 {
4604 int32_t cnt;
4605 nsresult rv = GetRangeCount(&cnt);
4606 if (NS_FAILED(rv) || cnt <= 0) {
4607 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
4608 return;
4609 }
4611 // Get the first range
4612 nsRange* firstRange = mRanges[0].mRange;
4613 if (!firstRange) {
4614 aRv.Throw(NS_ERROR_FAILURE);
4615 return;
4616 }
4618 if (mFrameSelection) {
4619 int16_t reason = mFrameSelection->PopReason() | nsISelectionListener::COLLAPSETOSTART_REASON;
4620 mFrameSelection->PostReason(reason);
4621 }
4622 nsINode* parent = firstRange->GetStartParent();
4623 if (!parent) {
4624 aRv.Throw(NS_ERROR_FAILURE);
4625 return;
4626 }
4627 Collapse(*parent, firstRange->StartOffset(), aRv);
4628 }
4630 /*
4631 * Sets the whole selection to be one point
4632 * at the end of the current selection
4633 */
4634 NS_IMETHODIMP
4635 Selection::CollapseToEnd()
4636 {
4637 ErrorResult result;
4638 CollapseToEnd(result);
4639 return result.ErrorCode();
4640 }
4642 void
4643 Selection::CollapseToEnd(ErrorResult& aRv)
4644 {
4645 int32_t cnt;
4646 nsresult rv = GetRangeCount(&cnt);
4647 if (NS_FAILED(rv) || cnt <= 0) {
4648 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
4649 return;
4650 }
4652 // Get the last range
4653 nsRange* lastRange = mRanges[cnt - 1].mRange;
4654 if (!lastRange) {
4655 aRv.Throw(NS_ERROR_FAILURE);
4656 return;
4657 }
4659 if (mFrameSelection) {
4660 int16_t reason = mFrameSelection->PopReason() | nsISelectionListener::COLLAPSETOEND_REASON;
4661 mFrameSelection->PostReason(reason);
4662 }
4663 nsINode* parent = lastRange->GetEndParent();
4664 if (!parent) {
4665 aRv.Throw(NS_ERROR_FAILURE);
4666 return;
4667 }
4668 Collapse(*parent, lastRange->EndOffset(), aRv);
4669 }
4671 /*
4672 * IsCollapsed -- is the whole selection just one point, or unset?
4673 */
4674 bool
4675 Selection::IsCollapsed()
4676 {
4677 uint32_t cnt = mRanges.Length();
4678 if (cnt == 0) {
4679 return true;
4680 }
4682 if (cnt != 1) {
4683 return false;
4684 }
4686 return mRanges[0].mRange->Collapsed();
4687 }
4689 /* virtual */
4690 bool
4691 Selection::Collapsed()
4692 {
4693 return IsCollapsed();
4694 }
4696 NS_IMETHODIMP
4697 Selection::GetIsCollapsed(bool* aIsCollapsed)
4698 {
4699 NS_ENSURE_TRUE(aIsCollapsed, NS_ERROR_NULL_POINTER);
4701 *aIsCollapsed = IsCollapsed();
4702 return NS_OK;
4703 }
4705 NS_IMETHODIMP
4706 Selection::GetRangeCount(int32_t* aRangeCount)
4707 {
4708 *aRangeCount = (int32_t)RangeCount();
4710 return NS_OK;
4711 }
4713 NS_IMETHODIMP
4714 Selection::GetRangeAt(int32_t aIndex, nsIDOMRange** aReturn)
4715 {
4716 ErrorResult result;
4717 *aReturn = GetRangeAt(aIndex, result);
4718 NS_IF_ADDREF(*aReturn);
4719 return result.ErrorCode();
4720 }
4722 nsRange*
4723 Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv)
4724 {
4725 nsRange* range = GetRangeAt(aIndex);
4726 if (!range) {
4727 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
4728 return nullptr;
4729 }
4731 return range;
4732 }
4734 nsRange*
4735 Selection::GetRangeAt(int32_t aIndex)
4736 {
4737 RangeData empty(nullptr);
4738 return mRanges.SafeElementAt(aIndex, empty).mRange;
4739 }
4741 /*
4742 utility function
4743 */
4744 nsresult
4745 Selection::SetAnchorFocusToRange(nsRange* aRange)
4746 {
4747 NS_ENSURE_STATE(mAnchorFocusRange);
4749 nsresult res = RemoveItem(mAnchorFocusRange);
4750 if (NS_FAILED(res))
4751 return res;
4753 int32_t aOutIndex = -1;
4754 res = AddItem(aRange, &aOutIndex);
4755 if (NS_FAILED(res))
4756 return res;
4757 setAnchorFocusRange(aOutIndex);
4759 return NS_OK;
4760 }
4762 void
4763 Selection::ReplaceAnchorFocusRange(nsRange* aRange)
4764 {
4765 NS_ENSURE_TRUE_VOID(mAnchorFocusRange);
4766 nsRefPtr<nsPresContext> presContext = GetPresContext();
4767 if (presContext) {
4768 selectFrames(presContext, mAnchorFocusRange, false);
4769 SetAnchorFocusToRange(aRange);
4770 selectFrames(presContext, mAnchorFocusRange, true);
4771 }
4772 }
4774 /*
4775 Notes which might come in handy for extend:
4777 We can tell the direction of the selection by asking for the anchors selection
4778 if the begin is less than the end then we know the selection is to the "right".
4779 else it is a backwards selection.
4780 a = anchor
4781 1 = old cursor
4782 2 = new cursor
4784 if (a <= 1 && 1 <=2) a,1,2 or (a1,2)
4785 if (a < 2 && 1 > 2) a,2,1
4786 if (1 < a && a <2) 1,a,2
4787 if (a > 2 && 2 >1) 1,2,a
4788 if (2 < a && a <1) 2,a,1
4789 if (a > 1 && 1 >2) 2,1,a
4790 then execute
4791 a 1 2 select from 1 to 2
4792 a 2 1 deselect from 2 to 1
4793 1 a 2 deselect from 1 to a select from a to 2
4794 1 2 a deselect from 1 to 2
4795 2 1 a = continue selection from 2 to 1
4796 */
4799 /*
4800 * Extend extends the selection away from the anchor.
4801 * We don't need to know the direction, because we always change the focus.
4802 */
4803 NS_IMETHODIMP
4804 Selection::Extend(nsIDOMNode* aParentNode, int32_t aOffset)
4805 {
4806 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
4807 return Extend(parentNode, aOffset);
4808 }
4810 NS_IMETHODIMP
4811 Selection::ExtendNative(nsINode* aParentNode, int32_t aOffset)
4812 {
4813 return Extend(aParentNode, aOffset);
4814 }
4816 nsresult
4817 Selection::Extend(nsINode* aParentNode, int32_t aOffset)
4818 {
4819 if (!aParentNode)
4820 return NS_ERROR_INVALID_ARG;
4822 ErrorResult result;
4823 Extend(*aParentNode, static_cast<uint32_t>(aOffset), result);
4824 return result.ErrorCode();
4825 }
4827 void
4828 Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv)
4829 {
4830 // First, find the range containing the old focus point:
4831 if (!mAnchorFocusRange) {
4832 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
4833 return;
4834 }
4836 if (!mFrameSelection) {
4837 aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection
4838 return;
4839 }
4841 nsresult res;
4842 if (!IsValidSelectionPoint(mFrameSelection, &aParentNode)) {
4843 aRv.Throw(NS_ERROR_FAILURE);
4844 return;
4845 }
4847 nsRefPtr<nsPresContext> presContext = GetPresContext();
4848 if (!presContext || presContext->Document() != aParentNode.OwnerDoc()) {
4849 aRv.Throw(NS_ERROR_FAILURE);
4850 return;
4851 }
4853 //mFrameSelection->InvalidateDesiredX();
4855 nsINode* anchorNode = GetAnchorNode();
4856 nsINode* focusNode = GetFocusNode();
4857 uint32_t anchorOffset = AnchorOffset();
4858 uint32_t focusOffset = FocusOffset();
4860 nsRefPtr<nsRange> range = mAnchorFocusRange->CloneRange();
4862 nsINode* startNode = range->GetStartParent();
4863 nsINode* endNode = range->GetEndParent();
4864 int32_t startOffset = range->StartOffset();
4865 int32_t endOffset = range->EndOffset();
4867 nsDirection dir = GetDirection();
4869 //compare anchor to old cursor.
4871 // We pass |disconnected| to the following ComparePoints calls in order
4872 // to avoid assertions. ComparePoints returns 1 in the disconnected case
4873 // and we can end up in various cases below, but it is assumed that in
4874 // any of the cases we end up, the nsRange implementation will collapse
4875 // the range to the new point because we can not make a valid range with
4876 // a disconnected point. This means that whatever range is currently
4877 // selected will be cleared.
4878 bool disconnected = false;
4879 bool shouldClearRange = false;
4880 int32_t result1 = nsContentUtils::ComparePoints(anchorNode, anchorOffset,
4881 focusNode, focusOffset,
4882 &disconnected);
4883 //compare old cursor to new cursor
4884 shouldClearRange |= disconnected;
4885 int32_t result2 = nsContentUtils::ComparePoints(focusNode, focusOffset,
4886 &aParentNode, aOffset,
4887 &disconnected);
4888 //compare anchor to new cursor
4889 shouldClearRange |= disconnected;
4890 int32_t result3 = nsContentUtils::ComparePoints(anchorNode, anchorOffset,
4891 &aParentNode, aOffset,
4892 &disconnected);
4894 // If the points are disconnected, the range will be collapsed below,
4895 // resulting in a range that selects nothing.
4896 if (shouldClearRange) {
4897 // Repaint the current range with the selection removed.
4898 selectFrames(presContext, range, false);
4899 }
4901 nsRefPtr<nsRange> difRange = new nsRange(&aParentNode);
4902 if ((result1 == 0 && result3 < 0) || (result1 <= 0 && result2 < 0)){//a1,2 a,1,2
4903 //select from 1 to 2 unless they are collapsed
4904 range->SetEnd(aParentNode, aOffset, aRv);
4905 if (aRv.Failed()) {
4906 return;
4907 }
4908 dir = eDirNext;
4909 res = difRange->SetEnd(range->GetEndParent(), range->EndOffset());
4910 nsresult tmp = difRange->SetStart(focusNode, focusOffset);
4911 if (NS_FAILED(tmp)) {
4912 res = tmp;
4913 }
4914 if (NS_FAILED(res)) {
4915 aRv.Throw(res);
4916 return;
4917 }
4918 selectFrames(presContext, difRange , true);
4919 res = SetAnchorFocusToRange(range);
4920 if (NS_FAILED(res)) {
4921 aRv.Throw(res);
4922 return;
4923 }
4924 }
4925 else if (result1 == 0 && result3 > 0){//2, a1
4926 //select from 2 to 1a
4927 dir = eDirPrevious;
4928 range->SetStart(aParentNode, aOffset, aRv);
4929 if (aRv.Failed()) {
4930 return;
4931 }
4932 selectFrames(presContext, range, true);
4933 res = SetAnchorFocusToRange(range);
4934 if (NS_FAILED(res)) {
4935 aRv.Throw(res);
4936 return;
4937 }
4938 }
4939 else if (result3 <= 0 && result2 >= 0) {//a,2,1 or a2,1 or a,21 or a21
4940 //deselect from 2 to 1
4941 res = difRange->SetEnd(focusNode, focusOffset);
4942 difRange->SetStart(aParentNode, aOffset, aRv);
4943 if (aRv.Failed()) {
4944 return;
4945 }
4946 if (NS_FAILED(res)) {
4947 aRv.Throw(res);
4948 return;
4949 }
4951 range->SetEnd(aParentNode, aOffset, aRv);
4952 if (aRv.Failed()) {
4953 return;
4954 }
4955 res = SetAnchorFocusToRange(range);
4956 if (NS_FAILED(res)) {
4957 aRv.Throw(res);
4958 return;
4959 }
4960 selectFrames(presContext, difRange, false); // deselect now
4961 difRange->SetEnd(range->GetEndParent(), range->EndOffset());
4962 selectFrames(presContext, difRange, true); // must reselect last node maybe more
4963 }
4964 else if (result1 >= 0 && result3 <= 0) {//1,a,2 or 1a,2 or 1,a2 or 1a2
4965 if (GetDirection() == eDirPrevious){
4966 res = range->SetStart(endNode, endOffset);
4967 if (NS_FAILED(res)) {
4968 aRv.Throw(res);
4969 return;
4970 }
4971 }
4972 dir = eDirNext;
4973 range->SetEnd(aParentNode, aOffset, aRv);
4974 if (aRv.Failed()) {
4975 return;
4976 }
4977 if (focusNode != anchorNode || focusOffset != anchorOffset) {//if collapsed diff dont do anything
4978 res = difRange->SetStart(focusNode, focusOffset);
4979 nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset);
4980 if (NS_FAILED(tmp)) {
4981 res = tmp;
4982 }
4983 if (NS_FAILED(res)) {
4984 aRv.Throw(res);
4985 return;
4986 }
4987 res = SetAnchorFocusToRange(range);
4988 if (NS_FAILED(res)) {
4989 aRv.Throw(res);
4990 return;
4991 }
4992 //deselect from 1 to a
4993 selectFrames(presContext, difRange , false);
4994 }
4995 else
4996 {
4997 res = SetAnchorFocusToRange(range);
4998 if (NS_FAILED(res)) {
4999 aRv.Throw(res);
5000 return;
5001 }
5002 }
5003 //select from a to 2
5004 selectFrames(presContext, range , true);
5005 }
5006 else if (result2 <= 0 && result3 >= 0) {//1,2,a or 12,a or 1,2a or 12a
5007 //deselect from 1 to 2
5008 difRange->SetEnd(aParentNode, aOffset, aRv);
5009 res = difRange->SetStart(focusNode, focusOffset);
5010 if (aRv.Failed()) {
5011 return;
5012 }
5013 if (NS_FAILED(res)) {
5014 aRv.Throw(res);
5015 return;
5016 }
5017 dir = eDirPrevious;
5018 range->SetStart(aParentNode, aOffset, aRv);
5019 if (aRv.Failed()) {
5020 return;
5021 }
5023 res = SetAnchorFocusToRange(range);
5024 if (NS_FAILED(res)) {
5025 aRv.Throw(res);
5026 return;
5027 }
5028 selectFrames(presContext, difRange , false);
5029 difRange->SetStart(range->GetStartParent(), range->StartOffset());
5030 selectFrames(presContext, difRange, true);//must reselect last node
5031 }
5032 else if (result3 >= 0 && result1 <= 0) {//2,a,1 or 2a,1 or 2,a1 or 2a1
5033 if (GetDirection() == eDirNext){
5034 range->SetEnd(startNode, startOffset);
5035 }
5036 dir = eDirPrevious;
5037 range->SetStart(aParentNode, aOffset, aRv);
5038 if (aRv.Failed()) {
5039 return;
5040 }
5041 //deselect from a to 1
5042 if (focusNode != anchorNode || focusOffset!= anchorOffset) {//if collapsed diff dont do anything
5043 res = difRange->SetStart(anchorNode, anchorOffset);
5044 nsresult tmp = difRange->SetEnd(focusNode, focusOffset);
5045 if (NS_FAILED(tmp)) {
5046 res = tmp;
5047 }
5048 tmp = SetAnchorFocusToRange(range);
5049 if (NS_FAILED(tmp)) {
5050 res = tmp;
5051 }
5052 if (NS_FAILED(res)) {
5053 aRv.Throw(res);
5054 return;
5055 }
5056 selectFrames(presContext, difRange, false);
5057 }
5058 else
5059 {
5060 res = SetAnchorFocusToRange(range);
5061 if (NS_FAILED(res)) {
5062 aRv.Throw(res);
5063 return;
5064 }
5065 }
5066 //select from 2 to a
5067 selectFrames(presContext, range , true);
5068 }
5069 else if (result2 >= 0 && result1 >= 0) {//2,1,a or 21,a or 2,1a or 21a
5070 //select from 2 to 1
5071 range->SetStart(aParentNode, aOffset, aRv);
5072 if (aRv.Failed()) {
5073 return;
5074 }
5075 dir = eDirPrevious;
5076 res = difRange->SetEnd(focusNode, focusOffset);
5077 nsresult tmp = difRange->SetStart(range->GetStartParent(), range->StartOffset());
5078 if (NS_FAILED(tmp)) {
5079 res = tmp;
5080 }
5081 if (NS_FAILED(res)) {
5082 aRv.Throw(res);
5083 return;
5084 }
5086 selectFrames(presContext, difRange, true);
5087 res = SetAnchorFocusToRange(range);
5088 if (NS_FAILED(res)) {
5089 aRv.Throw(res);
5090 return;
5091 }
5092 }
5094 DEBUG_OUT_RANGE(range);
5095 #ifdef DEBUG_SELECTION
5096 if (eDirNext == mDirection)
5097 printf(" direction = 1 LEFT TO RIGHT\n");
5098 else
5099 printf(" direction = 0 RIGHT TO LEFT\n");
5100 #endif
5101 SetDirection(dir);
5102 #ifdef DEBUG_SELECTION
5103 nsCOMPtr<nsIContent> content = do_QueryInterface(&aParentNode);
5105 printf ("Sel. Extend to %p %s %d\n", content.get(),
5106 nsAtomCString(content->Tag()).get(), aOffset);
5107 #endif
5108 res = mFrameSelection->NotifySelectionListeners(GetType());
5109 if (NS_FAILED(res)) {
5110 aRv.Throw(res);
5111 }
5112 }
5114 NS_IMETHODIMP
5115 Selection::SelectAllChildren(nsIDOMNode* aParentNode)
5116 {
5117 ErrorResult result;
5118 nsCOMPtr<nsINode> node = do_QueryInterface(aParentNode);
5119 NS_ENSURE_TRUE(node, NS_ERROR_INVALID_ARG);
5120 SelectAllChildren(*node, result);
5121 return result.ErrorCode();
5122 }
5124 void
5125 Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv)
5126 {
5127 if (mFrameSelection)
5128 {
5129 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON);
5130 }
5131 Collapse(aNode, 0, aRv);
5132 if (aRv.Failed()) {
5133 return;
5134 }
5136 if (mFrameSelection)
5137 {
5138 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON);
5139 }
5140 Extend(aNode, aNode.GetChildCount(), aRv);
5141 }
5143 NS_IMETHODIMP
5144 Selection::ContainsNode(nsIDOMNode* aNode, bool aAllowPartial, bool* aYes)
5145 {
5146 if (!aYes)
5147 return NS_ERROR_NULL_POINTER;
5148 *aYes = false;
5150 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
5151 ErrorResult result;
5152 *aYes = ContainsNode(node, aAllowPartial, result);
5153 return result.ErrorCode();
5154 }
5156 bool
5157 Selection::ContainsNode(nsINode* aNode, bool aAllowPartial, ErrorResult& aRv)
5158 {
5159 nsresult rv;
5160 if (mRanges.Length() == 0 || !aNode)
5161 return false;
5163 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp
5164 uint32_t nodeLength;
5165 bool isData = aNode->IsNodeOfType(nsINode::eDATA_NODE);
5166 if (isData) {
5167 nodeLength = static_cast<nsIContent*>(aNode)->TextLength();
5168 } else {
5169 nodeLength = aNode->GetChildCount();
5170 }
5172 nsTArray<nsRange*> overlappingRanges;
5173 rv = GetRangesForIntervalArray(aNode, 0, aNode, nodeLength,
5174 false, &overlappingRanges);
5175 if (NS_FAILED(rv)) {
5176 aRv.Throw(rv);
5177 return false;
5178 }
5179 if (overlappingRanges.Length() == 0)
5180 return false; // no ranges overlap
5182 // if the caller said partial intersections are OK, we're done
5183 if (aAllowPartial) {
5184 return true;
5185 }
5187 // text nodes always count as inside
5188 if (isData) {
5189 return true;
5190 }
5192 // The caller wants to know if the node is entirely within the given range,
5193 // so we have to check all intersecting ranges.
5194 for (uint32_t i = 0; i < overlappingRanges.Length(); i++) {
5195 bool nodeStartsBeforeRange, nodeEndsAfterRange;
5196 if (NS_SUCCEEDED(nsRange::CompareNodeToRange(aNode, overlappingRanges[i],
5197 &nodeStartsBeforeRange,
5198 &nodeEndsAfterRange))) {
5199 if (!nodeStartsBeforeRange && !nodeEndsAfterRange) {
5200 return true;
5201 }
5202 }
5203 }
5204 return false;
5205 }
5208 nsPresContext*
5209 Selection::GetPresContext() const
5210 {
5211 nsIPresShell *shell = GetPresShell();
5212 if (!shell) {
5213 return nullptr;
5214 }
5216 return shell->GetPresContext();
5217 }
5219 nsIPresShell*
5220 Selection::GetPresShell() const
5221 {
5222 if (!mFrameSelection)
5223 return nullptr;//nothing to do
5225 return mFrameSelection->GetShell();
5226 }
5228 nsIFrame *
5229 Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect* aRect)
5230 {
5231 if (!mFrameSelection)
5232 return nullptr; // nothing to do
5234 NS_ENSURE_TRUE(aRect, nullptr);
5236 aRect->SetRect(0, 0, 0, 0);
5238 switch (aRegion) {
5239 case nsISelectionController::SELECTION_ANCHOR_REGION:
5240 case nsISelectionController::SELECTION_FOCUS_REGION:
5241 return GetSelectionEndPointGeometry(aRegion, aRect);
5242 break;
5243 case nsISelectionController::SELECTION_WHOLE_SELECTION:
5244 break;
5245 default:
5246 return nullptr;
5247 }
5249 NS_ASSERTION(aRegion == nsISelectionController::SELECTION_WHOLE_SELECTION,
5250 "should only be SELECTION_WHOLE_SELECTION here");
5252 nsRect anchorRect;
5253 nsIFrame* anchorFrame = GetSelectionEndPointGeometry(
5254 nsISelectionController::SELECTION_ANCHOR_REGION, &anchorRect);
5255 if (!anchorFrame)
5256 return nullptr;
5258 nsRect focusRect;
5259 nsIFrame* focusFrame = GetSelectionEndPointGeometry(
5260 nsISelectionController::SELECTION_FOCUS_REGION, &focusRect);
5261 if (!focusFrame)
5262 return nullptr;
5264 NS_ASSERTION(anchorFrame->PresContext() == focusFrame->PresContext(),
5265 "points of selection in different documents?");
5266 // make focusRect relative to anchorFrame
5267 focusRect += focusFrame->GetOffsetTo(anchorFrame);
5269 aRect->UnionRectEdges(anchorRect, focusRect);
5270 return anchorFrame;
5271 }
5273 nsIFrame *
5274 Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion, nsRect* aRect)
5275 {
5276 if (!mFrameSelection)
5277 return nullptr; // nothing to do
5279 NS_ENSURE_TRUE(aRect, nullptr);
5281 aRect->SetRect(0, 0, 0, 0);
5283 nsINode *node = nullptr;
5284 uint32_t nodeOffset = 0;
5285 nsIFrame *frame = nullptr;
5287 switch (aRegion) {
5288 case nsISelectionController::SELECTION_ANCHOR_REGION:
5289 node = GetAnchorNode();
5290 nodeOffset = AnchorOffset();
5291 break;
5292 case nsISelectionController::SELECTION_FOCUS_REGION:
5293 node = GetFocusNode();
5294 nodeOffset = FocusOffset();
5295 break;
5296 default:
5297 return nullptr;
5298 }
5300 if (!node)
5301 return nullptr;
5303 nsCOMPtr<nsIContent> content = do_QueryInterface(node);
5304 NS_ENSURE_TRUE(content.get(), nullptr);
5305 int32_t frameOffset = 0;
5306 frame = mFrameSelection->GetFrameForNodeOffset(content, nodeOffset,
5307 mFrameSelection->GetHint(),
5308 &frameOffset);
5309 if (!frame)
5310 return nullptr;
5312 // Figure out what node type we have, then get the
5313 // appropriate rect for it's nodeOffset.
5314 bool isText = node->IsNodeOfType(nsINode::eTEXT);
5316 nsPoint pt(0, 0);
5317 if (isText) {
5318 nsIFrame* childFrame = nullptr;
5319 frameOffset = 0;
5320 nsresult rv =
5321 frame->GetChildFrameContainingOffset(nodeOffset,
5322 mFrameSelection->GetHint(),
5323 &frameOffset, &childFrame);
5324 if (NS_FAILED(rv))
5325 return nullptr;
5326 if (!childFrame)
5327 return nullptr;
5329 frame = childFrame;
5331 // Get the x coordinate of the offset into the text frame.
5332 rv = GetCachedFrameOffset(frame, nodeOffset, pt);
5333 if (NS_FAILED(rv))
5334 return nullptr;
5335 }
5337 // Return the rect relative to the frame, with zero width.
5338 if (isText) {
5339 aRect->x = pt.x;
5340 } else if (mFrameSelection->GetHint() == nsFrameSelection::HINTLEFT) {
5341 // It's the frame's right edge we're interested in.
5342 aRect->x = frame->GetRect().width;
5343 }
5344 aRect->height = frame->GetRect().height;
5346 return frame;
5347 }
5349 NS_IMETHODIMP
5350 Selection::ScrollSelectionIntoViewEvent::Run()
5351 {
5352 if (!mSelection)
5353 return NS_OK; // event revoked
5355 int32_t flags = Selection::SCROLL_DO_FLUSH |
5356 Selection::SCROLL_SYNCHRONOUS;
5358 mSelection->mScrollEvent.Forget();
5359 mSelection->ScrollIntoView(mRegion, mVerticalScroll,
5360 mHorizontalScroll, mFlags | flags);
5361 return NS_OK;
5362 }
5364 nsresult
5365 Selection::PostScrollSelectionIntoViewEvent(
5366 SelectionRegion aRegion,
5367 int32_t aFlags,
5368 nsIPresShell::ScrollAxis aVertical,
5369 nsIPresShell::ScrollAxis aHorizontal)
5370 {
5371 // If we've already posted an event, revoke it and place a new one at the
5372 // end of the queue to make sure that any new pending reflow events are
5373 // processed before we scroll. This will insure that we scroll to the
5374 // correct place on screen.
5375 mScrollEvent.Revoke();
5377 nsRefPtr<ScrollSelectionIntoViewEvent> ev =
5378 new ScrollSelectionIntoViewEvent(this, aRegion, aVertical, aHorizontal,
5379 aFlags);
5380 nsresult rv = NS_DispatchToCurrentThread(ev);
5381 NS_ENSURE_SUCCESS(rv, rv);
5383 mScrollEvent = ev;
5384 return NS_OK;
5385 }
5387 NS_IMETHODIMP
5388 Selection::ScrollIntoView(SelectionRegion aRegion, bool aIsSynchronous,
5389 int16_t aVPercent, int16_t aHPercent)
5390 {
5391 ErrorResult result;
5392 ScrollIntoView(aRegion, aIsSynchronous, aVPercent, aHPercent, result);
5393 if (result.Failed()) {
5394 return result.ErrorCode();
5395 }
5396 return NS_OK;
5397 }
5399 void
5400 Selection::ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
5401 int16_t aVPercent, int16_t aHPercent,
5402 ErrorResult& aRv)
5403 {
5404 nsresult rv = ScrollIntoViewInternal(aRegion, aIsSynchronous,
5405 nsIPresShell::ScrollAxis(aVPercent),
5406 nsIPresShell::ScrollAxis(aHPercent));
5407 if (NS_FAILED(rv)) {
5408 aRv.Throw(rv);
5409 }
5410 }
5412 NS_IMETHODIMP
5413 Selection::ScrollIntoViewInternal(SelectionRegion aRegion, bool aIsSynchronous,
5414 nsIPresShell::ScrollAxis aVertical,
5415 nsIPresShell::ScrollAxis aHorizontal)
5416 {
5417 return ScrollIntoView(aRegion, aVertical, aHorizontal,
5418 aIsSynchronous ? Selection::SCROLL_SYNCHRONOUS : 0);
5419 }
5421 nsresult
5422 Selection::ScrollIntoView(SelectionRegion aRegion,
5423 nsIPresShell::ScrollAxis aVertical,
5424 nsIPresShell::ScrollAxis aHorizontal,
5425 int32_t aFlags)
5426 {
5427 if (!mFrameSelection)
5428 return NS_OK;//nothing to do
5430 nsCOMPtr<nsIPresShell> presShell = mFrameSelection->GetShell();
5431 if (!presShell)
5432 return NS_OK;
5434 if (mFrameSelection->GetBatching())
5435 return NS_OK;
5437 if (!(aFlags & Selection::SCROLL_SYNCHRONOUS))
5438 return PostScrollSelectionIntoViewEvent(aRegion, aFlags,
5439 aVertical, aHorizontal);
5441 // Now that text frame character offsets are always valid (though not
5442 // necessarily correct), the worst that will happen if we don't flush here
5443 // is that some callers might scroll to the wrong place. Those should
5444 // either manually flush if they're in a safe position for it or use the
5445 // async version of this method.
5446 if (aFlags & Selection::SCROLL_DO_FLUSH) {
5447 presShell->FlushPendingNotifications(Flush_Layout);
5449 // Reget the presshell, since it might have been Destroy'ed.
5450 presShell = mFrameSelection ? mFrameSelection->GetShell() : nullptr;
5451 if (!presShell)
5452 return NS_OK;
5453 }
5455 //
5456 // Scroll the selection region into view.
5457 //
5459 nsRect rect;
5460 nsIFrame* frame = GetSelectionAnchorGeometry(aRegion, &rect);
5461 if (!frame)
5462 return NS_ERROR_FAILURE;
5464 // Scroll vertically to get the caret into view, but only if the container
5465 // is perceived to be scrollable in that direction (i.e. there is a visible
5466 // vertical scrollbar or the scroll range is at least one device pixel)
5467 aVertical.mOnlyIfPerceivedScrollableDirection = true;
5469 uint32_t flags = 0;
5470 if (aFlags & Selection::SCROLL_FIRST_ANCESTOR_ONLY) {
5471 flags |= nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY;
5472 }
5473 if (aFlags & Selection::SCROLL_OVERFLOW_HIDDEN) {
5474 flags |= nsIPresShell::SCROLL_OVERFLOW_HIDDEN;
5475 }
5477 presShell->ScrollFrameRectIntoView(frame, rect, aVertical, aHorizontal,
5478 flags);
5479 return NS_OK;
5480 }
5482 NS_IMETHODIMP
5483 Selection::AddSelectionListener(nsISelectionListener* aNewListener)
5484 {
5485 if (!aNewListener)
5486 return NS_ERROR_NULL_POINTER;
5487 ErrorResult result;
5488 AddSelectionListener(aNewListener, result);
5489 if (result.Failed()) {
5490 return result.ErrorCode();
5491 }
5492 return NS_OK;
5493 }
5495 void
5496 Selection::AddSelectionListener(nsISelectionListener* aNewListener,
5497 ErrorResult& aRv)
5498 {
5499 bool result = mSelectionListeners.AppendObject(aNewListener); // AddRefs
5500 if (!result) {
5501 aRv.Throw(NS_ERROR_FAILURE);
5502 }
5503 }
5505 NS_IMETHODIMP
5506 Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove)
5507 {
5508 if (!aListenerToRemove)
5509 return NS_ERROR_NULL_POINTER;
5510 ErrorResult result;
5511 RemoveSelectionListener(aListenerToRemove, result);
5512 if (result.Failed()) {
5513 return result.ErrorCode();
5514 }
5515 return NS_OK;
5516 }
5518 void
5519 Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove,
5520 ErrorResult& aRv)
5521 {
5522 bool result = mSelectionListeners.RemoveObject(aListenerToRemove); // Releases
5523 if (!result) {
5524 aRv.Throw(NS_ERROR_FAILURE);
5525 }
5526 }
5528 nsresult
5529 Selection::NotifySelectionListeners()
5530 {
5531 if (!mFrameSelection)
5532 return NS_OK;//nothing to do
5534 if (mFrameSelection->GetBatching()) {
5535 mFrameSelection->SetDirty();
5536 return NS_OK;
5537 }
5538 nsCOMArray<nsISelectionListener> selectionListeners(mSelectionListeners);
5539 int32_t cnt = selectionListeners.Count();
5540 if (cnt != mSelectionListeners.Count()) {
5541 return NS_ERROR_OUT_OF_MEMORY; // nsCOMArray is fallible
5542 }
5544 nsCOMPtr<nsIDOMDocument> domdoc;
5545 nsIPresShell* ps = GetPresShell();
5546 if (ps) {
5547 domdoc = do_QueryInterface(ps->GetDocument());
5548 }
5550 short reason = mFrameSelection->PopReason();
5551 for (int32_t i = 0; i < cnt; i++) {
5552 selectionListeners[i]->NotifySelectionChanged(domdoc, this, reason);
5553 }
5554 return NS_OK;
5555 }
5557 NS_IMETHODIMP
5558 Selection::StartBatchChanges()
5559 {
5560 if (mFrameSelection)
5561 mFrameSelection->StartBatchChanges();
5563 return NS_OK;
5564 }
5568 NS_IMETHODIMP
5569 Selection::EndBatchChanges()
5570 {
5571 if (mFrameSelection)
5572 mFrameSelection->EndBatchChanges();
5574 return NS_OK;
5575 }
5579 NS_IMETHODIMP
5580 Selection::DeleteFromDocument()
5581 {
5582 ErrorResult result;
5583 DeleteFromDocument(result);
5584 return result.ErrorCode();
5585 }
5587 void
5588 Selection::DeleteFromDocument(ErrorResult& aRv)
5589 {
5590 if (!mFrameSelection)
5591 return;//nothing to do
5592 nsresult rv = mFrameSelection->DeleteFromDocument();
5593 if (NS_FAILED(rv)) {
5594 aRv.Throw(rv);
5595 }
5596 }
5598 NS_IMETHODIMP
5599 Selection::Modify(const nsAString& aAlter, const nsAString& aDirection,
5600 const nsAString& aGranularity)
5601 {
5602 ErrorResult result;
5603 Modify(aAlter, aDirection, aGranularity, result);
5604 return result.ErrorCode();
5605 }
5607 void
5608 Selection::Modify(const nsAString& aAlter, const nsAString& aDirection,
5609 const nsAString& aGranularity, ErrorResult& aRv)
5610 {
5611 // Silently exit if there's no selection or no focus node.
5612 if (!mFrameSelection || !GetAnchorFocusRange() || !GetFocusNode()) {
5613 return;
5614 }
5616 if (!aAlter.LowerCaseEqualsLiteral("move") &&
5617 !aAlter.LowerCaseEqualsLiteral("extend")) {
5618 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
5619 return;
5620 }
5622 if (!aDirection.LowerCaseEqualsLiteral("forward") &&
5623 !aDirection.LowerCaseEqualsLiteral("backward") &&
5624 !aDirection.LowerCaseEqualsLiteral("left") &&
5625 !aDirection.LowerCaseEqualsLiteral("right")) {
5626 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
5627 return;
5628 }
5630 // Line moves are always visual.
5631 bool visual = aDirection.LowerCaseEqualsLiteral("left") ||
5632 aDirection.LowerCaseEqualsLiteral("right") ||
5633 aGranularity.LowerCaseEqualsLiteral("line");
5635 bool forward = aDirection.LowerCaseEqualsLiteral("forward") ||
5636 aDirection.LowerCaseEqualsLiteral("right");
5638 bool extend = aAlter.LowerCaseEqualsLiteral("extend");
5640 // The uint32_t casts below prevent an enum mismatch warning.
5641 nsSelectionAmount amount;
5642 uint32_t keycode;
5643 if (aGranularity.LowerCaseEqualsLiteral("character")) {
5644 amount = eSelectCluster;
5645 keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_RIGHT :
5646 (uint32_t) nsIDOMKeyEvent::DOM_VK_LEFT;
5647 }
5648 else if (aGranularity.LowerCaseEqualsLiteral("word")) {
5649 amount = eSelectWordNoSpace;
5650 keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_RIGHT :
5651 (uint32_t) nsIDOMKeyEvent::DOM_VK_LEFT;
5652 }
5653 else if (aGranularity.LowerCaseEqualsLiteral("line")) {
5654 amount = eSelectLine;
5655 keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_DOWN :
5656 (uint32_t) nsIDOMKeyEvent::DOM_VK_UP;
5657 }
5658 else if (aGranularity.LowerCaseEqualsLiteral("lineboundary")) {
5659 amount = eSelectLine;
5660 keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_END :
5661 (uint32_t) nsIDOMKeyEvent::DOM_VK_HOME;
5662 }
5663 else if (aGranularity.LowerCaseEqualsLiteral("sentence") ||
5664 aGranularity.LowerCaseEqualsLiteral("sentenceboundary") ||
5665 aGranularity.LowerCaseEqualsLiteral("paragraph") ||
5666 aGranularity.LowerCaseEqualsLiteral("paragraphboundary") ||
5667 aGranularity.LowerCaseEqualsLiteral("documentboundary")) {
5668 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
5669 }
5670 else {
5671 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
5672 return;
5673 }
5675 // If the anchor doesn't equal the focus and we try to move without first
5676 // collapsing the selection, MoveCaret will collapse the selection and quit.
5677 // To avoid this, we need to collapse the selection first.
5678 nsresult rv = NS_OK;
5679 if (!extend) {
5680 nsINode* focusNode = GetFocusNode();
5681 // We should have checked earlier that there was a focus node.
5682 if (!focusNode) {
5683 aRv.Throw(NS_ERROR_UNEXPECTED);
5684 return;
5685 }
5686 uint32_t focusOffset = FocusOffset();
5687 Collapse(focusNode, focusOffset);
5688 }
5690 // If the base level of the focused frame is odd, we may have to swap the
5691 // direction of the keycode.
5692 nsIFrame *frame;
5693 int32_t offset;
5694 rv = GetPrimaryFrameForFocusNode(&frame, &offset, visual);
5695 if (NS_SUCCEEDED(rv) && frame) {
5696 nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(frame);
5698 if (baseLevel & 1) {
5699 if (!visual && keycode == nsIDOMKeyEvent::DOM_VK_RIGHT) {
5700 keycode = nsIDOMKeyEvent::DOM_VK_LEFT;
5701 }
5702 else if (!visual && keycode == nsIDOMKeyEvent::DOM_VK_LEFT) {
5703 keycode = nsIDOMKeyEvent::DOM_VK_RIGHT;
5704 }
5705 else if (visual && keycode == nsIDOMKeyEvent::DOM_VK_HOME) {
5706 keycode = nsIDOMKeyEvent::DOM_VK_END;
5707 }
5708 else if (visual && keycode == nsIDOMKeyEvent::DOM_VK_END) {
5709 keycode = nsIDOMKeyEvent::DOM_VK_HOME;
5710 }
5711 }
5712 }
5714 // MoveCaret will return an error if it can't move in the specified
5715 // direction, but we just ignore this error unless it's a line move, in which
5716 // case we call nsISelectionController::CompleteMove to move the cursor to
5717 // the beginning/end of the line.
5718 rv = mFrameSelection->MoveCaret(keycode, extend, amount, visual);
5720 if (aGranularity.LowerCaseEqualsLiteral("line") && NS_FAILED(rv)) {
5721 nsCOMPtr<nsISelectionController> shell =
5722 do_QueryInterface(mFrameSelection->GetShell());
5723 if (!shell)
5724 return;
5725 shell->CompleteMove(forward, extend);
5726 }
5727 }
5729 /** SelectionLanguageChange modifies the cursor Bidi level after a change in keyboard direction
5730 * @param aLangRTL is true if the new language is right-to-left or false if the new language is left-to-right
5731 */
5732 NS_IMETHODIMP
5733 Selection::SelectionLanguageChange(bool aLangRTL)
5734 {
5735 if (!mFrameSelection)
5736 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
5737 nsresult result;
5738 nsIFrame *focusFrame = 0;
5740 result = GetPrimaryFrameForFocusNode(&focusFrame, nullptr, false);
5741 if (NS_FAILED(result)) {
5742 return result;
5743 }
5744 if (!focusFrame) {
5745 return NS_ERROR_FAILURE;
5746 }
5748 int32_t frameStart, frameEnd;
5749 focusFrame->GetOffsets(frameStart, frameEnd);
5750 nsRefPtr<nsPresContext> context = GetPresContext();
5751 uint8_t levelBefore, levelAfter;
5752 if (!context) {
5753 return NS_ERROR_FAILURE;
5754 }
5756 uint8_t level = NS_GET_EMBEDDING_LEVEL(focusFrame);
5757 int32_t focusOffset = static_cast<int32_t>(FocusOffset());
5758 if ((focusOffset != frameStart) && (focusOffset != frameEnd))
5759 // the cursor is not at a frame boundary, so the level of both the characters (logically) before and after the cursor
5760 // is equal to the frame level
5761 levelBefore = levelAfter = level;
5762 else {
5763 // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find the level of the characters
5764 // before and after the cursor
5765 nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode());
5766 /*
5767 nsFrameSelection::HINT hint;
5769 if ((focusOffset == frameStart && level) // beginning of an RTL frame
5770 || (focusOffset == frameEnd && !level)) { // end of an LTR frame
5771 hint = nsFrameSelection::HINTRIGHT;
5772 }
5773 else { // end of an RTL frame or beginning of an LTR frame
5774 hint = nsFrameSelection::HINTLEFT;
5775 }
5776 mFrameSelection->SetHint(hint);
5777 */
5778 nsPrevNextBidiLevels levels = mFrameSelection->
5779 GetPrevNextBidiLevels(focusContent, focusOffset, false);
5781 levelBefore = levels.mLevelBefore;
5782 levelAfter = levels.mLevelAfter;
5783 }
5785 if ((levelBefore & 1) == (levelAfter & 1)) {
5786 // if cursor is between two characters with the same orientation, changing the keyboard language
5787 // must toggle the cursor level between the level of the character with the lowest level
5788 // (if the new language corresponds to the orientation of that character) and this level plus 1
5789 // (if the new language corresponds to the opposite orientation)
5790 if ((level != levelBefore) && (level != levelAfter))
5791 level = std::min(levelBefore, levelAfter);
5792 if ((level & 1) == aLangRTL)
5793 mFrameSelection->SetCaretBidiLevel(level);
5794 else
5795 mFrameSelection->SetCaretBidiLevel(level + 1);
5796 }
5797 else {
5798 // if cursor is between characters with opposite orientations, changing the keyboard language must change
5799 // the cursor level to that of the adjacent character with the orientation corresponding to the new language.
5800 if ((levelBefore & 1) == aLangRTL)
5801 mFrameSelection->SetCaretBidiLevel(levelBefore);
5802 else
5803 mFrameSelection->SetCaretBidiLevel(levelAfter);
5804 }
5806 // The caret might have moved, so invalidate the desired X position
5807 // for future usages of up-arrow or down-arrow
5808 mFrameSelection->InvalidateDesiredX();
5810 return NS_OK;
5811 }
5813 NS_IMETHODIMP_(nsDirection)
5814 Selection::GetSelectionDirection() {
5815 return mDirection;
5816 }
5818 NS_IMETHODIMP_(void)
5819 Selection::SetSelectionDirection(nsDirection aDirection) {
5820 mDirection = aDirection;
5821 }
5823 JSObject*
5824 Selection::WrapObject(JSContext* aCx)
5825 {
5826 return mozilla::dom::SelectionBinding::Wrap(aCx, this);
5827 }
5829 // nsAutoCopyListener
5831 nsAutoCopyListener* nsAutoCopyListener::sInstance = nullptr;
5833 NS_IMPL_ISUPPORTS(nsAutoCopyListener, nsISelectionListener)
5835 /*
5836 * What we do now:
5837 * On every selection change, we copy to the clipboard anew, creating a
5838 * HTML buffer, a transferable, an nsISupportsString and
5839 * a huge mess every time. This is basically what nsPresShell::DoCopy does
5840 * to move the selection into the clipboard for Edit->Copy.
5841 *
5842 * What we should do, to make our end of the deal faster:
5843 * Create a singleton transferable with our own magic converter. When selection
5844 * changes (use a quick cache to detect ``real'' changes), we put the new
5845 * nsISelection in the transferable. Our magic converter will take care of
5846 * transferable->whatever-other-format when the time comes to actually
5847 * hand over the clipboard contents.
5848 *
5849 * Other issues:
5850 * - which X clipboard should we populate?
5851 * - should we use a different one than Edit->Copy, so that inadvertant
5852 * selections (or simple clicks, which currently cause a selection
5853 * notification, regardless of if they're in the document which currently has
5854 * selection!) don't lose the contents of the ``application''? Or should we
5855 * just put some intelligence in the ``is this a real selection?'' code to
5856 * protect our selection against clicks in other documents that don't create
5857 * selections?
5858 * - maybe we should just never clear the X clipboard? That would make this
5859 * problem just go away, which is very tempting.
5860 */
5862 NS_IMETHODIMP
5863 nsAutoCopyListener::NotifySelectionChanged(nsIDOMDocument *aDoc,
5864 nsISelection *aSel, int16_t aReason)
5865 {
5866 if (!(aReason & nsISelectionListener::MOUSEUP_REASON ||
5867 aReason & nsISelectionListener::SELECTALL_REASON ||
5868 aReason & nsISelectionListener::KEYPRESS_REASON))
5869 return NS_OK; //dont care if we are still dragging
5871 bool collapsed;
5872 if (!aDoc || !aSel ||
5873 NS_FAILED(aSel->GetIsCollapsed(&collapsed)) || collapsed) {
5874 #ifdef DEBUG_CLIPBOARD
5875 fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n");
5876 #endif
5877 /* clear X clipboard? */
5878 return NS_OK;
5879 }
5881 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
5882 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
5884 // call the copy code
5885 return nsCopySupport::HTMLCopy(aSel, doc, nsIClipboard::kSelectionClipboard);
5886 }