michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Implementation of selection: nsISelection,nsISelectionPrivate and nsFrameSelection michael@0: */ michael@0: michael@0: #include "mozilla/dom/Selection.h" michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/EventStates.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsFrameSelection.h" michael@0: #include "nsISelectionListener.h" michael@0: #include "nsContentCID.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsRange.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsITableCellLayout.h" michael@0: #include "nsTArray.h" michael@0: #include "nsTableOuterFrame.h" michael@0: #include "nsTableCellFrame.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsCCUncollectableMarker.h" michael@0: #include "nsIContentIterator.h" michael@0: #include "nsIDocumentEncoder.h" michael@0: #include "nsTextFragment.h" michael@0: #include michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIFrameTraversal.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsLayoutCID.h" michael@0: #include "nsBidiPresUtils.h" michael@0: static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); michael@0: #include "nsTextFrame.h" michael@0: michael@0: #include "nsIDOMText.h" michael@0: michael@0: #include "nsContentUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsDOMClassInfoID.h" michael@0: michael@0: //included for desired x position; michael@0: #include "nsPresContext.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsCaret.h" michael@0: michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: #include "nsITimer.h" michael@0: #include "nsFrameManager.h" michael@0: // notifications michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDocument.h" michael@0: michael@0: #include "nsISelectionController.h"//for the enums michael@0: #include "nsAutoCopyListener.h" michael@0: #include "nsCopySupport.h" michael@0: #include "nsIClipboard.h" michael@0: #include "nsIFrameInlines.h" michael@0: michael@0: #include "nsIBidiKeyboard.h" michael@0: michael@0: #include "nsError.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/ShadowRoot.h" michael@0: #include "mozilla/ErrorResult.h" michael@0: #include "mozilla/dom/SelectionBinding.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: //#define DEBUG_TABLE 1 michael@0: michael@0: static bool IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode); michael@0: michael@0: static nsIAtom *GetTag(nsINode *aNode); michael@0: // returns the parent michael@0: static nsINode* ParentOffset(nsINode *aNode, int32_t *aChildOffset); michael@0: static nsINode* GetCellParent(nsINode *aDomNode); michael@0: michael@0: #ifdef PRINT_RANGE michael@0: static void printRange(nsRange *aDomRange); michael@0: #define DEBUG_OUT_RANGE(x) printRange(x) michael@0: #else michael@0: #define DEBUG_OUT_RANGE(x) michael@0: #endif // PRINT_RANGE michael@0: michael@0: michael@0: michael@0: //#define DEBUG_SELECTION // uncomment for printf describing every collapse and extend. michael@0: //#define DEBUG_NAVIGATION michael@0: michael@0: michael@0: //#define DEBUG_TABLE_SELECTION 1 michael@0: michael@0: struct CachedOffsetForFrame { michael@0: CachedOffsetForFrame() michael@0: : mCachedFrameOffset(0, 0) // nsPoint ctor michael@0: , mLastCaretFrame(nullptr) michael@0: , mLastContentOffset(0) michael@0: , mCanCacheFrameOffset(false) michael@0: {} michael@0: michael@0: nsPoint mCachedFrameOffset; // cached frame offset michael@0: nsIFrame* mLastCaretFrame; // store the frame the caret was last drawn in. michael@0: int32_t mLastContentOffset; // store last content offset michael@0: bool mCanCacheFrameOffset; // cached frame offset is valid? michael@0: }; michael@0: michael@0: // Stack-class to turn on/off selection batching for table selection michael@0: class MOZ_STACK_CLASS nsSelectionBatcher MOZ_FINAL michael@0: { michael@0: private: michael@0: nsCOMPtr mSelection; michael@0: public: michael@0: nsSelectionBatcher(nsISelectionPrivate *aSelection) : mSelection(aSelection) michael@0: { michael@0: if (mSelection) mSelection->StartBatchChanges(); michael@0: } michael@0: ~nsSelectionBatcher() michael@0: { michael@0: if (mSelection) mSelection->EndBatchChanges(); michael@0: } michael@0: }; michael@0: michael@0: class nsAutoScrollTimer : public nsITimerCallback michael@0: { michael@0: public: michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: nsAutoScrollTimer() michael@0: : mFrameSelection(0), mSelection(0), mPresContext(0), mPoint(0,0), mDelay(30) michael@0: { michael@0: } michael@0: michael@0: virtual ~nsAutoScrollTimer() michael@0: { michael@0: if (mTimer) michael@0: mTimer->Cancel(); michael@0: } michael@0: michael@0: // aPoint is relative to aPresContext's root frame michael@0: nsresult Start(nsPresContext *aPresContext, nsPoint &aPoint) michael@0: { michael@0: mPoint = aPoint; michael@0: michael@0: // Store the presentation context. The timer will be michael@0: // stopped by the selection if the prescontext is destroyed. michael@0: mPresContext = aPresContext; michael@0: michael@0: mContent = nsIPresShell::GetCapturingContent(); michael@0: michael@0: if (!mTimer) michael@0: { michael@0: nsresult result; michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1", &result); michael@0: michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: } michael@0: michael@0: return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: nsresult Stop() michael@0: { michael@0: if (mTimer) michael@0: { michael@0: mTimer->Cancel(); michael@0: mTimer = 0; michael@0: } michael@0: michael@0: mContent = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult Init(nsFrameSelection* aFrameSelection, Selection* aSelection) michael@0: { michael@0: mFrameSelection = aFrameSelection; michael@0: mSelection = aSelection; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult SetDelay(uint32_t aDelay) michael@0: { michael@0: mDelay = aDelay; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD Notify(nsITimer *timer) MOZ_OVERRIDE michael@0: { michael@0: if (mSelection && mPresContext) michael@0: { michael@0: nsWeakFrame frame = michael@0: mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nullptr; michael@0: if (!frame) michael@0: return NS_OK; michael@0: mContent = nullptr; michael@0: michael@0: nsPoint pt = mPoint - michael@0: frame->GetOffsetTo(mPresContext->PresShell()->FrameManager()->GetRootFrame()); michael@0: mFrameSelection->HandleDrag(frame, pt); michael@0: if (!frame.IsAlive()) michael@0: return NS_OK; michael@0: michael@0: NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?"); michael@0: mSelection->DoAutoScroll(frame, pt); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsFrameSelection *mFrameSelection; michael@0: Selection* mSelection; michael@0: nsPresContext *mPresContext; michael@0: // relative to mPresContext's root frame michael@0: nsPoint mPoint; michael@0: nsCOMPtr mTimer; michael@0: nsCOMPtr mContent; michael@0: uint32_t mDelay; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAutoScrollTimer, nsITimerCallback) michael@0: michael@0: nsresult NS_NewDomSelection(nsISelection **aDomSelection) michael@0: { michael@0: Selection* rlist = new Selection; michael@0: *aDomSelection = (nsISelection *)rlist; michael@0: NS_ADDREF(rlist); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static int8_t michael@0: GetIndexFromSelectionType(SelectionType aType) michael@0: { michael@0: switch (aType) michael@0: { michael@0: case nsISelectionController::SELECTION_NORMAL: return 0; break; michael@0: case nsISelectionController::SELECTION_SPELLCHECK: return 1; break; michael@0: case nsISelectionController::SELECTION_IME_RAWINPUT: return 2; break; michael@0: case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: return 3; break; michael@0: case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: return 4; break; michael@0: case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: return 5; break; michael@0: case nsISelectionController::SELECTION_ACCESSIBILITY: return 6; break; michael@0: case nsISelectionController::SELECTION_FIND: return 7; break; michael@0: case nsISelectionController::SELECTION_URLSECONDARY: return 8; break; michael@0: default: michael@0: return -1; break; michael@0: } michael@0: /* NOTREACHED */ michael@0: return 0; michael@0: } michael@0: michael@0: static SelectionType michael@0: GetSelectionTypeFromIndex(int8_t aIndex) michael@0: { michael@0: switch (aIndex) michael@0: { michael@0: case 0: return nsISelectionController::SELECTION_NORMAL; break; michael@0: case 1: return nsISelectionController::SELECTION_SPELLCHECK; break; michael@0: case 2: return nsISelectionController::SELECTION_IME_RAWINPUT; break; michael@0: case 3: return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT; break; michael@0: case 4: return nsISelectionController::SELECTION_IME_CONVERTEDTEXT; break; michael@0: case 5: return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; break; michael@0: case 6: return nsISelectionController::SELECTION_ACCESSIBILITY; break; michael@0: case 7: return nsISelectionController::SELECTION_FIND; break; michael@0: case 8: return nsISelectionController::SELECTION_URLSECONDARY; break; michael@0: default: michael@0: return nsISelectionController::SELECTION_NORMAL; break; michael@0: } michael@0: /* NOTREACHED */ michael@0: return 0; michael@0: } michael@0: michael@0: /* michael@0: The limiter is used specifically for the text areas and textfields michael@0: In that case it is the DIV tag that is anonymously created for the text michael@0: areas/fields. Text nodes and BR nodes fall beneath it. In the case of a michael@0: BR node the limiter will be the parent and the offset will point before or michael@0: after the BR node. In the case of the text node the parent content is michael@0: the text node itself and the offset will be the exact character position. michael@0: The offset is not important to check for validity. Simply look at the michael@0: passed in content. If it equals the limiter then the selection point is valid. michael@0: If its parent it the limiter then the point is also valid. In the case of michael@0: NO limiter all points are valid since you are in a topmost iframe. (browser michael@0: or composer) michael@0: */ michael@0: bool michael@0: IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode) michael@0: { michael@0: if (!aFrameSel || !aNode) michael@0: return false; michael@0: michael@0: nsIContent *limiter = aFrameSel->GetLimiter(); michael@0: if (limiter && limiter != aNode && limiter != aNode->GetParent()) { michael@0: //if newfocus == the limiter. that's ok. but if not there and not parent bad michael@0: return false; //not in the right content. tLimiter said so michael@0: } michael@0: michael@0: limiter = aFrameSel->GetAncestorLimiter(); michael@0: return !limiter || nsContentUtils::ContentIsDescendantOf(aNode, limiter); michael@0: } michael@0: michael@0: michael@0: ////////////BEGIN nsFrameSelection methods michael@0: michael@0: nsFrameSelection::nsFrameSelection() michael@0: { michael@0: int32_t i; michael@0: for (i = 0;iSetType(GetSelectionTypeFromIndex(i)); michael@0: } michael@0: mBatching = 0; michael@0: mChangesDuringBatching = false; michael@0: mNotifyFrames = true; michael@0: michael@0: mMouseDoubleDownState = false; michael@0: michael@0: mHint = HINTLEFT; michael@0: mCaretBidiLevel = BIDI_LEVEL_UNDEFINED; michael@0: mDragSelectingCells = false; michael@0: mSelectingTableCellMode = 0; michael@0: mSelectedCellIndex = 0; michael@0: michael@0: // Check to see if the autocopy pref is enabled michael@0: // and add the autocopy listener if it is michael@0: if (Preferences::GetBool("clipboard.autocopy")) { michael@0: nsAutoCopyListener *autoCopy = nsAutoCopyListener::GetInstance(); michael@0: michael@0: if (autoCopy) { michael@0: int8_t index = michael@0: GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (mDomSelections[index]) { michael@0: autoCopy->Listen(mDomSelections[index]); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mDisplaySelection = nsISelectionController::SELECTION_OFF; michael@0: mSelectionChangeReason = nsISelectionListener::NO_REASON; michael@0: michael@0: mDelayedMouseEventValid = false; michael@0: // These values are not used since they are only valid when michael@0: // mDelayedMouseEventValid is true, and setting mDelayedMouseEventValid michael@0: //alwaysoverrides these values. michael@0: mDelayedMouseEventIsShift = false; michael@0: mDelayedMouseEventClickCount = 0; michael@0: } michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection) michael@0: int32_t i; michael@0: for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) { michael@0: tmp->mDomSelections[i] = nullptr; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mCellParent) michael@0: tmp->mSelectingTableCellMode = 0; michael@0: tmp->mDragSelectingCells = false; michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartSelectedCell) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSelectedCell) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mAppendStartSelectedCell) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnselectCellOnMouseUp) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaintainRange) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mLimiter) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mAncestorLimiter) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection) michael@0: if (tmp->mShell && tmp->mShell->GetDocument() && michael@0: nsCCUncollectableMarker::InGeneration(cb, michael@0: tmp->mShell->GetDocument()-> michael@0: GetMarkedCCGeneration())) { michael@0: return NS_SUCCESS_INTERRUPTED_TRAVERSE; michael@0: } michael@0: int32_t i; michael@0: for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) { michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDomSelections[i]) michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCellParent) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartSelectedCell) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSelectedCell) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAppendStartSelectedCell) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnselectCellOnMouseUp) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMaintainRange) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLimiter) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAncestorLimiter) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsFrameSelection, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsFrameSelection, Release) michael@0: michael@0: michael@0: nsresult michael@0: nsFrameSelection::FetchDesiredX(nscoord &aDesiredX) //the x position requested by the Key Handling for up down michael@0: { michael@0: if (!mShell) michael@0: { michael@0: NS_ERROR("fetch desired X failed"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (mDesiredXSet) michael@0: { michael@0: aDesiredX = mDesiredX; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr caret = mShell->GetCaret(); michael@0: if (!caret) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: nsresult result = caret->SetCaretDOMSelection(mDomSelections[index]); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: nsRect coord; michael@0: nsIFrame* caretFrame = caret->GetGeometry(mDomSelections[index], &coord); michael@0: if (!caretFrame) michael@0: return NS_ERROR_FAILURE; michael@0: nsPoint viewOffset(0, 0); michael@0: nsView* view = nullptr; michael@0: caretFrame->GetOffsetFromView(viewOffset, &view); michael@0: if (view) michael@0: coord.x += viewOffset.x; michael@0: michael@0: aDesiredX = coord.x; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: void michael@0: nsFrameSelection::InvalidateDesiredX() //do not listen to mDesiredX you must get another. michael@0: { michael@0: mDesiredXSet = false; michael@0: } michael@0: michael@0: michael@0: michael@0: void michael@0: nsFrameSelection::SetDesiredX(nscoord aX) //set the mDesiredX michael@0: { michael@0: mDesiredX = aX; michael@0: mDesiredXSet = true; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(nsIFrame *aFrame, michael@0: nsPoint& aPoint, michael@0: nsIFrame **aRetFrame, michael@0: nsPoint& aRetPoint) michael@0: { michael@0: // michael@0: // The whole point of this method is to return a frame and point that michael@0: // that lie within the same valid subtree as the anchor node's frame, michael@0: // for use with the method GetContentAndOffsetsFromPoint(). michael@0: // michael@0: // A valid subtree is defined to be one where all the content nodes in michael@0: // the tree have a valid parent-child relationship. michael@0: // michael@0: // If the anchor frame and aFrame are in the same subtree, aFrame will michael@0: // be returned in aRetFrame. If they are in different subtrees, we michael@0: // return the frame for the root of the subtree. michael@0: // michael@0: michael@0: if (!aFrame || !aRetFrame) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: *aRetFrame = aFrame; michael@0: aRetPoint = aPoint; michael@0: michael@0: // michael@0: // Get the frame and content for the selection's anchor point! michael@0: // michael@0: michael@0: nsresult result; michael@0: nsCOMPtr anchorNode; michael@0: int32_t anchorOffset = 0; michael@0: michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: result = mDomSelections[index]->GetAnchorNode(getter_AddRefs(anchorNode)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: if (!anchorNode) michael@0: return NS_OK; michael@0: michael@0: result = mDomSelections[index]->GetAnchorOffset(&anchorOffset); michael@0: michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: nsCOMPtr anchorContent = do_QueryInterface(anchorNode); michael@0: michael@0: if (!anchorContent) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // michael@0: // Now find the root of the subtree containing the anchor's content. michael@0: // michael@0: michael@0: NS_ENSURE_STATE(mShell); michael@0: nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(mShell); michael@0: NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED); michael@0: michael@0: // michael@0: // Now find the root of the subtree containing aFrame's content. michael@0: // michael@0: michael@0: nsIContent* content = aFrame->GetContent(); michael@0: michael@0: if (content) michael@0: { michael@0: nsIContent* contentRoot = content->GetSelectionRootContent(mShell); michael@0: NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED); michael@0: michael@0: if (anchorRoot == contentRoot) michael@0: { michael@0: // If the aFrame's content isn't the capturing content, it should be michael@0: // a descendant. At this time, we can return simply. michael@0: nsIContent* capturedContent = nsIPresShell::GetCapturingContent(); michael@0: if (capturedContent != content) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Find the frame under the mouse cursor with the root frame. michael@0: // At this time, don't use the anchor's frame because it may not have michael@0: // fixed positioned frames. michael@0: nsIFrame* rootFrame = mShell->FrameManager()->GetRootFrame(); michael@0: nsPoint ptInRoot = aPoint + aFrame->GetOffsetTo(rootFrame); michael@0: nsIFrame* cursorFrame = michael@0: nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot); michael@0: michael@0: // If the mouse cursor in on a frame which is descendant of same michael@0: // selection root, we can expand the selection to the frame. michael@0: if (cursorFrame && cursorFrame->PresContext()->PresShell() == mShell) michael@0: { michael@0: nsIContent* cursorContent = cursorFrame->GetContent(); michael@0: NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE); michael@0: nsIContent* cursorContentRoot = michael@0: cursorContent->GetSelectionRootContent(mShell); michael@0: NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED); michael@0: if (cursorContentRoot == anchorRoot) michael@0: { michael@0: *aRetFrame = cursorFrame; michael@0: aRetPoint = aPoint + aFrame->GetOffsetTo(cursorFrame); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse michael@0: // cursor is out of the window), we should use the frame of the anchor michael@0: // root. michael@0: } michael@0: } michael@0: michael@0: // michael@0: // When we can't find a frame which is under the mouse cursor and has a same michael@0: // selection root as the anchor node's, we should return the selection root michael@0: // frame. michael@0: // michael@0: michael@0: *aRetFrame = anchorRoot->GetPrimaryFrame(); michael@0: michael@0: if (!*aRetFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // michael@0: // Now make sure that aRetPoint is converted to the same coordinate michael@0: // system used by aRetFrame. michael@0: // michael@0: michael@0: aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFrameSelection::SetCaretBidiLevel(uint8_t aLevel) michael@0: { michael@0: // If the current level is undefined, we have just inserted new text. michael@0: // In this case, we don't want to reset the keyboard language michael@0: mCaretBidiLevel = aLevel; michael@0: return; michael@0: } michael@0: michael@0: uint8_t michael@0: nsFrameSelection::GetCaretBidiLevel() const michael@0: { michael@0: return mCaretBidiLevel; michael@0: } michael@0: michael@0: void michael@0: nsFrameSelection::UndefineCaretBidiLevel() michael@0: { michael@0: mCaretBidiLevel |= BIDI_LEVEL_UNDEFINED; michael@0: } michael@0: michael@0: #ifdef PRINT_RANGE michael@0: void printRange(nsRange *aDomRange) michael@0: { michael@0: if (!aDomRange) michael@0: { michael@0: printf("NULL nsIDOMRange\n"); michael@0: } michael@0: nsINode* startNode = aDomRange->GetStartParent(); michael@0: nsINode* endNode = aDomRange->GetEndParent(); michael@0: int32_t startOffset = aDomRange->StartOffset(); michael@0: int32_t endOffset = aDomRange->EndOffset(); michael@0: michael@0: printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n", michael@0: (unsigned long)aDomRange, michael@0: (unsigned long)startNode, (long)startOffset, michael@0: (unsigned long)endNode, (long)endOffset); michael@0: michael@0: } michael@0: #endif /* PRINT_RANGE */ michael@0: michael@0: static michael@0: nsIAtom *GetTag(nsINode *aNode) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: if (!content) michael@0: { michael@0: NS_NOTREACHED("bad node passed to GetTag()"); michael@0: return nullptr; michael@0: } michael@0: michael@0: return content->Tag(); michael@0: } michael@0: michael@0: // Returns the parent michael@0: nsINode* michael@0: ParentOffset(nsINode *aNode, int32_t *aChildOffset) michael@0: { michael@0: if (!aNode || !aChildOffset) michael@0: return nullptr; michael@0: michael@0: nsIContent* parent = aNode->GetParent(); michael@0: if (parent) michael@0: { michael@0: *aChildOffset = parent->IndexOf(aNode); michael@0: michael@0: return parent; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: static nsINode* michael@0: GetCellParent(nsINode *aDomNode) michael@0: { michael@0: if (!aDomNode) michael@0: return nullptr; michael@0: nsINode* current = aDomNode; michael@0: // Start with current node and look for a table cell michael@0: while (current) michael@0: { michael@0: nsIAtom* tag = GetTag(current); michael@0: if (tag == nsGkAtoms::td || tag == nsGkAtoms::th) michael@0: return current; michael@0: current = current->GetParent(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsFrameSelection::Init(nsIPresShell *aShell, nsIContent *aLimiter) michael@0: { michael@0: mShell = aShell; michael@0: mMouseDownState = false; michael@0: mDesiredXSet = false; michael@0: mLimiter = aLimiter; michael@0: mCaretMovementStyle = michael@0: Preferences::GetInt("bidi.edit.caret_movement_style", 2); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::MoveCaret(uint32_t aKeycode, michael@0: bool aContinueSelection, michael@0: nsSelectionAmount aAmount) michael@0: { michael@0: bool visualMovement = michael@0: (aKeycode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE || michael@0: aKeycode == nsIDOMKeyEvent::DOM_VK_DELETE || michael@0: aKeycode == nsIDOMKeyEvent::DOM_VK_HOME || michael@0: aKeycode == nsIDOMKeyEvent::DOM_VK_END) ? michael@0: false : // Delete operations and home/end are always logical michael@0: mCaretMovementStyle == 1 || michael@0: (mCaretMovementStyle == 2 && !aContinueSelection); michael@0: michael@0: return MoveCaret(aKeycode, aContinueSelection, aAmount, visualMovement); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::MoveCaret(uint32_t aKeycode, michael@0: bool aContinueSelection, michael@0: nsSelectionAmount aAmount, michael@0: bool aVisualMovement) michael@0: { michael@0: NS_ENSURE_STATE(mShell); michael@0: // Flush out layout, since we need it to be up to date to do caret michael@0: // positioning. michael@0: mShell->FlushPendingNotifications(Flush_Layout); michael@0: michael@0: if (!mShell) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsPresContext *context = mShell->GetPresContext(); michael@0: if (!context) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: bool isCollapsed; michael@0: nscoord desiredX = 0; //we must keep this around and revalidate it when its just UP/DOWN michael@0: michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: nsRefPtr sel = mDomSelections[index]; michael@0: if (!sel) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: int32_t scrollFlags = 0; michael@0: nsINode* focusNode = sel->GetFocusNode(); michael@0: if (focusNode && michael@0: (focusNode->IsEditable() || michael@0: (focusNode->IsElement() && michael@0: focusNode->AsElement()->State(). michael@0: HasState(NS_EVENT_STATE_MOZ_READWRITE)))) { michael@0: // If caret moves in editor, it should cause scrolling even if it's in michael@0: // overflow: hidden;. michael@0: scrollFlags |= Selection::SCROLL_OVERFLOW_HIDDEN; michael@0: } michael@0: michael@0: nsresult result = sel->GetIsCollapsed(&isCollapsed); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: if (aKeycode == nsIDOMKeyEvent::DOM_VK_UP || michael@0: aKeycode == nsIDOMKeyEvent::DOM_VK_DOWN) michael@0: { michael@0: result = FetchDesiredX(desiredX); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: SetDesiredX(desiredX); michael@0: } michael@0: michael@0: int32_t caretStyle = Preferences::GetInt("layout.selection.caret_style", 0); michael@0: if (caretStyle == 0 michael@0: #ifdef XP_WIN michael@0: && aKeycode != nsIDOMKeyEvent::DOM_VK_UP michael@0: && aKeycode != nsIDOMKeyEvent::DOM_VK_DOWN michael@0: #endif michael@0: ) { michael@0: // Put caret at the selection edge in the |aKeycode| direction. michael@0: caretStyle = 2; michael@0: } michael@0: michael@0: if (!isCollapsed && !aContinueSelection && caretStyle == 2) { michael@0: switch (aKeycode){ michael@0: case nsIDOMKeyEvent::DOM_VK_LEFT : michael@0: case nsIDOMKeyEvent::DOM_VK_UP : michael@0: { michael@0: const nsRange* anchorFocusRange = sel->GetAnchorFocusRange(); michael@0: if (anchorFocusRange) { michael@0: PostReason(nsISelectionListener::COLLAPSETOSTART_REASON); michael@0: sel->Collapse(anchorFocusRange->GetStartParent(), michael@0: anchorFocusRange->StartOffset()); michael@0: } michael@0: mHint = HINTRIGHT; michael@0: sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, michael@0: nsIPresShell::ScrollAxis(), michael@0: nsIPresShell::ScrollAxis(), scrollFlags); michael@0: return NS_OK; michael@0: } michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_RIGHT : michael@0: case nsIDOMKeyEvent::DOM_VK_DOWN : michael@0: { michael@0: const nsRange* anchorFocusRange = sel->GetAnchorFocusRange(); michael@0: if (anchorFocusRange) { michael@0: PostReason(nsISelectionListener::COLLAPSETOEND_REASON); michael@0: sel->Collapse(anchorFocusRange->GetEndParent(), michael@0: anchorFocusRange->EndOffset()); michael@0: } michael@0: mHint = HINTLEFT; michael@0: sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, michael@0: nsIPresShell::ScrollAxis(), michael@0: nsIPresShell::ScrollAxis(), scrollFlags); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIFrame *frame; michael@0: int32_t offsetused = 0; michael@0: result = sel->GetPrimaryFrameForFocusNode(&frame, &offsetused, michael@0: aVisualMovement); michael@0: michael@0: if (NS_FAILED(result) || !frame) michael@0: return NS_FAILED(result) ? result : NS_ERROR_FAILURE; michael@0: michael@0: //set data using mLimiter to stop on scroll views. If we have a limiter then we stop peeking michael@0: //when we hit scrollable views. If no limiter then just let it go ahead michael@0: nsPeekOffsetStruct pos(aAmount, eDirPrevious, offsetused, desiredX, michael@0: true, mLimiter != nullptr, true, aVisualMovement); michael@0: michael@0: nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(frame); michael@0: michael@0: HINT tHint(mHint); //temporary variable so we dont set mHint until it is necessary michael@0: switch (aKeycode){ michael@0: case nsIDOMKeyEvent::DOM_VK_RIGHT : michael@0: InvalidateDesiredX(); michael@0: pos.mDirection = (baseLevel & 1) ? eDirPrevious : eDirNext; michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_LEFT : michael@0: InvalidateDesiredX(); michael@0: pos.mDirection = (baseLevel & 1) ? eDirNext : eDirPrevious; michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_DELETE : michael@0: InvalidateDesiredX(); michael@0: pos.mDirection = eDirNext; michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_BACK_SPACE : michael@0: InvalidateDesiredX(); michael@0: pos.mDirection = eDirPrevious; michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_DOWN : michael@0: pos.mAmount = eSelectLine; michael@0: pos.mDirection = eDirNext; michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_UP : michael@0: pos.mAmount = eSelectLine; michael@0: pos.mDirection = eDirPrevious; michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_HOME : michael@0: InvalidateDesiredX(); michael@0: pos.mAmount = eSelectBeginLine; michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_END : michael@0: InvalidateDesiredX(); michael@0: pos.mAmount = eSelectEndLine; michael@0: break; michael@0: default :return NS_ERROR_FAILURE; michael@0: } michael@0: PostReason(nsISelectionListener::KEYPRESS_REASON); michael@0: if (NS_SUCCEEDED(result = frame->PeekOffset(&pos)) && pos.mResultContent) michael@0: { michael@0: nsIFrame *theFrame; michael@0: int32_t currentOffset, frameStart, frameEnd; michael@0: michael@0: if (aAmount >= eSelectCharacter && aAmount <= eSelectWord) michael@0: { michael@0: // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does not set pos.mAttachForward, michael@0: // so determine the hint here based on the result frame and offset: michael@0: // If we're at the end of a text frame, set the hint to HINTLEFT to indicate that we michael@0: // want the caret displayed at the end of this frame, not at the beginning of the next one. michael@0: theFrame = pos.mResultFrame; michael@0: theFrame->GetOffsets(frameStart, frameEnd); michael@0: currentOffset = pos.mContentOffset; michael@0: if (frameEnd == currentOffset && !(frameStart == 0 && frameEnd == 0)) michael@0: tHint = HINTLEFT; michael@0: else michael@0: tHint = HINTRIGHT; michael@0: } else { michael@0: // For up/down and home/end, pos.mResultFrame might not be set correctly, or not at all. michael@0: // In these cases, get the frame based on the content and hint returned by PeekOffset(). michael@0: tHint = (HINT)pos.mAttachForward; michael@0: theFrame = GetFrameForNodeOffset(pos.mResultContent, pos.mContentOffset, michael@0: tHint, ¤tOffset); michael@0: if (!theFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: theFrame->GetOffsets(frameStart, frameEnd); michael@0: } michael@0: michael@0: if (context->BidiEnabled()) michael@0: { michael@0: switch (aKeycode) { michael@0: case nsIDOMKeyEvent::DOM_VK_HOME: michael@0: case nsIDOMKeyEvent::DOM_VK_END: michael@0: // set the caret Bidi level to the paragraph embedding level michael@0: SetCaretBidiLevel(NS_GET_BASE_LEVEL(theFrame)); michael@0: break; michael@0: michael@0: default: michael@0: // If the current position is not a frame boundary, it's enough just to take the Bidi level of the current frame michael@0: if ((pos.mContentOffset != frameStart && pos.mContentOffset != frameEnd) michael@0: || (eSelectLine == aAmount)) michael@0: { michael@0: SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame)); michael@0: } michael@0: else michael@0: BidiLevelFromMove(mShell, pos.mResultContent, pos.mContentOffset, aKeycode, tHint); michael@0: } michael@0: } michael@0: result = TakeFocus(pos.mResultContent, pos.mContentOffset, pos.mContentOffset, michael@0: tHint, aContinueSelection, false); michael@0: } else if (aKeycode == nsIDOMKeyEvent::DOM_VK_RIGHT && !aContinueSelection) { michael@0: // Collapse selection if PeekOffset failed, we either michael@0: // 1. bumped into the BRFrame, bug 207623 michael@0: // 2. had select-all in a text input (DIV range), bug 352759. michael@0: bool isBRFrame = frame->GetType() == nsGkAtoms::brFrame; michael@0: sel->Collapse(sel->GetFocusNode(), sel->FocusOffset()); michael@0: // Note: 'frame' might be dead here. michael@0: if (!isBRFrame) { michael@0: mHint = HINTLEFT; // We're now at the end of the frame to the left. michael@0: } michael@0: result = NS_OK; michael@0: } michael@0: if (NS_SUCCEEDED(result)) michael@0: { michael@0: result = mDomSelections[index]-> michael@0: ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, michael@0: nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(), michael@0: scrollFlags); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: //END nsFrameSelection methods michael@0: michael@0: michael@0: //BEGIN nsFrameSelection methods michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::ToString(nsAString& aReturn) michael@0: { michael@0: // We need Flush_Style here to make sure frames have been created for michael@0: // the selected content. Use mFrameSelection->GetShell() which returns michael@0: // null if the Selection has been disconnected (the shell is Destroyed). michael@0: nsCOMPtr shell = michael@0: mFrameSelection ? mFrameSelection->GetShell() : nullptr; michael@0: if (!shell) { michael@0: aReturn.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: shell->FlushPendingNotifications(Flush_Style); michael@0: michael@0: return ToStringWithFormat("text/plain", michael@0: nsIDocumentEncoder::SkipInvisibleContent, michael@0: 0, aReturn); michael@0: } michael@0: michael@0: void michael@0: Selection::Stringify(nsAString& aResult) michael@0: { michael@0: // Eat the error code michael@0: ToString(aResult); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::ToStringWithFormat(const char* aFormatType, uint32_t aFlags, michael@0: int32_t aWrapCol, nsAString& aReturn) michael@0: { michael@0: ErrorResult result; michael@0: NS_ConvertUTF8toUTF16 format(aFormatType); michael@0: ToStringWithFormat(format, aFlags, aWrapCol, aReturn, result); michael@0: if (result.Failed()) { michael@0: return result.ErrorCode(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Selection::ToStringWithFormat(const nsAString& aFormatType, uint32_t aFlags, michael@0: int32_t aWrapCol, nsAString& aReturn, michael@0: ErrorResult& aRv) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: NS_ConvertUTF8toUTF16 formatType( NS_DOC_ENCODER_CONTRACTID_BASE ); michael@0: formatType.Append(aFormatType); michael@0: nsCOMPtr encoder = michael@0: do_CreateInstance(NS_ConvertUTF16toUTF8(formatType).get(), &rv); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: nsIPresShell* shell = GetPresShell(); michael@0: if (!shell) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: nsIDocument *doc = shell->GetDocument(); michael@0: michael@0: nsCOMPtr domDoc = do_QueryInterface(doc); michael@0: NS_ASSERTION(domDoc, "Need a document"); michael@0: michael@0: // Flags should always include OutputSelectionOnly if we're coming from here: michael@0: aFlags |= nsIDocumentEncoder::OutputSelectionOnly; michael@0: nsAutoString readstring; michael@0: readstring.Assign(aFormatType); michael@0: rv = encoder->Init(domDoc, readstring, aFlags); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: encoder->SetSelection(this); michael@0: if (aWrapCol != 0) michael@0: encoder->SetWrapColumn(aWrapCol); michael@0: michael@0: rv = encoder->EncodeToString(aReturn); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::SetInterlinePosition(bool aHintRight) michael@0: { michael@0: ErrorResult result; michael@0: SetInterlinePosition(aHintRight, result); michael@0: if (result.Failed()) { michael@0: return result.ErrorCode(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Selection::SetInterlinePosition(bool aHintRight, ErrorResult& aRv) michael@0: { michael@0: if (!mFrameSelection) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection michael@0: return; michael@0: } michael@0: nsFrameSelection::HINT hint; michael@0: if (aHintRight) michael@0: hint = nsFrameSelection::HINTRIGHT; michael@0: else michael@0: hint = nsFrameSelection::HINTLEFT; michael@0: mFrameSelection->SetHint(hint); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetInterlinePosition(bool* aHintRight) michael@0: { michael@0: ErrorResult result; michael@0: *aHintRight = GetInterlinePosition(result); michael@0: if (result.Failed()) { michael@0: return result.ErrorCode(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: Selection::GetInterlinePosition(ErrorResult& aRv) michael@0: { michael@0: if (!mFrameSelection) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection michael@0: return false; michael@0: } michael@0: return (mFrameSelection->GetHint() == nsFrameSelection::HINTRIGHT); michael@0: } michael@0: michael@0: nsPrevNextBidiLevels michael@0: nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode, michael@0: uint32_t aContentOffset, michael@0: bool aJumpLines) const michael@0: { michael@0: return GetPrevNextBidiLevels(aNode, aContentOffset, mHint, aJumpLines); michael@0: } michael@0: michael@0: nsPrevNextBidiLevels michael@0: nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode, michael@0: uint32_t aContentOffset, michael@0: HINT aHint, michael@0: bool aJumpLines) const michael@0: { michael@0: // Get the level of the frames on each side michael@0: nsIFrame *currentFrame; michael@0: int32_t currentOffset; michael@0: int32_t frameStart, frameEnd; michael@0: nsDirection direction; michael@0: michael@0: nsPrevNextBidiLevels levels; michael@0: levels.SetData(nullptr, nullptr, 0, 0); michael@0: michael@0: currentFrame = GetFrameForNodeOffset(aNode, aContentOffset, michael@0: aHint, ¤tOffset); michael@0: if (!currentFrame) michael@0: return levels; michael@0: michael@0: currentFrame->GetOffsets(frameStart, frameEnd); michael@0: michael@0: if (0 == frameStart && 0 == frameEnd) michael@0: direction = eDirPrevious; michael@0: else if (frameStart == currentOffset) michael@0: direction = eDirPrevious; michael@0: else if (frameEnd == currentOffset) michael@0: direction = eDirNext; michael@0: else { michael@0: // we are neither at the beginning nor at the end of the frame, so we have no worries michael@0: levels.SetData(currentFrame, currentFrame, michael@0: NS_GET_EMBEDDING_LEVEL(currentFrame), michael@0: NS_GET_EMBEDDING_LEVEL(currentFrame)); michael@0: return levels; michael@0: } michael@0: michael@0: nsIFrame *newFrame; michael@0: int32_t offset; michael@0: bool jumpedLine; michael@0: nsresult rv = currentFrame->GetFrameFromDirection(direction, false, michael@0: aJumpLines, true, michael@0: &newFrame, &offset, &jumpedLine); michael@0: if (NS_FAILED(rv)) michael@0: newFrame = nullptr; michael@0: michael@0: uint8_t baseLevel = NS_GET_BASE_LEVEL(currentFrame); michael@0: uint8_t currentLevel = NS_GET_EMBEDDING_LEVEL(currentFrame); michael@0: uint8_t newLevel = newFrame ? NS_GET_EMBEDDING_LEVEL(newFrame) : baseLevel; michael@0: michael@0: // If not jumping lines, disregard br frames, since they might be positioned incorrectly. michael@0: // XXX This could be removed once bug 339786 is fixed. michael@0: if (!aJumpLines) { michael@0: if (currentFrame->GetType() == nsGkAtoms::brFrame) { michael@0: currentFrame = nullptr; michael@0: currentLevel = baseLevel; michael@0: } michael@0: if (newFrame && newFrame->GetType() == nsGkAtoms::brFrame) { michael@0: newFrame = nullptr; michael@0: newLevel = baseLevel; michael@0: } michael@0: } michael@0: michael@0: if (direction == eDirNext) michael@0: levels.SetData(currentFrame, newFrame, currentLevel, newLevel); michael@0: else michael@0: levels.SetData(newFrame, currentFrame, newLevel, currentLevel); michael@0: michael@0: return levels; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::GetFrameFromLevel(nsIFrame *aFrameIn, michael@0: nsDirection aDirection, michael@0: uint8_t aBidiLevel, michael@0: nsIFrame **aFrameOut) const michael@0: { michael@0: NS_ENSURE_STATE(mShell); michael@0: uint8_t foundLevel = 0; michael@0: nsIFrame *foundFrame = aFrameIn; michael@0: michael@0: nsCOMPtr frameTraversal; michael@0: nsresult result; michael@0: nsCOMPtr trav(do_CreateInstance(kFrameTraversalCID,&result)); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal), michael@0: mShell->GetPresContext(), aFrameIn, michael@0: eLeaf, michael@0: false, // aVisual michael@0: false, // aLockInScrollView michael@0: false // aFollowOOFs michael@0: ); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: do { michael@0: *aFrameOut = foundFrame; michael@0: if (aDirection == eDirNext) michael@0: frameTraversal->Next(); michael@0: else michael@0: frameTraversal->Prev(); michael@0: michael@0: foundFrame = frameTraversal->CurrentItem(); michael@0: if (!foundFrame) michael@0: return NS_ERROR_FAILURE; michael@0: foundLevel = NS_GET_EMBEDDING_LEVEL(foundFrame); michael@0: michael@0: } while (foundLevel > aBidiLevel); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount) michael@0: { michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: mMaintainedAmount = aAmount; michael@0: michael@0: const nsRange* anchorFocusRange = michael@0: mDomSelections[index]->GetAnchorFocusRange(); michael@0: if (anchorFocusRange) { michael@0: mMaintainRange = anchorFocusRange->CloneRange(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mMaintainRange = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** After moving the caret, its Bidi level is set according to the following rules: michael@0: * michael@0: * After moving over a character with left/right arrow, set to the Bidi level of the last moved over character. michael@0: * After Home and End, set to the paragraph embedding level. michael@0: * After up/down arrow, PageUp/Down, set to the lower level of the 2 surrounding characters. michael@0: * After mouse click, set to the level of the current frame. michael@0: * michael@0: * The following two methods use GetPrevNextBidiLevels to determine the new Bidi level. michael@0: * BidiLevelFromMove is called when the caret is moved in response to a keyboard event michael@0: * michael@0: * @param aPresShell is the presentation shell michael@0: * @param aNode is the content node michael@0: * @param aContentOffset is the new caret position, as an offset into aNode michael@0: * @param aKeycode is the keyboard event that moved the caret to the new position michael@0: * @param aHint is the hint indicating in what logical direction the caret moved michael@0: */ michael@0: void nsFrameSelection::BidiLevelFromMove(nsIPresShell* aPresShell, michael@0: nsIContent *aNode, michael@0: uint32_t aContentOffset, michael@0: uint32_t aKeycode, michael@0: HINT aHint) michael@0: { michael@0: switch (aKeycode) { michael@0: michael@0: // Right and Left: the new cursor Bidi level is the level of the character moved over michael@0: case nsIDOMKeyEvent::DOM_VK_RIGHT: michael@0: case nsIDOMKeyEvent::DOM_VK_LEFT: michael@0: { michael@0: nsPrevNextBidiLevels levels = GetPrevNextBidiLevels(aNode, aContentOffset, michael@0: aHint, false); michael@0: michael@0: if (HINTLEFT == aHint) michael@0: SetCaretBidiLevel(levels.mLevelBefore); michael@0: else michael@0: SetCaretBidiLevel(levels.mLevelAfter); michael@0: break; michael@0: } michael@0: /* michael@0: // Up and Down: the new cursor Bidi level is the smaller of the two surrounding characters michael@0: case nsIDOMKeyEvent::DOM_VK_UP: michael@0: case nsIDOMKeyEvent::DOM_VK_DOWN: michael@0: GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame, &secondFrame, &firstLevel, &secondLevel); michael@0: aPresShell->SetCaretBidiLevel(std::min(firstLevel, secondLevel)); michael@0: break; michael@0: */ michael@0: michael@0: default: michael@0: UndefineCaretBidiLevel(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * BidiLevelFromClick is called when the caret is repositioned by clicking the mouse michael@0: * michael@0: * @param aNode is the content node michael@0: * @param aContentOffset is the new caret position, as an offset into aNode michael@0: */ michael@0: void nsFrameSelection::BidiLevelFromClick(nsIContent *aNode, michael@0: uint32_t aContentOffset) michael@0: { michael@0: nsIFrame* clickInFrame=nullptr; michael@0: int32_t OffsetNotUsed; michael@0: michael@0: clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, mHint, &OffsetNotUsed); michael@0: if (!clickInFrame) michael@0: return; michael@0: michael@0: SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(clickInFrame)); michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsFrameSelection::AdjustForMaintainedSelection(nsIContent *aContent, michael@0: int32_t aOffset) michael@0: { michael@0: if (!mMaintainRange) michael@0: return false; michael@0: michael@0: if (!aContent) { michael@0: return false; michael@0: } michael@0: michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return false; michael@0: michael@0: nsINode* rangeStartNode = mMaintainRange->GetStartParent(); michael@0: nsINode* rangeEndNode = mMaintainRange->GetEndParent(); michael@0: int32_t rangeStartOffset = mMaintainRange->StartOffset(); michael@0: int32_t rangeEndOffset = mMaintainRange->EndOffset(); michael@0: michael@0: int32_t relToStart = michael@0: nsContentUtils::ComparePoints(rangeStartNode, rangeStartOffset, michael@0: aContent, aOffset); michael@0: int32_t relToEnd = michael@0: nsContentUtils::ComparePoints(rangeEndNode, rangeEndOffset, michael@0: aContent, aOffset); michael@0: michael@0: // If aContent/aOffset is inside the maintained selection, or if it is on the michael@0: // "anchor" side of the maintained selection, we need to do something. michael@0: if ((relToStart < 0 && relToEnd > 0) || michael@0: (relToStart > 0 && michael@0: mDomSelections[index]->GetDirection() == eDirNext) || michael@0: (relToEnd < 0 && michael@0: mDomSelections[index]->GetDirection() == eDirPrevious)) { michael@0: // Set the current range to the maintained range. michael@0: mDomSelections[index]->ReplaceAnchorFocusRange(mMaintainRange); michael@0: if (relToStart < 0 && relToEnd > 0) { michael@0: // We're inside the maintained selection, just keep it selected. michael@0: return true; michael@0: } michael@0: // Reverse the direction of the selection so that the anchor will be on the michael@0: // far side of the maintained selection, relative to aContent/aOffset. michael@0: mDomSelections[index]->SetDirection(relToStart > 0 ? eDirPrevious : eDirNext); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsFrameSelection::HandleClick(nsIContent *aNewFocus, michael@0: uint32_t aContentOffset, michael@0: uint32_t aContentEndOffset, michael@0: bool aContinueSelection, michael@0: bool aMultipleSelection, michael@0: bool aHint) michael@0: { michael@0: if (!aNewFocus) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: InvalidateDesiredX(); michael@0: michael@0: if (!aContinueSelection) { michael@0: mMaintainRange = nullptr; michael@0: if (!IsValidSelectionPoint(this, aNewFocus)) { michael@0: mAncestorLimiter = nullptr; michael@0: } michael@0: } michael@0: michael@0: // Don't take focus when dragging off of a table michael@0: if (!mDragSelectingCells) michael@0: { michael@0: BidiLevelFromClick(aNewFocus, aContentOffset); michael@0: PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON); michael@0: if (aContinueSelection && michael@0: AdjustForMaintainedSelection(aNewFocus, aContentOffset)) michael@0: return NS_OK; //shift clicked to maintained selection. rejected. michael@0: michael@0: return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, HINT(aHint), michael@0: aContinueSelection, aMultipleSelection); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFrameSelection::HandleDrag(nsIFrame *aFrame, nsPoint aPoint) michael@0: { michael@0: if (!aFrame || !mShell) michael@0: return; michael@0: michael@0: nsresult result; michael@0: nsIFrame *newFrame = 0; michael@0: nsPoint newPoint; michael@0: michael@0: result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame, newPoint); michael@0: if (NS_FAILED(result)) michael@0: return; michael@0: if (!newFrame) michael@0: return; michael@0: michael@0: nsIFrame::ContentOffsets offsets = michael@0: newFrame->GetContentOffsetsFromPoint(newPoint); michael@0: if (!offsets.content) michael@0: return; michael@0: michael@0: if (newFrame->IsSelected() && michael@0: AdjustForMaintainedSelection(offsets.content, offsets.offset)) michael@0: return; michael@0: michael@0: // Adjust offsets according to maintained amount michael@0: if (mMaintainRange && michael@0: mMaintainedAmount != eSelectNoAmount) { michael@0: michael@0: nsINode* rangenode = mMaintainRange->GetStartParent(); michael@0: int32_t rangeOffset = mMaintainRange->StartOffset(); michael@0: int32_t relativePosition = michael@0: nsContentUtils::ComparePoints(rangenode, rangeOffset, michael@0: offsets.content, offsets.offset); michael@0: michael@0: nsDirection direction = relativePosition > 0 ? eDirPrevious : eDirNext; michael@0: nsSelectionAmount amount = mMaintainedAmount; michael@0: if (amount == eSelectBeginLine && direction == eDirNext) michael@0: amount = eSelectEndLine; michael@0: michael@0: int32_t offset; michael@0: nsIFrame* frame = GetFrameForNodeOffset(offsets.content, offsets.offset, HINTRIGHT, &offset); michael@0: michael@0: if (frame && amount == eSelectWord && direction == eDirPrevious) { michael@0: // To avoid selecting the previous word when at start of word, michael@0: // first move one character forward. michael@0: nsPeekOffsetStruct charPos(eSelectCharacter, eDirNext, offset, 0, michael@0: false, mLimiter != nullptr, false, false); michael@0: if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) { michael@0: frame = charPos.mResultFrame; michael@0: offset = charPos.mContentOffset; michael@0: } michael@0: } michael@0: michael@0: nsPeekOffsetStruct pos(amount, direction, offset, 0, michael@0: false, mLimiter != nullptr, false, false); michael@0: michael@0: if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) { michael@0: offsets.content = pos.mResultContent; michael@0: offsets.offset = pos.mContentOffset; michael@0: } michael@0: } michael@0: michael@0: HandleClick(offsets.content, offsets.offset, offsets.offset, michael@0: true, false, offsets.associateWithNext); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::StartAutoScrollTimer(nsIFrame *aFrame, michael@0: nsPoint aPoint, michael@0: uint32_t aDelay) michael@0: { michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: return mDomSelections[index]->StartAutoScrollTimer(aFrame, aPoint, aDelay); michael@0: } michael@0: michael@0: void michael@0: nsFrameSelection::StopAutoScrollTimer() michael@0: { michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return; michael@0: michael@0: mDomSelections[index]->StopAutoScrollTimer(); michael@0: } michael@0: michael@0: /** michael@0: hard to go from nodes to frames, easy the other way! michael@0: */ michael@0: nsresult michael@0: nsFrameSelection::TakeFocus(nsIContent *aNewFocus, michael@0: uint32_t aContentOffset, michael@0: uint32_t aContentEndOffset, michael@0: HINT aHint, michael@0: bool aContinueSelection, michael@0: bool aMultipleSelection) michael@0: { michael@0: if (!aNewFocus) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: NS_ENSURE_STATE(mShell); michael@0: michael@0: if (!IsValidSelectionPoint(this,aNewFocus)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Clear all table selection data michael@0: mSelectingTableCellMode = 0; michael@0: mDragSelectingCells = false; michael@0: mStartSelectedCell = nullptr; michael@0: mEndSelectedCell = nullptr; michael@0: mAppendStartSelectedCell = nullptr; michael@0: mHint = aHint; michael@0: michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: //traverse through document and unselect crap here michael@0: if (!aContinueSelection) {//single click? setting cursor down michael@0: uint32_t batching = mBatching;//hack to use the collapse code. michael@0: bool changes = mChangesDuringBatching; michael@0: mBatching = 1; michael@0: michael@0: if (aMultipleSelection) { michael@0: // Remove existing collapsed ranges as there's no point in having michael@0: // non-anchor/focus collapsed ranges. michael@0: mDomSelections[index]->RemoveCollapsedRanges(); michael@0: michael@0: nsRefPtr newRange = new nsRange(aNewFocus); michael@0: michael@0: newRange->SetStart(aNewFocus, aContentOffset); michael@0: newRange->SetEnd(aNewFocus, aContentOffset); michael@0: mDomSelections[index]->AddRange(newRange); michael@0: mBatching = batching; michael@0: mChangesDuringBatching = changes; michael@0: } michael@0: else michael@0: { michael@0: bool oldDesiredXSet = mDesiredXSet; //need to keep old desired X if it was set. michael@0: mDomSelections[index]->Collapse(aNewFocus, aContentOffset); michael@0: mDesiredXSet = oldDesiredXSet; //now reset desired X back. michael@0: mBatching = batching; michael@0: mChangesDuringBatching = changes; michael@0: } michael@0: if (aContentEndOffset != aContentOffset) michael@0: mDomSelections[index]->Extend(aNewFocus, aContentEndOffset); michael@0: michael@0: //find out if we are inside a table. if so, find out which one and which cell michael@0: //once we do that, the next time we get a takefocus, check the parent tree. michael@0: //if we are no longer inside same table ,cell then switch to table selection mode. michael@0: // BUT only do this in an editor michael@0: michael@0: NS_ENSURE_STATE(mShell); michael@0: int16_t displaySelection = mShell->GetSelectionFlags(); michael@0: michael@0: // Editor has DISPLAY_ALL selection type michael@0: if (displaySelection == nsISelectionDisplay::DISPLAY_ALL) michael@0: { michael@0: mCellParent = GetCellParent(aNewFocus); michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: if (mCellParent) michael@0: printf(" * TakeFocus - Collapsing into new cell\n"); michael@0: #endif michael@0: } michael@0: } michael@0: else { michael@0: // Now update the range list: michael@0: if (aContinueSelection && aNewFocus) michael@0: { michael@0: int32_t offset; michael@0: nsINode *cellparent = GetCellParent(aNewFocus); michael@0: if (mCellParent && cellparent && cellparent != mCellParent) //switch to cell selection mode michael@0: { michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf(" * TakeFocus - moving into new cell\n"); michael@0: #endif michael@0: WidgetMouseEvent event(false, 0, nullptr, WidgetMouseEvent::eReal); michael@0: michael@0: // Start selecting in the cell we were in before michael@0: nsINode* parent = ParentOffset(mCellParent, &offset); michael@0: if (parent) michael@0: HandleTableSelection(parent, offset, michael@0: nsISelectionPrivate::TABLESELECTION_CELL, &event); michael@0: michael@0: // Find the parent of this new cell and extend selection to it michael@0: parent = ParentOffset(cellparent, &offset); michael@0: michael@0: // XXXX We need to REALLY get the current key shift state michael@0: // (we'd need to add event listener -- let's not bother for now) michael@0: event.modifiers &= ~MODIFIER_SHIFT; //aContinueSelection; michael@0: if (parent) michael@0: { michael@0: mCellParent = cellparent; michael@0: // Continue selection into next cell michael@0: HandleTableSelection(parent, offset, michael@0: nsISelectionPrivate::TABLESELECTION_CELL, &event); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // XXXX Problem: Shift+click in browser is appending text selection to selected table!!! michael@0: // is this the place to erase seleced cells ????? michael@0: if (mDomSelections[index]->GetDirection() == eDirNext && aContentEndOffset > aContentOffset) //didn't go far enough michael@0: { michael@0: mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);//this will only redraw the diff michael@0: } michael@0: else michael@0: mDomSelections[index]->Extend(aNewFocus, aContentOffset); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Don't notify selection listeners if batching is on: michael@0: if (GetBatching()) michael@0: return NS_OK; michael@0: return NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL); michael@0: } michael@0: michael@0: michael@0: SelectionDetails* michael@0: nsFrameSelection::LookUpSelection(nsIContent *aContent, michael@0: int32_t aContentOffset, michael@0: int32_t aContentLength, michael@0: bool aSlowCheck) const michael@0: { michael@0: if (!aContent || !mShell) michael@0: return nullptr; michael@0: michael@0: SelectionDetails* details = nullptr; michael@0: michael@0: for (int32_t j = 0; j < nsISelectionController::NUM_SELECTIONTYPES; j++) { michael@0: if (mDomSelections[j]) { michael@0: mDomSelections[j]->LookUpSelection(aContent, aContentOffset, michael@0: aContentLength, &details, (SelectionType)(1<ScrollIntoView(aRegion, michael@0: verticalScroll, michael@0: nsIPresShell::ScrollAxis(), michael@0: flags); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::RepaintSelection(SelectionType aType) const michael@0: { michael@0: int8_t index = GetIndexFromSelectionType(aType); michael@0: if (index < 0) michael@0: return NS_ERROR_INVALID_ARG; michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: NS_ENSURE_STATE(mShell); michael@0: return mDomSelections[index]->Repaint(mShell->GetPresContext()); michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsFrameSelection::GetFrameForNodeOffset(nsIContent *aNode, michael@0: int32_t aOffset, michael@0: HINT aHint, michael@0: int32_t *aReturnOffset) const michael@0: { michael@0: if (!aNode || !aReturnOffset || !mShell) michael@0: return nullptr; michael@0: michael@0: if (aOffset < 0) michael@0: return nullptr; michael@0: michael@0: *aReturnOffset = aOffset; michael@0: michael@0: nsCOMPtr theNode = aNode; michael@0: michael@0: if (aNode->IsElement()) michael@0: { michael@0: int32_t childIndex = 0; michael@0: int32_t numChildren = theNode->GetChildCount(); michael@0: michael@0: if (aHint == HINTLEFT) michael@0: { michael@0: if (aOffset > 0) michael@0: childIndex = aOffset - 1; michael@0: else michael@0: childIndex = aOffset; michael@0: } michael@0: else // HINTRIGHT michael@0: { michael@0: if (aOffset >= numChildren) michael@0: { michael@0: if (numChildren > 0) michael@0: childIndex = numChildren - 1; michael@0: else michael@0: childIndex = 0; michael@0: } michael@0: else michael@0: childIndex = aOffset; michael@0: } michael@0: michael@0: if (childIndex > 0 || numChildren > 0) { michael@0: nsCOMPtr childNode = theNode->GetChildAt(childIndex); michael@0: michael@0: if (!childNode) michael@0: return nullptr; michael@0: michael@0: theNode = childNode; michael@0: } michael@0: michael@0: #ifdef DONT_DO_THIS_YET michael@0: // XXX: We can't use this code yet because the hinting michael@0: // can cause us to attach to the wrong line frame. michael@0: michael@0: // Now that we have the child node, check if it too michael@0: // can contain children. If so, call this method again! michael@0: michael@0: if (theNode->IsElement()) michael@0: { michael@0: int32_t newOffset = 0; michael@0: michael@0: if (aOffset > childIndex) michael@0: { michael@0: numChildren = theNode->GetChildCount(); michael@0: michael@0: newOffset = numChildren; michael@0: } michael@0: michael@0: return GetFrameForNodeOffset(theNode, newOffset, aHint, aReturnOffset); michael@0: } michael@0: else michael@0: #endif // DONT_DO_THIS_YET michael@0: { michael@0: // Check to see if theNode is a text node. If it is, translate michael@0: // aOffset into an offset into the text node. michael@0: michael@0: nsCOMPtr textNode = do_QueryInterface(theNode); michael@0: michael@0: if (textNode) michael@0: { michael@0: if (theNode->GetPrimaryFrame()) michael@0: { michael@0: if (aOffset > childIndex) michael@0: { michael@0: uint32_t textLength = 0; michael@0: michael@0: nsresult rv = textNode->GetLength(&textLength); michael@0: if (NS_FAILED(rv)) michael@0: return nullptr; michael@0: michael@0: *aReturnOffset = (int32_t)textLength; michael@0: } michael@0: else michael@0: *aReturnOffset = 0; michael@0: } michael@0: else michael@0: { michael@0: // If we're at a collapsed whitespace content node (which michael@0: // does not have a primary frame), just use the original node michael@0: // to get the frame on which we should put the caret. michael@0: theNode = aNode; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If the node is a ShadowRoot, the frame needs to be adjusted, michael@0: // because a ShadowRoot does not get a frame. Its children are rendered michael@0: // as children of the host. michael@0: mozilla::dom::ShadowRoot* shadowRoot = michael@0: mozilla::dom::ShadowRoot::FromNode(theNode); michael@0: if (shadowRoot) { michael@0: theNode = shadowRoot->GetHost(); michael@0: } michael@0: michael@0: nsIFrame* returnFrame = theNode->GetPrimaryFrame(); michael@0: if (!returnFrame) michael@0: return nullptr; michael@0: michael@0: // find the child frame containing the offset we want michael@0: returnFrame->GetChildFrameContainingOffset(*aReturnOffset, aHint == HINTRIGHT, michael@0: &aOffset, &returnFrame); michael@0: return returnFrame; michael@0: } michael@0: michael@0: void michael@0: nsFrameSelection::CommonPageMove(bool aForward, michael@0: bool aExtend, michael@0: nsIScrollableFrame* aScrollableFrame) michael@0: { michael@0: // expected behavior for PageMove is to scroll AND move the caret michael@0: // and remain relative position of the caret in view. see Bug 4302. michael@0: michael@0: //get the frame from the scrollable view michael@0: michael@0: nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame(); michael@0: if (!scrolledFrame) michael@0: return; michael@0: michael@0: // find out where the caret is. michael@0: // we should know mDesiredX value of nsFrameSelection, but I havent seen that behavior in other windows applications yet. michael@0: nsISelection* domSel = GetSelection(nsISelectionController::SELECTION_NORMAL); michael@0: if (!domSel) michael@0: return; michael@0: michael@0: nsRefPtr caret = mShell->GetCaret(); michael@0: michael@0: nsRect caretPos; michael@0: nsIFrame* caretFrame = caret->GetGeometry(domSel, &caretPos); michael@0: if (!caretFrame) michael@0: return; michael@0: michael@0: //need to adjust caret jump by percentage scroll michael@0: nsSize scrollDelta = aScrollableFrame->GetPageScrollAmount(); michael@0: michael@0: if (aForward) michael@0: caretPos.y += scrollDelta.height; michael@0: else michael@0: caretPos.y -= scrollDelta.height; michael@0: michael@0: caretPos += caretFrame->GetOffsetTo(scrolledFrame); michael@0: michael@0: // get a content at desired location michael@0: nsPoint desiredPoint; michael@0: desiredPoint.x = caretPos.x; michael@0: desiredPoint.y = caretPos.y + caretPos.height/2; michael@0: nsIFrame::ContentOffsets offsets = michael@0: scrolledFrame->GetContentOffsetsFromPoint(desiredPoint); michael@0: michael@0: if (!offsets.content) michael@0: return; michael@0: michael@0: // scroll one page michael@0: aScrollableFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), michael@0: nsIScrollableFrame::PAGES, michael@0: nsIScrollableFrame::SMOOTH); michael@0: michael@0: // place the caret michael@0: HandleClick(offsets.content, offsets.offset, michael@0: offsets.offset, aExtend, false, true); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::CharacterMove(bool aForward, bool aExtend) michael@0: { michael@0: if (aForward) michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT, aExtend, eSelectCluster); michael@0: else michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT, aExtend, eSelectCluster); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::CharacterExtendForDelete() michael@0: { michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, true, eSelectCluster); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::CharacterExtendForBackspace() michael@0: { michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_BACK_SPACE, true, eSelectCharacter); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::WordMove(bool aForward, bool aExtend) michael@0: { michael@0: if (aForward) michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT,aExtend,eSelectWord); michael@0: else michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT,aExtend,eSelectWord); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::WordExtendForDelete(bool aForward) michael@0: { michael@0: if (aForward) michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, true, eSelectWord); michael@0: else michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_BACK_SPACE, true, eSelectWord); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::LineMove(bool aForward, bool aExtend) michael@0: { michael@0: if (aForward) michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_DOWN,aExtend,eSelectLine); michael@0: else michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_UP,aExtend,eSelectLine); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::IntraLineMove(bool aForward, bool aExtend) michael@0: { michael@0: if (aForward) michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_END,aExtend,eSelectLine); michael@0: else michael@0: return MoveCaret(nsIDOMKeyEvent::DOM_VK_HOME,aExtend,eSelectLine); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::SelectAll() michael@0: { michael@0: nsCOMPtr rootContent; michael@0: if (mLimiter) michael@0: { michael@0: rootContent = mLimiter;//addrefit michael@0: } michael@0: else if (mAncestorLimiter) { michael@0: rootContent = mAncestorLimiter; michael@0: } michael@0: else michael@0: { michael@0: NS_ENSURE_STATE(mShell); michael@0: nsIDocument *doc = mShell->GetDocument(); michael@0: if (!doc) michael@0: return NS_ERROR_FAILURE; michael@0: rootContent = doc->GetRootElement(); michael@0: if (!rootContent) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: int32_t numChildren = rootContent->GetChildCount(); michael@0: PostReason(nsISelectionListener::NO_REASON); michael@0: return TakeFocus(rootContent, 0, numChildren, HINTLEFT, false, false); michael@0: } michael@0: michael@0: //////////END FRAMESELECTION michael@0: michael@0: void michael@0: nsFrameSelection::StartBatchChanges() michael@0: { michael@0: mBatching++; michael@0: } michael@0: michael@0: void michael@0: nsFrameSelection::EndBatchChanges() michael@0: { michael@0: mBatching--; michael@0: NS_ASSERTION(mBatching >=0,"Bad mBatching"); michael@0: if (mBatching == 0 && mChangesDuringBatching){ michael@0: mChangesDuringBatching = false; michael@0: NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL); michael@0: } michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsFrameSelection::NotifySelectionListeners(SelectionType aType) michael@0: { michael@0: int8_t index = GetIndexFromSelectionType(aType); michael@0: if (index >=0 && mDomSelections[index]) michael@0: { michael@0: return mDomSelections[index]->NotifySelectionListeners(); michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Start of Table Selection methods michael@0: michael@0: static bool IsCell(nsIContent *aContent) michael@0: { michael@0: return ((aContent->Tag() == nsGkAtoms::td || michael@0: aContent->Tag() == nsGkAtoms::th) && michael@0: aContent->IsHTML()); michael@0: } michael@0: michael@0: nsITableCellLayout* michael@0: nsFrameSelection::GetCellLayout(nsIContent *aCellContent) const michael@0: { michael@0: NS_ENSURE_TRUE(mShell, nullptr); michael@0: nsITableCellLayout *cellLayoutObject = michael@0: do_QueryFrame(aCellContent->GetPrimaryFrame()); michael@0: return cellLayoutObject; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::ClearNormalSelection() michael@0: { michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: return mDomSelections[index]->RemoveAllRanges(); michael@0: } michael@0: michael@0: static nsIContent* michael@0: GetFirstSelectedContent(nsRange* aRange) michael@0: { michael@0: if (!aRange) { michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_PRECONDITION(aRange->GetStartParent(), "Must have start parent!"); michael@0: NS_PRECONDITION(aRange->GetStartParent()->IsElement(), michael@0: "Unexpected parent"); michael@0: michael@0: return aRange->GetStartParent()->GetChildAt(aRange->StartOffset()); michael@0: } michael@0: michael@0: // Table selection support. michael@0: // TODO: Separate table methods into a separate nsITableSelection interface michael@0: nsresult michael@0: nsFrameSelection::HandleTableSelection(nsINode* aParentContent, michael@0: int32_t aContentOffset, michael@0: int32_t aTarget, michael@0: WidgetMouseEvent* aMouseEvent) michael@0: { michael@0: NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (mMouseDownState && mDragSelectingCells && (aTarget & nsISelectionPrivate::TABLESELECTION_TABLE)) michael@0: { michael@0: // We were selecting cells and user drags mouse in table border or inbetween cells, michael@0: // just do nothing michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult result = NS_OK; michael@0: michael@0: nsIContent *childContent = aParentContent->GetChildAt(aContentOffset); michael@0: michael@0: // When doing table selection, always set the direction to next so michael@0: // we can be sure that anchorNode's offset always points to the michael@0: // selected cell michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: mDomSelections[index]->SetDirection(eDirNext); michael@0: michael@0: // Stack-class to wrap all table selection changes in michael@0: // BeginBatchChanges() / EndBatchChanges() michael@0: nsSelectionBatcher selectionBatcher(mDomSelections[index]); michael@0: michael@0: int32_t startRowIndex, startColIndex, curRowIndex, curColIndex; michael@0: if (mMouseDownState && mDragSelectingCells) michael@0: { michael@0: // We are drag-selecting michael@0: if (aTarget != nsISelectionPrivate::TABLESELECTION_TABLE) michael@0: { michael@0: // If dragging in the same cell as last event, do nothing michael@0: if (mEndSelectedCell == childContent) michael@0: return NS_OK; michael@0: michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf(" mStartSelectedCell = %x, mEndSelectedCell = %x, childContent = %x \n", mStartSelectedCell, mEndSelectedCell, childContent); michael@0: #endif michael@0: // aTarget can be any "cell mode", michael@0: // so we can easily drag-select rows and columns michael@0: // Once we are in row or column mode, michael@0: // we can drift into any cell to stay in that mode michael@0: // even if aTarget = TABLESELECTION_CELL michael@0: michael@0: if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW || michael@0: mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN) michael@0: { michael@0: if (mEndSelectedCell) michael@0: { michael@0: // Also check if cell is in same row/col michael@0: result = GetCellIndexes(mEndSelectedCell, startRowIndex, startColIndex); michael@0: if (NS_FAILED(result)) return result; michael@0: result = GetCellIndexes(childContent, curRowIndex, curColIndex); michael@0: if (NS_FAILED(result)) return result; michael@0: michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf(" curRowIndex = %d, startRowIndex = %d, curColIndex = %d, startColIndex = %d\n", curRowIndex, startRowIndex, curColIndex, startColIndex); michael@0: #endif michael@0: if ((mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW && startRowIndex == curRowIndex) || michael@0: (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN && startColIndex == curColIndex)) michael@0: return NS_OK; michael@0: } michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf(" Dragged into a new column or row\n"); michael@0: #endif michael@0: // Continue dragging row or column selection michael@0: return SelectRowOrColumn(childContent, mSelectingTableCellMode); michael@0: } michael@0: else if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_CELL) michael@0: { michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf("HandleTableSelection: Dragged into a new cell\n"); michael@0: #endif michael@0: // Trick for quick selection of rows and columns michael@0: // Hold down shift, then start selecting in one direction michael@0: // If next cell dragged into is in same row, select entire row, michael@0: // if next cell is in same column, select entire column michael@0: if (mStartSelectedCell && aMouseEvent->IsShift()) michael@0: { michael@0: result = GetCellIndexes(mStartSelectedCell, startRowIndex, startColIndex); michael@0: if (NS_FAILED(result)) return result; michael@0: result = GetCellIndexes(childContent, curRowIndex, curColIndex); michael@0: if (NS_FAILED(result)) return result; michael@0: michael@0: if (startRowIndex == curRowIndex || michael@0: startColIndex == curColIndex) michael@0: { michael@0: // Force new selection block michael@0: mStartSelectedCell = nullptr; michael@0: mDomSelections[index]->RemoveAllRanges(); michael@0: michael@0: if (startRowIndex == curRowIndex) michael@0: mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_ROW; michael@0: else michael@0: mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_COLUMN; michael@0: michael@0: return SelectRowOrColumn(childContent, mSelectingTableCellMode); michael@0: } michael@0: } michael@0: michael@0: // Reselect block of cells to new end location michael@0: return SelectBlockOfCells(mStartSelectedCell, childContent); michael@0: } michael@0: } michael@0: // Do nothing if dragging in table, but outside a cell michael@0: return NS_OK; michael@0: } michael@0: else michael@0: { michael@0: // Not dragging -- mouse event is down or up michael@0: if (mMouseDownState) michael@0: { michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf("HandleTableSelection: Mouse down event\n"); michael@0: #endif michael@0: // Clear cell we stored in mouse-down michael@0: mUnselectCellOnMouseUp = nullptr; michael@0: michael@0: if (aTarget == nsISelectionPrivate::TABLESELECTION_CELL) michael@0: { michael@0: bool isSelected = false; michael@0: michael@0: // Check if we have other selected cells michael@0: nsIContent* previousCellNode = michael@0: GetFirstSelectedContent(GetFirstCellRange()); michael@0: if (previousCellNode) michael@0: { michael@0: // We have at least 1 other selected cell michael@0: michael@0: // Check if new cell is already selected michael@0: nsIFrame *cellFrame = childContent->GetPrimaryFrame(); michael@0: if (!cellFrame) return NS_ERROR_NULL_POINTER; michael@0: isSelected = cellFrame->IsSelected(); michael@0: } michael@0: else michael@0: { michael@0: // No cells selected -- remove non-cell selection michael@0: mDomSelections[index]->RemoveAllRanges(); michael@0: } michael@0: mDragSelectingCells = true; // Signal to start drag-cell-selection michael@0: mSelectingTableCellMode = aTarget; michael@0: // Set start for new drag-selection block (not appended) michael@0: mStartSelectedCell = childContent; michael@0: // The initial block end is same as the start michael@0: mEndSelectedCell = childContent; michael@0: michael@0: if (isSelected) michael@0: { michael@0: // Remember this cell to (possibly) unselect it on mouseup michael@0: mUnselectCellOnMouseUp = childContent; michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf("HandleTableSelection: Saving mUnselectCellOnMouseUp\n"); michael@0: #endif michael@0: } michael@0: else michael@0: { michael@0: // Select an unselected cell michael@0: // but first remove existing selection if not in same table michael@0: if (previousCellNode && michael@0: !IsInSameTable(previousCellNode, childContent)) michael@0: { michael@0: mDomSelections[index]->RemoveAllRanges(); michael@0: // Reset selection mode that is cleared in RemoveAllRanges michael@0: mSelectingTableCellMode = aTarget; michael@0: } michael@0: michael@0: return SelectCellElement(childContent); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: else if (aTarget == nsISelectionPrivate::TABLESELECTION_TABLE) michael@0: { michael@0: //TODO: We currently select entire table when clicked between cells, michael@0: // should we restrict to only around border? michael@0: // *** How do we get location data for cell and click? michael@0: mDragSelectingCells = false; michael@0: mStartSelectedCell = nullptr; michael@0: mEndSelectedCell = nullptr; michael@0: michael@0: // Remove existing selection and select the table michael@0: mDomSelections[index]->RemoveAllRanges(); michael@0: return CreateAndAddRange(aParentContent, aContentOffset); michael@0: } michael@0: else if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW || aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN) michael@0: { michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf("aTarget == %d\n", aTarget); michael@0: #endif michael@0: michael@0: // Start drag-selecting mode so multiple rows/cols can be selected michael@0: // Note: Currently, nsFrame::GetDataForTableSelection michael@0: // will never call us for row or column selection on mouse down michael@0: mDragSelectingCells = true; michael@0: michael@0: // Force new selection block michael@0: mStartSelectedCell = nullptr; michael@0: mDomSelections[index]->RemoveAllRanges(); michael@0: // Always do this AFTER RemoveAllRanges michael@0: mSelectingTableCellMode = aTarget; michael@0: return SelectRowOrColumn(childContent, aTarget); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf("HandleTableSelection: Mouse UP event. mDragSelectingCells=%d, mStartSelectedCell=%d\n", mDragSelectingCells, mStartSelectedCell); michael@0: #endif michael@0: // First check if we are extending a block selection michael@0: int32_t rangeCount; michael@0: result = mDomSelections[index]->GetRangeCount(&rangeCount); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: if (rangeCount > 0 && aMouseEvent->IsShift() && michael@0: mAppendStartSelectedCell && mAppendStartSelectedCell != childContent) michael@0: { michael@0: // Shift key is down: append a block selection michael@0: mDragSelectingCells = false; michael@0: return SelectBlockOfCells(mAppendStartSelectedCell, childContent); michael@0: } michael@0: michael@0: if (mDragSelectingCells) michael@0: mAppendStartSelectedCell = mStartSelectedCell; michael@0: michael@0: mDragSelectingCells = false; michael@0: mStartSelectedCell = nullptr; michael@0: mEndSelectedCell = nullptr; michael@0: michael@0: // Any other mouseup actions require that Ctrl or Cmd key is pressed michael@0: // else stop table selection mode michael@0: bool doMouseUpAction = false; michael@0: #ifdef XP_MACOSX michael@0: doMouseUpAction = aMouseEvent->IsMeta(); michael@0: #else michael@0: doMouseUpAction = aMouseEvent->IsControl(); michael@0: #endif michael@0: if (!doMouseUpAction) michael@0: { michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf("HandleTableSelection: Ending cell selection on mouseup: mAppendStartSelectedCell=%d\n", mAppendStartSelectedCell); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: // Unselect a cell only if it wasn't michael@0: // just selected on mousedown michael@0: if( childContent == mUnselectCellOnMouseUp) michael@0: { michael@0: // Scan ranges to find the cell to unselect (the selection range to remove) michael@0: // XXXbz it's really weird that this lives outside the loop, so once we michael@0: // find one we keep looking at it even if we find no more cells... michael@0: nsINode* previousCellParent = nullptr; michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf("HandleTableSelection: Unselecting mUnselectCellOnMouseUp; rangeCount=%d\n", rangeCount); michael@0: #endif michael@0: for( int32_t i = 0; i < rangeCount; i++) michael@0: { michael@0: // Strong reference, because sometimes we want to remove michael@0: // this range, and then we might be the only owner. michael@0: nsRefPtr range = mDomSelections[index]->GetRangeAt(i); michael@0: if (!range) return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsINode* parent = range->GetStartParent(); michael@0: if (!parent) return NS_ERROR_NULL_POINTER; michael@0: michael@0: int32_t offset = range->StartOffset(); michael@0: // Be sure previous selection is a table cell michael@0: nsIContent* child = parent->GetChildAt(offset); michael@0: if (child && IsCell(child)) michael@0: previousCellParent = parent; michael@0: michael@0: // We're done if we didn't find parent of a previously-selected cell michael@0: if (!previousCellParent) break; michael@0: michael@0: if (previousCellParent == aParentContent && offset == aContentOffset) michael@0: { michael@0: // Cell is already selected michael@0: if (rangeCount == 1) michael@0: { michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf("HandleTableSelection: Unselecting single selected cell\n"); michael@0: #endif michael@0: // This was the only cell selected. michael@0: // Collapse to "normal" selection inside the cell michael@0: mStartSelectedCell = nullptr; michael@0: mEndSelectedCell = nullptr; michael@0: mAppendStartSelectedCell = nullptr; michael@0: //TODO: We need a "Collapse to just before deepest child" routine michael@0: // Even better, should we collapse to just after the LAST deepest child michael@0: // (i.e., at the end of the cell's contents)? michael@0: return mDomSelections[index]->Collapse(childContent, 0); michael@0: } michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: printf("HandleTableSelection: Removing cell from multi-cell selection\n"); michael@0: #endif michael@0: // Unselecting the start of previous block michael@0: // XXX What do we use now! michael@0: if (childContent == mAppendStartSelectedCell) michael@0: mAppendStartSelectedCell = nullptr; michael@0: michael@0: // Deselect cell by removing its range from selection michael@0: return mDomSelections[index]->RemoveRange(range); michael@0: } michael@0: } michael@0: mUnselectCellOnMouseUp = nullptr; michael@0: } michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::SelectBlockOfCells(nsIContent *aStartCell, nsIContent *aEndCell) michael@0: { michael@0: NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER); michael@0: mEndSelectedCell = aEndCell; michael@0: michael@0: nsCOMPtr startCell; michael@0: nsresult result = NS_OK; michael@0: michael@0: // If new end cell is in a different table, do nothing michael@0: nsIContent* table = IsInSameTable(aStartCell, aEndCell); michael@0: if (!table) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Get starting and ending cells' location in the cellmap michael@0: int32_t startRowIndex, startColIndex, endRowIndex, endColIndex; michael@0: result = GetCellIndexes(aStartCell, startRowIndex, startColIndex); michael@0: if(NS_FAILED(result)) return result; michael@0: result = GetCellIndexes(aEndCell, endRowIndex, endColIndex); michael@0: if(NS_FAILED(result)) return result; michael@0: michael@0: if (mDragSelectingCells) michael@0: { michael@0: // Drag selecting: remove selected cells outside of new block limits michael@0: UnselectCells(table, startRowIndex, startColIndex, endRowIndex, endColIndex, michael@0: true); michael@0: } michael@0: michael@0: // Note that we select block in the direction of user's mouse dragging, michael@0: // which means start cell may be after the end cell in either row or column michael@0: return AddCellsToSelection(table, startRowIndex, startColIndex, michael@0: endRowIndex, endColIndex); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::UnselectCells(nsIContent *aTableContent, michael@0: int32_t aStartRowIndex, michael@0: int32_t aStartColumnIndex, michael@0: int32_t aEndRowIndex, michael@0: int32_t aEndColumnIndex, michael@0: bool aRemoveOutsideOfCellRange) michael@0: { michael@0: int8_t index = michael@0: GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsTableOuterFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame()); michael@0: if (!tableFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: int32_t minRowIndex = std::min(aStartRowIndex, aEndRowIndex); michael@0: int32_t maxRowIndex = std::max(aStartRowIndex, aEndRowIndex); michael@0: int32_t minColIndex = std::min(aStartColumnIndex, aEndColumnIndex); michael@0: int32_t maxColIndex = std::max(aStartColumnIndex, aEndColumnIndex); michael@0: michael@0: // Strong reference because we sometimes remove the range michael@0: nsRefPtr range = GetFirstCellRange(); michael@0: nsIContent* cellNode = GetFirstSelectedContent(range); michael@0: NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range"); michael@0: michael@0: int32_t curRowIndex, curColIndex; michael@0: while (cellNode) michael@0: { michael@0: nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: #ifdef DEBUG_TABLE_SELECTION michael@0: if (!range) michael@0: printf("RemoveCellsToSelection -- range is null\n"); michael@0: #endif michael@0: michael@0: if (range) { michael@0: if (aRemoveOutsideOfCellRange) { michael@0: if (curRowIndex < minRowIndex || curRowIndex > maxRowIndex || michael@0: curColIndex < minColIndex || curColIndex > maxColIndex) { michael@0: michael@0: mDomSelections[index]->RemoveRange(range); michael@0: // Since we've removed the range, decrement pointer to next range michael@0: mSelectedCellIndex--; michael@0: } michael@0: michael@0: } else { michael@0: // Remove cell from selection if it belongs to the given cells range or michael@0: // it is spanned onto the cells range. michael@0: nsTableCellFrame* cellFrame = michael@0: tableFrame->GetCellFrameAt(curRowIndex, curColIndex); michael@0: michael@0: int32_t origRowIndex, origColIndex; michael@0: cellFrame->GetRowIndex(origRowIndex); michael@0: cellFrame->GetColIndex(origColIndex); michael@0: uint32_t actualRowSpan = michael@0: tableFrame->GetEffectiveRowSpanAt(origRowIndex, origColIndex); michael@0: uint32_t actualColSpan = michael@0: tableFrame->GetEffectiveColSpanAt(curRowIndex, curColIndex); michael@0: if (origRowIndex <= maxRowIndex && maxRowIndex >= 0 && michael@0: origRowIndex + actualRowSpan - 1 >= static_cast(minRowIndex) && michael@0: origColIndex <= maxColIndex && maxColIndex >= 0 && michael@0: origColIndex + actualColSpan - 1 >= static_cast(minColIndex)) { michael@0: michael@0: mDomSelections[index]->RemoveRange(range); michael@0: // Since we've removed the range, decrement pointer to next range michael@0: mSelectedCellIndex--; michael@0: } michael@0: } michael@0: } michael@0: michael@0: range = GetNextCellRange(); michael@0: cellNode = GetFirstSelectedContent(range); michael@0: NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::AddCellsToSelection(nsIContent *aTableContent, michael@0: int32_t aStartRowIndex, michael@0: int32_t aStartColumnIndex, michael@0: int32_t aEndRowIndex, michael@0: int32_t aEndColumnIndex) michael@0: { michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsTableOuterFrame* tableFrame = do_QueryFrame(aTableContent->GetPrimaryFrame()); michael@0: if (!tableFrame) // Check that |table| is a table. michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult result = NS_OK; michael@0: int32_t row = aStartRowIndex; michael@0: while(true) michael@0: { michael@0: int32_t col = aStartColumnIndex; michael@0: while(true) michael@0: { michael@0: nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(row, col); michael@0: michael@0: // Skip cells that are spanned from previous locations or are already selected michael@0: if (cellFrame) { michael@0: int32_t origRow, origCol; michael@0: cellFrame->GetRowIndex(origRow); michael@0: cellFrame->GetColIndex(origCol); michael@0: if (origRow == row && origCol == col && !cellFrame->IsSelected()) { michael@0: result = SelectCellElement(cellFrame->GetContent()); michael@0: if (NS_FAILED(result)) return result; michael@0: } michael@0: } michael@0: // Done when we reach end column michael@0: if (col == aEndColumnIndex) break; michael@0: michael@0: if (aStartColumnIndex < aEndColumnIndex) michael@0: col ++; michael@0: else michael@0: col--; michael@0: }; michael@0: if (row == aEndRowIndex) break; michael@0: michael@0: if (aStartRowIndex < aEndRowIndex) michael@0: row++; michael@0: else michael@0: row--; michael@0: }; michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::RemoveCellsFromSelection(nsIContent *aTable, michael@0: int32_t aStartRowIndex, michael@0: int32_t aStartColumnIndex, michael@0: int32_t aEndRowIndex, michael@0: int32_t aEndColumnIndex) michael@0: { michael@0: return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex, michael@0: aEndRowIndex, aEndColumnIndex, false); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::RestrictCellsToSelection(nsIContent *aTable, michael@0: int32_t aStartRowIndex, michael@0: int32_t aStartColumnIndex, michael@0: int32_t aEndRowIndex, michael@0: int32_t aEndColumnIndex) michael@0: { michael@0: return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex, michael@0: aEndRowIndex, aEndColumnIndex, true); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::SelectRowOrColumn(nsIContent *aCellContent, uint32_t aTarget) michael@0: { michael@0: if (!aCellContent) return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsIContent* table = GetParentTable(aCellContent); michael@0: if (!table) return NS_ERROR_NULL_POINTER; michael@0: michael@0: // Get table and cell layout interfaces to access michael@0: // cell data based on cellmap location michael@0: // Frames are not ref counted, so don't use an nsCOMPtr michael@0: nsTableOuterFrame* tableFrame = do_QueryFrame(table->GetPrimaryFrame()); michael@0: if (!tableFrame) return NS_ERROR_FAILURE; michael@0: nsITableCellLayout *cellLayout = GetCellLayout(aCellContent); michael@0: if (!cellLayout) return NS_ERROR_FAILURE; michael@0: michael@0: // Get location of target cell: michael@0: int32_t rowIndex, colIndex; michael@0: nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex); michael@0: if (NS_FAILED(result)) return result; michael@0: michael@0: // Be sure we start at proper beginning michael@0: // (This allows us to select row or col given ANY cell!) michael@0: if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW) michael@0: colIndex = 0; michael@0: if (aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN) michael@0: rowIndex = 0; michael@0: michael@0: nsCOMPtr firstCell, lastCell; michael@0: while (true) { michael@0: // Loop through all cells in column or row to find first and last michael@0: nsCOMPtr curCellContent = michael@0: tableFrame->GetCellAt(rowIndex, colIndex); michael@0: if (!curCellContent) michael@0: break; michael@0: michael@0: if (!firstCell) michael@0: firstCell = curCellContent; michael@0: michael@0: lastCell = curCellContent.forget(); michael@0: michael@0: // Move to next cell in cellmap, skipping spanned locations michael@0: if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW) michael@0: colIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex); michael@0: else michael@0: rowIndex += tableFrame->GetEffectiveRowSpanAt(rowIndex, colIndex); michael@0: } michael@0: michael@0: // Use SelectBlockOfCells: michael@0: // This will replace existing selection, michael@0: // but allow unselecting by dragging out of selected region michael@0: if (firstCell && lastCell) michael@0: { michael@0: if (!mStartSelectedCell) michael@0: { michael@0: // We are starting a new block, so select the first cell michael@0: result = SelectCellElement(firstCell); michael@0: if (NS_FAILED(result)) return result; michael@0: mStartSelectedCell = firstCell; michael@0: } michael@0: nsCOMPtr lastCellContent = do_QueryInterface(lastCell); michael@0: result = SelectBlockOfCells(mStartSelectedCell, lastCellContent); michael@0: michael@0: // This gets set to the cell at end of row/col, michael@0: // but we need it to be the cell under cursor michael@0: mEndSelectedCell = aCellContent; michael@0: return result; michael@0: } michael@0: michael@0: #if 0 michael@0: // This is a more efficient strategy that appends row to current selection, michael@0: // but doesn't allow dragging OFF of an existing selection to unselect! michael@0: do { michael@0: // Loop through all cells in column or row michael@0: result = tableLayout->GetCellDataAt(rowIndex, colIndex, michael@0: getter_AddRefs(cellElement), michael@0: curRowIndex, curColIndex, michael@0: rowSpan, colSpan, michael@0: actualRowSpan, actualColSpan, michael@0: isSelected); michael@0: if (NS_FAILED(result)) return result; michael@0: // We're done when cell is not found michael@0: if (!cellElement) break; michael@0: michael@0: michael@0: // Check spans else we infinitely loop michael@0: NS_ASSERTION(actualColSpan, "actualColSpan is 0!"); michael@0: NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!"); michael@0: michael@0: // Skip cells that are already selected or span from outside our region michael@0: if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex) michael@0: { michael@0: result = SelectCellElement(cellElement); michael@0: if (NS_FAILED(result)) return result; michael@0: } michael@0: // Move to next row or column in cellmap, skipping spanned locations michael@0: if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW) michael@0: colIndex += actualColSpan; michael@0: else michael@0: rowIndex += actualRowSpan; michael@0: } michael@0: while (cellElement); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsFrameSelection::GetFirstCellNodeInRange(nsRange *aRange) const michael@0: { michael@0: if (!aRange) return nullptr; michael@0: michael@0: nsINode* startParent = aRange->GetStartParent(); michael@0: if (!startParent) michael@0: return nullptr; michael@0: michael@0: int32_t offset = aRange->StartOffset(); michael@0: michael@0: nsIContent* childContent = startParent->GetChildAt(offset); michael@0: if (!childContent) michael@0: return nullptr; michael@0: // Don't return node if not a cell michael@0: if (!IsCell(childContent)) michael@0: return nullptr; michael@0: michael@0: return childContent; michael@0: } michael@0: michael@0: nsRange* michael@0: nsFrameSelection::GetFirstCellRange() michael@0: { michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return nullptr; michael@0: michael@0: nsRange* firstRange = mDomSelections[index]->GetRangeAt(0); michael@0: if (!GetFirstCellNodeInRange(firstRange)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Setup for next cell michael@0: mSelectedCellIndex = 1; michael@0: michael@0: return firstRange; michael@0: } michael@0: michael@0: nsRange* michael@0: nsFrameSelection::GetNextCellRange() michael@0: { michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return nullptr; michael@0: michael@0: nsRange* range = mDomSelections[index]->GetRangeAt(mSelectedCellIndex); michael@0: michael@0: // Get first node in next range of selection - test if it's a cell michael@0: if (!GetFirstCellNodeInRange(range)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Setup for next cell michael@0: mSelectedCellIndex++; michael@0: michael@0: return range; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::GetCellIndexes(nsIContent *aCell, michael@0: int32_t &aRowIndex, michael@0: int32_t &aColIndex) michael@0: { michael@0: if (!aCell) return NS_ERROR_NULL_POINTER; michael@0: michael@0: aColIndex=0; // initialize out params michael@0: aRowIndex=0; michael@0: michael@0: nsITableCellLayout *cellLayoutObject = GetCellLayout(aCell); michael@0: if (!cellLayoutObject) return NS_ERROR_FAILURE; michael@0: return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex); michael@0: } michael@0: michael@0: nsIContent* michael@0: nsFrameSelection::IsInSameTable(nsIContent *aContent1, michael@0: nsIContent *aContent2) const michael@0: { michael@0: if (!aContent1 || !aContent2) return nullptr; michael@0: michael@0: nsIContent* tableNode1 = GetParentTable(aContent1); michael@0: nsIContent* tableNode2 = GetParentTable(aContent2); michael@0: michael@0: // Must be in the same table. Note that we want to return false for michael@0: // the test if both tables are null. michael@0: return (tableNode1 == tableNode2) ? tableNode1 : nullptr; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsFrameSelection::GetParentTable(nsIContent *aCell) const michael@0: { michael@0: if (!aCell) { michael@0: return nullptr; michael@0: } michael@0: michael@0: for (nsIContent* parent = aCell->GetParent(); parent; michael@0: parent = parent->GetParent()) { michael@0: if (parent->Tag() == nsGkAtoms::table && michael@0: parent->IsHTML()) { michael@0: return parent; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::SelectCellElement(nsIContent *aCellElement) michael@0: { michael@0: nsIContent *parent = aCellElement->GetParent(); michael@0: michael@0: // Get child offset michael@0: int32_t offset = parent->IndexOf(aCellElement); michael@0: michael@0: return CreateAndAddRange(parent, offset); michael@0: } michael@0: michael@0: nsresult michael@0: Selection::getTableCellLocationFromRange(nsRange* aRange, michael@0: int32_t* aSelectionType, michael@0: int32_t* aRow, int32_t* aCol) michael@0: { michael@0: if (!aRange || !aSelectionType || !aRow || !aCol) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: *aSelectionType = nsISelectionPrivate::TABLESELECTION_NONE; michael@0: *aRow = 0; michael@0: *aCol = 0; michael@0: michael@0: // Must have access to frame selection to get cell info michael@0: if (!mFrameSelection) return NS_OK; michael@0: michael@0: nsresult result = GetTableSelectionType(aRange, aSelectionType); michael@0: if (NS_FAILED(result)) return result; michael@0: michael@0: // Don't fail if range does not point to a single table cell, michael@0: // let aSelectionType tell user if we don't have a cell michael@0: if (*aSelectionType != nsISelectionPrivate::TABLESELECTION_CELL) michael@0: return NS_OK; michael@0: michael@0: // Get the child content (the cell) pointed to by starting node of range michael@0: // We do minimal checking since GetTableSelectionType assures michael@0: // us that this really is a table cell michael@0: nsCOMPtr content = do_QueryInterface(aRange->GetStartParent()); michael@0: if (!content) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIContent *child = content->GetChildAt(aRange->StartOffset()); michael@0: if (!child) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: //Note: This is a non-ref-counted pointer to the frame michael@0: nsITableCellLayout *cellLayout = mFrameSelection->GetCellLayout(child); michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: if (!cellLayout) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return cellLayout->GetCellIndexes(*aRow, *aCol); michael@0: } michael@0: michael@0: nsresult michael@0: Selection::addTableCellRange(nsRange* aRange, bool* aDidAddRange, michael@0: int32_t* aOutIndex) michael@0: { michael@0: if (!aDidAddRange || !aOutIndex) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: *aDidAddRange = false; michael@0: *aOutIndex = -1; michael@0: michael@0: if (!mFrameSelection) michael@0: return NS_OK; michael@0: michael@0: if (!aRange) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsresult result; michael@0: michael@0: // Get if we are adding a cell selection and the row, col of cell if we are michael@0: int32_t newRow, newCol, tableMode; michael@0: result = getTableCellLocationFromRange(aRange, &tableMode, &newRow, &newCol); michael@0: if (NS_FAILED(result)) return result; michael@0: michael@0: // If not adding a cell range, we are done here michael@0: if (tableMode != nsISelectionPrivate::TABLESELECTION_CELL) michael@0: { michael@0: mFrameSelection->mSelectingTableCellMode = tableMode; michael@0: // Don't fail if range isn't a selected cell, aDidAddRange tells caller if we didn't proceed michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Set frame selection mode only if not already set to a table mode michael@0: // so we don't lose the select row and column flags (not detected by getTableCellLocation) michael@0: if (mFrameSelection->mSelectingTableCellMode == TABLESELECTION_NONE) michael@0: mFrameSelection->mSelectingTableCellMode = tableMode; michael@0: michael@0: *aDidAddRange = true; michael@0: return AddItem(aRange, aOutIndex); michael@0: } michael@0: michael@0: //TODO: Figure out TABLESELECTION_COLUMN and TABLESELECTION_ALLCELLS michael@0: nsresult michael@0: Selection::GetTableSelectionType(nsIDOMRange* aDOMRange, michael@0: int32_t* aTableSelectionType) michael@0: { michael@0: if (!aDOMRange || !aTableSelectionType) michael@0: return NS_ERROR_NULL_POINTER; michael@0: nsRange* range = static_cast(aDOMRange); michael@0: michael@0: *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_NONE; michael@0: michael@0: // Must have access to frame selection to get cell info michael@0: if(!mFrameSelection) return NS_OK; michael@0: michael@0: nsINode* startNode = range->GetStartParent(); michael@0: if (!startNode) return NS_ERROR_FAILURE; michael@0: michael@0: nsINode* endNode = range->GetEndParent(); michael@0: if (!endNode) return NS_ERROR_FAILURE; michael@0: michael@0: // Not a single selected node michael@0: if (startNode != endNode) return NS_OK; michael@0: michael@0: int32_t startOffset = range->StartOffset(); michael@0: int32_t endOffset = range->EndOffset(); michael@0: michael@0: // Not a single selected node michael@0: if ((endOffset - startOffset) != 1) michael@0: return NS_OK; michael@0: michael@0: nsIContent* startContent = static_cast(startNode); michael@0: if (!(startNode->IsElement() && startContent->IsHTML())) { michael@0: // Implies a check for being an element; if we ever make this work michael@0: // for non-HTML, need to keep checking for elements. michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIAtom *tag = startContent->Tag(); michael@0: michael@0: if (tag == nsGkAtoms::tr) michael@0: { michael@0: *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_CELL; michael@0: } michael@0: else //check to see if we are selecting a table or row (column and all cells not done yet) michael@0: { michael@0: nsIContent *child = startNode->GetChildAt(startOffset); michael@0: if (!child) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: tag = child->Tag(); michael@0: michael@0: if (tag == nsGkAtoms::table) michael@0: *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_TABLE; michael@0: else if (tag == nsGkAtoms::tr) michael@0: *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_ROW; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameSelection::CreateAndAddRange(nsINode *aParentNode, int32_t aOffset) michael@0: { michael@0: if (!aParentNode) return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsRefPtr range = new nsRange(aParentNode); michael@0: michael@0: // Set range around child at given offset michael@0: nsresult result = range->SetStart(aParentNode, aOffset); michael@0: if (NS_FAILED(result)) return result; michael@0: result = range->SetEnd(aParentNode, aOffset+1); michael@0: if (NS_FAILED(result)) return result; michael@0: michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: return mDomSelections[index]->AddRange(range); michael@0: } michael@0: michael@0: // End of Table Selection michael@0: michael@0: void michael@0: nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter) michael@0: { michael@0: if (mAncestorLimiter != aLimiter) { michael@0: mAncestorLimiter = aLimiter; michael@0: int8_t index = michael@0: GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return; michael@0: michael@0: if (!IsValidSelectionPoint(this, mDomSelections[index]->GetFocusNode())) { michael@0: ClearNormalSelection(); michael@0: if (mAncestorLimiter) { michael@0: PostReason(nsISelectionListener::NO_REASON); michael@0: TakeFocus(mAncestorLimiter, 0, 0, HINTLEFT, false, false); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: //END nsFrameSelection methods michael@0: michael@0: michael@0: //BEGIN nsISelection interface implementations michael@0: michael@0: michael@0: michael@0: nsresult michael@0: nsFrameSelection::DeleteFromDocument() michael@0: { michael@0: nsresult res; michael@0: michael@0: // If we're already collapsed, then we do nothing (bug 719503). michael@0: bool isCollapsed; michael@0: int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); michael@0: if (!mDomSelections[index]) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: mDomSelections[index]->GetIsCollapsed( &isCollapsed); michael@0: if (isCollapsed) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr selection = mDomSelections[index]; michael@0: for (int32_t rangeIdx = 0; rangeIdx < selection->GetRangeCount(); ++rangeIdx) { michael@0: nsRefPtr range = selection->GetRangeAt(rangeIdx); michael@0: res = range->DeleteContents(); michael@0: if (NS_FAILED(res)) michael@0: return res; michael@0: } michael@0: michael@0: // Collapse to the new location. michael@0: // If we deleted one character, then we move back one element. michael@0: // FIXME We don't know how to do this past frame boundaries yet. michael@0: if (isCollapsed) michael@0: mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset()-1); michael@0: else if (mDomSelections[index]->AnchorOffset() > 0) michael@0: mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset()); michael@0: #ifdef DEBUG michael@0: else michael@0: printf("Don't know how to set selection back past frame boundary\n"); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFrameSelection::SetDelayedCaretData(WidgetMouseEvent* aMouseEvent) michael@0: { michael@0: if (aMouseEvent) { michael@0: mDelayedMouseEventValid = true; michael@0: mDelayedMouseEventIsShift = aMouseEvent->IsShift(); michael@0: mDelayedMouseEventClickCount = aMouseEvent->clickCount; michael@0: } else { michael@0: mDelayedMouseEventValid = false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFrameSelection::DisconnectFromPresShell() michael@0: { michael@0: StopAutoScrollTimer(); michael@0: for (int32_t i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; i++) { michael@0: mDomSelections[i]->Clear(nullptr); michael@0: } michael@0: mShell = nullptr; michael@0: } michael@0: michael@0: //END nsISelection interface implementations michael@0: michael@0: #if 0 michael@0: #pragma mark - michael@0: #endif michael@0: michael@0: // mozilla::dom::Selection implementation michael@0: michael@0: // note: this can return a nil anchor node michael@0: michael@0: Selection::Selection() michael@0: : mCachedOffsetForFrame(nullptr) michael@0: , mDirection(eDirNext) michael@0: , mType(nsISelectionController::SELECTION_NORMAL) michael@0: { michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: Selection::Selection(nsFrameSelection* aList) michael@0: : mFrameSelection(aList) michael@0: , mCachedOffsetForFrame(nullptr) michael@0: , mDirection(eDirNext) michael@0: , mType(nsISelectionController::SELECTION_NORMAL) michael@0: { michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: Selection::~Selection() michael@0: { michael@0: setAnchorFocusRange(-1); michael@0: michael@0: uint32_t count = mRanges.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: mRanges[i].mRange->SetInSelection(false); michael@0: } michael@0: michael@0: if (mAutoScrollTimer) { michael@0: mAutoScrollTimer->Stop(); michael@0: mAutoScrollTimer = nullptr; michael@0: } michael@0: michael@0: mScrollEvent.Revoke(); michael@0: michael@0: if (mCachedOffsetForFrame) { michael@0: delete mCachedOffsetForFrame; michael@0: mCachedOffsetForFrame = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsIDocument* michael@0: Selection::GetParentObject() const michael@0: { michael@0: nsIPresShell* shell = GetPresShell(); michael@0: if (shell) { michael@0: return shell->GetDocument(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(Selection) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection) michael@0: // Unlink the selection listeners *before* we do RemoveAllRanges since michael@0: // we don't want to notify the listeners during JS GC (they could be michael@0: // in JS!). michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners) michael@0: tmp->RemoveAllRanges(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection) michael@0: { michael@0: uint32_t i, count = tmp->mRanges.Length(); michael@0: for (i = 0; i < count; ++i) { michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRanges[i].mRange) michael@0: } michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Selection) michael@0: michael@0: DOMCI_DATA(Selection, Selection) michael@0: michael@0: // QueryInterface implementation for Selection michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Selection) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISelection) michael@0: NS_INTERFACE_MAP_ENTRY(nsISelectionPrivate) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelection) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(Selection) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(Selection) michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetAnchorNode(nsIDOMNode** aAnchorNode) michael@0: { michael@0: nsINode* anchorNode = GetAnchorNode(); michael@0: if (anchorNode) { michael@0: return CallQueryInterface(anchorNode, aAnchorNode); michael@0: } michael@0: michael@0: *aAnchorNode = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsINode* michael@0: Selection::GetAnchorNode() michael@0: { michael@0: if (!mAnchorFocusRange) michael@0: return nullptr; michael@0: michael@0: if (GetDirection() == eDirNext) { michael@0: return mAnchorFocusRange->GetStartParent(); michael@0: } michael@0: michael@0: return mAnchorFocusRange->GetEndParent(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetAnchorOffset(int32_t* aAnchorOffset) michael@0: { michael@0: *aAnchorOffset = static_cast(AnchorOffset()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // note: this can return a nil focus node michael@0: NS_IMETHODIMP michael@0: Selection::GetFocusNode(nsIDOMNode** aFocusNode) michael@0: { michael@0: nsINode* focusNode = GetFocusNode(); michael@0: if (focusNode) { michael@0: return CallQueryInterface(focusNode, aFocusNode); michael@0: } michael@0: michael@0: *aFocusNode = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsINode* michael@0: Selection::GetFocusNode() michael@0: { michael@0: if (!mAnchorFocusRange) michael@0: return nullptr; michael@0: michael@0: if (GetDirection() == eDirNext){ michael@0: return mAnchorFocusRange->GetEndParent(); michael@0: } michael@0: michael@0: return mAnchorFocusRange->GetStartParent(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetFocusOffset(int32_t* aFocusOffset) michael@0: { michael@0: *aFocusOffset = static_cast(FocusOffset()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Selection::setAnchorFocusRange(int32_t indx) michael@0: { michael@0: if (indx >= (int32_t)mRanges.Length()) michael@0: return; michael@0: if (indx < 0) //release all michael@0: { michael@0: mAnchorFocusRange = nullptr; michael@0: } michael@0: else{ michael@0: mAnchorFocusRange = mRanges[indx].mRange; michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: Selection::AnchorOffset() michael@0: { michael@0: if (!mAnchorFocusRange) michael@0: return 0; michael@0: michael@0: if (GetDirection() == eDirNext){ michael@0: return mAnchorFocusRange->StartOffset(); michael@0: } michael@0: michael@0: return mAnchorFocusRange->EndOffset(); michael@0: } michael@0: michael@0: uint32_t michael@0: Selection::FocusOffset() michael@0: { michael@0: if (!mAnchorFocusRange) michael@0: return 0; michael@0: michael@0: if (GetDirection() == eDirNext){ michael@0: return mAnchorFocusRange->EndOffset(); michael@0: } michael@0: michael@0: return mAnchorFocusRange->StartOffset(); michael@0: } michael@0: michael@0: static nsresult michael@0: CompareToRangeStart(nsINode* aCompareNode, int32_t aCompareOffset, michael@0: nsRange* aRange, int32_t* aCmp) michael@0: { michael@0: nsINode* start = aRange->GetStartParent(); michael@0: NS_ENSURE_STATE(aCompareNode && start); michael@0: // If the nodes that we're comparing are not in the same document, michael@0: // assume that aCompareNode will fall at the end of the ranges. michael@0: if (aCompareNode->GetCurrentDoc() != start->GetCurrentDoc() || michael@0: !start->GetCurrentDoc()) { michael@0: *aCmp = 1; michael@0: } else { michael@0: *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset, michael@0: start, aRange->StartOffset()); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: CompareToRangeEnd(nsINode* aCompareNode, int32_t aCompareOffset, michael@0: nsRange* aRange, int32_t* aCmp) michael@0: { michael@0: nsINode* end = aRange->GetEndParent(); michael@0: NS_ENSURE_STATE(aCompareNode && end); michael@0: // If the nodes that we're comparing are not in the same document, michael@0: // assume that aCompareNode will fall at the end of the ranges. michael@0: if (aCompareNode->GetCurrentDoc() != end->GetCurrentDoc() || michael@0: !end->GetCurrentDoc()) { michael@0: *aCmp = 1; michael@0: } else { michael@0: *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset, michael@0: end, aRange->EndOffset()); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Selection::FindInsertionPoint michael@0: // michael@0: // Binary searches the given sorted array of ranges for the insertion point michael@0: // for the given node/offset. The given comparator is used, and the index michael@0: // where the point should appear in the array is placed in *aInsertionPoint. michael@0: // michael@0: // If there is an item in the array equal to the input point, we will return michael@0: // the index of this item. michael@0: michael@0: nsresult michael@0: Selection::FindInsertionPoint( michael@0: nsTArray* aElementArray, michael@0: nsINode* aPointNode, int32_t aPointOffset, michael@0: nsresult (*aComparator)(nsINode*,int32_t,nsRange*,int32_t*), michael@0: int32_t* aPoint) michael@0: { michael@0: *aPoint = 0; michael@0: int32_t beginSearch = 0; michael@0: int32_t endSearch = aElementArray->Length(); // one beyond what to check michael@0: michael@0: if (endSearch) { michael@0: int32_t center = endSearch - 1; // Check last index, then binary search michael@0: do { michael@0: nsRange* range = (*aElementArray)[center].mRange; michael@0: michael@0: int32_t cmp; michael@0: nsresult rv = aComparator(aPointNode, aPointOffset, range, &cmp); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (cmp < 0) { // point < cur michael@0: endSearch = center; michael@0: } else if (cmp > 0) { // point > cur michael@0: beginSearch = center + 1; michael@0: } else { // found match, done michael@0: beginSearch = center; michael@0: break; michael@0: } michael@0: center = (endSearch - beginSearch) / 2 + beginSearch; michael@0: } while (endSearch - beginSearch > 0); michael@0: } michael@0: michael@0: *aPoint = beginSearch; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Selection::SubtractRange michael@0: // michael@0: // A helper function that subtracts aSubtract from aRange, and adds michael@0: // 1 or 2 RangeData objects representing the remaining non-overlapping michael@0: // difference to aOutput. It is assumed that the caller has checked that michael@0: // aRange and aSubtract do indeed overlap michael@0: michael@0: nsresult michael@0: Selection::SubtractRange(RangeData* aRange, nsRange* aSubtract, michael@0: nsTArray* aOutput) michael@0: { michael@0: nsRange* range = aRange->mRange; michael@0: michael@0: // First we want to compare to the range start michael@0: int32_t cmp; michael@0: nsresult rv = CompareToRangeStart(range->GetStartParent(), michael@0: range->StartOffset(), michael@0: aSubtract, &cmp); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Also, make a comparison to the range end michael@0: int32_t cmp2; michael@0: rv = CompareToRangeEnd(range->GetEndParent(), michael@0: range->EndOffset(), michael@0: aSubtract, &cmp2); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If the existing range left overlaps the new range (aSubtract) then michael@0: // cmp < 0, and cmp2 < 0 michael@0: // If it right overlaps the new range then cmp > 0 and cmp2 > 0 michael@0: // If it fully contains the new range, then cmp < 0 and cmp2 > 0 michael@0: michael@0: if (cmp2 > 0) { michael@0: // We need to add a new RangeData to the output, running from michael@0: // the end of aSubtract to the end of range michael@0: nsRefPtr postOverlap = new nsRange(aSubtract->GetEndParent()); michael@0: michael@0: rv = michael@0: postOverlap->SetStart(aSubtract->GetEndParent(), aSubtract->EndOffset()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = michael@0: postOverlap->SetEnd(range->GetEndParent(), range->EndOffset()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!postOverlap->Collapsed()) { michael@0: if (!aOutput->InsertElementAt(0, RangeData(postOverlap))) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle; michael@0: } michael@0: } michael@0: michael@0: if (cmp < 0) { michael@0: // We need to add a new RangeData to the output, running from michael@0: // the start of the range to the start of aSubtract michael@0: nsRefPtr preOverlap = new nsRange(range->GetStartParent()); michael@0: michael@0: nsresult rv = michael@0: preOverlap->SetStart(range->GetStartParent(), range->StartOffset()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = michael@0: preOverlap->SetEnd(aSubtract->GetStartParent(), aSubtract->StartOffset()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!preOverlap->Collapsed()) { michael@0: if (!aOutput->InsertElementAt(0, RangeData(preOverlap))) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Selection::AddItem(nsRange* aItem, int32_t* aOutIndex) michael@0: { michael@0: if (!aItem) michael@0: return NS_ERROR_NULL_POINTER; michael@0: if (!aItem->IsPositioned()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: NS_ASSERTION(aOutIndex, "aOutIndex can't be null"); michael@0: michael@0: *aOutIndex = -1; michael@0: michael@0: // a common case is that we have no ranges yet michael@0: if (mRanges.Length() == 0) { michael@0: if (!mRanges.AppendElement(RangeData(aItem))) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: aItem->SetInSelection(true); michael@0: michael@0: *aOutIndex = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t startIndex, endIndex; michael@0: nsresult rv = GetIndicesForInterval(aItem->GetStartParent(), michael@0: aItem->StartOffset(), michael@0: aItem->GetEndParent(), michael@0: aItem->EndOffset(), false, michael@0: &startIndex, &endIndex); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (endIndex == -1) { michael@0: // All ranges start after the given range. We can insert our range at michael@0: // position 0, knowing there are no overlaps (handled below) michael@0: startIndex = 0; michael@0: endIndex = 0; michael@0: } else if (startIndex == -1) { michael@0: // All ranges end before the given range. We can insert our range at michael@0: // the end of the array, knowing there are no overlaps (handled below) michael@0: startIndex = mRanges.Length(); michael@0: endIndex = startIndex; michael@0: } michael@0: michael@0: // If the range is already contained in mRanges, silently succeed michael@0: bool sameRange = EqualsRangeAtPoint(aItem->GetStartParent(), michael@0: aItem->StartOffset(), michael@0: aItem->GetEndParent(), michael@0: aItem->EndOffset(), startIndex); michael@0: if (sameRange) { michael@0: *aOutIndex = startIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (startIndex == endIndex) { michael@0: // The new range doesn't overlap any existing ranges michael@0: if (!mRanges.InsertElementAt(startIndex, RangeData(aItem))) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: aItem->SetInSelection(true); michael@0: *aOutIndex = startIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We now know that at least 1 existing range overlaps with the range that michael@0: // we are trying to add. In fact, the only ranges of interest are those at michael@0: // the two end points, startIndex and endIndex - 1 (which may point to the michael@0: // same range) as these may partially overlap the new range. Any ranges michael@0: // between these indices are fully overlapped by the new range, and so can be michael@0: // removed michael@0: nsTArray overlaps; michael@0: if (!overlaps.InsertElementAt(0, mRanges[startIndex])) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (endIndex - 1 != startIndex) { michael@0: if (!overlaps.InsertElementAt(1, mRanges[endIndex - 1])) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // Remove all the overlapping ranges michael@0: for (int32_t i = startIndex; i < endIndex; ++i) { michael@0: mRanges[i].mRange->SetInSelection(false); michael@0: } michael@0: mRanges.RemoveElementsAt(startIndex, endIndex - startIndex); michael@0: michael@0: nsTArray temp; michael@0: for (int32_t i = overlaps.Length() - 1; i >= 0; i--) { michael@0: nsresult rv = SubtractRange(&overlaps[i], aItem, &temp); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Insert the new element into our "leftovers" array michael@0: int32_t insertionPoint; michael@0: rv = FindInsertionPoint(&temp, aItem->GetStartParent(), michael@0: aItem->StartOffset(), CompareToRangeStart, michael@0: &insertionPoint); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!temp.InsertElementAt(insertionPoint, RangeData(aItem))) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // Merge the leftovers back in to mRanges michael@0: if (!mRanges.InsertElementsAt(startIndex, temp)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: for (uint32_t i = 0; i < temp.Length(); ++i) { michael@0: temp[i].mRange->SetInSelection(true); michael@0: } michael@0: michael@0: *aOutIndex = startIndex + insertionPoint; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Selection::RemoveItem(nsRange* aItem) michael@0: { michael@0: if (!aItem) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: // Find the range's index & remove it. We could use FindInsertionPoint to michael@0: // get O(log n) time, but that requires many expensive DOM comparisons. michael@0: // For even several thousand items, this is probably faster because the michael@0: // comparisons are so fast. michael@0: int32_t idx = -1; michael@0: uint32_t i; michael@0: for (i = 0; i < mRanges.Length(); i ++) { michael@0: if (mRanges[i].mRange == aItem) { michael@0: idx = (int32_t)i; michael@0: break; michael@0: } michael@0: } michael@0: if (idx < 0) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: mRanges.RemoveElementAt(idx); michael@0: aItem->SetInSelection(false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Selection::RemoveCollapsedRanges() michael@0: { michael@0: uint32_t i = 0; michael@0: while (i < mRanges.Length()) { michael@0: if (mRanges[i].mRange->Collapsed()) { michael@0: nsresult rv = RemoveItem(mRanges[i].mRange); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: ++i; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Selection::Clear(nsPresContext* aPresContext) michael@0: { michael@0: setAnchorFocusRange(-1); michael@0: michael@0: for (uint32_t i = 0; i < mRanges.Length(); ++i) { michael@0: mRanges[i].mRange->SetInSelection(false); michael@0: selectFrames(aPresContext, mRanges[i].mRange, false); michael@0: } michael@0: mRanges.Clear(); michael@0: michael@0: // Reset direction so for more dependable table selection range handling michael@0: SetDirection(eDirNext); michael@0: michael@0: // If this was an ATTENTION selection, change it back to normal now michael@0: if (mFrameSelection && michael@0: mFrameSelection->GetDisplaySelection() == michael@0: nsISelectionController::SELECTION_ATTENTION) { michael@0: mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetType(int16_t* aType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aType); michael@0: *aType = Type(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // RangeMatches*Point michael@0: // michael@0: // Compares the range beginning or ending point, and returns true if it michael@0: // exactly matches the given DOM point. michael@0: michael@0: static inline bool michael@0: RangeMatchesBeginPoint(nsRange* aRange, nsINode* aNode, int32_t aOffset) michael@0: { michael@0: return aRange->GetStartParent() == aNode && aRange->StartOffset() == aOffset; michael@0: } michael@0: michael@0: static inline bool michael@0: RangeMatchesEndPoint(nsRange* aRange, nsINode* aNode, int32_t aOffset) michael@0: { michael@0: return aRange->GetEndParent() == aNode && aRange->EndOffset() == aOffset; michael@0: } michael@0: michael@0: // Selection::EqualsRangeAtPoint michael@0: // michael@0: // Utility method for checking equivalence of two ranges. michael@0: michael@0: bool michael@0: Selection::EqualsRangeAtPoint( michael@0: nsINode* aBeginNode, int32_t aBeginOffset, michael@0: nsINode* aEndNode, int32_t aEndOffset, michael@0: int32_t aRangeIndex) michael@0: { michael@0: if (aRangeIndex >=0 && aRangeIndex < (int32_t) mRanges.Length()) { michael@0: nsRange* range = mRanges[aRangeIndex].mRange; michael@0: if (RangeMatchesBeginPoint(range, aBeginNode, aBeginOffset) && michael@0: RangeMatchesEndPoint(range, aEndNode, aEndOffset)) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // Selection::GetRangesForInterval michael@0: // michael@0: // XPCOM wrapper for the nsTArray version michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetRangesForInterval(nsIDOMNode* aBeginNode, int32_t aBeginOffset, michael@0: nsIDOMNode* aEndNode, int32_t aEndOffset, michael@0: bool aAllowAdjacent, michael@0: uint32_t* aResultCount, michael@0: nsIDOMRange*** aResults) michael@0: { michael@0: if (!aBeginNode || ! aEndNode || ! aResultCount || ! aResults) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: *aResultCount = 0; michael@0: *aResults = nullptr; michael@0: michael@0: nsTArray> results; michael@0: ErrorResult result; michael@0: nsCOMPtr beginNode = do_QueryInterface(aBeginNode); michael@0: nsCOMPtr endNode = do_QueryInterface(aEndNode); michael@0: NS_ENSURE_TRUE(beginNode && endNode, NS_ERROR_NULL_POINTER); michael@0: GetRangesForInterval(*beginNode, aBeginOffset, *endNode, aEndOffset, michael@0: aAllowAdjacent, results, result); michael@0: if (result.Failed()) { michael@0: return result.ErrorCode(); michael@0: } michael@0: *aResultCount = results.Length(); michael@0: if (*aResultCount == 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aResults = static_cast michael@0: (nsMemory::Alloc(sizeof(nsIDOMRange*) * *aResultCount)); michael@0: NS_ENSURE_TRUE(*aResults, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: for (uint32_t i = 0; i < *aResultCount; i++) { michael@0: (*aResults)[i] = results[i].forget().take(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: void michael@0: Selection::GetRangesForInterval(nsINode& aBeginNode, int32_t aBeginOffset, michael@0: nsINode& aEndNode, int32_t aEndOffset, michael@0: bool aAllowAdjacent, michael@0: nsTArray>& aReturn, michael@0: mozilla::ErrorResult& aRv) michael@0: { michael@0: nsTArray results; michael@0: nsresult rv = GetRangesForIntervalArray(&aBeginNode, aBeginOffset, michael@0: &aEndNode, aEndOffset, michael@0: aAllowAdjacent, &results); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: aReturn.SetLength(results.Length()); michael@0: for (uint32_t i = 0; i < results.Length(); ++i) { michael@0: aReturn[i] = results[i]; // AddRefs michael@0: } michael@0: } michael@0: michael@0: // Selection::GetRangesForIntervalArray michael@0: // michael@0: // Fills a nsTArray with the ranges overlapping the range specified by michael@0: // the given endpoints. Ranges in the selection exactly adjacent to the michael@0: // input range are not returned unless aAllowAdjacent is set. michael@0: // michael@0: // For example, if the following ranges were in the selection michael@0: // (assume everything is within the same node) michael@0: // michael@0: // Start Offset: 0 2 7 9 michael@0: // End Offset: 2 5 9 10 michael@0: // michael@0: // and passed aBeginOffset of 2 and aEndOffset of 9, then with michael@0: // aAllowAdjacent set, all the ranges should be returned. If michael@0: // aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only michael@0: // should be returned michael@0: // michael@0: // Now that overlapping ranges are disallowed, there can be a maximum of michael@0: // 2 adjacent ranges michael@0: michael@0: nsresult michael@0: Selection::GetRangesForIntervalArray(nsINode* aBeginNode, int32_t aBeginOffset, michael@0: nsINode* aEndNode, int32_t aEndOffset, michael@0: bool aAllowAdjacent, michael@0: nsTArray* aRanges) michael@0: { michael@0: aRanges->Clear(); michael@0: int32_t startIndex, endIndex; michael@0: nsresult res = GetIndicesForInterval(aBeginNode, aBeginOffset, michael@0: aEndNode, aEndOffset, aAllowAdjacent, michael@0: &startIndex, &endIndex); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (startIndex == -1 || endIndex == -1) michael@0: return NS_OK; michael@0: michael@0: for (int32_t i = startIndex; i < endIndex; i++) { michael@0: if (!aRanges->AppendElement(mRanges[i].mRange)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Selection::GetIndicesForInterval michael@0: // michael@0: // Works on the same principle as GetRangesForIntervalArray above, however michael@0: // instead this returns the indices into mRanges between which the michael@0: // overlapping ranges lie. michael@0: michael@0: nsresult michael@0: Selection::GetIndicesForInterval(nsINode* aBeginNode, int32_t aBeginOffset, michael@0: nsINode* aEndNode, int32_t aEndOffset, michael@0: bool aAllowAdjacent, michael@0: int32_t* aStartIndex, int32_t* aEndIndex) michael@0: { michael@0: int32_t startIndex; michael@0: int32_t endIndex; michael@0: michael@0: if (!aStartIndex) michael@0: aStartIndex = &startIndex; michael@0: if (!aEndIndex) michael@0: aEndIndex = &endIndex; michael@0: michael@0: *aStartIndex = -1; michael@0: *aEndIndex = -1; michael@0: michael@0: if (mRanges.Length() == 0) michael@0: return NS_OK; michael@0: michael@0: bool intervalIsCollapsed = aBeginNode == aEndNode && michael@0: aBeginOffset == aEndOffset; michael@0: michael@0: // Ranges that end before the given interval and begin after the given michael@0: // interval can be discarded michael@0: int32_t endsBeforeIndex; michael@0: if (NS_FAILED(FindInsertionPoint(&mRanges, aEndNode, aEndOffset, michael@0: &CompareToRangeStart, michael@0: &endsBeforeIndex))) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (endsBeforeIndex == 0) { michael@0: nsRange* endRange = mRanges[endsBeforeIndex].mRange; michael@0: michael@0: // If the interval is strictly before the range at index 0, we can optimize michael@0: // by returning now - all ranges start after the given interval michael@0: if (!RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset)) michael@0: return NS_OK; michael@0: michael@0: // We now know that the start point of mRanges[0].mRange equals the end of michael@0: // the interval. Thus, when aAllowadjacent is true, the caller is always michael@0: // interested in this range. However, when excluding adjacencies, we must michael@0: // remember to include the range when both it and the given interval are michael@0: // collapsed to the same point michael@0: if (!aAllowAdjacent && !(endRange->Collapsed() && intervalIsCollapsed)) michael@0: return NS_OK; michael@0: } michael@0: *aEndIndex = endsBeforeIndex; michael@0: michael@0: int32_t beginsAfterIndex; michael@0: if (NS_FAILED(FindInsertionPoint(&mRanges, aBeginNode, aBeginOffset, michael@0: &CompareToRangeEnd, michael@0: &beginsAfterIndex))) { michael@0: return NS_OK; michael@0: } michael@0: if (beginsAfterIndex == (int32_t) mRanges.Length()) michael@0: return NS_OK; // optimization: all ranges are strictly before us michael@0: michael@0: if (aAllowAdjacent) { michael@0: // At this point, one of the following holds: michael@0: // endsBeforeIndex == mRanges.Length(), michael@0: // endsBeforeIndex points to a range whose start point does not equal the michael@0: // given interval's start point michael@0: // endsBeforeIndex points to a range whose start point equals the given michael@0: // interval's start point michael@0: // In the final case, there can be two such ranges, a collapsed range, and michael@0: // an adjacent range (they will appear in mRanges in that order). For this michael@0: // final case, we need to increment endsBeforeIndex, until one of the michael@0: // first two possibilites hold michael@0: while (endsBeforeIndex < (int32_t) mRanges.Length()) { michael@0: nsRange* endRange = mRanges[endsBeforeIndex].mRange; michael@0: if (!RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset)) michael@0: break; michael@0: endsBeforeIndex++; michael@0: } michael@0: michael@0: // Likewise, one of the following holds: michael@0: // beginsAfterIndex == 0, michael@0: // beginsAfterIndex points to a range whose end point does not equal michael@0: // the given interval's end point michael@0: // beginsOnOrAfter points to a range whose end point equals the given michael@0: // interval's end point michael@0: // In the final case, there can be two such ranges, an adjacent range, and michael@0: // a collapsed range (they will appear in mRanges in that order). For this michael@0: // final case, we only need to take action if both those ranges exist, and michael@0: // we are pointing to the collapsed range - we need to point to the michael@0: // adjacent range michael@0: nsRange* beginRange = mRanges[beginsAfterIndex].mRange; michael@0: if (beginsAfterIndex > 0 && beginRange->Collapsed() && michael@0: RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset)) { michael@0: beginRange = mRanges[beginsAfterIndex - 1].mRange; michael@0: if (RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset)) michael@0: beginsAfterIndex--; michael@0: } michael@0: } else { michael@0: // See above for the possibilities at this point. The only case where we michael@0: // need to take action is when the range at beginsAfterIndex ends on michael@0: // the given interval's start point, but that range isn't collapsed (a michael@0: // collapsed range should be included in the returned results). michael@0: nsRange* beginRange = mRanges[beginsAfterIndex].mRange; michael@0: if (RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset) && michael@0: !beginRange->Collapsed()) michael@0: beginsAfterIndex++; michael@0: michael@0: // Again, see above for the meaning of endsBeforeIndex at this point. michael@0: // In particular, endsBeforeIndex may point to a collaped range which michael@0: // represents the point at the end of the interval - this range should be michael@0: // included michael@0: if (endsBeforeIndex < (int32_t) mRanges.Length()) { michael@0: nsRange* endRange = mRanges[endsBeforeIndex].mRange; michael@0: if (RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset) && michael@0: endRange->Collapsed()) michael@0: endsBeforeIndex++; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(beginsAfterIndex <= endsBeforeIndex, michael@0: "Is mRanges not ordered?"); michael@0: NS_ENSURE_STATE(beginsAfterIndex <= endsBeforeIndex); michael@0: michael@0: *aStartIndex = beginsAfterIndex; michael@0: *aEndIndex = endsBeforeIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetPrimaryFrameForAnchorNode(nsIFrame** aReturnFrame) michael@0: { michael@0: if (!aReturnFrame) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: int32_t frameOffset = 0; michael@0: *aReturnFrame = 0; michael@0: nsCOMPtr content = do_QueryInterface(GetAnchorNode()); michael@0: if (content && mFrameSelection) michael@0: { michael@0: *aReturnFrame = mFrameSelection-> michael@0: GetFrameForNodeOffset(content, AnchorOffset(), michael@0: mFrameSelection->GetHint(), &frameOffset); michael@0: if (*aReturnFrame) michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetPrimaryFrameForFocusNode(nsIFrame** aReturnFrame, michael@0: int32_t* aOffsetUsed, michael@0: bool aVisual) michael@0: { michael@0: if (!aReturnFrame) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsCOMPtr content = do_QueryInterface(GetFocusNode()); michael@0: if (!content || !mFrameSelection) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: int32_t frameOffset = 0; michael@0: *aReturnFrame = 0; michael@0: if (!aOffsetUsed) michael@0: aOffsetUsed = &frameOffset; michael@0: michael@0: nsFrameSelection::HINT hint = mFrameSelection->GetHint(); michael@0: michael@0: if (aVisual) { michael@0: nsIPresShell *presShell = mFrameSelection->GetShell(); michael@0: if (!presShell) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsRefPtr caret = presShell->GetCaret(); michael@0: if (!caret) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: uint8_t caretBidiLevel = mFrameSelection->GetCaretBidiLevel(); michael@0: michael@0: return caret->GetCaretFrameForNodeOffset(content, FocusOffset(), michael@0: hint, caretBidiLevel, aReturnFrame, aOffsetUsed); michael@0: } michael@0: michael@0: *aReturnFrame = mFrameSelection-> michael@0: GetFrameForNodeOffset(content, FocusOffset(), michael@0: hint, aOffsetUsed); michael@0: if (!*aReturnFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //select all content children of aContent michael@0: nsresult michael@0: Selection::SelectAllFramesForContent(nsIContentIterator* aInnerIter, michael@0: nsIContent* aContent, michael@0: bool aSelected) michael@0: { michael@0: nsresult result = aInnerIter->Init(aContent); michael@0: nsIFrame *frame; michael@0: if (NS_SUCCEEDED(result)) michael@0: { michael@0: // First select frame of content passed in michael@0: frame = aContent->GetPrimaryFrame(); michael@0: if (frame && frame->GetType() == nsGkAtoms::textFrame) { michael@0: nsTextFrame* textFrame = static_cast(frame); michael@0: textFrame->SetSelectedRange(0, aContent->GetText()->GetLength(), aSelected, mType); michael@0: } michael@0: // Now iterated through the child frames and set them michael@0: while (!aInnerIter->IsDone()) { michael@0: nsCOMPtr innercontent = michael@0: do_QueryInterface(aInnerIter->GetCurrentNode()); michael@0: michael@0: frame = innercontent->GetPrimaryFrame(); michael@0: if (frame) { michael@0: if (frame->GetType() == nsGkAtoms::textFrame) { michael@0: nsTextFrame* textFrame = static_cast(frame); michael@0: textFrame->SetSelectedRange(0, innercontent->GetText()->GetLength(), aSelected, mType); michael@0: } else { michael@0: frame->InvalidateFrameSubtree(); // frame continuations? michael@0: } michael@0: } michael@0: michael@0: aInnerIter->Next(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: /** michael@0: * The idea of this helper method is to select or deselect "top to bottom", michael@0: * traversing through the frames michael@0: */ michael@0: nsresult michael@0: Selection::selectFrames(nsPresContext* aPresContext, nsRange* aRange, michael@0: bool aSelect) michael@0: { michael@0: if (!mFrameSelection || !aPresContext || !aPresContext->GetPresShell()) { michael@0: // nothing to do michael@0: return NS_OK; michael@0: } michael@0: MOZ_ASSERT(aRange); michael@0: michael@0: if (mFrameSelection->GetTableCellSelection()) { michael@0: nsINode* node = aRange->GetCommonAncestor(); michael@0: nsIFrame* frame = node->IsContent() ? node->AsContent()->GetPrimaryFrame() michael@0: : aPresContext->FrameManager()->GetRootFrame(); michael@0: if (frame) { michael@0: frame->InvalidateFrameSubtree(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr iter = NS_NewContentSubtreeIterator(); michael@0: iter->Init(aRange); michael@0: michael@0: // Loop through the content iterator for each content node; for each text michael@0: // node, call SetSelected on it: michael@0: nsCOMPtr content = do_QueryInterface(aRange->GetStartParent()); michael@0: NS_ENSURE_STATE(content); michael@0: michael@0: // We must call first one explicitly michael@0: if (content->IsNodeOfType(nsINode::eTEXT)) { michael@0: nsIFrame* frame = content->GetPrimaryFrame(); michael@0: // The frame could be an SVG text frame, in which case we'll ignore it. michael@0: if (frame && frame->GetType() == nsGkAtoms::textFrame) { michael@0: nsTextFrame* textFrame = static_cast(frame); michael@0: uint32_t startOffset = aRange->StartOffset(); michael@0: uint32_t endOffset; michael@0: if (aRange->GetEndParent() == content) { michael@0: endOffset = aRange->EndOffset(); michael@0: } else { michael@0: endOffset = content->Length(); michael@0: } michael@0: textFrame->SetSelectedRange(startOffset, endOffset, aSelect, mType); michael@0: } michael@0: } michael@0: michael@0: iter->First(); michael@0: nsCOMPtr inneriter = NS_NewContentIterator(); michael@0: for (iter->First(); !iter->IsDone(); iter->Next()) { michael@0: content = do_QueryInterface(iter->GetCurrentNode()); michael@0: SelectAllFramesForContent(inneriter, content, aSelect); michael@0: } michael@0: michael@0: // We must now do the last one if it is not the same as the first michael@0: if (aRange->GetEndParent() != aRange->GetStartParent()) { michael@0: nsresult res; michael@0: content = do_QueryInterface(aRange->GetEndParent(), &res); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(content, res); michael@0: michael@0: if (content->IsNodeOfType(nsINode::eTEXT)) { michael@0: nsIFrame* frame = content->GetPrimaryFrame(); michael@0: // The frame could be an SVG text frame, in which case we'll ignore it. michael@0: if (frame && frame->GetType() == nsGkAtoms::textFrame) { michael@0: nsTextFrame* textFrame = static_cast(frame); michael@0: textFrame->SetSelectedRange(0, aRange->EndOffset(), aSelect, mType); michael@0: } michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // Selection::LookUpSelection michael@0: // michael@0: // This function is called when a node wants to know where the selection is michael@0: // over itself. michael@0: // michael@0: // Usually, this is called when we already know there is a selection over michael@0: // the node in question, and we only need to find the boundaries of it on michael@0: // that node. This is when slowCheck is false--a strict test is not needed. michael@0: // Other times, the caller has no idea, and wants us to test everything, michael@0: // so we are supposed to determine whether there is a selection over the michael@0: // node at all. michael@0: // michael@0: // A previous version of this code used this flag to do less work when michael@0: // inclusion was already known (slowCheck=false). However, our tree michael@0: // structure allows us to quickly determine ranges overlapping the node, michael@0: // so we just ignore the slowCheck flag and do the full test every time. michael@0: // michael@0: // PERFORMANCE: a common case is that we are doing a fast check with exactly michael@0: // one range in the selection. In this case, this function is slower than michael@0: // brute force because of the overhead of checking the tree. We can optimize michael@0: // this case to make it faster by doing the same thing the previous version michael@0: // of this function did in the case of 1 range. This would also mean that michael@0: // the aSlowCheck flag would have meaning again. michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::LookUpSelection(nsIContent* aContent, int32_t aContentOffset, michael@0: int32_t aContentLength, michael@0: SelectionDetails** aReturnDetails, michael@0: SelectionType aType, bool aSlowCheck) michael@0: { michael@0: nsresult rv; michael@0: if (!aContent || ! aReturnDetails) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: // it is common to have no ranges, to optimize that michael@0: if (mRanges.Length() == 0) michael@0: return NS_OK; michael@0: michael@0: nsTArray overlappingRanges; michael@0: rv = GetRangesForIntervalArray(aContent, aContentOffset, michael@0: aContent, aContentOffset + aContentLength, michael@0: false, michael@0: &overlappingRanges); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (overlappingRanges.Length() == 0) michael@0: return NS_OK; michael@0: michael@0: for (uint32_t i = 0; i < overlappingRanges.Length(); i++) { michael@0: nsRange* range = overlappingRanges[i]; michael@0: nsINode* startNode = range->GetStartParent(); michael@0: nsINode* endNode = range->GetEndParent(); michael@0: int32_t startOffset = range->StartOffset(); michael@0: int32_t endOffset = range->EndOffset(); michael@0: michael@0: int32_t start = -1, end = -1; michael@0: if (startNode == aContent && endNode == aContent) { michael@0: if (startOffset < (aContentOffset + aContentLength) && michael@0: endOffset > aContentOffset) { michael@0: // this range is totally inside the requested content range michael@0: start = std::max(0, startOffset - aContentOffset); michael@0: end = std::min(aContentLength, endOffset - aContentOffset); michael@0: } michael@0: // otherwise, range is inside the requested node, but does not intersect michael@0: // the requested content range, so ignore it michael@0: } else if (startNode == aContent) { michael@0: if (startOffset < (aContentOffset + aContentLength)) { michael@0: // the beginning of the range is inside the requested node, but the michael@0: // end is outside, select everything from there to the end michael@0: start = std::max(0, startOffset - aContentOffset); michael@0: end = aContentLength; michael@0: } michael@0: } else if (endNode == aContent) { michael@0: if (endOffset > aContentOffset) { michael@0: // the end of the range is inside the requested node, but the beginning michael@0: // is outside, select everything from the beginning to there michael@0: start = 0; michael@0: end = std::min(aContentLength, endOffset - aContentOffset); michael@0: } michael@0: } else { michael@0: // this range does not begin or end in the requested node, but since michael@0: // GetRangesForInterval returned this range, we know it overlaps. michael@0: // Therefore, this node is enclosed in the range, and we select all michael@0: // of it. michael@0: start = 0; michael@0: end = aContentLength; michael@0: } michael@0: if (start < 0) michael@0: continue; // the ranges do not overlap the input range michael@0: michael@0: SelectionDetails* details = new SelectionDetails; michael@0: michael@0: details->mNext = *aReturnDetails; michael@0: details->mStart = start; michael@0: details->mEnd = end; michael@0: details->mType = aType; michael@0: RangeData *rd = FindRangeData(range); michael@0: if (rd) { michael@0: details->mTextRangeStyle = rd->mTextRangeStyle; michael@0: } michael@0: *aReturnDetails = details; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::Repaint(nsPresContext* aPresContext) michael@0: { michael@0: int32_t arrCount = (int32_t)mRanges.Length(); michael@0: michael@0: if (arrCount < 1) michael@0: return NS_OK; michael@0: michael@0: int32_t i; michael@0: michael@0: for (i = 0; i < arrCount; i++) michael@0: { michael@0: nsresult rv = selectFrames(aPresContext, mRanges[i].mRange, true); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetCanCacheFrameOffset(bool* aCanCacheFrameOffset) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanCacheFrameOffset); michael@0: michael@0: if (mCachedOffsetForFrame) michael@0: *aCanCacheFrameOffset = mCachedOffsetForFrame->mCanCacheFrameOffset; michael@0: else michael@0: *aCanCacheFrameOffset = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::SetCanCacheFrameOffset(bool aCanCacheFrameOffset) michael@0: { michael@0: if (!mCachedOffsetForFrame) { michael@0: mCachedOffsetForFrame = new CachedOffsetForFrame; michael@0: } michael@0: michael@0: mCachedOffsetForFrame->mCanCacheFrameOffset = aCanCacheFrameOffset; michael@0: michael@0: // clean up cached frame when turn off cache michael@0: // fix bug 207936 michael@0: if (!aCanCacheFrameOffset) { michael@0: mCachedOffsetForFrame->mLastCaretFrame = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset, michael@0: nsPoint& aPoint) michael@0: { michael@0: if (!mCachedOffsetForFrame) { michael@0: mCachedOffsetForFrame = new CachedOffsetForFrame; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (mCachedOffsetForFrame->mCanCacheFrameOffset && michael@0: mCachedOffsetForFrame->mLastCaretFrame && michael@0: (aFrame == mCachedOffsetForFrame->mLastCaretFrame) && michael@0: (inOffset == mCachedOffsetForFrame->mLastContentOffset)) michael@0: { michael@0: // get cached frame offset michael@0: aPoint = mCachedOffsetForFrame->mCachedFrameOffset; michael@0: } michael@0: else michael@0: { michael@0: // Recalculate frame offset and cache it. Don't cache a frame offset if michael@0: // GetPointFromOffset fails, though. michael@0: rv = aFrame->GetPointFromOffset(inOffset, &aPoint); michael@0: if (NS_SUCCEEDED(rv) && mCachedOffsetForFrame->mCanCacheFrameOffset) { michael@0: mCachedOffsetForFrame->mCachedFrameOffset = aPoint; michael@0: mCachedOffsetForFrame->mLastCaretFrame = aFrame; michael@0: mCachedOffsetForFrame->mLastContentOffset = inOffset; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::SetAncestorLimiter(nsIContent* aContent) michael@0: { michael@0: if (mFrameSelection) michael@0: mFrameSelection->SetAncestorLimiter(aContent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: RangeData* michael@0: Selection::FindRangeData(nsIDOMRange* aRange) michael@0: { michael@0: NS_ENSURE_TRUE(aRange, nullptr); michael@0: for (uint32_t i = 0; i < mRanges.Length(); i++) { michael@0: if (mRanges[i].mRange == aRange) michael@0: return &mRanges[i]; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::SetTextRangeStyle(nsIDOMRange* aRange, michael@0: const TextRangeStyle& aTextRangeStyle) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aRange); michael@0: RangeData *rd = FindRangeData(aRange); michael@0: if (rd) { michael@0: rd->mTextRangeStyle = aTextRangeStyle; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Selection::StartAutoScrollTimer(nsIFrame* aFrame, nsPoint& aPoint, michael@0: uint32_t aDelay) michael@0: { michael@0: NS_PRECONDITION(aFrame, "Need a frame"); michael@0: michael@0: nsresult result; michael@0: if (!mFrameSelection) michael@0: return NS_OK;//nothing to do michael@0: michael@0: if (!mAutoScrollTimer) michael@0: { michael@0: mAutoScrollTimer = new nsAutoScrollTimer(); michael@0: michael@0: result = mAutoScrollTimer->Init(mFrameSelection, this); michael@0: michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: } michael@0: michael@0: result = mAutoScrollTimer->SetDelay(aDelay); michael@0: michael@0: if (NS_FAILED(result)) michael@0: return result; michael@0: michael@0: return DoAutoScroll(aFrame, aPoint); michael@0: } michael@0: michael@0: nsresult michael@0: Selection::StopAutoScrollTimer() michael@0: { michael@0: if (mAutoScrollTimer) michael@0: return mAutoScrollTimer->Stop(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Selection::DoAutoScroll(nsIFrame* aFrame, nsPoint& aPoint) michael@0: { michael@0: NS_PRECONDITION(aFrame, "Need a frame"); michael@0: michael@0: if (mAutoScrollTimer) michael@0: (void)mAutoScrollTimer->Stop(); michael@0: michael@0: nsPresContext* presContext = aFrame->PresContext(); michael@0: nsRootPresContext* rootPC = presContext->GetRootPresContext(); michael@0: if (!rootPC) michael@0: return NS_OK; michael@0: nsIFrame* rootmostFrame = rootPC->PresShell()->FrameManager()->GetRootFrame(); michael@0: // Get the point relative to the root most frame because the scroll we are michael@0: // about to do will change the coordinates of aFrame. michael@0: nsPoint globalPoint = aPoint + aFrame->GetOffsetToCrossDoc(rootmostFrame); michael@0: michael@0: bool didScroll = presContext->PresShell()->ScrollFrameRectIntoView( michael@0: aFrame, michael@0: nsRect(aPoint, nsSize(0, 0)), michael@0: nsIPresShell::ScrollAxis(), michael@0: nsIPresShell::ScrollAxis(), michael@0: 0); michael@0: michael@0: // michael@0: // Start the AutoScroll timer if necessary. michael@0: // michael@0: michael@0: if (didScroll && mAutoScrollTimer) michael@0: { michael@0: nsPoint presContextPoint = globalPoint - michael@0: presContext->PresShell()->FrameManager()->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame); michael@0: mAutoScrollTimer->Start(presContext, presContextPoint); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** RemoveAllRanges zeroes the selection michael@0: */ michael@0: NS_IMETHODIMP michael@0: Selection::RemoveAllRanges() michael@0: { michael@0: ErrorResult result; michael@0: RemoveAllRanges(result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::RemoveAllRanges(ErrorResult& aRv) michael@0: { michael@0: if (!mFrameSelection) michael@0: return; // nothing to do michael@0: nsRefPtr presContext = GetPresContext(); michael@0: nsresult result = Clear(presContext); michael@0: if (NS_FAILED(result)) { michael@0: aRv.Throw(result); michael@0: return; michael@0: } michael@0: michael@0: // Turn off signal for table selection michael@0: mFrameSelection->ClearTableCellSelection(); michael@0: michael@0: result = mFrameSelection->NotifySelectionListeners(GetType()); michael@0: // Also need to notify the frames! michael@0: // PresShell::CharacterDataChanged should do that on DocumentChanged michael@0: if (NS_FAILED(result)) { michael@0: aRv.Throw(result); michael@0: } michael@0: } michael@0: michael@0: /** AddRange adds the specified range to the selection michael@0: * @param aRange is the range to be added michael@0: */ michael@0: NS_IMETHODIMP michael@0: Selection::AddRange(nsIDOMRange* aDOMRange) michael@0: { michael@0: if (!aDOMRange) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: nsRange* range = static_cast(aDOMRange); michael@0: ErrorResult result; michael@0: AddRange(*range, result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::AddRange(nsRange& aRange, ErrorResult& aRv) michael@0: { michael@0: // This inserts a table cell range in proper document order michael@0: // and returns NS_OK if range doesn't contain just one table cell michael@0: bool didAddRange; michael@0: int32_t rangeIndex; michael@0: nsresult result = addTableCellRange(&aRange, &didAddRange, &rangeIndex); michael@0: if (NS_FAILED(result)) { michael@0: aRv.Throw(result); michael@0: return; michael@0: } michael@0: michael@0: if (!didAddRange) michael@0: { michael@0: result = AddItem(&aRange, &rangeIndex); michael@0: if (NS_FAILED(result)) { michael@0: aRv.Throw(result); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(rangeIndex >= 0, "Range index not returned"); michael@0: setAnchorFocusRange(rangeIndex); michael@0: michael@0: // Make sure the caret appears on the next line, if at a newline michael@0: if (mType == nsISelectionController::SELECTION_NORMAL) michael@0: SetInterlinePosition(true); michael@0: michael@0: nsRefPtr presContext = GetPresContext(); michael@0: selectFrames(presContext, &aRange, true); michael@0: michael@0: if (!mFrameSelection) michael@0: return;//nothing to do michael@0: michael@0: result = mFrameSelection->NotifySelectionListeners(GetType()); michael@0: if (NS_FAILED(result)) { michael@0: aRv.Throw(result); michael@0: } michael@0: } michael@0: michael@0: // Selection::RemoveRange michael@0: // michael@0: // Removes the given range from the selection. The tricky part is updating michael@0: // the flags on the frames that indicate whether they have a selection or michael@0: // not. There could be several selection ranges on the frame, and clearing michael@0: // the bit would cause the selection to not be drawn, even when there is michael@0: // another range on the frame (bug 346185). michael@0: // michael@0: // We therefore find any ranges that intersect the same nodes as the range michael@0: // being removed, and cause them to set the selected bits back on their michael@0: // selected frames after we've cleared the bit from ours. michael@0: michael@0: nsresult michael@0: Selection::RemoveRange(nsIDOMRange* aDOMRange) michael@0: { michael@0: if (!aDOMRange) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: nsRange* range = static_cast(aDOMRange); michael@0: ErrorResult result; michael@0: RemoveRange(*range, result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::RemoveRange(nsRange& aRange, ErrorResult& aRv) michael@0: { michael@0: nsresult rv = RemoveItem(&aRange); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: nsINode* beginNode = aRange.GetStartParent(); michael@0: nsINode* endNode = aRange.GetEndParent(); michael@0: michael@0: if (!beginNode || !endNode) { michael@0: // Detached range; nothing else to do here. michael@0: return; michael@0: } michael@0: michael@0: // find out the length of the end node, so we can select all of it michael@0: int32_t beginOffset, endOffset; michael@0: if (endNode->IsNodeOfType(nsINode::eTEXT)) { michael@0: // Get the length of the text. We can't just use the offset because michael@0: // another range could be touching this text node but not intersect our michael@0: // range. michael@0: beginOffset = 0; michael@0: endOffset = static_cast(endNode)->TextLength(); michael@0: } else { michael@0: // For non-text nodes, the given offsets should be sufficient. michael@0: beginOffset = aRange.StartOffset(); michael@0: endOffset = aRange.EndOffset(); michael@0: } michael@0: michael@0: // clear the selected bit from the removed range's frames michael@0: nsRefPtr presContext = GetPresContext(); michael@0: selectFrames(presContext, &aRange, false); michael@0: michael@0: // add back the selected bit for each range touching our nodes michael@0: nsTArray affectedRanges; michael@0: rv = GetRangesForIntervalArray(beginNode, beginOffset, michael@0: endNode, endOffset, michael@0: true, &affectedRanges); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: for (uint32_t i = 0; i < affectedRanges.Length(); i++) { michael@0: selectFrames(presContext, affectedRanges[i], true); michael@0: } michael@0: michael@0: int32_t cnt = mRanges.Length(); michael@0: if (&aRange == mAnchorFocusRange) { michael@0: // Reset anchor to LAST range or clear it if there are no ranges. michael@0: setAnchorFocusRange(cnt - 1); michael@0: michael@0: // When the selection is user-created it makes sense to scroll the range michael@0: // into view. The spell-check selection, however, is created and destroyed michael@0: // in the background. We don't want to scroll in this case or the view michael@0: // might appear to be moving randomly (bug 337871). michael@0: if (mType != nsISelectionController::SELECTION_SPELLCHECK && cnt > 0) michael@0: ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION); michael@0: } michael@0: michael@0: if (!mFrameSelection) michael@0: return;//nothing to do michael@0: rv = mFrameSelection->NotifySelectionListeners(GetType()); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: } michael@0: } michael@0: michael@0: michael@0: michael@0: /* michael@0: * Collapse sets the whole selection to be one point. michael@0: */ michael@0: NS_IMETHODIMP michael@0: Selection::Collapse(nsIDOMNode* aParentNode, int32_t aOffset) michael@0: { michael@0: nsCOMPtr parentNode = do_QueryInterface(aParentNode); michael@0: return Collapse(parentNode, aOffset); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::CollapseNative(nsINode* aParentNode, int32_t aOffset) michael@0: { michael@0: return Collapse(aParentNode, aOffset); michael@0: } michael@0: michael@0: nsresult michael@0: Selection::Collapse(nsINode* aParentNode, int32_t aOffset) michael@0: { michael@0: if (!aParentNode) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: ErrorResult result; michael@0: Collapse(*aParentNode, static_cast(aOffset), result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::Collapse(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv) michael@0: { michael@0: if (!mFrameSelection) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr kungfuDeathGrip = &aParentNode; michael@0: michael@0: mFrameSelection->InvalidateDesiredX(); michael@0: if (!IsValidSelectionPoint(mFrameSelection, &aParentNode)) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: nsresult result; michael@0: michael@0: nsRefPtr presContext = GetPresContext(); michael@0: if (!presContext || presContext->Document() != aParentNode.OwnerDoc()) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: // Delete all of the current ranges michael@0: Clear(presContext); michael@0: michael@0: // Turn off signal for table selection michael@0: mFrameSelection->ClearTableCellSelection(); michael@0: michael@0: nsRefPtr range = new nsRange(&aParentNode); michael@0: result = range->SetEnd(&aParentNode, aOffset); michael@0: if (NS_FAILED(result)) { michael@0: aRv.Throw(result); michael@0: return; michael@0: } michael@0: result = range->SetStart(&aParentNode, aOffset); michael@0: if (NS_FAILED(result)) { michael@0: aRv.Throw(result); michael@0: return; michael@0: } michael@0: michael@0: #ifdef DEBUG_SELECTION michael@0: nsCOMPtr content = do_QueryInterface(&aParentNode); michael@0: nsCOMPtr doc = do_QueryInterface(&aParentNode); michael@0: printf ("Sel. Collapse to %p %s %d\n", &aParentNode, michael@0: content ? nsAtomCString(content->Tag()).get() michael@0: : (doc ? "DOCUMENT" : "???"), michael@0: aOffset); michael@0: #endif michael@0: michael@0: int32_t rangeIndex = -1; michael@0: result = AddItem(range, &rangeIndex); michael@0: if (NS_FAILED(result)) { michael@0: aRv.Throw(result); michael@0: return; michael@0: } michael@0: setAnchorFocusRange(0); michael@0: selectFrames(presContext, range, true); michael@0: result = mFrameSelection->NotifySelectionListeners(GetType()); michael@0: if (NS_FAILED(result)) { michael@0: aRv.Throw(result); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Sets the whole selection to be one point michael@0: * at the start of the current selection michael@0: */ michael@0: NS_IMETHODIMP michael@0: Selection::CollapseToStart() michael@0: { michael@0: ErrorResult result; michael@0: CollapseToStart(result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::CollapseToStart(ErrorResult& aRv) michael@0: { michael@0: int32_t cnt; michael@0: nsresult rv = GetRangeCount(&cnt); michael@0: if (NS_FAILED(rv) || cnt <= 0) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Get the first range michael@0: nsRange* firstRange = mRanges[0].mRange; michael@0: if (!firstRange) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: if (mFrameSelection) { michael@0: int16_t reason = mFrameSelection->PopReason() | nsISelectionListener::COLLAPSETOSTART_REASON; michael@0: mFrameSelection->PostReason(reason); michael@0: } michael@0: nsINode* parent = firstRange->GetStartParent(); michael@0: if (!parent) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: Collapse(*parent, firstRange->StartOffset(), aRv); michael@0: } michael@0: michael@0: /* michael@0: * Sets the whole selection to be one point michael@0: * at the end of the current selection michael@0: */ michael@0: NS_IMETHODIMP michael@0: Selection::CollapseToEnd() michael@0: { michael@0: ErrorResult result; michael@0: CollapseToEnd(result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::CollapseToEnd(ErrorResult& aRv) michael@0: { michael@0: int32_t cnt; michael@0: nsresult rv = GetRangeCount(&cnt); michael@0: if (NS_FAILED(rv) || cnt <= 0) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Get the last range michael@0: nsRange* lastRange = mRanges[cnt - 1].mRange; michael@0: if (!lastRange) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: if (mFrameSelection) { michael@0: int16_t reason = mFrameSelection->PopReason() | nsISelectionListener::COLLAPSETOEND_REASON; michael@0: mFrameSelection->PostReason(reason); michael@0: } michael@0: nsINode* parent = lastRange->GetEndParent(); michael@0: if (!parent) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: Collapse(*parent, lastRange->EndOffset(), aRv); michael@0: } michael@0: michael@0: /* michael@0: * IsCollapsed -- is the whole selection just one point, or unset? michael@0: */ michael@0: bool michael@0: Selection::IsCollapsed() michael@0: { michael@0: uint32_t cnt = mRanges.Length(); michael@0: if (cnt == 0) { michael@0: return true; michael@0: } michael@0: michael@0: if (cnt != 1) { michael@0: return false; michael@0: } michael@0: michael@0: return mRanges[0].mRange->Collapsed(); michael@0: } michael@0: michael@0: /* virtual */ michael@0: bool michael@0: Selection::Collapsed() michael@0: { michael@0: return IsCollapsed(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetIsCollapsed(bool* aIsCollapsed) michael@0: { michael@0: NS_ENSURE_TRUE(aIsCollapsed, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aIsCollapsed = IsCollapsed(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetRangeCount(int32_t* aRangeCount) michael@0: { michael@0: *aRangeCount = (int32_t)RangeCount(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::GetRangeAt(int32_t aIndex, nsIDOMRange** aReturn) michael@0: { michael@0: ErrorResult result; michael@0: *aReturn = GetRangeAt(aIndex, result); michael@0: NS_IF_ADDREF(*aReturn); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: nsRange* michael@0: Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv) michael@0: { michael@0: nsRange* range = GetRangeAt(aIndex); michael@0: if (!range) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: return range; michael@0: } michael@0: michael@0: nsRange* michael@0: Selection::GetRangeAt(int32_t aIndex) michael@0: { michael@0: RangeData empty(nullptr); michael@0: return mRanges.SafeElementAt(aIndex, empty).mRange; michael@0: } michael@0: michael@0: /* michael@0: utility function michael@0: */ michael@0: nsresult michael@0: Selection::SetAnchorFocusToRange(nsRange* aRange) michael@0: { michael@0: NS_ENSURE_STATE(mAnchorFocusRange); michael@0: michael@0: nsresult res = RemoveItem(mAnchorFocusRange); michael@0: if (NS_FAILED(res)) michael@0: return res; michael@0: michael@0: int32_t aOutIndex = -1; michael@0: res = AddItem(aRange, &aOutIndex); michael@0: if (NS_FAILED(res)) michael@0: return res; michael@0: setAnchorFocusRange(aOutIndex); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Selection::ReplaceAnchorFocusRange(nsRange* aRange) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(mAnchorFocusRange); michael@0: nsRefPtr presContext = GetPresContext(); michael@0: if (presContext) { michael@0: selectFrames(presContext, mAnchorFocusRange, false); michael@0: SetAnchorFocusToRange(aRange); michael@0: selectFrames(presContext, mAnchorFocusRange, true); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: Notes which might come in handy for extend: michael@0: michael@0: We can tell the direction of the selection by asking for the anchors selection michael@0: if the begin is less than the end then we know the selection is to the "right". michael@0: else it is a backwards selection. michael@0: a = anchor michael@0: 1 = old cursor michael@0: 2 = new cursor michael@0: michael@0: if (a <= 1 && 1 <=2) a,1,2 or (a1,2) michael@0: if (a < 2 && 1 > 2) a,2,1 michael@0: if (1 < a && a <2) 1,a,2 michael@0: if (a > 2 && 2 >1) 1,2,a michael@0: if (2 < a && a <1) 2,a,1 michael@0: if (a > 1 && 1 >2) 2,1,a michael@0: then execute michael@0: a 1 2 select from 1 to 2 michael@0: a 2 1 deselect from 2 to 1 michael@0: 1 a 2 deselect from 1 to a select from a to 2 michael@0: 1 2 a deselect from 1 to 2 michael@0: 2 1 a = continue selection from 2 to 1 michael@0: */ michael@0: michael@0: michael@0: /* michael@0: * Extend extends the selection away from the anchor. michael@0: * We don't need to know the direction, because we always change the focus. michael@0: */ michael@0: NS_IMETHODIMP michael@0: Selection::Extend(nsIDOMNode* aParentNode, int32_t aOffset) michael@0: { michael@0: nsCOMPtr parentNode = do_QueryInterface(aParentNode); michael@0: return Extend(parentNode, aOffset); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::ExtendNative(nsINode* aParentNode, int32_t aOffset) michael@0: { michael@0: return Extend(aParentNode, aOffset); michael@0: } michael@0: michael@0: nsresult michael@0: Selection::Extend(nsINode* aParentNode, int32_t aOffset) michael@0: { michael@0: if (!aParentNode) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: ErrorResult result; michael@0: Extend(*aParentNode, static_cast(aOffset), result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv) michael@0: { michael@0: // First, find the range containing the old focus point: michael@0: if (!mAnchorFocusRange) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!mFrameSelection) { michael@0: aRv.Throw(NS_ERROR_NOT_INITIALIZED); // Can't do selection michael@0: return; michael@0: } michael@0: michael@0: nsresult res; michael@0: if (!IsValidSelectionPoint(mFrameSelection, &aParentNode)) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr presContext = GetPresContext(); michael@0: if (!presContext || presContext->Document() != aParentNode.OwnerDoc()) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: //mFrameSelection->InvalidateDesiredX(); michael@0: michael@0: nsINode* anchorNode = GetAnchorNode(); michael@0: nsINode* focusNode = GetFocusNode(); michael@0: uint32_t anchorOffset = AnchorOffset(); michael@0: uint32_t focusOffset = FocusOffset(); michael@0: michael@0: nsRefPtr range = mAnchorFocusRange->CloneRange(); michael@0: michael@0: nsINode* startNode = range->GetStartParent(); michael@0: nsINode* endNode = range->GetEndParent(); michael@0: int32_t startOffset = range->StartOffset(); michael@0: int32_t endOffset = range->EndOffset(); michael@0: michael@0: nsDirection dir = GetDirection(); michael@0: michael@0: //compare anchor to old cursor. michael@0: michael@0: // We pass |disconnected| to the following ComparePoints calls in order michael@0: // to avoid assertions. ComparePoints returns 1 in the disconnected case michael@0: // and we can end up in various cases below, but it is assumed that in michael@0: // any of the cases we end up, the nsRange implementation will collapse michael@0: // the range to the new point because we can not make a valid range with michael@0: // a disconnected point. This means that whatever range is currently michael@0: // selected will be cleared. michael@0: bool disconnected = false; michael@0: bool shouldClearRange = false; michael@0: int32_t result1 = nsContentUtils::ComparePoints(anchorNode, anchorOffset, michael@0: focusNode, focusOffset, michael@0: &disconnected); michael@0: //compare old cursor to new cursor michael@0: shouldClearRange |= disconnected; michael@0: int32_t result2 = nsContentUtils::ComparePoints(focusNode, focusOffset, michael@0: &aParentNode, aOffset, michael@0: &disconnected); michael@0: //compare anchor to new cursor michael@0: shouldClearRange |= disconnected; michael@0: int32_t result3 = nsContentUtils::ComparePoints(anchorNode, anchorOffset, michael@0: &aParentNode, aOffset, michael@0: &disconnected); michael@0: michael@0: // If the points are disconnected, the range will be collapsed below, michael@0: // resulting in a range that selects nothing. michael@0: if (shouldClearRange) { michael@0: // Repaint the current range with the selection removed. michael@0: selectFrames(presContext, range, false); michael@0: } michael@0: michael@0: nsRefPtr difRange = new nsRange(&aParentNode); michael@0: if ((result1 == 0 && result3 < 0) || (result1 <= 0 && result2 < 0)){//a1,2 a,1,2 michael@0: //select from 1 to 2 unless they are collapsed michael@0: range->SetEnd(aParentNode, aOffset, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: dir = eDirNext; michael@0: res = difRange->SetEnd(range->GetEndParent(), range->EndOffset()); michael@0: nsresult tmp = difRange->SetStart(focusNode, focusOffset); michael@0: if (NS_FAILED(tmp)) { michael@0: res = tmp; michael@0: } michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: selectFrames(presContext, difRange , true); michael@0: res = SetAnchorFocusToRange(range); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: } michael@0: else if (result1 == 0 && result3 > 0){//2, a1 michael@0: //select from 2 to 1a michael@0: dir = eDirPrevious; michael@0: range->SetStart(aParentNode, aOffset, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: selectFrames(presContext, range, true); michael@0: res = SetAnchorFocusToRange(range); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: } michael@0: else if (result3 <= 0 && result2 >= 0) {//a,2,1 or a2,1 or a,21 or a21 michael@0: //deselect from 2 to 1 michael@0: res = difRange->SetEnd(focusNode, focusOffset); michael@0: difRange->SetStart(aParentNode, aOffset, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: michael@0: range->SetEnd(aParentNode, aOffset, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: res = SetAnchorFocusToRange(range); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: selectFrames(presContext, difRange, false); // deselect now michael@0: difRange->SetEnd(range->GetEndParent(), range->EndOffset()); michael@0: selectFrames(presContext, difRange, true); // must reselect last node maybe more michael@0: } michael@0: else if (result1 >= 0 && result3 <= 0) {//1,a,2 or 1a,2 or 1,a2 or 1a2 michael@0: if (GetDirection() == eDirPrevious){ michael@0: res = range->SetStart(endNode, endOffset); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: } michael@0: dir = eDirNext; michael@0: range->SetEnd(aParentNode, aOffset, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: if (focusNode != anchorNode || focusOffset != anchorOffset) {//if collapsed diff dont do anything michael@0: res = difRange->SetStart(focusNode, focusOffset); michael@0: nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset); michael@0: if (NS_FAILED(tmp)) { michael@0: res = tmp; michael@0: } michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: res = SetAnchorFocusToRange(range); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: //deselect from 1 to a michael@0: selectFrames(presContext, difRange , false); michael@0: } michael@0: else michael@0: { michael@0: res = SetAnchorFocusToRange(range); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: } michael@0: //select from a to 2 michael@0: selectFrames(presContext, range , true); michael@0: } michael@0: else if (result2 <= 0 && result3 >= 0) {//1,2,a or 12,a or 1,2a or 12a michael@0: //deselect from 1 to 2 michael@0: difRange->SetEnd(aParentNode, aOffset, aRv); michael@0: res = difRange->SetStart(focusNode, focusOffset); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: dir = eDirPrevious; michael@0: range->SetStart(aParentNode, aOffset, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: res = SetAnchorFocusToRange(range); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: selectFrames(presContext, difRange , false); michael@0: difRange->SetStart(range->GetStartParent(), range->StartOffset()); michael@0: selectFrames(presContext, difRange, true);//must reselect last node michael@0: } michael@0: else if (result3 >= 0 && result1 <= 0) {//2,a,1 or 2a,1 or 2,a1 or 2a1 michael@0: if (GetDirection() == eDirNext){ michael@0: range->SetEnd(startNode, startOffset); michael@0: } michael@0: dir = eDirPrevious; michael@0: range->SetStart(aParentNode, aOffset, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: //deselect from a to 1 michael@0: if (focusNode != anchorNode || focusOffset!= anchorOffset) {//if collapsed diff dont do anything michael@0: res = difRange->SetStart(anchorNode, anchorOffset); michael@0: nsresult tmp = difRange->SetEnd(focusNode, focusOffset); michael@0: if (NS_FAILED(tmp)) { michael@0: res = tmp; michael@0: } michael@0: tmp = SetAnchorFocusToRange(range); michael@0: if (NS_FAILED(tmp)) { michael@0: res = tmp; michael@0: } michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: selectFrames(presContext, difRange, false); michael@0: } michael@0: else michael@0: { michael@0: res = SetAnchorFocusToRange(range); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: } michael@0: //select from 2 to a michael@0: selectFrames(presContext, range , true); michael@0: } michael@0: else if (result2 >= 0 && result1 >= 0) {//2,1,a or 21,a or 2,1a or 21a michael@0: //select from 2 to 1 michael@0: range->SetStart(aParentNode, aOffset, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: dir = eDirPrevious; michael@0: res = difRange->SetEnd(focusNode, focusOffset); michael@0: nsresult tmp = difRange->SetStart(range->GetStartParent(), range->StartOffset()); michael@0: if (NS_FAILED(tmp)) { michael@0: res = tmp; michael@0: } michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: michael@0: selectFrames(presContext, difRange, true); michael@0: res = SetAnchorFocusToRange(range); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: DEBUG_OUT_RANGE(range); michael@0: #ifdef DEBUG_SELECTION michael@0: if (eDirNext == mDirection) michael@0: printf(" direction = 1 LEFT TO RIGHT\n"); michael@0: else michael@0: printf(" direction = 0 RIGHT TO LEFT\n"); michael@0: #endif michael@0: SetDirection(dir); michael@0: #ifdef DEBUG_SELECTION michael@0: nsCOMPtr content = do_QueryInterface(&aParentNode); michael@0: michael@0: printf ("Sel. Extend to %p %s %d\n", content.get(), michael@0: nsAtomCString(content->Tag()).get(), aOffset); michael@0: #endif michael@0: res = mFrameSelection->NotifySelectionListeners(GetType()); michael@0: if (NS_FAILED(res)) { michael@0: aRv.Throw(res); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::SelectAllChildren(nsIDOMNode* aParentNode) michael@0: { michael@0: ErrorResult result; michael@0: nsCOMPtr node = do_QueryInterface(aParentNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_INVALID_ARG); michael@0: SelectAllChildren(*node, result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) michael@0: { michael@0: if (mFrameSelection) michael@0: { michael@0: mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON); michael@0: } michael@0: Collapse(aNode, 0, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: if (mFrameSelection) michael@0: { michael@0: mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON); michael@0: } michael@0: Extend(aNode, aNode.GetChildCount(), aRv); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::ContainsNode(nsIDOMNode* aNode, bool aAllowPartial, bool* aYes) michael@0: { michael@0: if (!aYes) michael@0: return NS_ERROR_NULL_POINTER; michael@0: *aYes = false; michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: ErrorResult result; michael@0: *aYes = ContainsNode(node, aAllowPartial, result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: bool michael@0: Selection::ContainsNode(nsINode* aNode, bool aAllowPartial, ErrorResult& aRv) michael@0: { michael@0: nsresult rv; michael@0: if (mRanges.Length() == 0 || !aNode) michael@0: return false; michael@0: michael@0: // XXXbz this duplicates the GetNodeLength code in nsRange.cpp michael@0: uint32_t nodeLength; michael@0: bool isData = aNode->IsNodeOfType(nsINode::eDATA_NODE); michael@0: if (isData) { michael@0: nodeLength = static_cast(aNode)->TextLength(); michael@0: } else { michael@0: nodeLength = aNode->GetChildCount(); michael@0: } michael@0: michael@0: nsTArray overlappingRanges; michael@0: rv = GetRangesForIntervalArray(aNode, 0, aNode, nodeLength, michael@0: false, &overlappingRanges); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return false; michael@0: } michael@0: if (overlappingRanges.Length() == 0) michael@0: return false; // no ranges overlap michael@0: michael@0: // if the caller said partial intersections are OK, we're done michael@0: if (aAllowPartial) { michael@0: return true; michael@0: } michael@0: michael@0: // text nodes always count as inside michael@0: if (isData) { michael@0: return true; michael@0: } michael@0: michael@0: // The caller wants to know if the node is entirely within the given range, michael@0: // so we have to check all intersecting ranges. michael@0: for (uint32_t i = 0; i < overlappingRanges.Length(); i++) { michael@0: bool nodeStartsBeforeRange, nodeEndsAfterRange; michael@0: if (NS_SUCCEEDED(nsRange::CompareNodeToRange(aNode, overlappingRanges[i], michael@0: &nodeStartsBeforeRange, michael@0: &nodeEndsAfterRange))) { michael@0: if (!nodeStartsBeforeRange && !nodeEndsAfterRange) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: michael@0: nsPresContext* michael@0: Selection::GetPresContext() const michael@0: { michael@0: nsIPresShell *shell = GetPresShell(); michael@0: if (!shell) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return shell->GetPresContext(); michael@0: } michael@0: michael@0: nsIPresShell* michael@0: Selection::GetPresShell() const michael@0: { michael@0: if (!mFrameSelection) michael@0: return nullptr;//nothing to do michael@0: michael@0: return mFrameSelection->GetShell(); michael@0: } michael@0: michael@0: nsIFrame * michael@0: Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect* aRect) michael@0: { michael@0: if (!mFrameSelection) michael@0: return nullptr; // nothing to do michael@0: michael@0: NS_ENSURE_TRUE(aRect, nullptr); michael@0: michael@0: aRect->SetRect(0, 0, 0, 0); michael@0: michael@0: switch (aRegion) { michael@0: case nsISelectionController::SELECTION_ANCHOR_REGION: michael@0: case nsISelectionController::SELECTION_FOCUS_REGION: michael@0: return GetSelectionEndPointGeometry(aRegion, aRect); michael@0: break; michael@0: case nsISelectionController::SELECTION_WHOLE_SELECTION: michael@0: break; michael@0: default: michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_ASSERTION(aRegion == nsISelectionController::SELECTION_WHOLE_SELECTION, michael@0: "should only be SELECTION_WHOLE_SELECTION here"); michael@0: michael@0: nsRect anchorRect; michael@0: nsIFrame* anchorFrame = GetSelectionEndPointGeometry( michael@0: nsISelectionController::SELECTION_ANCHOR_REGION, &anchorRect); michael@0: if (!anchorFrame) michael@0: return nullptr; michael@0: michael@0: nsRect focusRect; michael@0: nsIFrame* focusFrame = GetSelectionEndPointGeometry( michael@0: nsISelectionController::SELECTION_FOCUS_REGION, &focusRect); michael@0: if (!focusFrame) michael@0: return nullptr; michael@0: michael@0: NS_ASSERTION(anchorFrame->PresContext() == focusFrame->PresContext(), michael@0: "points of selection in different documents?"); michael@0: // make focusRect relative to anchorFrame michael@0: focusRect += focusFrame->GetOffsetTo(anchorFrame); michael@0: michael@0: aRect->UnionRectEdges(anchorRect, focusRect); michael@0: return anchorFrame; michael@0: } michael@0: michael@0: nsIFrame * michael@0: Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion, nsRect* aRect) michael@0: { michael@0: if (!mFrameSelection) michael@0: return nullptr; // nothing to do michael@0: michael@0: NS_ENSURE_TRUE(aRect, nullptr); michael@0: michael@0: aRect->SetRect(0, 0, 0, 0); michael@0: michael@0: nsINode *node = nullptr; michael@0: uint32_t nodeOffset = 0; michael@0: nsIFrame *frame = nullptr; michael@0: michael@0: switch (aRegion) { michael@0: case nsISelectionController::SELECTION_ANCHOR_REGION: michael@0: node = GetAnchorNode(); michael@0: nodeOffset = AnchorOffset(); michael@0: break; michael@0: case nsISelectionController::SELECTION_FOCUS_REGION: michael@0: node = GetFocusNode(); michael@0: nodeOffset = FocusOffset(); michael@0: break; michael@0: default: michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!node) michael@0: return nullptr; michael@0: michael@0: nsCOMPtr content = do_QueryInterface(node); michael@0: NS_ENSURE_TRUE(content.get(), nullptr); michael@0: int32_t frameOffset = 0; michael@0: frame = mFrameSelection->GetFrameForNodeOffset(content, nodeOffset, michael@0: mFrameSelection->GetHint(), michael@0: &frameOffset); michael@0: if (!frame) michael@0: return nullptr; michael@0: michael@0: // Figure out what node type we have, then get the michael@0: // appropriate rect for it's nodeOffset. michael@0: bool isText = node->IsNodeOfType(nsINode::eTEXT); michael@0: michael@0: nsPoint pt(0, 0); michael@0: if (isText) { michael@0: nsIFrame* childFrame = nullptr; michael@0: frameOffset = 0; michael@0: nsresult rv = michael@0: frame->GetChildFrameContainingOffset(nodeOffset, michael@0: mFrameSelection->GetHint(), michael@0: &frameOffset, &childFrame); michael@0: if (NS_FAILED(rv)) michael@0: return nullptr; michael@0: if (!childFrame) michael@0: return nullptr; michael@0: michael@0: frame = childFrame; michael@0: michael@0: // Get the x coordinate of the offset into the text frame. michael@0: rv = GetCachedFrameOffset(frame, nodeOffset, pt); michael@0: if (NS_FAILED(rv)) michael@0: return nullptr; michael@0: } michael@0: michael@0: // Return the rect relative to the frame, with zero width. michael@0: if (isText) { michael@0: aRect->x = pt.x; michael@0: } else if (mFrameSelection->GetHint() == nsFrameSelection::HINTLEFT) { michael@0: // It's the frame's right edge we're interested in. michael@0: aRect->x = frame->GetRect().width; michael@0: } michael@0: aRect->height = frame->GetRect().height; michael@0: michael@0: return frame; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::ScrollSelectionIntoViewEvent::Run() michael@0: { michael@0: if (!mSelection) michael@0: return NS_OK; // event revoked michael@0: michael@0: int32_t flags = Selection::SCROLL_DO_FLUSH | michael@0: Selection::SCROLL_SYNCHRONOUS; michael@0: michael@0: mSelection->mScrollEvent.Forget(); michael@0: mSelection->ScrollIntoView(mRegion, mVerticalScroll, michael@0: mHorizontalScroll, mFlags | flags); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Selection::PostScrollSelectionIntoViewEvent( michael@0: SelectionRegion aRegion, michael@0: int32_t aFlags, michael@0: nsIPresShell::ScrollAxis aVertical, michael@0: nsIPresShell::ScrollAxis aHorizontal) michael@0: { michael@0: // If we've already posted an event, revoke it and place a new one at the michael@0: // end of the queue to make sure that any new pending reflow events are michael@0: // processed before we scroll. This will insure that we scroll to the michael@0: // correct place on screen. michael@0: mScrollEvent.Revoke(); michael@0: michael@0: nsRefPtr ev = michael@0: new ScrollSelectionIntoViewEvent(this, aRegion, aVertical, aHorizontal, michael@0: aFlags); michael@0: nsresult rv = NS_DispatchToCurrentThread(ev); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mScrollEvent = ev; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::ScrollIntoView(SelectionRegion aRegion, bool aIsSynchronous, michael@0: int16_t aVPercent, int16_t aHPercent) michael@0: { michael@0: ErrorResult result; michael@0: ScrollIntoView(aRegion, aIsSynchronous, aVPercent, aHPercent, result); michael@0: if (result.Failed()) { michael@0: return result.ErrorCode(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Selection::ScrollIntoView(int16_t aRegion, bool aIsSynchronous, michael@0: int16_t aVPercent, int16_t aHPercent, michael@0: ErrorResult& aRv) michael@0: { michael@0: nsresult rv = ScrollIntoViewInternal(aRegion, aIsSynchronous, michael@0: nsIPresShell::ScrollAxis(aVPercent), michael@0: nsIPresShell::ScrollAxis(aHPercent)); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::ScrollIntoViewInternal(SelectionRegion aRegion, bool aIsSynchronous, michael@0: nsIPresShell::ScrollAxis aVertical, michael@0: nsIPresShell::ScrollAxis aHorizontal) michael@0: { michael@0: return ScrollIntoView(aRegion, aVertical, aHorizontal, michael@0: aIsSynchronous ? Selection::SCROLL_SYNCHRONOUS : 0); michael@0: } michael@0: michael@0: nsresult michael@0: Selection::ScrollIntoView(SelectionRegion aRegion, michael@0: nsIPresShell::ScrollAxis aVertical, michael@0: nsIPresShell::ScrollAxis aHorizontal, michael@0: int32_t aFlags) michael@0: { michael@0: if (!mFrameSelection) michael@0: return NS_OK;//nothing to do michael@0: michael@0: nsCOMPtr presShell = mFrameSelection->GetShell(); michael@0: if (!presShell) michael@0: return NS_OK; michael@0: michael@0: if (mFrameSelection->GetBatching()) michael@0: return NS_OK; michael@0: michael@0: if (!(aFlags & Selection::SCROLL_SYNCHRONOUS)) michael@0: return PostScrollSelectionIntoViewEvent(aRegion, aFlags, michael@0: aVertical, aHorizontal); michael@0: michael@0: // Now that text frame character offsets are always valid (though not michael@0: // necessarily correct), the worst that will happen if we don't flush here michael@0: // is that some callers might scroll to the wrong place. Those should michael@0: // either manually flush if they're in a safe position for it or use the michael@0: // async version of this method. michael@0: if (aFlags & Selection::SCROLL_DO_FLUSH) { michael@0: presShell->FlushPendingNotifications(Flush_Layout); michael@0: michael@0: // Reget the presshell, since it might have been Destroy'ed. michael@0: presShell = mFrameSelection ? mFrameSelection->GetShell() : nullptr; michael@0: if (!presShell) michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // Scroll the selection region into view. michael@0: // michael@0: michael@0: nsRect rect; michael@0: nsIFrame* frame = GetSelectionAnchorGeometry(aRegion, &rect); michael@0: if (!frame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Scroll vertically to get the caret into view, but only if the container michael@0: // is perceived to be scrollable in that direction (i.e. there is a visible michael@0: // vertical scrollbar or the scroll range is at least one device pixel) michael@0: aVertical.mOnlyIfPerceivedScrollableDirection = true; michael@0: michael@0: uint32_t flags = 0; michael@0: if (aFlags & Selection::SCROLL_FIRST_ANCESTOR_ONLY) { michael@0: flags |= nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY; michael@0: } michael@0: if (aFlags & Selection::SCROLL_OVERFLOW_HIDDEN) { michael@0: flags |= nsIPresShell::SCROLL_OVERFLOW_HIDDEN; michael@0: } michael@0: michael@0: presShell->ScrollFrameRectIntoView(frame, rect, aVertical, aHorizontal, michael@0: flags); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::AddSelectionListener(nsISelectionListener* aNewListener) michael@0: { michael@0: if (!aNewListener) michael@0: return NS_ERROR_NULL_POINTER; michael@0: ErrorResult result; michael@0: AddSelectionListener(aNewListener, result); michael@0: if (result.Failed()) { michael@0: return result.ErrorCode(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Selection::AddSelectionListener(nsISelectionListener* aNewListener, michael@0: ErrorResult& aRv) michael@0: { michael@0: bool result = mSelectionListeners.AppendObject(aNewListener); // AddRefs michael@0: if (!result) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove) michael@0: { michael@0: if (!aListenerToRemove) michael@0: return NS_ERROR_NULL_POINTER; michael@0: ErrorResult result; michael@0: RemoveSelectionListener(aListenerToRemove, result); michael@0: if (result.Failed()) { michael@0: return result.ErrorCode(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove, michael@0: ErrorResult& aRv) michael@0: { michael@0: bool result = mSelectionListeners.RemoveObject(aListenerToRemove); // Releases michael@0: if (!result) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: Selection::NotifySelectionListeners() michael@0: { michael@0: if (!mFrameSelection) michael@0: return NS_OK;//nothing to do michael@0: michael@0: if (mFrameSelection->GetBatching()) { michael@0: mFrameSelection->SetDirty(); michael@0: return NS_OK; michael@0: } michael@0: nsCOMArray selectionListeners(mSelectionListeners); michael@0: int32_t cnt = selectionListeners.Count(); michael@0: if (cnt != mSelectionListeners.Count()) { michael@0: return NS_ERROR_OUT_OF_MEMORY; // nsCOMArray is fallible michael@0: } michael@0: michael@0: nsCOMPtr domdoc; michael@0: nsIPresShell* ps = GetPresShell(); michael@0: if (ps) { michael@0: domdoc = do_QueryInterface(ps->GetDocument()); michael@0: } michael@0: michael@0: short reason = mFrameSelection->PopReason(); michael@0: for (int32_t i = 0; i < cnt; i++) { michael@0: selectionListeners[i]->NotifySelectionChanged(domdoc, this, reason); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::StartBatchChanges() michael@0: { michael@0: if (mFrameSelection) michael@0: mFrameSelection->StartBatchChanges(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::EndBatchChanges() michael@0: { michael@0: if (mFrameSelection) michael@0: mFrameSelection->EndBatchChanges(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::DeleteFromDocument() michael@0: { michael@0: ErrorResult result; michael@0: DeleteFromDocument(result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::DeleteFromDocument(ErrorResult& aRv) michael@0: { michael@0: if (!mFrameSelection) michael@0: return;//nothing to do michael@0: nsresult rv = mFrameSelection->DeleteFromDocument(); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Selection::Modify(const nsAString& aAlter, const nsAString& aDirection, michael@0: const nsAString& aGranularity) michael@0: { michael@0: ErrorResult result; michael@0: Modify(aAlter, aDirection, aGranularity, result); michael@0: return result.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: Selection::Modify(const nsAString& aAlter, const nsAString& aDirection, michael@0: const nsAString& aGranularity, ErrorResult& aRv) michael@0: { michael@0: // Silently exit if there's no selection or no focus node. michael@0: if (!mFrameSelection || !GetAnchorFocusRange() || !GetFocusNode()) { michael@0: return; michael@0: } michael@0: michael@0: if (!aAlter.LowerCaseEqualsLiteral("move") && michael@0: !aAlter.LowerCaseEqualsLiteral("extend")) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!aDirection.LowerCaseEqualsLiteral("forward") && michael@0: !aDirection.LowerCaseEqualsLiteral("backward") && michael@0: !aDirection.LowerCaseEqualsLiteral("left") && michael@0: !aDirection.LowerCaseEqualsLiteral("right")) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Line moves are always visual. michael@0: bool visual = aDirection.LowerCaseEqualsLiteral("left") || michael@0: aDirection.LowerCaseEqualsLiteral("right") || michael@0: aGranularity.LowerCaseEqualsLiteral("line"); michael@0: michael@0: bool forward = aDirection.LowerCaseEqualsLiteral("forward") || michael@0: aDirection.LowerCaseEqualsLiteral("right"); michael@0: michael@0: bool extend = aAlter.LowerCaseEqualsLiteral("extend"); michael@0: michael@0: // The uint32_t casts below prevent an enum mismatch warning. michael@0: nsSelectionAmount amount; michael@0: uint32_t keycode; michael@0: if (aGranularity.LowerCaseEqualsLiteral("character")) { michael@0: amount = eSelectCluster; michael@0: keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_RIGHT : michael@0: (uint32_t) nsIDOMKeyEvent::DOM_VK_LEFT; michael@0: } michael@0: else if (aGranularity.LowerCaseEqualsLiteral("word")) { michael@0: amount = eSelectWordNoSpace; michael@0: keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_RIGHT : michael@0: (uint32_t) nsIDOMKeyEvent::DOM_VK_LEFT; michael@0: } michael@0: else if (aGranularity.LowerCaseEqualsLiteral("line")) { michael@0: amount = eSelectLine; michael@0: keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_DOWN : michael@0: (uint32_t) nsIDOMKeyEvent::DOM_VK_UP; michael@0: } michael@0: else if (aGranularity.LowerCaseEqualsLiteral("lineboundary")) { michael@0: amount = eSelectLine; michael@0: keycode = forward ? (uint32_t) nsIDOMKeyEvent::DOM_VK_END : michael@0: (uint32_t) nsIDOMKeyEvent::DOM_VK_HOME; michael@0: } michael@0: else if (aGranularity.LowerCaseEqualsLiteral("sentence") || michael@0: aGranularity.LowerCaseEqualsLiteral("sentenceboundary") || michael@0: aGranularity.LowerCaseEqualsLiteral("paragraph") || michael@0: aGranularity.LowerCaseEqualsLiteral("paragraphboundary") || michael@0: aGranularity.LowerCaseEqualsLiteral("documentboundary")) { michael@0: aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); michael@0: } michael@0: else { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: michael@0: // If the anchor doesn't equal the focus and we try to move without first michael@0: // collapsing the selection, MoveCaret will collapse the selection and quit. michael@0: // To avoid this, we need to collapse the selection first. michael@0: nsresult rv = NS_OK; michael@0: if (!extend) { michael@0: nsINode* focusNode = GetFocusNode(); michael@0: // We should have checked earlier that there was a focus node. michael@0: if (!focusNode) { michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: uint32_t focusOffset = FocusOffset(); michael@0: Collapse(focusNode, focusOffset); michael@0: } michael@0: michael@0: // If the base level of the focused frame is odd, we may have to swap the michael@0: // direction of the keycode. michael@0: nsIFrame *frame; michael@0: int32_t offset; michael@0: rv = GetPrimaryFrameForFocusNode(&frame, &offset, visual); michael@0: if (NS_SUCCEEDED(rv) && frame) { michael@0: nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(frame); michael@0: michael@0: if (baseLevel & 1) { michael@0: if (!visual && keycode == nsIDOMKeyEvent::DOM_VK_RIGHT) { michael@0: keycode = nsIDOMKeyEvent::DOM_VK_LEFT; michael@0: } michael@0: else if (!visual && keycode == nsIDOMKeyEvent::DOM_VK_LEFT) { michael@0: keycode = nsIDOMKeyEvent::DOM_VK_RIGHT; michael@0: } michael@0: else if (visual && keycode == nsIDOMKeyEvent::DOM_VK_HOME) { michael@0: keycode = nsIDOMKeyEvent::DOM_VK_END; michael@0: } michael@0: else if (visual && keycode == nsIDOMKeyEvent::DOM_VK_END) { michael@0: keycode = nsIDOMKeyEvent::DOM_VK_HOME; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // MoveCaret will return an error if it can't move in the specified michael@0: // direction, but we just ignore this error unless it's a line move, in which michael@0: // case we call nsISelectionController::CompleteMove to move the cursor to michael@0: // the beginning/end of the line. michael@0: rv = mFrameSelection->MoveCaret(keycode, extend, amount, visual); michael@0: michael@0: if (aGranularity.LowerCaseEqualsLiteral("line") && NS_FAILED(rv)) { michael@0: nsCOMPtr shell = michael@0: do_QueryInterface(mFrameSelection->GetShell()); michael@0: if (!shell) michael@0: return; michael@0: shell->CompleteMove(forward, extend); michael@0: } michael@0: } michael@0: michael@0: /** SelectionLanguageChange modifies the cursor Bidi level after a change in keyboard direction michael@0: * @param aLangRTL is true if the new language is right-to-left or false if the new language is left-to-right michael@0: */ michael@0: NS_IMETHODIMP michael@0: Selection::SelectionLanguageChange(bool aLangRTL) michael@0: { michael@0: if (!mFrameSelection) michael@0: return NS_ERROR_NOT_INITIALIZED; // Can't do selection michael@0: nsresult result; michael@0: nsIFrame *focusFrame = 0; michael@0: michael@0: result = GetPrimaryFrameForFocusNode(&focusFrame, nullptr, false); michael@0: if (NS_FAILED(result)) { michael@0: return result; michael@0: } michael@0: if (!focusFrame) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: int32_t frameStart, frameEnd; michael@0: focusFrame->GetOffsets(frameStart, frameEnd); michael@0: nsRefPtr context = GetPresContext(); michael@0: uint8_t levelBefore, levelAfter; michael@0: if (!context) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint8_t level = NS_GET_EMBEDDING_LEVEL(focusFrame); michael@0: int32_t focusOffset = static_cast(FocusOffset()); michael@0: if ((focusOffset != frameStart) && (focusOffset != frameEnd)) michael@0: // the cursor is not at a frame boundary, so the level of both the characters (logically) before and after the cursor michael@0: // is equal to the frame level michael@0: levelBefore = levelAfter = level; michael@0: else { michael@0: // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find the level of the characters michael@0: // before and after the cursor michael@0: nsCOMPtr focusContent = do_QueryInterface(GetFocusNode()); michael@0: /* michael@0: nsFrameSelection::HINT hint; michael@0: michael@0: if ((focusOffset == frameStart && level) // beginning of an RTL frame michael@0: || (focusOffset == frameEnd && !level)) { // end of an LTR frame michael@0: hint = nsFrameSelection::HINTRIGHT; michael@0: } michael@0: else { // end of an RTL frame or beginning of an LTR frame michael@0: hint = nsFrameSelection::HINTLEFT; michael@0: } michael@0: mFrameSelection->SetHint(hint); michael@0: */ michael@0: nsPrevNextBidiLevels levels = mFrameSelection-> michael@0: GetPrevNextBidiLevels(focusContent, focusOffset, false); michael@0: michael@0: levelBefore = levels.mLevelBefore; michael@0: levelAfter = levels.mLevelAfter; michael@0: } michael@0: michael@0: if ((levelBefore & 1) == (levelAfter & 1)) { michael@0: // if cursor is between two characters with the same orientation, changing the keyboard language michael@0: // must toggle the cursor level between the level of the character with the lowest level michael@0: // (if the new language corresponds to the orientation of that character) and this level plus 1 michael@0: // (if the new language corresponds to the opposite orientation) michael@0: if ((level != levelBefore) && (level != levelAfter)) michael@0: level = std::min(levelBefore, levelAfter); michael@0: if ((level & 1) == aLangRTL) michael@0: mFrameSelection->SetCaretBidiLevel(level); michael@0: else michael@0: mFrameSelection->SetCaretBidiLevel(level + 1); michael@0: } michael@0: else { michael@0: // if cursor is between characters with opposite orientations, changing the keyboard language must change michael@0: // the cursor level to that of the adjacent character with the orientation corresponding to the new language. michael@0: if ((levelBefore & 1) == aLangRTL) michael@0: mFrameSelection->SetCaretBidiLevel(levelBefore); michael@0: else michael@0: mFrameSelection->SetCaretBidiLevel(levelAfter); michael@0: } michael@0: michael@0: // The caret might have moved, so invalidate the desired X position michael@0: // for future usages of up-arrow or down-arrow michael@0: mFrameSelection->InvalidateDesiredX(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(nsDirection) michael@0: Selection::GetSelectionDirection() { michael@0: return mDirection; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: Selection::SetSelectionDirection(nsDirection aDirection) { michael@0: mDirection = aDirection; michael@0: } michael@0: michael@0: JSObject* michael@0: Selection::WrapObject(JSContext* aCx) michael@0: { michael@0: return mozilla::dom::SelectionBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: // nsAutoCopyListener michael@0: michael@0: nsAutoCopyListener* nsAutoCopyListener::sInstance = nullptr; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAutoCopyListener, nsISelectionListener) michael@0: michael@0: /* michael@0: * What we do now: michael@0: * On every selection change, we copy to the clipboard anew, creating a michael@0: * HTML buffer, a transferable, an nsISupportsString and michael@0: * a huge mess every time. This is basically what nsPresShell::DoCopy does michael@0: * to move the selection into the clipboard for Edit->Copy. michael@0: * michael@0: * What we should do, to make our end of the deal faster: michael@0: * Create a singleton transferable with our own magic converter. When selection michael@0: * changes (use a quick cache to detect ``real'' changes), we put the new michael@0: * nsISelection in the transferable. Our magic converter will take care of michael@0: * transferable->whatever-other-format when the time comes to actually michael@0: * hand over the clipboard contents. michael@0: * michael@0: * Other issues: michael@0: * - which X clipboard should we populate? michael@0: * - should we use a different one than Edit->Copy, so that inadvertant michael@0: * selections (or simple clicks, which currently cause a selection michael@0: * notification, regardless of if they're in the document which currently has michael@0: * selection!) don't lose the contents of the ``application''? Or should we michael@0: * just put some intelligence in the ``is this a real selection?'' code to michael@0: * protect our selection against clicks in other documents that don't create michael@0: * selections? michael@0: * - maybe we should just never clear the X clipboard? That would make this michael@0: * problem just go away, which is very tempting. michael@0: */ michael@0: michael@0: NS_IMETHODIMP michael@0: nsAutoCopyListener::NotifySelectionChanged(nsIDOMDocument *aDoc, michael@0: nsISelection *aSel, int16_t aReason) michael@0: { michael@0: if (!(aReason & nsISelectionListener::MOUSEUP_REASON || michael@0: aReason & nsISelectionListener::SELECTALL_REASON || michael@0: aReason & nsISelectionListener::KEYPRESS_REASON)) michael@0: return NS_OK; //dont care if we are still dragging michael@0: michael@0: bool collapsed; michael@0: if (!aDoc || !aSel || michael@0: NS_FAILED(aSel->GetIsCollapsed(&collapsed)) || collapsed) { michael@0: #ifdef DEBUG_CLIPBOARD michael@0: fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n"); michael@0: #endif michael@0: /* clear X clipboard? */ michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr doc = do_QueryInterface(aDoc); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); michael@0: michael@0: // call the copy code michael@0: return nsCopySupport::HTMLCopy(aSel, doc, nsIClipboard::kSelectionClipboard); michael@0: }