michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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: #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc michael@0: #include "mozilla/dom/Selection.h" // for Selection michael@0: #include "nsAString.h" // for nsAString_internal::Length michael@0: #include "nsAutoPtr.h" // for nsRefPtr, getter_AddRefs, etc michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "nsDebug.h" // for NS_ENSURE_TRUE, etc michael@0: #include "nsEditor.h" // for nsEditor michael@0: #include "nsEditorUtils.h" // for nsEditorUtils michael@0: #include "nsError.h" // for NS_OK, etc michael@0: #include "nsIDOMCharacterData.h" // for nsIDOMCharacterData michael@0: #include "nsIDOMNode.h" // for nsIDOMNode michael@0: #include "nsIDOMRange.h" // for nsIDOMRange, etc michael@0: #include "nsISelection.h" // for nsISelection michael@0: #include "nsISupportsImpl.h" // for nsRange::Release michael@0: #include "nsRange.h" // for nsRange michael@0: #include "nsSelectionState.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: /*************************************************************************** michael@0: * class for recording selection info. stores selection as collection of michael@0: * { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store michael@0: * ranges since dom gravity will possibly change the ranges. michael@0: */ michael@0: nsSelectionState::nsSelectionState() : mArray(){} michael@0: michael@0: nsSelectionState::~nsSelectionState() michael@0: { michael@0: MakeEmpty(); michael@0: } michael@0: michael@0: void michael@0: nsSelectionState::DoTraverse(nsCycleCollectionTraversalCallback &cb) michael@0: { michael@0: for (uint32_t i = 0, iEnd = mArray.Length(); i < iEnd; ++i) michael@0: { michael@0: nsRangeStore* item = mArray[i]; michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, michael@0: "selection state mArray[i].startNode"); michael@0: cb.NoteXPCOMChild(item->startNode); michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, michael@0: "selection state mArray[i].endNode"); michael@0: cb.NoteXPCOMChild(item->endNode); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSelectionState::SaveSelection(Selection* aSel) michael@0: { michael@0: MOZ_ASSERT(aSel); michael@0: int32_t arrayCount = mArray.Length(); michael@0: int32_t rangeCount = aSel->GetRangeCount(); michael@0: michael@0: // if we need more items in the array, new them michael@0: if (arrayCount < rangeCount) { michael@0: for (int32_t i = arrayCount; i < rangeCount; i++) { michael@0: mArray.AppendElement(); michael@0: mArray[i] = new nsRangeStore(); michael@0: } michael@0: } else if (arrayCount > rangeCount) { michael@0: // else if we have too many, delete them michael@0: for (int32_t i = arrayCount - 1; i >= rangeCount; i--) { michael@0: mArray.RemoveElementAt(i); michael@0: } michael@0: } michael@0: michael@0: // now store the selection ranges michael@0: for (int32_t i = 0; i < rangeCount; i++) { michael@0: mArray[i]->StoreRange(aSel->GetRangeAt(i)); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsSelectionState::RestoreSelection(nsISelection *aSel) michael@0: { michael@0: NS_ENSURE_TRUE(aSel, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: uint32_t i, arrayCount = mArray.Length(); michael@0: michael@0: // clear out selection michael@0: aSel->RemoveAllRanges(); michael@0: michael@0: // set the selection ranges anew michael@0: for (i=0; i range; michael@0: mArray[i]->GetRange(getter_AddRefs(range)); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_UNEXPECTED); michael@0: michael@0: res = aSel->AddRange(range); michael@0: if(NS_FAILED(res)) return res; michael@0: michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsSelectionState::IsCollapsed() michael@0: { michael@0: if (1 != mArray.Length()) return false; michael@0: nsRefPtr range; michael@0: mArray[0]->GetRange(getter_AddRefs(range)); michael@0: NS_ENSURE_TRUE(range, false); michael@0: bool bIsCollapsed = false; michael@0: range->GetCollapsed(&bIsCollapsed); michael@0: return bIsCollapsed; michael@0: } michael@0: michael@0: bool michael@0: nsSelectionState::IsEqual(nsSelectionState *aSelState) michael@0: { michael@0: NS_ENSURE_TRUE(aSelState, false); michael@0: uint32_t i, myCount = mArray.Length(), itsCount = aSelState->mArray.Length(); michael@0: if (myCount != itsCount) return false; michael@0: if (myCount < 1) return false; michael@0: michael@0: for (i=0; i myRange, itsRange; michael@0: mArray[i]->GetRange(getter_AddRefs(myRange)); michael@0: aSelState->mArray[i]->GetRange(getter_AddRefs(itsRange)); michael@0: NS_ENSURE_TRUE(myRange && itsRange, false); michael@0: michael@0: int16_t compResult; michael@0: nsresult rv; michael@0: rv = myRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, itsRange, &compResult); michael@0: if (NS_FAILED(rv) || compResult) return false; michael@0: rv = myRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END, itsRange, &compResult); michael@0: if (NS_FAILED(rv) || compResult) return false; michael@0: } michael@0: // if we got here, they are equal michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsSelectionState::MakeEmpty() michael@0: { michael@0: // free any items in the array michael@0: mArray.Clear(); michael@0: } michael@0: michael@0: bool michael@0: nsSelectionState::IsEmpty() michael@0: { michael@0: return mArray.IsEmpty(); michael@0: } michael@0: michael@0: /*************************************************************************** michael@0: * nsRangeUpdater: class for updating nsIDOMRanges in response to editor actions. michael@0: */ michael@0: michael@0: nsRangeUpdater::nsRangeUpdater() : mArray(), mLock(false) {} michael@0: michael@0: nsRangeUpdater::~nsRangeUpdater() michael@0: { michael@0: // nothing to do, we don't own the items in our array. michael@0: } michael@0: michael@0: void michael@0: nsRangeUpdater::RegisterRangeItem(nsRangeStore *aRangeItem) michael@0: { michael@0: if (!aRangeItem) return; michael@0: if (mArray.Contains(aRangeItem)) michael@0: { michael@0: NS_ERROR("tried to register an already registered range"); michael@0: return; // don't register it again. It would get doubly adjusted. michael@0: } michael@0: mArray.AppendElement(aRangeItem); michael@0: } michael@0: michael@0: void michael@0: nsRangeUpdater::DropRangeItem(nsRangeStore *aRangeItem) michael@0: { michael@0: if (!aRangeItem) return; michael@0: mArray.RemoveElement(aRangeItem); michael@0: } michael@0: michael@0: nsresult michael@0: nsRangeUpdater::RegisterSelectionState(nsSelectionState &aSelState) michael@0: { michael@0: uint32_t i, theCount = aSelState.mArray.Length(); michael@0: if (theCount < 1) return NS_ERROR_FAILURE; michael@0: michael@0: for (i=0; istartNode.get() == aParent) && (item->startOffset > aPosition)) michael@0: item->startOffset++; michael@0: if ((item->endNode.get() == aParent) && (item->endOffset > aPosition)) michael@0: item->endOffset++; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsRangeUpdater::SelAdjInsertNode(nsIDOMNode *aParent, int32_t aPosition) michael@0: { michael@0: return SelAdjCreateNode(aParent, aPosition); michael@0: } michael@0: michael@0: void michael@0: nsRangeUpdater::SelAdjDeleteNode(nsIDOMNode *aNode) michael@0: { michael@0: if (mLock) { michael@0: // lock set by Will/DidReplaceParent, etc... michael@0: return; michael@0: } michael@0: MOZ_ASSERT(aNode); michael@0: uint32_t i, count = mArray.Length(); michael@0: if (!count) { michael@0: return; michael@0: } michael@0: michael@0: int32_t offset = 0; michael@0: nsCOMPtr parent = nsEditor::GetNodeLocation(aNode, &offset); michael@0: michael@0: // check for range endpoints that are after aNode and in the same parent michael@0: nsRangeStore *item; michael@0: for (i=0; istartNode.get() == parent) && (item->startOffset > offset)) michael@0: item->startOffset--; michael@0: if ((item->endNode.get() == parent) && (item->endOffset > offset)) michael@0: item->endOffset--; michael@0: michael@0: // check for range endpoints that are in aNode michael@0: if (item->startNode == aNode) michael@0: { michael@0: item->startNode = parent; michael@0: item->startOffset = offset; michael@0: } michael@0: if (item->endNode == aNode) michael@0: { michael@0: item->endNode = parent; michael@0: item->endOffset = offset; michael@0: } michael@0: michael@0: // check for range endpoints that are in descendants of aNode michael@0: nsCOMPtr oldStart; michael@0: if (nsEditorUtils::IsDescendantOf(item->startNode, aNode)) michael@0: { michael@0: oldStart = item->startNode; // save for efficiency hack below. michael@0: item->startNode = parent; michael@0: item->startOffset = offset; michael@0: } michael@0: michael@0: // avoid having to call IsDescendantOf() for common case of range startnode == range endnode. michael@0: if ((item->endNode == oldStart) || nsEditorUtils::IsDescendantOf(item->endNode, aNode)) michael@0: { michael@0: item->endNode = parent; michael@0: item->endOffset = offset; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::SelAdjSplitNode(nsIDOMNode *aOldRightNode, int32_t aOffset, nsIDOMNode *aNewLeftNode) michael@0: { michael@0: if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc... michael@0: NS_ENSURE_TRUE(aOldRightNode && aNewLeftNode, NS_ERROR_NULL_POINTER); michael@0: uint32_t i, count = mArray.Length(); michael@0: if (!count) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t offset; michael@0: nsCOMPtr parent = nsEditor::GetNodeLocation(aOldRightNode, &offset); michael@0: michael@0: // first part is same as inserting aNewLeftnode michael@0: nsresult result = SelAdjInsertNode(parent,offset-1); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // next step is to check for range enpoints inside aOldRightNode michael@0: nsRangeStore *item; michael@0: michael@0: for (i=0; istartNode.get() == aOldRightNode) michael@0: { michael@0: if (item->startOffset > aOffset) michael@0: { michael@0: item->startOffset -= aOffset; michael@0: } michael@0: else michael@0: { michael@0: item->startNode = aNewLeftNode; michael@0: } michael@0: } michael@0: if (item->endNode.get() == aOldRightNode) michael@0: { michael@0: if (item->endOffset > aOffset) michael@0: { michael@0: item->endOffset -= aOffset; michael@0: } michael@0: else michael@0: { michael@0: item->endNode = aNewLeftNode; michael@0: } michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::SelAdjJoinNodes(nsIDOMNode *aLeftNode, michael@0: nsIDOMNode *aRightNode, michael@0: nsIDOMNode *aParent, michael@0: int32_t aOffset, michael@0: int32_t aOldLeftNodeLength) michael@0: { michael@0: if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc... michael@0: NS_ENSURE_TRUE(aLeftNode && aRightNode && aParent, NS_ERROR_NULL_POINTER); michael@0: uint32_t i, count = mArray.Length(); michael@0: if (!count) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRangeStore *item; michael@0: michael@0: for (i=0; istartNode.get() == aParent) michael@0: { michael@0: // adjust start point in aParent michael@0: if (item->startOffset > aOffset) michael@0: { michael@0: item->startOffset--; michael@0: } michael@0: else if (item->startOffset == aOffset) michael@0: { michael@0: // join keeps right hand node michael@0: item->startNode = aRightNode; michael@0: item->startOffset = aOldLeftNodeLength; michael@0: } michael@0: } michael@0: else if (item->startNode.get() == aRightNode) michael@0: { michael@0: // adjust start point in aRightNode michael@0: item->startOffset += aOldLeftNodeLength; michael@0: } michael@0: else if (item->startNode.get() == aLeftNode) michael@0: { michael@0: // adjust start point in aLeftNode michael@0: item->startNode = aRightNode; michael@0: } michael@0: michael@0: if (item->endNode.get() == aParent) michael@0: { michael@0: // adjust end point in aParent michael@0: if (item->endOffset > aOffset) michael@0: { michael@0: item->endOffset--; michael@0: } michael@0: else if (item->endOffset == aOffset) michael@0: { michael@0: // join keeps right hand node michael@0: item->endNode = aRightNode; michael@0: item->endOffset = aOldLeftNodeLength; michael@0: } michael@0: } michael@0: else if (item->endNode.get() == aRightNode) michael@0: { michael@0: // adjust end point in aRightNode michael@0: item->endOffset += aOldLeftNodeLength; michael@0: } michael@0: else if (item->endNode.get() == aLeftNode) michael@0: { michael@0: // adjust end point in aLeftNode michael@0: item->endNode = aRightNode; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::SelAdjInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString) michael@0: { michael@0: if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc... michael@0: michael@0: uint32_t count = mArray.Length(); michael@0: if (!count) { michael@0: return NS_OK; michael@0: } michael@0: nsCOMPtr node(do_QueryInterface(aTextNode)); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: uint32_t len=aString.Length(), i; michael@0: nsRangeStore *item; michael@0: for (i=0; istartNode.get() == node) && (item->startOffset > aOffset)) michael@0: item->startOffset += len; michael@0: if ((item->endNode.get() == node) && (item->endOffset > aOffset)) michael@0: item->endOffset += len; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::SelAdjDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) michael@0: { michael@0: if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc... michael@0: michael@0: uint32_t i, count = mArray.Length(); michael@0: if (!count) { michael@0: return NS_OK; michael@0: } michael@0: nsRangeStore *item; michael@0: nsCOMPtr node(do_QueryInterface(aTextNode)); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: for (i=0; istartNode.get() == node) && (item->startOffset > aOffset)) michael@0: { michael@0: item->startOffset -= aLength; michael@0: if (item->startOffset < 0) item->startOffset = 0; michael@0: } michael@0: if ((item->endNode.get() == node) && (item->endOffset > aOffset)) michael@0: { michael@0: item->endOffset -= aLength; michael@0: if (item->endOffset < 0) item->endOffset = 0; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::WillReplaceContainer() michael@0: { michael@0: if (mLock) return NS_ERROR_UNEXPECTED; michael@0: mLock = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::DidReplaceContainer(nsIDOMNode *aOriginalNode, nsIDOMNode *aNewNode) michael@0: { michael@0: NS_ENSURE_TRUE(mLock, NS_ERROR_UNEXPECTED); michael@0: mLock = false; michael@0: michael@0: NS_ENSURE_TRUE(aOriginalNode && aNewNode, NS_ERROR_NULL_POINTER); michael@0: uint32_t i, count = mArray.Length(); michael@0: if (!count) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRangeStore *item; michael@0: michael@0: for (i=0; istartNode.get() == aOriginalNode) michael@0: item->startNode = aNewNode; michael@0: if (item->endNode.get() == aOriginalNode) michael@0: item->endNode = aNewNode; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::WillRemoveContainer() michael@0: { michael@0: if (mLock) return NS_ERROR_UNEXPECTED; michael@0: mLock = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aOffset, uint32_t aNodeOrigLen) michael@0: { michael@0: NS_ENSURE_TRUE(mLock, NS_ERROR_UNEXPECTED); michael@0: mLock = false; michael@0: michael@0: NS_ENSURE_TRUE(aNode && aParent, NS_ERROR_NULL_POINTER); michael@0: uint32_t i, count = mArray.Length(); michael@0: if (!count) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRangeStore *item; michael@0: michael@0: for (i=0; istartNode.get() == aNode) michael@0: { michael@0: item->startNode = aParent; michael@0: item->startOffset += aOffset; michael@0: } michael@0: else if ((item->startNode.get() == aParent) && (item->startOffset > aOffset)) michael@0: item->startOffset += (int32_t)aNodeOrigLen-1; michael@0: michael@0: if (item->endNode.get() == aNode) michael@0: { michael@0: item->endNode = aParent; michael@0: item->endOffset += aOffset; michael@0: } michael@0: else if ((item->endNode.get() == aParent) && (item->endOffset > aOffset)) michael@0: item->endOffset += (int32_t)aNodeOrigLen-1; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::WillInsertContainer() michael@0: { michael@0: if (mLock) return NS_ERROR_UNEXPECTED; michael@0: mLock = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsRangeUpdater::DidInsertContainer() michael@0: { michael@0: NS_ENSURE_TRUE(mLock, NS_ERROR_UNEXPECTED); michael@0: mLock = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsRangeUpdater::WillMoveNode() michael@0: { michael@0: mLock = true; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsRangeUpdater::DidMoveNode(nsINode* aOldParent, int32_t aOldOffset, michael@0: nsINode* aNewParent, int32_t aNewOffset) michael@0: { michael@0: MOZ_ASSERT(aOldParent); michael@0: MOZ_ASSERT(aNewParent); michael@0: NS_ENSURE_TRUE_VOID(mLock); michael@0: mLock = false; michael@0: michael@0: nsIDOMNode* oldParent = aOldParent->AsDOMNode(); michael@0: nsIDOMNode* newParent = aNewParent->AsDOMNode(); michael@0: michael@0: for (uint32_t i = 0, count = mArray.Length(); i < count; ++i) { michael@0: nsRangeStore* item = mArray[i]; michael@0: NS_ENSURE_TRUE_VOID(item); michael@0: michael@0: // like a delete in aOldParent michael@0: if (item->startNode == oldParent && item->startOffset > aOldOffset) { michael@0: item->startOffset--; michael@0: } michael@0: if (item->endNode == oldParent && item->endOffset > aOldOffset) { michael@0: item->endOffset--; michael@0: } michael@0: michael@0: // and like an insert in aNewParent michael@0: if (item->startNode == newParent && item->startOffset > aNewOffset) { michael@0: item->startOffset++; michael@0: } michael@0: if (item->endNode == newParent && item->endOffset > aNewOffset) { michael@0: item->endOffset++; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: michael@0: /*************************************************************************** michael@0: * helper class for nsSelectionState. nsRangeStore stores range endpoints. michael@0: */ michael@0: michael@0: // DEBUG: int32_t nsRangeStore::n = 0; michael@0: michael@0: nsRangeStore::nsRangeStore() michael@0: { michael@0: // DEBUG: n++; printf("range store alloc count=%d\n", n); michael@0: } michael@0: nsRangeStore::~nsRangeStore() michael@0: { michael@0: // DEBUG: n--; printf("range store alloc count=%d\n", n); michael@0: } michael@0: michael@0: nsresult nsRangeStore::StoreRange(nsIDOMRange *aRange) michael@0: { michael@0: NS_ENSURE_TRUE(aRange, NS_ERROR_NULL_POINTER); michael@0: aRange->GetStartContainer(getter_AddRefs(startNode)); michael@0: aRange->GetEndContainer(getter_AddRefs(endNode)); michael@0: aRange->GetStartOffset(&startOffset); michael@0: aRange->GetEndOffset(&endOffset); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsRangeStore::GetRange(nsRange** outRange) michael@0: { michael@0: return nsRange::CreateRange(startNode, startOffset, endNode, endOffset, michael@0: outRange); michael@0: }