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 // for nullptr michael@0: michael@0: #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc michael@0: #include "mozilla/mozalloc.h" // for operator new, etc michael@0: #include "nsAString.h" // for nsAString_internal::Length, etc michael@0: #include "nsAutoPtr.h" // for nsRefPtr michael@0: #include "nsContentUtils.h" // for nsContentUtils michael@0: #include "nsDebug.h" // for NS_ENSURE_TRUE, etc michael@0: #include "nsDependentSubstring.h" // for Substring michael@0: #include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc michael@0: #include "nsFilteredContentIterator.h" // for nsFilteredContentIterator michael@0: #include "nsIContent.h" // for nsIContent, etc michael@0: #include "nsIContentIterator.h" // for nsIContentIterator michael@0: #include "nsID.h" // for NS_GET_IID michael@0: #include "nsIDOMDocument.h" // for nsIDOMDocument michael@0: #include "nsIDOMElement.h" // for nsIDOMElement michael@0: #include "nsIDOMHTMLDocument.h" // for nsIDOMHTMLDocument michael@0: #include "nsIDOMHTMLElement.h" // for nsIDOMHTMLElement michael@0: #include "nsIDOMNode.h" // for nsIDOMNode, etc michael@0: #include "nsIDOMRange.h" // for nsIDOMRange, etc michael@0: #include "nsIEditor.h" // for nsIEditor, etc michael@0: #include "nsINode.h" // for nsINode michael@0: #include "nsIPlaintextEditor.h" // for nsIPlaintextEditor michael@0: #include "nsISelection.h" // for nsISelection michael@0: #include "nsISelectionController.h" // for nsISelectionController, etc michael@0: #include "nsISupportsBase.h" // for nsISupports michael@0: #include "nsISupportsUtils.h" // for NS_IF_ADDREF, NS_ADDREF, etc michael@0: #include "nsITextServicesFilter.h" // for nsITextServicesFilter michael@0: #include "nsIWordBreaker.h" // for nsWordRange, nsIWordBreaker michael@0: #include "nsRange.h" // for nsRange michael@0: #include "nsStaticAtom.h" // for NS_STATIC_ATOM, etc michael@0: #include "nsString.h" // for nsString, nsAutoString michael@0: #include "nsTextServicesDocument.h" michael@0: #include "nscore.h" // for nsresult, NS_IMETHODIMP, etc michael@0: michael@0: #define LOCK_DOC(doc) michael@0: #define UNLOCK_DOC(doc) michael@0: michael@0: using namespace mozilla; michael@0: michael@0: class OffsetEntry michael@0: { michael@0: public: michael@0: OffsetEntry(nsIDOMNode *aNode, int32_t aOffset, int32_t aLength) michael@0: : mNode(aNode), mNodeOffset(0), mStrOffset(aOffset), mLength(aLength), michael@0: mIsInsertedText(false), mIsValid(true) michael@0: { michael@0: if (mStrOffset < 1) michael@0: mStrOffset = 0; michael@0: michael@0: if (mLength < 1) michael@0: mLength = 0; michael@0: } michael@0: michael@0: virtual ~OffsetEntry() michael@0: { michael@0: mNode = 0; michael@0: mNodeOffset = 0; michael@0: mStrOffset = 0; michael@0: mLength = 0; michael@0: mIsValid = false; michael@0: } michael@0: michael@0: nsIDOMNode *mNode; michael@0: int32_t mNodeOffset; michael@0: int32_t mStrOffset; michael@0: int32_t mLength; michael@0: bool mIsInsertedText; michael@0: bool mIsValid; michael@0: }; michael@0: michael@0: #define TS_ATOM(name_, value_) nsIAtom* nsTextServicesDocument::name_ = 0; michael@0: #include "nsTSAtomList.h" // IWYU pragma: keep michael@0: #undef TS_ATOM michael@0: michael@0: nsTextServicesDocument::nsTextServicesDocument() michael@0: { michael@0: mRefCnt = 0; michael@0: michael@0: mSelStartIndex = -1; michael@0: mSelStartOffset = -1; michael@0: mSelEndIndex = -1; michael@0: mSelEndOffset = -1; michael@0: michael@0: mIteratorStatus = eIsDone; michael@0: } michael@0: michael@0: nsTextServicesDocument::~nsTextServicesDocument() michael@0: { michael@0: ClearOffsetTable(&mOffsetTable); michael@0: } michael@0: michael@0: #define TS_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_) michael@0: #include "nsTSAtomList.h" // IWYU pragma: keep michael@0: #undef TS_ATOM michael@0: michael@0: /* static */ michael@0: void michael@0: nsTextServicesDocument::RegisterAtoms() michael@0: { michael@0: static const nsStaticAtom ts_atoms[] = { michael@0: #define TS_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &name_), michael@0: #include "nsTSAtomList.h" // IWYU pragma: keep michael@0: #undef TS_ATOM michael@0: }; michael@0: michael@0: NS_RegisterStaticAtoms(ts_atoms); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextServicesDocument) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextServicesDocument) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsTextServicesDocument) michael@0: NS_INTERFACE_MAP_ENTRY(nsITextServicesDocument) michael@0: NS_INTERFACE_MAP_ENTRY(nsIEditActionListener) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITextServicesDocument) michael@0: NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsTextServicesDocument) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsTextServicesDocument, michael@0: mDOMDocument, michael@0: mSelCon, michael@0: mIterator, michael@0: mPrevTextBlock, michael@0: mNextTextBlock, michael@0: mExtent, michael@0: mTxtSvcFilter) michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::InitWithEditor(nsIEditor *aEditor) michael@0: { michael@0: nsresult result = NS_OK; michael@0: nsCOMPtr selCon; michael@0: nsCOMPtr doc; michael@0: michael@0: NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: // Check to see if we already have an mSelCon. If we do, it michael@0: // better be the same one the editor uses! michael@0: michael@0: result = aEditor->GetSelectionController(getter_AddRefs(selCon)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (!selCon || (mSelCon && selCon != mSelCon)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mSelCon) michael@0: mSelCon = selCon; michael@0: michael@0: // Check to see if we already have an mDOMDocument. If we do, it michael@0: // better be the same one the editor uses! michael@0: michael@0: result = aEditor->GetDocument(getter_AddRefs(doc)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (!doc || (mDOMDocument && doc != mDOMDocument)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mDOMDocument) michael@0: { michael@0: mDOMDocument = doc; michael@0: michael@0: result = CreateDocumentContentIterator(getter_AddRefs(mIterator)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: michael@0: result = FirstBlock(); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: mEditor = do_GetWeakReference(aEditor); michael@0: michael@0: result = aEditor->AddEditActionListener(this); michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::GetDocument(nsIDOMDocument **aDoc) michael@0: { michael@0: NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aDoc = nullptr; // init out param michael@0: NS_ENSURE_TRUE(mDOMDocument, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: *aDoc = mDOMDocument; michael@0: NS_ADDREF(*aDoc); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::SetExtent(nsIDOMRange* aDOMRange) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aDOMRange); michael@0: NS_ENSURE_TRUE(mDOMDocument, NS_ERROR_FAILURE); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: // We need to store a copy of aDOMRange since we don't michael@0: // know where it came from. michael@0: michael@0: nsresult result = aDOMRange->CloneRange(getter_AddRefs(mExtent)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: // Create a new iterator based on our new extent range. michael@0: michael@0: result = CreateContentIterator(mExtent, getter_AddRefs(mIterator)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: // Now position the iterator at the start of the first block michael@0: // in the range. michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: michael@0: result = FirstBlock(); michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::ExpandRangeToWordBoundaries(nsIDOMRange *aRange) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aRange); michael@0: michael@0: // Get the end points of the range. michael@0: michael@0: nsCOMPtr rngStartNode, rngEndNode; michael@0: int32_t rngStartOffset, rngEndOffset; michael@0: michael@0: nsresult result = GetRangeEndPoints(aRange, michael@0: getter_AddRefs(rngStartNode), michael@0: &rngStartOffset, michael@0: getter_AddRefs(rngEndNode), michael@0: &rngEndOffset); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // Create a content iterator based on the range. michael@0: michael@0: nsCOMPtr iter; michael@0: result = CreateContentIterator(aRange, getter_AddRefs(iter)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // Find the first text node in the range. michael@0: michael@0: TSDIteratorStatus iterStatus; michael@0: michael@0: result = FirstTextNode(iter, &iterStatus); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: if (iterStatus == nsTextServicesDocument::eIsDone) michael@0: { michael@0: // No text was found so there's no adjustment necessary! michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsINode *firstText = iter->GetCurrentNode(); michael@0: NS_ENSURE_TRUE(firstText, NS_ERROR_FAILURE); michael@0: michael@0: // Find the last text node in the range. michael@0: michael@0: result = LastTextNode(iter, &iterStatus); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: if (iterStatus == nsTextServicesDocument::eIsDone) michael@0: { michael@0: // We should never get here because a first text block michael@0: // was found above. michael@0: NS_ASSERTION(false, "Found a first without a last!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsINode *lastText = iter->GetCurrentNode(); michael@0: NS_ENSURE_TRUE(lastText, NS_ERROR_FAILURE); michael@0: michael@0: // Now make sure our end points are in terms of text nodes in the range! michael@0: michael@0: nsCOMPtr firstTextNode = do_QueryInterface(firstText); michael@0: NS_ENSURE_TRUE(firstTextNode, NS_ERROR_FAILURE); michael@0: michael@0: if (rngStartNode != firstTextNode) michael@0: { michael@0: // The range includes the start of the first text node! michael@0: rngStartNode = firstTextNode; michael@0: rngStartOffset = 0; michael@0: } michael@0: michael@0: nsCOMPtr lastTextNode = do_QueryInterface(lastText); michael@0: NS_ENSURE_TRUE(lastTextNode, NS_ERROR_FAILURE); michael@0: michael@0: if (rngEndNode != lastTextNode) michael@0: { michael@0: // The range includes the end of the last text node! michael@0: rngEndNode = lastTextNode; michael@0: nsAutoString str; michael@0: result = lastTextNode->GetNodeValue(str); michael@0: rngEndOffset = str.Length(); michael@0: } michael@0: michael@0: // Create a doc iterator so that we can scan beyond michael@0: // the bounds of the extent range. michael@0: michael@0: nsCOMPtr docIter; michael@0: result = CreateDocumentContentIterator(getter_AddRefs(docIter)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // Grab all the text in the block containing our michael@0: // first text node. michael@0: michael@0: result = docIter->PositionAt(firstText); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: iterStatus = nsTextServicesDocument::eValid; michael@0: michael@0: nsTArray offsetTable; michael@0: nsAutoString blockStr; michael@0: michael@0: result = CreateOffsetTable(&offsetTable, docIter, &iterStatus, michael@0: nullptr, &blockStr); michael@0: if (NS_FAILED(result)) michael@0: { michael@0: ClearOffsetTable(&offsetTable); michael@0: return result; michael@0: } michael@0: michael@0: nsCOMPtr wordStartNode, wordEndNode; michael@0: int32_t wordStartOffset, wordEndOffset; michael@0: michael@0: result = FindWordBounds(&offsetTable, &blockStr, michael@0: rngStartNode, rngStartOffset, michael@0: getter_AddRefs(wordStartNode), &wordStartOffset, michael@0: getter_AddRefs(wordEndNode), &wordEndOffset); michael@0: michael@0: ClearOffsetTable(&offsetTable); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: rngStartNode = wordStartNode; michael@0: rngStartOffset = wordStartOffset; michael@0: michael@0: // Grab all the text in the block containing our michael@0: // last text node. michael@0: michael@0: result = docIter->PositionAt(lastText); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: iterStatus = nsTextServicesDocument::eValid; michael@0: michael@0: result = CreateOffsetTable(&offsetTable, docIter, &iterStatus, michael@0: nullptr, &blockStr); michael@0: if (NS_FAILED(result)) michael@0: { michael@0: ClearOffsetTable(&offsetTable); michael@0: return result; michael@0: } michael@0: michael@0: result = FindWordBounds(&offsetTable, &blockStr, michael@0: rngEndNode, rngEndOffset, michael@0: getter_AddRefs(wordStartNode), &wordStartOffset, michael@0: getter_AddRefs(wordEndNode), &wordEndOffset); michael@0: michael@0: ClearOffsetTable(&offsetTable); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // To prevent expanding the range too much, we only change michael@0: // rngEndNode and rngEndOffset if it isn't already at the start of the michael@0: // word and isn't equivalent to rngStartNode and rngStartOffset. michael@0: michael@0: if (rngEndNode != wordStartNode || rngEndOffset != wordStartOffset || michael@0: (rngEndNode == rngStartNode && rngEndOffset == rngStartOffset)) michael@0: { michael@0: rngEndNode = wordEndNode; michael@0: rngEndOffset = wordEndOffset; michael@0: } michael@0: michael@0: // Now adjust the range so that it uses our new michael@0: // end points. michael@0: michael@0: result = aRange->SetEnd(rngEndNode, rngEndOffset); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: return aRange->SetStart(rngStartNode, rngStartOffset); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::SetFilter(nsITextServicesFilter *aFilter) michael@0: { michael@0: // Hang on to the filter so we can set it into the filtered iterator. michael@0: mTxtSvcFilter = aFilter; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::GetCurrentTextBlock(nsString *aStr) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aStr, NS_ERROR_NULL_POINTER); michael@0: michael@0: aStr->Truncate(); michael@0: michael@0: NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: result = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, michael@0: mExtent, aStr); michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::FirstBlock() michael@0: { michael@0: NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: nsresult result = FirstTextNode(mIterator, &mIteratorStatus); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: // Keep track of prev and next blocks, just in case michael@0: // the text service blows away the current block. michael@0: michael@0: if (mIteratorStatus == nsTextServicesDocument::eValid) michael@0: { michael@0: mPrevTextBlock = nullptr; michael@0: result = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); michael@0: } michael@0: else michael@0: { michael@0: // There's no text block in the document! michael@0: michael@0: mPrevTextBlock = nullptr; michael@0: mNextTextBlock = nullptr; michael@0: } michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::LastSelectedBlock(TSDBlockSelectionStatus *aSelStatus, michael@0: int32_t *aSelOffset, michael@0: int32_t *aSelLength) michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: michael@0: *aSelStatus = nsITextServicesDocument::eBlockNotFound; michael@0: *aSelOffset = *aSelLength = -1; michael@0: michael@0: if (!mSelCon || !mIterator) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr selection; michael@0: bool isCollapsed = false; michael@0: michael@0: result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = selection->GetIsCollapsed(&isCollapsed); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: nsCOMPtr iter; michael@0: nsCOMPtr range; michael@0: nsCOMPtr parent; michael@0: int32_t i, rangeCount, offset; michael@0: michael@0: if (isCollapsed) michael@0: { michael@0: // We have a caret. Check if the caret is in a text node. michael@0: // If it is, make the text node's block the current block. michael@0: // If the caret isn't in a text node, search forwards in michael@0: // the document, till we find a text node. michael@0: michael@0: result = selection->GetRangeAt(0, getter_AddRefs(range)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (!range) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: result = range->GetStartContainer(getter_AddRefs(parent)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (!parent) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: result = range->GetStartOffset(&offset); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (IsTextNode(parent)) michael@0: { michael@0: // The caret is in a text node. Find the beginning michael@0: // of the text block containing this text node and michael@0: // return. michael@0: michael@0: nsCOMPtr content(do_QueryInterface(parent)); michael@0: michael@0: if (!content) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: result = mIterator->PositionAt(content); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = FirstTextNodeInCurrentBlock(mIterator); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eValid; michael@0: michael@0: result = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, michael@0: mExtent, nullptr); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = GetSelection(aSelStatus, aSelOffset, aSelLength); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (*aSelStatus == nsITextServicesDocument::eBlockContains) michael@0: result = SetSelectionInternal(*aSelOffset, *aSelLength, false); michael@0: } michael@0: else michael@0: { michael@0: // The caret isn't in a text node. Create an iterator michael@0: // based on a range that extends from the current caret michael@0: // position to the end of the document, then walk forwards michael@0: // till you find a text node, then find the beginning of it's block. michael@0: michael@0: result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, false, getter_AddRefs(range)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = range->GetCollapsed(&isCollapsed); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (isCollapsed) michael@0: { michael@0: // If we get here, the range is collapsed because there is nothing after michael@0: // the caret! Just return NS_OK; michael@0: michael@0: UNLOCK_DOC(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = CreateContentIterator(range, getter_AddRefs(iter)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: iter->First(); michael@0: michael@0: nsCOMPtr content; michael@0: while (!iter->IsDone()) michael@0: { michael@0: content = do_QueryInterface(iter->GetCurrentNode()); michael@0: michael@0: if (IsTextNode(content)) michael@0: break; michael@0: michael@0: content = nullptr; michael@0: michael@0: iter->Next(); michael@0: } michael@0: michael@0: if (!content) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = mIterator->PositionAt(content); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = FirstTextNodeInCurrentBlock(mIterator); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eValid; michael@0: michael@0: result = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, michael@0: mExtent, nullptr); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = GetSelection(aSelStatus, aSelOffset, aSelLength); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: // If we get here, we have an uncollapsed selection! michael@0: // Look backwards through each range in the selection till you michael@0: // find the first text node. If you find one, find the michael@0: // beginning of its text block, and make it the current michael@0: // block. michael@0: michael@0: result = selection->GetRangeCount(&rangeCount); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: NS_ASSERTION(rangeCount > 0, "Unexpected range count!"); michael@0: michael@0: if (rangeCount <= 0) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXX: We may need to add some code here to make sure michael@0: // the ranges are sorted in document appearance order! michael@0: michael@0: for (i = rangeCount - 1; i >= 0; i--) michael@0: { michael@0: // Get the i'th range from the selection. michael@0: michael@0: result = selection->GetRangeAt(i, getter_AddRefs(range)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: // Create an iterator for the range. michael@0: michael@0: result = CreateContentIterator(range, getter_AddRefs(iter)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: iter->Last(); michael@0: michael@0: // Now walk through the range till we find a text node. michael@0: michael@0: while (!iter->IsDone()) michael@0: { michael@0: if (iter->GetCurrentNode()->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: // We found a text node, so position the document's michael@0: // iterator at the beginning of the block, then get michael@0: // the selection in terms of the string offset. michael@0: nsCOMPtr content = iter->GetCurrentNode()->AsContent(); michael@0: michael@0: result = mIterator->PositionAt(content); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = FirstTextNodeInCurrentBlock(mIterator); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eValid; michael@0: michael@0: result = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, michael@0: mExtent, nullptr); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = GetSelection(aSelStatus, aSelOffset, aSelLength); michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: michael@0: } michael@0: michael@0: iter->Prev(); michael@0: } michael@0: } michael@0: michael@0: // If we get here, we didn't find any text node in the selection! michael@0: // Create a range that extends from the end of the selection, michael@0: // to the end of the document, then iterate forwards through michael@0: // it till you find a text node! michael@0: michael@0: result = selection->GetRangeAt(rangeCount - 1, getter_AddRefs(range)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (!range) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: result = range->GetEndContainer(getter_AddRefs(parent)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (!parent) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: result = range->GetEndOffset(&offset); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, false, getter_AddRefs(range)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = range->GetCollapsed(&isCollapsed); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (isCollapsed) michael@0: { michael@0: // If we get here, the range is collapsed because there is nothing after michael@0: // the current selection! Just return NS_OK; michael@0: michael@0: UNLOCK_DOC(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = CreateContentIterator(range, getter_AddRefs(iter)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: iter->First(); michael@0: michael@0: while (!iter->IsDone()) michael@0: { michael@0: if (iter->GetCurrentNode()->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: // We found a text node! Adjust the document's iterator to point michael@0: // to the beginning of its text block, then get the current selection. michael@0: nsCOMPtr content = iter->GetCurrentNode()->AsContent(); michael@0: michael@0: result = mIterator->PositionAt(content); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = FirstTextNodeInCurrentBlock(mIterator); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eValid; michael@0: michael@0: result = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, michael@0: mExtent, nullptr); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = GetSelection(aSelStatus, aSelOffset, aSelLength); michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: iter->Next(); michael@0: } michael@0: michael@0: // If we get here, we didn't find any block before or inside michael@0: // the selection! Just return OK. michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::PrevBlock() michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: if (mIteratorStatus == nsTextServicesDocument::eIsDone) michael@0: return NS_OK; michael@0: michael@0: switch (mIteratorStatus) michael@0: { michael@0: case nsTextServicesDocument::eValid: michael@0: case nsTextServicesDocument::eNext: michael@0: michael@0: result = FirstTextNodeInPrevBlock(mIterator); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: mIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (mIterator->IsDone()) michael@0: { michael@0: mIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: UNLOCK_DOC(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eValid; michael@0: break; michael@0: michael@0: case nsTextServicesDocument::ePrev: michael@0: michael@0: // The iterator already points to the previous michael@0: // block, so don't do anything. michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eValid; michael@0: break; michael@0: michael@0: default: michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: break; michael@0: } michael@0: michael@0: // Keep track of prev and next blocks, just in case michael@0: // the text service blows away the current block. michael@0: michael@0: if (mIteratorStatus == nsTextServicesDocument::eValid) michael@0: { michael@0: result = GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock)); michael@0: result = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); michael@0: } michael@0: else michael@0: { michael@0: // We must be done! michael@0: michael@0: mPrevTextBlock = nullptr; michael@0: mNextTextBlock = nullptr; michael@0: } michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::NextBlock() michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: if (mIteratorStatus == nsTextServicesDocument::eIsDone) michael@0: return NS_OK; michael@0: michael@0: switch (mIteratorStatus) michael@0: { michael@0: case nsTextServicesDocument::eValid: michael@0: michael@0: // Advance the iterator to the next text block. michael@0: michael@0: result = FirstTextNodeInNextBlock(mIterator); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: mIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (mIterator->IsDone()) michael@0: { michael@0: mIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: UNLOCK_DOC(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eValid; michael@0: break; michael@0: michael@0: case nsTextServicesDocument::eNext: michael@0: michael@0: // The iterator already points to the next block, michael@0: // so don't do anything to it! michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eValid; michael@0: break; michael@0: michael@0: case nsTextServicesDocument::ePrev: michael@0: michael@0: // If the iterator is pointing to the previous block, michael@0: // we know that there is no next text block! Just michael@0: // fall through to the default case! michael@0: michael@0: default: michael@0: michael@0: mIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: break; michael@0: } michael@0: michael@0: // Keep track of prev and next blocks, just in case michael@0: // the text service blows away the current block. michael@0: michael@0: if (mIteratorStatus == nsTextServicesDocument::eValid) michael@0: { michael@0: result = GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock)); michael@0: result = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); michael@0: } michael@0: else michael@0: { michael@0: // We must be done. michael@0: michael@0: mPrevTextBlock = nullptr; michael@0: mNextTextBlock = nullptr; michael@0: } michael@0: michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::IsDone(bool *aIsDone) michael@0: { michael@0: NS_ENSURE_TRUE(aIsDone, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aIsDone = false; michael@0: michael@0: NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: *aIsDone = (mIteratorStatus == nsTextServicesDocument::eIsDone) ? true : false; michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::SetSelection(int32_t aOffset, int32_t aLength) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(mSelCon && aOffset >= 0 && aLength >= 0, NS_ERROR_FAILURE); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: result = SetSelectionInternal(aOffset, aLength, true); michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("\n * Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); michael@0: //**** KDEBUG **** michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::ScrollSelectionIntoView() michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE); michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: // After ScrollSelectionIntoView(), the pending notifications might be flushed michael@0: // and PresShell/PresContext/Frames may be dead. See bug 418470. michael@0: result = mSelCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_FOCUS_REGION, michael@0: nsISelectionController::SCROLL_SYNCHRONOUS); michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::DeleteSelection() michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: // We don't allow deletion during a collapsed selection! michael@0: nsCOMPtr editor (do_QueryReferent(mEditor)); michael@0: NS_ASSERTION(editor, "DeleteSelection called without an editor present!"); michael@0: NS_ASSERTION(SelectionIsValid(), "DeleteSelection called without a valid selection!"); michael@0: michael@0: if (!editor || !SelectionIsValid()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (SelectionIsCollapsed()) michael@0: return NS_OK; michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("\n---- Before Delete\n"); michael@0: // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); michael@0: // PrintOffsetTable(); michael@0: //**** KDEBUG **** michael@0: michael@0: // If we have an mExtent, save off its current set of michael@0: // end points so we can compare them against mExtent's michael@0: // set after the deletion of the content. michael@0: michael@0: nsCOMPtr origStartNode, origEndNode; michael@0: int32_t origStartOffset = 0, origEndOffset = 0; michael@0: michael@0: if (mExtent) michael@0: { michael@0: result = GetRangeEndPoints(mExtent, michael@0: getter_AddRefs(origStartNode), &origStartOffset, michael@0: getter_AddRefs(origEndNode), &origEndOffset); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: int32_t i, selLength; michael@0: OffsetEntry *entry, *newEntry; michael@0: michael@0: for (i = mSelStartIndex; i <= mSelEndIndex; i++) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: if (i == mSelStartIndex) michael@0: { michael@0: // Calculate the length of the selection. Note that the michael@0: // selection length can be zero if the start of the selection michael@0: // is at the very end of a text node entry. michael@0: michael@0: if (entry->mIsInsertedText) michael@0: { michael@0: // Inserted text offset entries have no width when michael@0: // talking in terms of string offsets! If the beginning michael@0: // of the selection is in an inserted text offset entry, michael@0: // the caret is always at the end of the entry! michael@0: michael@0: selLength = 0; michael@0: } michael@0: else michael@0: selLength = entry->mLength - (mSelStartOffset - entry->mStrOffset); michael@0: michael@0: if (selLength > 0 && mSelStartOffset > entry->mStrOffset) michael@0: { michael@0: // Selection doesn't start at the beginning of the michael@0: // text node entry. We need to split this entry into michael@0: // two pieces, the piece before the selection, and michael@0: // the piece inside the selection. michael@0: michael@0: result = SplitOffsetEntry(i, selLength); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: // Adjust selection indexes to account for new entry: michael@0: michael@0: ++mSelStartIndex; michael@0: ++mSelEndIndex; michael@0: ++i; michael@0: michael@0: entry = mOffsetTable[i]; michael@0: } michael@0: michael@0: michael@0: if (selLength > 0 && mSelStartIndex < mSelEndIndex) michael@0: { michael@0: // The entire entry is contained in the selection. Mark the michael@0: // entry invalid. michael@0: michael@0: entry->mIsValid = false; michael@0: } michael@0: } michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("\n---- Middle Delete\n"); michael@0: // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); michael@0: // PrintOffsetTable(); michael@0: //**** KDEBUG **** michael@0: michael@0: if (i == mSelEndIndex) michael@0: { michael@0: if (entry->mIsInsertedText) michael@0: { michael@0: // Inserted text offset entries have no width when michael@0: // talking in terms of string offsets! If the end michael@0: // of the selection is in an inserted text offset entry, michael@0: // the selection includes the entire entry! michael@0: michael@0: entry->mIsValid = false; michael@0: } michael@0: else michael@0: { michael@0: // Calculate the length of the selection. Note that the michael@0: // selection length can be zero if the end of the selection michael@0: // is at the very beginning of a text node entry. michael@0: michael@0: selLength = mSelEndOffset - entry->mStrOffset; michael@0: michael@0: if (selLength > 0 && mSelEndOffset < entry->mStrOffset + entry->mLength) michael@0: { michael@0: // mStrOffset is guaranteed to be inside the selection, even michael@0: // when mSelStartIndex == mSelEndIndex. michael@0: michael@0: result = SplitOffsetEntry(i, entry->mLength - selLength); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: // Update the entry fields: michael@0: michael@0: newEntry = mOffsetTable[i+1]; michael@0: newEntry->mNodeOffset = entry->mNodeOffset; michael@0: } michael@0: michael@0: michael@0: if (selLength > 0 && mSelEndOffset == entry->mStrOffset + entry->mLength) michael@0: { michael@0: // The entire entry is contained in the selection. Mark the michael@0: // entry invalid. michael@0: michael@0: entry->mIsValid = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (i != mSelStartIndex && i != mSelEndIndex) michael@0: { michael@0: // The entire entry is contained in the selection. Mark the michael@0: // entry invalid. michael@0: michael@0: entry->mIsValid = false; michael@0: } michael@0: } michael@0: michael@0: // Make sure mIterator always points to something valid! michael@0: michael@0: AdjustContentIterator(); michael@0: michael@0: // Now delete the actual content! michael@0: michael@0: result = editor->DeleteSelection(nsIEditor::ePrevious, nsIEditor::eStrip); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: // Now that we've actually deleted the selected content, michael@0: // check to see if our mExtent has changed, if so, then michael@0: // we have to create a new content iterator! michael@0: michael@0: if (origStartNode && origEndNode) michael@0: { michael@0: nsCOMPtr curStartNode, curEndNode; michael@0: int32_t curStartOffset = 0, curEndOffset = 0; michael@0: michael@0: result = GetRangeEndPoints(mExtent, michael@0: getter_AddRefs(curStartNode), &curStartOffset, michael@0: getter_AddRefs(curEndNode), &curEndOffset); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (origStartNode != curStartNode || origEndNode != curEndNode) michael@0: { michael@0: // The range has changed, so we need to create a new content michael@0: // iterator based on the new range. michael@0: michael@0: nsCOMPtr curContent; michael@0: michael@0: if (mIteratorStatus != nsTextServicesDocument::eIsDone) { michael@0: // The old iterator is still pointing to something valid, michael@0: // so get its current node so we can restore it after we michael@0: // create the new iterator! michael@0: michael@0: curContent = mIterator->GetCurrentNode() michael@0: ? mIterator->GetCurrentNode()->AsContent() michael@0: : nullptr; michael@0: } michael@0: michael@0: // Create the new iterator. michael@0: michael@0: result = CreateContentIterator(mExtent, getter_AddRefs(mIterator)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: // Now make the new iterator point to the content node michael@0: // the old one was pointing at. michael@0: michael@0: if (curContent) michael@0: { michael@0: result = mIterator->PositionAt(curContent); michael@0: michael@0: if (NS_FAILED(result)) michael@0: mIteratorStatus = eIsDone; michael@0: else michael@0: mIteratorStatus = eValid; michael@0: } michael@0: } michael@0: } michael@0: michael@0: entry = 0; michael@0: michael@0: // Move the caret to the end of the first valid entry. michael@0: // Start with mSelStartIndex since it may still be valid. michael@0: michael@0: for (i = mSelStartIndex; !entry && i >= 0; i--) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: if (!entry->mIsValid) michael@0: entry = 0; michael@0: else michael@0: { michael@0: mSelStartIndex = mSelEndIndex = i; michael@0: mSelStartOffset = mSelEndOffset = entry->mStrOffset + entry->mLength; michael@0: } michael@0: } michael@0: michael@0: // If we still don't have a valid entry, move the caret michael@0: // to the next valid entry after the selection: michael@0: michael@0: for (i = mSelEndIndex; !entry && i < int32_t(mOffsetTable.Length()); i++) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: if (!entry->mIsValid) michael@0: entry = 0; michael@0: else michael@0: { michael@0: mSelStartIndex = mSelEndIndex = i; michael@0: mSelStartOffset = mSelEndOffset = entry->mStrOffset; michael@0: } michael@0: } michael@0: michael@0: if (entry) michael@0: result = SetSelection(mSelStartOffset, 0); michael@0: else michael@0: { michael@0: // Uuughh we have no valid offset entry to place our michael@0: // caret ... just mark the selection invalid. michael@0: michael@0: mSelStartIndex = mSelEndIndex = -1; michael@0: mSelStartOffset = mSelEndOffset = -1; michael@0: } michael@0: michael@0: // Now remove any invalid entries from the offset table. michael@0: michael@0: result = RemoveInvalidOffsetEntries(); michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("\n---- After Delete\n"); michael@0: // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); michael@0: // PrintOffsetTable(); michael@0: //**** KDEBUG **** michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::InsertText(const nsString *aText) michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: nsCOMPtr editor (do_QueryReferent(mEditor)); michael@0: NS_ASSERTION(editor, "InsertText called without an editor present!"); michael@0: michael@0: if (!editor || !SelectionIsValid()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ENSURE_TRUE(aText, NS_ERROR_NULL_POINTER); michael@0: michael@0: // If the selection is not collapsed, we need to save michael@0: // off the selection offsets so we can restore the michael@0: // selection and delete the selected content after we've michael@0: // inserted the new text. This is necessary to try and michael@0: // retain as much of the original style of the content michael@0: // being deleted. michael@0: michael@0: bool collapsedSelection = SelectionIsCollapsed(); michael@0: int32_t savedSelOffset = mSelStartOffset; michael@0: int32_t savedSelLength = mSelEndOffset - mSelStartOffset; michael@0: michael@0: if (!collapsedSelection) michael@0: { michael@0: // Collapse to the start of the current selection michael@0: // for the insert! michael@0: michael@0: result = SetSelection(mSelStartOffset, 0); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: result = editor->BeginTransaction(); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: nsCOMPtr textEditor (do_QueryInterface(editor, &result)); michael@0: if (textEditor) michael@0: result = textEditor->InsertText(*aText); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("\n---- Before Insert\n"); michael@0: // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); michael@0: // PrintOffsetTable(); michael@0: //**** KDEBUG **** michael@0: michael@0: int32_t strLength = aText->Length(); michael@0: uint32_t i; michael@0: michael@0: nsCOMPtr selection; michael@0: OffsetEntry *itEntry; michael@0: OffsetEntry *entry = mOffsetTable[mSelStartIndex]; michael@0: void *node = entry->mNode; michael@0: michael@0: NS_ASSERTION((entry->mIsValid), "Invalid insertion point!"); michael@0: michael@0: if (entry->mStrOffset == mSelStartOffset) michael@0: { michael@0: if (entry->mIsInsertedText) michael@0: { michael@0: // If the caret is in an inserted text offset entry, michael@0: // we simply insert the text at the end of the entry. michael@0: michael@0: entry->mLength += strLength; michael@0: } michael@0: else michael@0: { michael@0: // Insert an inserted text offset entry before the current michael@0: // entry! michael@0: michael@0: itEntry = new OffsetEntry(entry->mNode, entry->mStrOffset, strLength); michael@0: michael@0: if (!itEntry) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: itEntry->mIsInsertedText = true; michael@0: itEntry->mNodeOffset = entry->mNodeOffset; michael@0: michael@0: if (!mOffsetTable.InsertElementAt(mSelStartIndex, itEntry)) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: else if ((entry->mStrOffset + entry->mLength) == mSelStartOffset) michael@0: { michael@0: // We are inserting text at the end of the current offset entry. michael@0: // Look at the next valid entry in the table. If it's an inserted michael@0: // text entry, add to its length and adjust its node offset. If michael@0: // it isn't, add a new inserted text entry. michael@0: michael@0: i = mSelStartIndex + 1; michael@0: itEntry = 0; michael@0: michael@0: if (mOffsetTable.Length() > i) michael@0: { michael@0: itEntry = mOffsetTable[i]; michael@0: michael@0: if (!itEntry) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Check if the entry is a match. If it isn't, set michael@0: // iEntry to zero. michael@0: michael@0: if (!itEntry->mIsInsertedText || itEntry->mStrOffset != mSelStartOffset) michael@0: itEntry = 0; michael@0: } michael@0: michael@0: if (!itEntry) michael@0: { michael@0: // We didn't find an inserted text offset entry, so michael@0: // create one. michael@0: michael@0: itEntry = new OffsetEntry(entry->mNode, mSelStartOffset, 0); michael@0: michael@0: if (!itEntry) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: itEntry->mNodeOffset = entry->mNodeOffset + entry->mLength; michael@0: itEntry->mIsInsertedText = true; michael@0: michael@0: if (!mOffsetTable.InsertElementAt(i, itEntry)) michael@0: { michael@0: delete itEntry; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // We have a valid inserted text offset entry. Update its michael@0: // length, adjust the selection indexes, and make sure the michael@0: // caret is properly placed! michael@0: michael@0: itEntry->mLength += strLength; michael@0: michael@0: mSelStartIndex = mSelEndIndex = i; michael@0: michael@0: result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = selection->Collapse(itEntry->mNode, itEntry->mNodeOffset + itEntry->mLength); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: } michael@0: else if ((entry->mStrOffset + entry->mLength) > mSelStartOffset) michael@0: { michael@0: // We are inserting text into the middle of the current offset entry. michael@0: // split the current entry into two parts, then insert an inserted text michael@0: // entry between them! michael@0: michael@0: i = entry->mLength - (mSelStartOffset - entry->mStrOffset); michael@0: michael@0: result = SplitOffsetEntry(mSelStartIndex, i); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: itEntry = new OffsetEntry(entry->mNode, mSelStartOffset, strLength); michael@0: michael@0: if (!itEntry) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: itEntry->mIsInsertedText = true; michael@0: itEntry->mNodeOffset = entry->mNodeOffset + entry->mLength; michael@0: michael@0: if (!mOffsetTable.InsertElementAt(mSelStartIndex + 1, itEntry)) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mSelEndIndex = ++mSelStartIndex; michael@0: } michael@0: michael@0: // We've just finished inserting an inserted text offset entry. michael@0: // update all entries with the same mNode pointer that follow michael@0: // it in the table! michael@0: michael@0: for (i = mSelStartIndex + 1; i < mOffsetTable.Length(); i++) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: if (entry->mNode == node) michael@0: { michael@0: if (entry->mIsValid) michael@0: entry->mNodeOffset += strLength; michael@0: } michael@0: else michael@0: break; michael@0: } michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("\n---- After Insert\n"); michael@0: // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); michael@0: // PrintOffsetTable(); michael@0: //**** KDEBUG **** michael@0: michael@0: if (!collapsedSelection) michael@0: { michael@0: result = SetSelection(savedSelOffset, savedSelLength); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: result = DeleteSelection(); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: editor->EndTransaction(); michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: result = editor->EndTransaction(); michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::DidInsertNode(nsIDOMNode *aNode, michael@0: nsIDOMNode *aParent, michael@0: int32_t aPosition, michael@0: nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) michael@0: { michael@0: NS_ENSURE_SUCCESS(aResult, NS_OK); michael@0: michael@0: NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("** DeleteNode: 0x%.8x\n", aChild); michael@0: // fflush(stdout); michael@0: //**** KDEBUG **** michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: int32_t nodeIndex = 0; michael@0: bool hasEntry = false; michael@0: OffsetEntry *entry; michael@0: michael@0: nsresult result = NodeHasOffsetEntry(&mOffsetTable, aChild, &hasEntry, &nodeIndex); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return result; michael@0: } michael@0: michael@0: if (!hasEntry) michael@0: { michael@0: // It's okay if the node isn't in the offset table, the michael@0: // editor could be cleaning house. michael@0: michael@0: UNLOCK_DOC(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr node = do_QueryInterface(mIterator->GetCurrentNode()); michael@0: michael@0: if (node && node == aChild && michael@0: mIteratorStatus != nsTextServicesDocument::eIsDone) michael@0: { michael@0: // XXX: This should never really happen because michael@0: // AdjustContentIterator() should have been called prior michael@0: // to the delete to try and position the iterator on the michael@0: // next valid text node in the offset table, and if there michael@0: // wasn't a next, it would've set mIteratorStatus to eIsDone. michael@0: michael@0: NS_ERROR("DeleteNode called for current iterator node."); michael@0: } michael@0: michael@0: int32_t tcount = mOffsetTable.Length(); michael@0: michael@0: while (nodeIndex < tcount) michael@0: { michael@0: entry = mOffsetTable[nodeIndex]; michael@0: michael@0: if (!entry) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (entry->mNode == aChild) michael@0: { michael@0: entry->mIsValid = false; michael@0: } michael@0: michael@0: nodeIndex++; michael@0: } michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::DidSplitNode(nsIDOMNode *aExistingRightNode, michael@0: int32_t aOffset, michael@0: nsIDOMNode *aNewLeftNode, michael@0: nsresult aResult) michael@0: { michael@0: //**** KDEBUG **** michael@0: // printf("** SplitNode: 0x%.8x %d 0x%.8x\n", aExistingRightNode, aOffset, aNewLeftNode); michael@0: // fflush(stdout); michael@0: //**** KDEBUG **** michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::DidJoinNodes(nsIDOMNode *aLeftNode, michael@0: nsIDOMNode *aRightNode, michael@0: nsIDOMNode *aParent, michael@0: nsresult aResult) michael@0: { michael@0: NS_ENSURE_SUCCESS(aResult, NS_OK); michael@0: michael@0: int32_t i; michael@0: uint16_t type; michael@0: nsresult result; michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("** JoinNodes: 0x%.8x 0x%.8x 0x%.8x\n", aLeftNode, aRightNode, aParent); michael@0: // fflush(stdout); michael@0: //**** KDEBUG **** michael@0: michael@0: // Make sure that both nodes are text nodes -- otherwise we don't care. michael@0: michael@0: result = aLeftNode->GetNodeType(&type); michael@0: NS_ENSURE_SUCCESS(result, NS_OK); michael@0: if (nsIDOMNode::TEXT_NODE != type) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = aRightNode->GetNodeType(&type); michael@0: NS_ENSURE_SUCCESS(result, NS_OK); michael@0: if (nsIDOMNode::TEXT_NODE != type) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Note: The editor merges the contents of the left node into the michael@0: // contents of the right. michael@0: michael@0: int32_t leftIndex = 0; michael@0: int32_t rightIndex = 0; michael@0: bool leftHasEntry = false; michael@0: bool rightHasEntry = false; michael@0: michael@0: result = NodeHasOffsetEntry(&mOffsetTable, aLeftNode, &leftHasEntry, &leftIndex); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: if (!leftHasEntry) michael@0: { michael@0: // It's okay if the node isn't in the offset table, the michael@0: // editor could be cleaning house. michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = NodeHasOffsetEntry(&mOffsetTable, aRightNode, &rightHasEntry, &rightIndex); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: if (!rightHasEntry) michael@0: { michael@0: // It's okay if the node isn't in the offset table, the michael@0: // editor could be cleaning house. michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(leftIndex < rightIndex, "Indexes out of order."); michael@0: michael@0: if (leftIndex > rightIndex) michael@0: { michael@0: // Don't know how to handle this situation. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: LOCK_DOC(this); michael@0: michael@0: OffsetEntry *entry = mOffsetTable[rightIndex]; michael@0: NS_ASSERTION(entry->mNodeOffset == 0, "Unexpected offset value for rightIndex."); michael@0: michael@0: // Run through the table and change all entries referring to michael@0: // the left node so that they now refer to the right node: michael@0: michael@0: nsAutoString str; michael@0: result = aLeftNode->GetNodeValue(str); michael@0: int32_t nodeLength = str.Length(); michael@0: michael@0: for (i = leftIndex; i < rightIndex; i++) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: if (entry->mNode == aLeftNode) michael@0: { michael@0: if (entry->mIsValid) michael@0: entry->mNode = aRightNode; michael@0: } michael@0: else michael@0: break; michael@0: } michael@0: michael@0: // Run through the table and adjust the node offsets michael@0: // for all entries referring to the right node. michael@0: michael@0: for (i = rightIndex; i < int32_t(mOffsetTable.Length()); i++) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: if (entry->mNode == aRightNode) michael@0: { michael@0: if (entry->mIsValid) michael@0: entry->mNodeOffset += nodeLength; michael@0: } michael@0: else michael@0: break; michael@0: } michael@0: michael@0: // Now check to see if the iterator is pointing to the michael@0: // left node. If it is, make it point to the right node! michael@0: michael@0: nsCOMPtr leftContent = do_QueryInterface(aLeftNode); michael@0: nsCOMPtr rightContent = do_QueryInterface(aRightNode); michael@0: michael@0: if (!leftContent || !rightContent) michael@0: { michael@0: UNLOCK_DOC(this); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (mIterator->GetCurrentNode() == leftContent) michael@0: result = mIterator->PositionAt(rightContent); michael@0: michael@0: UNLOCK_DOC(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::CreateContentIterator(nsIDOMRange *aRange, nsIContentIterator **aIterator) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aRange && aIterator, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aIterator = 0; michael@0: michael@0: // Create a nsFilteredContentIterator michael@0: // This class wraps the ContentIterator in order to give itself a chance michael@0: // to filter out certain content nodes michael@0: nsFilteredContentIterator* filter = new nsFilteredContentIterator(mTxtSvcFilter); michael@0: *aIterator = static_cast(filter); michael@0: if (*aIterator) { michael@0: NS_IF_ADDREF(*aIterator); michael@0: result = filter ? NS_OK : NS_ERROR_FAILURE; michael@0: } else { michael@0: delete filter; michael@0: result = NS_ERROR_FAILURE; michael@0: } michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: NS_ENSURE_TRUE(*aIterator, NS_ERROR_NULL_POINTER); michael@0: michael@0: result = (*aIterator)->Init(aRange); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: NS_RELEASE((*aIterator)); michael@0: *aIterator = 0; michael@0: return result; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::GetDocumentContentRootNode(nsIDOMNode **aNode) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aNode = 0; michael@0: michael@0: NS_ENSURE_TRUE(mDOMDocument, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr htmlDoc = do_QueryInterface(mDOMDocument); michael@0: michael@0: if (htmlDoc) michael@0: { michael@0: // For HTML documents, the content root node is the body. michael@0: michael@0: nsCOMPtr bodyElement; michael@0: michael@0: result = htmlDoc->GetBody(getter_AddRefs(bodyElement)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: NS_ENSURE_TRUE(bodyElement, NS_ERROR_FAILURE); michael@0: michael@0: result = bodyElement->QueryInterface(NS_GET_IID(nsIDOMNode), (void **)aNode); michael@0: } michael@0: else michael@0: { michael@0: // For non-HTML documents, the content root node will be the document element. michael@0: michael@0: nsCOMPtr docElement; michael@0: michael@0: result = mDOMDocument->GetDocumentElement(getter_AddRefs(docElement)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: NS_ENSURE_TRUE(docElement, NS_ERROR_FAILURE); michael@0: michael@0: result = docElement->QueryInterface(NS_GET_IID(nsIDOMNode), (void **)aNode); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::CreateDocumentContentRange(nsIDOMRange **aRange) michael@0: { michael@0: *aRange = nullptr; michael@0: michael@0: nsCOMPtr node; michael@0: nsresult rv = GetDocumentContentRootNode(getter_AddRefs(node)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr nativeNode = do_QueryInterface(node); michael@0: NS_ENSURE_STATE(nativeNode); michael@0: michael@0: nsRefPtr range = new nsRange(nativeNode); michael@0: michael@0: rv = range->SelectNodeContents(node); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: range.forget(aRange); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::CreateDocumentContentRootToNodeOffsetRange(nsIDOMNode *aParent, int32_t aOffset, bool aToStart, nsIDOMRange **aRange) michael@0: { michael@0: NS_ENSURE_TRUE(aParent && aRange, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aRange = 0; michael@0: michael@0: NS_ASSERTION(aOffset >= 0, "Invalid offset!"); michael@0: michael@0: if (aOffset < 0) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr bodyNode; michael@0: nsresult rv = GetDocumentContentRootNode(getter_AddRefs(bodyNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(bodyNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr startNode; michael@0: nsCOMPtr endNode; michael@0: int32_t startOffset, endOffset; michael@0: michael@0: if (aToStart) { michael@0: // The range should begin at the start of the document michael@0: // and extend up until (aParent, aOffset). michael@0: michael@0: startNode = bodyNode; michael@0: startOffset = 0; michael@0: endNode = aParent; michael@0: endOffset = aOffset; michael@0: } else { michael@0: // The range should begin at (aParent, aOffset) and michael@0: // extend to the end of the document. michael@0: michael@0: startNode = aParent; michael@0: startOffset = aOffset; michael@0: endNode = bodyNode; michael@0: michael@0: nsCOMPtr body = do_QueryInterface(bodyNode); michael@0: endOffset = body ? int32_t(body->GetChildCount()) : 0; michael@0: } michael@0: michael@0: return nsRange::CreateRange(startNode, startOffset, endNode, endOffset, michael@0: aRange); michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::CreateDocumentContentIterator(nsIContentIterator **aIterator) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aIterator, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr range; michael@0: michael@0: result = CreateDocumentContentRange(getter_AddRefs(range)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: result = CreateContentIterator(range, aIterator); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::AdjustContentIterator() michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr node(do_QueryInterface(mIterator->GetCurrentNode())); michael@0: michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: nsIDOMNode *nodePtr = node.get(); michael@0: int32_t tcount = mOffsetTable.Length(); michael@0: michael@0: nsIDOMNode *prevValidNode = 0; michael@0: nsIDOMNode *nextValidNode = 0; michael@0: bool foundEntry = false; michael@0: OffsetEntry *entry; michael@0: michael@0: for (int32_t i = 0; i < tcount && !nextValidNode; i++) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); michael@0: michael@0: if (entry->mNode == nodePtr) michael@0: { michael@0: if (entry->mIsValid) michael@0: { michael@0: // The iterator is still pointing to something valid! michael@0: // Do nothing! michael@0: michael@0: return NS_OK; michael@0: } michael@0: else michael@0: { michael@0: // We found an invalid entry that points to michael@0: // the current iterator node. Stop looking for michael@0: // a previous valid node! michael@0: michael@0: foundEntry = true; michael@0: } michael@0: } michael@0: michael@0: if (entry->mIsValid) michael@0: { michael@0: if (!foundEntry) michael@0: prevValidNode = entry->mNode; michael@0: else michael@0: nextValidNode = entry->mNode; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr content; michael@0: michael@0: if (prevValidNode) michael@0: content = do_QueryInterface(prevValidNode); michael@0: else if (nextValidNode) michael@0: content = do_QueryInterface(nextValidNode); michael@0: michael@0: if (content) michael@0: { michael@0: result = mIterator->PositionAt(content); michael@0: michael@0: if (NS_FAILED(result)) michael@0: mIteratorStatus = eIsDone; michael@0: else michael@0: mIteratorStatus = eValid; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: // If we get here, there aren't any valid entries michael@0: // in the offset table! Try to position the iterator michael@0: // on the next text block first, then previous if michael@0: // one doesn't exist! michael@0: michael@0: if (mNextTextBlock) michael@0: { michael@0: result = mIterator->PositionAt(mNextTextBlock); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: mIteratorStatus = eIsDone; michael@0: return result; michael@0: } michael@0: michael@0: mIteratorStatus = eNext; michael@0: } michael@0: else if (mPrevTextBlock) michael@0: { michael@0: result = mIterator->PositionAt(mPrevTextBlock); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: mIteratorStatus = eIsDone; michael@0: return result; michael@0: } michael@0: michael@0: mIteratorStatus = ePrev; michael@0: } michael@0: else michael@0: mIteratorStatus = eIsDone; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsTextServicesDocument::DidSkip(nsIContentIterator* aFilteredIter) michael@0: { michael@0: // We can assume here that the Iterator is a nsFilteredContentIterator because michael@0: // all the iterator are created in CreateContentIterator which create a michael@0: // nsFilteredContentIterator michael@0: // So if the iterator bailed on one of the "filtered" content nodes then we michael@0: // consider that to be a block and bail with true michael@0: if (aFilteredIter) { michael@0: nsFilteredContentIterator* filter = static_cast(aFilteredIter); michael@0: if (filter && filter->DidSkip()) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsTextServicesDocument::ClearDidSkip(nsIContentIterator* aFilteredIter) michael@0: { michael@0: // Clear filter's skip flag michael@0: if (aFilteredIter) { michael@0: nsFilteredContentIterator* filter = static_cast(aFilteredIter); michael@0: filter->ClearDidSkip(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsTextServicesDocument::IsBlockNode(nsIContent *aContent) michael@0: { michael@0: if (!aContent) { michael@0: NS_ERROR("How did a null pointer get passed to IsBlockNode?"); michael@0: return false; michael@0: } michael@0: michael@0: nsIAtom *atom = aContent->Tag(); michael@0: michael@0: return (sAAtom != atom && michael@0: sAddressAtom != atom && michael@0: sBigAtom != atom && michael@0: sBAtom != atom && michael@0: sCiteAtom != atom && michael@0: sCodeAtom != atom && michael@0: sDfnAtom != atom && michael@0: sEmAtom != atom && michael@0: sFontAtom != atom && michael@0: sIAtom != atom && michael@0: sKbdAtom != atom && michael@0: sKeygenAtom != atom && michael@0: sNobrAtom != atom && michael@0: sSAtom != atom && michael@0: sSampAtom != atom && michael@0: sSmallAtom != atom && michael@0: sSpacerAtom != atom && michael@0: sSpanAtom != atom && michael@0: sStrikeAtom != atom && michael@0: sStrongAtom != atom && michael@0: sSubAtom != atom && michael@0: sSupAtom != atom && michael@0: sTtAtom != atom && michael@0: sUAtom != atom && michael@0: sVarAtom != atom && michael@0: sWbrAtom != atom); michael@0: } michael@0: michael@0: bool michael@0: nsTextServicesDocument::HasSameBlockNodeParent(nsIContent *aContent1, nsIContent *aContent2) michael@0: { michael@0: nsIContent* p1 = aContent1->GetParent(); michael@0: nsIContent* p2 = aContent2->GetParent(); michael@0: michael@0: // Quick test: michael@0: michael@0: if (p1 == p2) michael@0: return true; michael@0: michael@0: // Walk up the parent hierarchy looking for closest block boundary node: michael@0: michael@0: while (p1 && !IsBlockNode(p1)) michael@0: { michael@0: p1 = p1->GetParent(); michael@0: } michael@0: michael@0: while (p2 && !IsBlockNode(p2)) michael@0: { michael@0: p2 = p2->GetParent(); michael@0: } michael@0: michael@0: return p1 == p2; michael@0: } michael@0: michael@0: bool michael@0: nsTextServicesDocument::IsTextNode(nsIContent *aContent) michael@0: { michael@0: NS_ENSURE_TRUE(aContent, false); michael@0: return nsIDOMNode::TEXT_NODE == aContent->NodeType(); michael@0: } michael@0: michael@0: bool michael@0: nsTextServicesDocument::IsTextNode(nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); michael@0: michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: return IsTextNode(content); michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::SetSelectionInternal(int32_t aOffset, int32_t aLength, bool aDoUpdate) michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: NS_ENSURE_TRUE(mSelCon && aOffset >= 0 && aLength >= 0, NS_ERROR_FAILURE); michael@0: michael@0: nsIDOMNode *sNode = 0, *eNode = 0; michael@0: int32_t i, sOffset = 0, eOffset = 0; michael@0: OffsetEntry *entry; michael@0: michael@0: // Find start of selection in node offset terms: michael@0: michael@0: for (i = 0; !sNode && i < int32_t(mOffsetTable.Length()); i++) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: if (entry->mIsValid) michael@0: { michael@0: if (entry->mIsInsertedText) michael@0: { michael@0: // Caret can only be placed at the end of an michael@0: // inserted text offset entry, if the offsets michael@0: // match exactly! michael@0: michael@0: if (entry->mStrOffset == aOffset) michael@0: { michael@0: sNode = entry->mNode; michael@0: sOffset = entry->mNodeOffset + entry->mLength; michael@0: } michael@0: } michael@0: else if (aOffset >= entry->mStrOffset) michael@0: { michael@0: bool foundEntry = false; michael@0: int32_t strEndOffset = entry->mStrOffset + entry->mLength; michael@0: michael@0: if (aOffset < strEndOffset) michael@0: foundEntry = true; michael@0: else if (aOffset == strEndOffset) michael@0: { michael@0: // Peek after this entry to see if we have any michael@0: // inserted text entries belonging to the same michael@0: // entry->mNode. If so, we have to place the selection michael@0: // after it! michael@0: michael@0: if ((i+1) < int32_t(mOffsetTable.Length())) michael@0: { michael@0: OffsetEntry *nextEntry = mOffsetTable[i+1]; michael@0: michael@0: if (!nextEntry->mIsValid || nextEntry->mStrOffset != aOffset) michael@0: { michael@0: // Next offset entry isn't an exact match, so we'll michael@0: // just use the current entry. michael@0: foundEntry = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (foundEntry) michael@0: { michael@0: sNode = entry->mNode; michael@0: sOffset = entry->mNodeOffset + aOffset - entry->mStrOffset; michael@0: } michael@0: } michael@0: michael@0: if (sNode) michael@0: { michael@0: mSelStartIndex = i; michael@0: mSelStartOffset = aOffset; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(sNode, NS_ERROR_FAILURE); michael@0: michael@0: // XXX: If we ever get a SetSelection() method in nsIEditor, we should michael@0: // use it. michael@0: michael@0: nsCOMPtr selection; michael@0: michael@0: if (aDoUpdate) michael@0: { michael@0: result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: result = selection->Collapse(sNode, sOffset); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: michael@0: if (aLength <= 0) michael@0: { michael@0: // We have a collapsed selection. (Caret) michael@0: michael@0: mSelEndIndex = mSelStartIndex; michael@0: mSelEndOffset = mSelStartOffset; michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("\n* Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); michael@0: //**** KDEBUG **** michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Find the end of the selection in node offset terms: michael@0: michael@0: int32_t endOffset = aOffset + aLength; michael@0: michael@0: for (i = mOffsetTable.Length() - 1; !eNode && i >= 0; i--) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: if (entry->mIsValid) michael@0: { michael@0: if (entry->mIsInsertedText) michael@0: { michael@0: if (entry->mStrOffset == eOffset) michael@0: { michael@0: // If the selection ends on an inserted text offset entry, michael@0: // the selection includes the entire entry! michael@0: michael@0: eNode = entry->mNode; michael@0: eOffset = entry->mNodeOffset + entry->mLength; michael@0: } michael@0: } michael@0: else if (endOffset >= entry->mStrOffset && endOffset <= entry->mStrOffset + entry->mLength) michael@0: { michael@0: eNode = entry->mNode; michael@0: eOffset = entry->mNodeOffset + endOffset - entry->mStrOffset; michael@0: } michael@0: michael@0: if (eNode) michael@0: { michael@0: mSelEndIndex = i; michael@0: mSelEndOffset = endOffset; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aDoUpdate && eNode) michael@0: { michael@0: result = selection->Extend(eNode, eOffset); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: michael@0: //**** KDEBUG **** michael@0: // printf("\n * Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); michael@0: //**** KDEBUG **** michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::GetSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aSelStatus = nsITextServicesDocument::eBlockNotFound; michael@0: *aSelOffset = -1; michael@0: *aSelLength = -1; michael@0: michael@0: NS_ENSURE_TRUE(mDOMDocument && mSelCon, NS_ERROR_FAILURE); michael@0: michael@0: if (mIteratorStatus == nsTextServicesDocument::eIsDone) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr selection; michael@0: bool isCollapsed; michael@0: michael@0: result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); michael@0: michael@0: result = selection->GetIsCollapsed(&isCollapsed); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // XXX: If we expose this method publicly, we need to michael@0: // add LOCK_DOC/UNLOCK_DOC calls! michael@0: michael@0: // LOCK_DOC(this); michael@0: michael@0: if (isCollapsed) michael@0: result = GetCollapsedSelection(aSelStatus, aSelOffset, aSelLength); michael@0: else michael@0: result = GetUncollapsedSelection(aSelStatus, aSelOffset, aSelLength); michael@0: michael@0: // UNLOCK_DOC(this); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::GetCollapsedSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength) michael@0: { michael@0: nsCOMPtr selection; michael@0: nsresult result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); michael@0: michael@0: // The calling function should have done the GetIsCollapsed() michael@0: // check already. Just assume it's collapsed! michael@0: *aSelStatus = nsITextServicesDocument::eBlockOutside; michael@0: *aSelOffset = *aSelLength = -1; michael@0: michael@0: int32_t tableCount = mOffsetTable.Length(); michael@0: michael@0: if (tableCount == 0) michael@0: return NS_OK; michael@0: michael@0: // Get pointers to the first and last offset entries michael@0: // in the table. michael@0: michael@0: OffsetEntry* eStart = mOffsetTable[0]; michael@0: OffsetEntry* eEnd; michael@0: if (tableCount > 1) michael@0: eEnd = mOffsetTable[tableCount - 1]; michael@0: else michael@0: eEnd = eStart; michael@0: michael@0: int32_t eStartOffset = eStart->mNodeOffset; michael@0: int32_t eEndOffset = eEnd->mNodeOffset + eEnd->mLength; michael@0: michael@0: nsCOMPtr range; michael@0: result = selection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: nsCOMPtr domParent; michael@0: result = range->GetStartContainer(getter_AddRefs(domParent)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: nsCOMPtr parent = do_QueryInterface(domParent); michael@0: MOZ_ASSERT(parent); michael@0: michael@0: int32_t offset; michael@0: result = range->GetStartOffset(&offset); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: int32_t e1s1 = nsContentUtils::ComparePoints(eStart->mNode, eStartOffset, michael@0: domParent, offset); michael@0: int32_t e2s1 = nsContentUtils::ComparePoints(eEnd->mNode, eEndOffset, michael@0: domParent, offset); michael@0: michael@0: if (e1s1 > 0 || e2s1 < 0) { michael@0: // We're done if the caret is outside the current text block. michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (parent->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: // Good news, the caret is in a text node. Look michael@0: // through the offset table for the entry that michael@0: // matches its parent and offset. michael@0: michael@0: for (int32_t i = 0; i < tableCount; i++) { michael@0: OffsetEntry* entry = mOffsetTable[i]; michael@0: NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); michael@0: michael@0: if (entry->mNode == domParent.get() && michael@0: entry->mNodeOffset <= offset && offset <= (entry->mNodeOffset + entry->mLength)) michael@0: { michael@0: *aSelStatus = nsITextServicesDocument::eBlockContains; michael@0: *aSelOffset = entry->mStrOffset + (offset - entry->mNodeOffset); michael@0: *aSelLength = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // If we get here, we didn't find a text node entry michael@0: // in our offset table that matched. michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // The caret is in our text block, but it's positioned in some michael@0: // non-text node (ex. ). Create a range based on the start michael@0: // and end of the text block, then create an iterator based on michael@0: // this range, with its initial position set to the closest michael@0: // child of this non-text node. Then look for the closest text michael@0: // node. michael@0: michael@0: result = CreateRange(eStart->mNode, eStartOffset, eEnd->mNode, eEndOffset, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: nsCOMPtr iter; michael@0: result = CreateContentIterator(range, getter_AddRefs(iter)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: nsIContent* saveNode; michael@0: if (parent->HasChildren()) { michael@0: // XXX: We need to make sure that all of parent's michael@0: // children are in the text block. michael@0: michael@0: // If the parent has children, position the iterator michael@0: // on the child that is to the left of the offset. michael@0: michael@0: uint32_t childIndex = (uint32_t)offset; michael@0: michael@0: if (childIndex > 0) { michael@0: uint32_t numChildren = parent->GetChildCount(); michael@0: NS_ASSERTION(childIndex <= numChildren, "Invalid selection offset!"); michael@0: michael@0: if (childIndex > numChildren) { michael@0: childIndex = numChildren; michael@0: } michael@0: michael@0: childIndex -= 1; michael@0: } michael@0: michael@0: nsIContent* content = parent->GetChildAt(childIndex); michael@0: NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); michael@0: michael@0: result = iter->PositionAt(content); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: saveNode = content; michael@0: } else { michael@0: // The parent has no children, so position the iterator michael@0: // on the parent. michael@0: NS_ENSURE_TRUE(parent->IsContent(), NS_ERROR_FAILURE); michael@0: nsCOMPtr content = parent->AsContent(); michael@0: michael@0: result = iter->PositionAt(content); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: saveNode = content; michael@0: } michael@0: michael@0: // Now iterate to the left, towards the beginning of michael@0: // the text block, to find the first text node you michael@0: // come across. michael@0: michael@0: nsIContent* node = nullptr; michael@0: while (!iter->IsDone()) { michael@0: nsINode* current = iter->GetCurrentNode(); michael@0: if (current->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: node = static_cast(current); michael@0: break; michael@0: } michael@0: michael@0: iter->Prev(); michael@0: } michael@0: michael@0: if (node) { michael@0: // We found a node, now set the offset to the end michael@0: // of the text node. michael@0: offset = node->TextLength(); michael@0: } else { michael@0: // We should never really get here, but I'm paranoid. michael@0: michael@0: // We didn't find a text node above, so iterate to michael@0: // the right, towards the end of the text block, looking michael@0: // for a text node. michael@0: michael@0: result = iter->PositionAt(saveNode); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: node = nullptr; michael@0: while (!iter->IsDone()) { michael@0: nsINode* current = iter->GetCurrentNode(); michael@0: michael@0: if (current->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: node = static_cast(current); michael@0: break; michael@0: } michael@0: michael@0: iter->Next(); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: // We found a text node, so set the offset to michael@0: // the beginning of the node. michael@0: michael@0: offset = 0; michael@0: } michael@0: michael@0: for (int32_t i = 0; i < tableCount; i++) { michael@0: OffsetEntry* entry = mOffsetTable[i]; michael@0: NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); michael@0: michael@0: if (entry->mNode == node->AsDOMNode() && michael@0: entry->mNodeOffset <= offset && offset <= (entry->mNodeOffset + entry->mLength)) michael@0: { michael@0: *aSelStatus = nsITextServicesDocument::eBlockContains; michael@0: *aSelOffset = entry->mStrOffset + (offset - entry->mNodeOffset); michael@0: *aSelLength = 0; michael@0: michael@0: // Now move the caret so that it is actually in the text node. michael@0: // We do this to keep things in sync. michael@0: // michael@0: // In most cases, the user shouldn't see any movement in the caret michael@0: // on screen. michael@0: michael@0: result = SetSelectionInternal(*aSelOffset, *aSelLength, true); michael@0: michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::GetUncollapsedSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength) michael@0: { michael@0: nsresult result; michael@0: michael@0: nsCOMPtr selection; michael@0: nsCOMPtr range; michael@0: OffsetEntry *entry; michael@0: michael@0: result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); michael@0: michael@0: // It is assumed that the calling function has made sure that the michael@0: // selection is not collapsed, and that the input params to this michael@0: // method are initialized to some defaults. michael@0: michael@0: nsCOMPtr startParent, endParent; michael@0: int32_t startOffset, endOffset; michael@0: int32_t rangeCount, tableCount, i; michael@0: int32_t e1s1, e1s2, e2s1, e2s2; michael@0: michael@0: OffsetEntry *eStart, *eEnd; michael@0: int32_t eStartOffset, eEndOffset; michael@0: michael@0: tableCount = mOffsetTable.Length(); michael@0: michael@0: // Get pointers to the first and last offset entries michael@0: // in the table. michael@0: michael@0: eStart = mOffsetTable[0]; michael@0: michael@0: if (tableCount > 1) michael@0: eEnd = mOffsetTable[tableCount - 1]; michael@0: else michael@0: eEnd = eStart; michael@0: michael@0: eStartOffset = eStart->mNodeOffset; michael@0: eEndOffset = eEnd->mNodeOffset + eEnd->mLength; michael@0: michael@0: result = selection->GetRangeCount(&rangeCount); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // Find the first range in the selection that intersects michael@0: // the current text block. michael@0: michael@0: for (i = 0; i < rangeCount; i++) michael@0: { michael@0: result = selection->GetRangeAt(i, getter_AddRefs(range)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: result = GetRangeEndPoints(range, michael@0: getter_AddRefs(startParent), &startOffset, michael@0: getter_AddRefs(endParent), &endOffset); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: e1s2 = nsContentUtils::ComparePoints(eStart->mNode, eStartOffset, michael@0: endParent, endOffset); michael@0: e2s1 = nsContentUtils::ComparePoints(eEnd->mNode, eEndOffset, michael@0: startParent, startOffset); michael@0: michael@0: // Break out of the loop if the text block intersects the current range. michael@0: michael@0: if (e1s2 <= 0 && e2s1 >= 0) michael@0: break; michael@0: } michael@0: michael@0: // We're done if we didn't find an intersecting range. michael@0: michael@0: if (rangeCount < 1 || e1s2 > 0 || e2s1 < 0) michael@0: { michael@0: *aSelStatus = nsITextServicesDocument::eBlockOutside; michael@0: *aSelOffset = *aSelLength = -1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Now that we have an intersecting range, find out more info: michael@0: michael@0: e1s1 = nsContentUtils::ComparePoints(eStart->mNode, eStartOffset, michael@0: startParent, startOffset); michael@0: e2s2 = nsContentUtils::ComparePoints(eEnd->mNode, eEndOffset, michael@0: endParent, endOffset); michael@0: michael@0: if (rangeCount > 1) michael@0: { michael@0: // There are multiple selection ranges, we only deal michael@0: // with the first one that intersects the current, michael@0: // text block, so mark this a as a partial. michael@0: michael@0: *aSelStatus = nsITextServicesDocument::eBlockPartial; michael@0: } michael@0: else if (e1s1 > 0 && e2s2 < 0) michael@0: { michael@0: // The range extends beyond the start and michael@0: // end of the current text block. michael@0: michael@0: *aSelStatus = nsITextServicesDocument::eBlockInside; michael@0: } michael@0: else if (e1s1 <= 0 && e2s2 >= 0) michael@0: { michael@0: // The current text block contains the entire michael@0: // range. michael@0: michael@0: *aSelStatus = nsITextServicesDocument::eBlockContains; michael@0: } michael@0: else michael@0: { michael@0: // The range partially intersects the block. michael@0: michael@0: *aSelStatus = nsITextServicesDocument::eBlockPartial; michael@0: } michael@0: michael@0: // Now create a range based on the intersection of the michael@0: // text block and range: michael@0: michael@0: nsCOMPtr p1, p2; michael@0: int32_t o1, o2; michael@0: michael@0: // The start of the range will be the rightmost michael@0: // start node. michael@0: michael@0: if (e1s1 >= 0) michael@0: { michael@0: p1 = do_QueryInterface(eStart->mNode); michael@0: o1 = eStartOffset; michael@0: } michael@0: else michael@0: { michael@0: p1 = startParent; michael@0: o1 = startOffset; michael@0: } michael@0: michael@0: // The end of the range will be the leftmost michael@0: // end node. michael@0: michael@0: if (e2s2 <= 0) michael@0: { michael@0: p2 = do_QueryInterface(eEnd->mNode); michael@0: o2 = eEndOffset; michael@0: } michael@0: else michael@0: { michael@0: p2 = endParent; michael@0: o2 = endOffset; michael@0: } michael@0: michael@0: result = CreateRange(p1, o1, p2, o2, getter_AddRefs(range)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // Now iterate over this range to figure out the selection's michael@0: // block offset and length. michael@0: michael@0: nsCOMPtr iter; michael@0: michael@0: result = CreateContentIterator(range, getter_AddRefs(iter)); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // Find the first text node in the range. michael@0: michael@0: bool found; michael@0: nsCOMPtr content; michael@0: michael@0: iter->First(); michael@0: michael@0: if (!IsTextNode(p1)) michael@0: { michael@0: found = false; michael@0: michael@0: while (!iter->IsDone()) michael@0: { michael@0: content = do_QueryInterface(iter->GetCurrentNode()); michael@0: michael@0: if (IsTextNode(content)) michael@0: { michael@0: p1 = do_QueryInterface(content); michael@0: michael@0: NS_ENSURE_TRUE(p1, NS_ERROR_FAILURE); michael@0: michael@0: o1 = 0; michael@0: found = true; michael@0: michael@0: break; michael@0: } michael@0: michael@0: iter->Next(); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(found, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: // Find the last text node in the range. michael@0: michael@0: iter->Last(); michael@0: michael@0: if (! IsTextNode(p2)) michael@0: { michael@0: found = false; michael@0: michael@0: while (!iter->IsDone()) michael@0: { michael@0: content = do_QueryInterface(iter->GetCurrentNode()); michael@0: michael@0: if (IsTextNode(content)) michael@0: { michael@0: p2 = do_QueryInterface(content); michael@0: michael@0: NS_ENSURE_TRUE(p2, NS_ERROR_FAILURE); michael@0: michael@0: nsString str; michael@0: michael@0: result = p2->GetNodeValue(str); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: o2 = str.Length(); michael@0: found = true; michael@0: michael@0: break; michael@0: } michael@0: michael@0: iter->Prev(); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(found, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: found = false; michael@0: *aSelLength = 0; michael@0: michael@0: for (i = 0; i < tableCount; i++) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); michael@0: michael@0: if (!found) michael@0: { michael@0: if (entry->mNode == p1.get() && michael@0: entry->mNodeOffset <= o1 && o1 <= (entry->mNodeOffset + entry->mLength)) michael@0: { michael@0: *aSelOffset = entry->mStrOffset + (o1 - entry->mNodeOffset); michael@0: michael@0: if (p1 == p2 && michael@0: entry->mNodeOffset <= o2 && o2 <= (entry->mNodeOffset + entry->mLength)) michael@0: { michael@0: // The start and end of the range are in the same offset michael@0: // entry. Calculate the length of the range then we're done. michael@0: michael@0: *aSelLength = o2 - o1; michael@0: break; michael@0: } michael@0: else michael@0: { michael@0: // Add the length of the sub string in this offset entry michael@0: // that follows the start of the range. michael@0: michael@0: *aSelLength = entry->mLength - (o1 - entry->mNodeOffset); michael@0: } michael@0: michael@0: found = true; michael@0: } michael@0: } michael@0: else // found michael@0: { michael@0: if (entry->mNode == p2.get() && michael@0: entry->mNodeOffset <= o2 && o2 <= (entry->mNodeOffset + entry->mLength)) michael@0: { michael@0: // We found the end of the range. Calculate the length of the michael@0: // sub string that is before the end of the range, then we're done. michael@0: michael@0: *aSelLength += o2 - entry->mNodeOffset; michael@0: break; michael@0: } michael@0: else michael@0: { michael@0: // The entire entry must be in the range. michael@0: michael@0: *aSelLength += entry->mLength; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: bool michael@0: nsTextServicesDocument::SelectionIsCollapsed() michael@0: { michael@0: return(mSelStartIndex == mSelEndIndex && mSelStartOffset == mSelEndOffset); michael@0: } michael@0: michael@0: bool michael@0: nsTextServicesDocument::SelectionIsValid() michael@0: { michael@0: return(mSelStartIndex >= 0); michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::GetRangeEndPoints(nsIDOMRange *aRange, michael@0: nsIDOMNode **aStartParent, int32_t *aStartOffset, michael@0: nsIDOMNode **aEndParent, int32_t *aEndOffset) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aRange && aStartParent && aStartOffset && aEndParent && aEndOffset, NS_ERROR_NULL_POINTER); michael@0: michael@0: result = aRange->GetStartContainer(aStartParent); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: NS_ENSURE_TRUE(aStartParent, NS_ERROR_FAILURE); michael@0: michael@0: result = aRange->GetStartOffset(aStartOffset); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: result = aRange->GetEndContainer(aEndParent); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: NS_ENSURE_TRUE(aEndParent, NS_ERROR_FAILURE); michael@0: michael@0: result = aRange->GetEndOffset(aEndOffset); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::CreateRange(nsIDOMNode *aStartParent, int32_t aStartOffset, michael@0: nsIDOMNode *aEndParent, int32_t aEndOffset, michael@0: nsIDOMRange **aRange) michael@0: { michael@0: return nsRange::CreateRange(aStartParent, aStartOffset, aEndParent, michael@0: aEndOffset, aRange); michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::FirstTextNode(nsIContentIterator *aIterator, michael@0: TSDIteratorStatus *aIteratorStatus) michael@0: { michael@0: if (aIteratorStatus) michael@0: *aIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: michael@0: aIterator->First(); michael@0: michael@0: while (!aIterator->IsDone()) { michael@0: if (aIterator->GetCurrentNode()->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: if (aIteratorStatus) michael@0: *aIteratorStatus = nsTextServicesDocument::eValid; michael@0: break; michael@0: } michael@0: michael@0: aIterator->Next(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::LastTextNode(nsIContentIterator *aIterator, michael@0: TSDIteratorStatus *aIteratorStatus) michael@0: { michael@0: if (aIteratorStatus) michael@0: *aIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: michael@0: aIterator->Last(); michael@0: michael@0: while (!aIterator->IsDone()) { michael@0: if (aIterator->GetCurrentNode()->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: if (aIteratorStatus) michael@0: *aIteratorStatus = nsTextServicesDocument::eValid; michael@0: break; michael@0: } michael@0: michael@0: aIterator->Prev(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::FirstTextNodeInCurrentBlock(nsIContentIterator *iter) michael@0: { michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); michael@0: michael@0: ClearDidSkip(iter); michael@0: michael@0: nsCOMPtr last; michael@0: michael@0: // Walk backwards over adjacent text nodes until michael@0: // we hit a block boundary: michael@0: michael@0: while (!iter->IsDone()) michael@0: { michael@0: nsCOMPtr content = iter->GetCurrentNode()->IsContent() michael@0: ? iter->GetCurrentNode()->AsContent() michael@0: : nullptr; michael@0: michael@0: if (IsTextNode(content)) michael@0: { michael@0: if (!last || HasSameBlockNodeParent(content, last)) michael@0: last = content; michael@0: else michael@0: { michael@0: // We're done, the current text node is in a michael@0: // different block. michael@0: break; michael@0: } michael@0: } michael@0: else if (last && IsBlockNode(content)) michael@0: break; michael@0: michael@0: iter->Prev(); michael@0: michael@0: if (DidSkip(iter)) michael@0: break; michael@0: } michael@0: michael@0: if (last) michael@0: iter->PositionAt(last); michael@0: michael@0: // XXX: What should we return if last is null? michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::FirstTextNodeInPrevBlock(nsIContentIterator *aIterator) michael@0: { michael@0: nsCOMPtr content; michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aIterator, NS_ERROR_NULL_POINTER); michael@0: michael@0: // XXX: What if mIterator is not currently on a text node? michael@0: michael@0: // Make sure mIterator is pointing to the first text node in the michael@0: // current block: michael@0: michael@0: result = FirstTextNodeInCurrentBlock(aIterator); michael@0: michael@0: NS_ENSURE_SUCCESS(result, NS_ERROR_FAILURE); michael@0: michael@0: // Point mIterator to the first node before the first text node: michael@0: michael@0: aIterator->Prev(); michael@0: michael@0: if (aIterator->IsDone()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Now find the first text node of the next block: michael@0: michael@0: return FirstTextNodeInCurrentBlock(aIterator); michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::FirstTextNodeInNextBlock(nsIContentIterator *aIterator) michael@0: { michael@0: nsCOMPtr prev; michael@0: bool crossedBlockBoundary = false; michael@0: michael@0: NS_ENSURE_TRUE(aIterator, NS_ERROR_NULL_POINTER); michael@0: michael@0: ClearDidSkip(aIterator); michael@0: michael@0: while (!aIterator->IsDone()) michael@0: { michael@0: nsCOMPtr content = aIterator->GetCurrentNode()->IsContent() michael@0: ? aIterator->GetCurrentNode()->AsContent() michael@0: : nullptr; michael@0: michael@0: if (IsTextNode(content)) michael@0: { michael@0: if (!crossedBlockBoundary && (!prev || HasSameBlockNodeParent(prev, content))) michael@0: prev = content; michael@0: else michael@0: break; michael@0: } michael@0: else if (!crossedBlockBoundary && IsBlockNode(content)) michael@0: crossedBlockBoundary = true; michael@0: michael@0: aIterator->Next(); michael@0: michael@0: if (!crossedBlockBoundary && DidSkip(aIterator)) michael@0: crossedBlockBoundary = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::GetFirstTextNodeInPrevBlock(nsIContent **aContent) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aContent = 0; michael@0: michael@0: // Save the iterator's current content node so we can restore michael@0: // it when we are done: michael@0: michael@0: nsINode* node = mIterator->GetCurrentNode(); michael@0: michael@0: result = FirstTextNodeInPrevBlock(mIterator); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: // Try to restore the iterator before returning. michael@0: mIterator->PositionAt(node); michael@0: return result; michael@0: } michael@0: michael@0: if (!mIterator->IsDone()) michael@0: { michael@0: nsCOMPtr current = mIterator->GetCurrentNode()->IsContent() michael@0: ? mIterator->GetCurrentNode()->AsContent() michael@0: : nullptr; michael@0: current.forget(aContent); michael@0: } michael@0: michael@0: // Restore the iterator: michael@0: michael@0: return mIterator->PositionAt(node); michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::GetFirstTextNodeInNextBlock(nsIContent **aContent) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aContent = 0; michael@0: michael@0: // Save the iterator's current content node so we can restore michael@0: // it when we are done: michael@0: michael@0: nsINode* node = mIterator->GetCurrentNode(); michael@0: michael@0: result = FirstTextNodeInNextBlock(mIterator); michael@0: michael@0: if (NS_FAILED(result)) michael@0: { michael@0: // Try to restore the iterator before returning. michael@0: mIterator->PositionAt(node); michael@0: return result; michael@0: } michael@0: michael@0: if (!mIterator->IsDone()) michael@0: { michael@0: nsCOMPtr current = mIterator->GetCurrentNode()->IsContent() michael@0: ? mIterator->GetCurrentNode()->AsContent() michael@0: : nullptr; michael@0: current.forget(aContent); michael@0: } michael@0: michael@0: // Restore the iterator: michael@0: return mIterator->PositionAt(node); michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::CreateOffsetTable(nsTArray *aOffsetTable, michael@0: nsIContentIterator *aIterator, michael@0: TSDIteratorStatus *aIteratorStatus, michael@0: nsIDOMRange *aIterRange, michael@0: nsString *aStr) michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: nsCOMPtr first; michael@0: nsCOMPtr prev; michael@0: michael@0: NS_ENSURE_TRUE(aIterator, NS_ERROR_NULL_POINTER); michael@0: michael@0: ClearOffsetTable(aOffsetTable); michael@0: michael@0: if (aStr) michael@0: aStr->Truncate(); michael@0: michael@0: if (*aIteratorStatus == nsTextServicesDocument::eIsDone) michael@0: return NS_OK; michael@0: michael@0: // If we have an aIterRange, retrieve the endpoints so michael@0: // they can be used in the while loop below to trim entries michael@0: // for text nodes that are partially selected by aIterRange. michael@0: michael@0: nsCOMPtr rngStartNode, rngEndNode; michael@0: int32_t rngStartOffset = 0, rngEndOffset = 0; michael@0: michael@0: if (aIterRange) michael@0: { michael@0: result = GetRangeEndPoints(aIterRange, michael@0: getter_AddRefs(rngStartNode), &rngStartOffset, michael@0: getter_AddRefs(rngEndNode), &rngEndOffset); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: michael@0: // The text service could have added text nodes to the beginning michael@0: // of the current block and called this method again. Make sure michael@0: // we really are at the beginning of the current block: michael@0: michael@0: result = FirstTextNodeInCurrentBlock(aIterator); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: int32_t offset = 0; michael@0: michael@0: ClearDidSkip(aIterator); michael@0: michael@0: while (!aIterator->IsDone()) michael@0: { michael@0: nsCOMPtr content = aIterator->GetCurrentNode()->IsContent() michael@0: ? aIterator->GetCurrentNode()->AsContent() michael@0: : nullptr; michael@0: michael@0: if (IsTextNode(content)) michael@0: { michael@0: if (!prev || HasSameBlockNodeParent(prev, content)) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(content); michael@0: michael@0: if (node) michael@0: { michael@0: nsString str; michael@0: michael@0: result = node->GetNodeValue(str); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: // Add an entry for this text node into the offset table: michael@0: michael@0: OffsetEntry *entry = new OffsetEntry(node, offset, str.Length()); michael@0: aOffsetTable->AppendElement(entry); michael@0: michael@0: // If one or both of the endpoints of the iteration range michael@0: // are in the text node for this entry, make sure the entry michael@0: // only accounts for the portion of the text node that is michael@0: // in the range. michael@0: michael@0: int32_t startOffset = 0; michael@0: int32_t endOffset = str.Length(); michael@0: bool adjustStr = false; michael@0: michael@0: if (entry->mNode == rngStartNode) michael@0: { michael@0: entry->mNodeOffset = startOffset = rngStartOffset; michael@0: adjustStr = true; michael@0: } michael@0: michael@0: if (entry->mNode == rngEndNode) michael@0: { michael@0: endOffset = rngEndOffset; michael@0: adjustStr = true; michael@0: } michael@0: michael@0: if (adjustStr) michael@0: { michael@0: entry->mLength = endOffset - startOffset; michael@0: str = Substring(str, startOffset, entry->mLength); michael@0: } michael@0: michael@0: offset += str.Length(); michael@0: michael@0: if (aStr) michael@0: { michael@0: // Append the text node's string to the output string: michael@0: michael@0: if (!first) michael@0: *aStr = str; michael@0: else michael@0: *aStr += str; michael@0: } michael@0: } michael@0: michael@0: prev = content; michael@0: michael@0: if (!first) michael@0: first = content; michael@0: } michael@0: else michael@0: break; michael@0: michael@0: } michael@0: else if (IsBlockNode(content)) michael@0: break; michael@0: michael@0: aIterator->Next(); michael@0: michael@0: if (DidSkip(aIterator)) michael@0: break; michael@0: } michael@0: michael@0: if (first) michael@0: { michael@0: // Always leave the iterator pointing at the first michael@0: // text node of the current block! michael@0: michael@0: aIterator->PositionAt(first); michael@0: } michael@0: else michael@0: { michael@0: // If we never ran across a text node, the iterator michael@0: // might have been pointing to something invalid to michael@0: // begin with. michael@0: michael@0: *aIteratorStatus = nsTextServicesDocument::eIsDone; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::RemoveInvalidOffsetEntries() michael@0: { michael@0: OffsetEntry *entry; michael@0: int32_t i = 0; michael@0: michael@0: while (uint32_t(i) < mOffsetTable.Length()) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: michael@0: if (!entry->mIsValid) michael@0: { michael@0: mOffsetTable.RemoveElementAt(i); michael@0: michael@0: if (mSelStartIndex >= 0 && mSelStartIndex >= i) michael@0: { michael@0: // We are deleting an entry that comes before michael@0: // mSelStartIndex, decrement mSelStartIndex so michael@0: // that it points to the correct entry! michael@0: michael@0: NS_ASSERTION(i != mSelStartIndex, "Invalid selection index."); michael@0: michael@0: --mSelStartIndex; michael@0: --mSelEndIndex; michael@0: } michael@0: } michael@0: else michael@0: i++; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::ClearOffsetTable(nsTArray *aOffsetTable) michael@0: { michael@0: uint32_t i; michael@0: michael@0: for (i = 0; i < aOffsetTable->Length(); i++) michael@0: { michael@0: delete aOffsetTable->ElementAt(i); michael@0: } michael@0: michael@0: aOffsetTable->Clear(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::SplitOffsetEntry(int32_t aTableIndex, int32_t aNewEntryLength) michael@0: { michael@0: OffsetEntry *entry = mOffsetTable[aTableIndex]; michael@0: michael@0: NS_ASSERTION((aNewEntryLength > 0), "aNewEntryLength <= 0"); michael@0: NS_ASSERTION((aNewEntryLength < entry->mLength), "aNewEntryLength >= mLength"); michael@0: michael@0: if (aNewEntryLength < 1 || aNewEntryLength >= entry->mLength) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: int32_t oldLength = entry->mLength - aNewEntryLength; michael@0: michael@0: OffsetEntry *newEntry = new OffsetEntry(entry->mNode, michael@0: entry->mStrOffset + oldLength, michael@0: aNewEntryLength); michael@0: michael@0: if (!mOffsetTable.InsertElementAt(aTableIndex + 1, newEntry)) michael@0: { michael@0: delete newEntry; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Adjust entry fields: michael@0: michael@0: entry->mLength = oldLength; michael@0: newEntry->mNodeOffset = entry->mNodeOffset + oldLength; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::NodeHasOffsetEntry(nsTArray *aOffsetTable, nsIDOMNode *aNode, bool *aHasEntry, int32_t *aEntryIndex) michael@0: { michael@0: OffsetEntry *entry; michael@0: uint32_t i; michael@0: michael@0: NS_ENSURE_TRUE(aNode && aHasEntry && aEntryIndex, NS_ERROR_NULL_POINTER); michael@0: michael@0: for (i = 0; i < aOffsetTable->Length(); i++) michael@0: { michael@0: entry = (*aOffsetTable)[i]; michael@0: michael@0: NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); michael@0: michael@0: if (entry->mNode == aNode) michael@0: { michael@0: *aHasEntry = true; michael@0: *aEntryIndex = i; michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: *aHasEntry = false; michael@0: *aEntryIndex = -1; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Spellchecker code has this. See bug 211343 michael@0: #define IS_NBSP_CHAR(c) (((unsigned char)0xa0)==(c)) michael@0: michael@0: nsresult michael@0: nsTextServicesDocument::FindWordBounds(nsTArray *aOffsetTable, michael@0: nsString *aBlockStr, michael@0: nsIDOMNode *aNode, michael@0: int32_t aNodeOffset, michael@0: nsIDOMNode **aWordStartNode, michael@0: int32_t *aWordStartOffset, michael@0: nsIDOMNode **aWordEndNode, michael@0: int32_t *aWordEndOffset) michael@0: { michael@0: // Initialize return values. michael@0: michael@0: if (aWordStartNode) michael@0: *aWordStartNode = nullptr; michael@0: if (aWordStartOffset) michael@0: *aWordStartOffset = 0; michael@0: if (aWordEndNode) michael@0: *aWordEndNode = nullptr; michael@0: if (aWordEndOffset) michael@0: *aWordEndOffset = 0; michael@0: michael@0: int32_t entryIndex = 0; michael@0: bool hasEntry = false; michael@0: michael@0: // It's assumed that aNode is a text node. The first thing michael@0: // we do is get its index in the offset table so we can michael@0: // calculate the dom point's string offset. michael@0: michael@0: nsresult result = NodeHasOffsetEntry(aOffsetTable, aNode, &hasEntry, &entryIndex); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: NS_ENSURE_TRUE(hasEntry, NS_ERROR_FAILURE); michael@0: michael@0: // Next we map aNodeOffset into a string offset. michael@0: michael@0: OffsetEntry *entry = (*aOffsetTable)[entryIndex]; michael@0: uint32_t strOffset = entry->mStrOffset + aNodeOffset - entry->mNodeOffset; michael@0: michael@0: // Now we use the word breaker to find the beginning and end michael@0: // of the word from our calculated string offset. michael@0: michael@0: const char16_t *str = aBlockStr->get(); michael@0: uint32_t strLen = aBlockStr->Length(); michael@0: michael@0: nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker(); michael@0: nsWordRange res = wordBreaker->FindWord(str, strLen, strOffset); michael@0: if (res.mBegin > strLen) { michael@0: return str ? NS_ERROR_ILLEGAL_VALUE : NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: // Strip out the NBSPs at the ends michael@0: while ((res.mBegin <= res.mEnd) && (IS_NBSP_CHAR(str[res.mBegin]))) michael@0: res.mBegin++; michael@0: if (str[res.mEnd] == (unsigned char)0x20) michael@0: { michael@0: uint32_t realEndWord = res.mEnd - 1; michael@0: while ((realEndWord > res.mBegin) && (IS_NBSP_CHAR(str[realEndWord]))) michael@0: realEndWord--; michael@0: if (realEndWord < res.mEnd - 1) michael@0: res.mEnd = realEndWord + 1; michael@0: } michael@0: michael@0: // Now that we have the string offsets for the beginning michael@0: // and end of the word, run through the offset table and michael@0: // convert them back into dom points. michael@0: michael@0: int32_t i, lastIndex = aOffsetTable->Length() - 1; michael@0: michael@0: for (i=0; i <= lastIndex; i++) michael@0: { michael@0: entry = (*aOffsetTable)[i]; michael@0: michael@0: int32_t strEndOffset = entry->mStrOffset + entry->mLength; michael@0: michael@0: // Check to see if res.mBegin is within the range covered michael@0: // by this entry. Note that if res.mBegin is after the last michael@0: // character covered by this entry, we will use the next michael@0: // entry if there is one. michael@0: michael@0: if (uint32_t(entry->mStrOffset) <= res.mBegin && michael@0: (res.mBegin < uint32_t(strEndOffset) || michael@0: (res.mBegin == uint32_t(strEndOffset) && i == lastIndex))) michael@0: { michael@0: if (aWordStartNode) michael@0: { michael@0: *aWordStartNode = entry->mNode; michael@0: NS_IF_ADDREF(*aWordStartNode); michael@0: } michael@0: michael@0: if (aWordStartOffset) michael@0: *aWordStartOffset = entry->mNodeOffset + res.mBegin - entry->mStrOffset; michael@0: michael@0: if (!aWordEndNode && !aWordEndOffset) michael@0: { michael@0: // We've found our start entry, but if we're not looking michael@0: // for end entries, we're done. michael@0: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Check to see if res.mEnd is within the range covered michael@0: // by this entry. michael@0: michael@0: if (uint32_t(entry->mStrOffset) <= res.mEnd && res.mEnd <= uint32_t(strEndOffset)) michael@0: { michael@0: if (res.mBegin == res.mEnd && res.mEnd == uint32_t(strEndOffset) && i != lastIndex) michael@0: { michael@0: // Wait for the next round so that we use the same entry michael@0: // we did for aWordStartNode. michael@0: michael@0: continue; michael@0: } michael@0: michael@0: if (aWordEndNode) michael@0: { michael@0: *aWordEndNode = entry->mNode; michael@0: NS_IF_ADDREF(*aWordEndNode); michael@0: } michael@0: michael@0: if (aWordEndOffset) michael@0: *aWordEndOffset = entry->mNodeOffset + res.mEnd - entry->mStrOffset; michael@0: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef DEBUG_kin michael@0: void michael@0: nsTextServicesDocument::PrintOffsetTable() michael@0: { michael@0: OffsetEntry *entry; michael@0: uint32_t i; michael@0: michael@0: for (i = 0; i < mOffsetTable.Length(); i++) michael@0: { michael@0: entry = mOffsetTable[i]; michael@0: printf("ENTRY %4d: %p %c %c %4d %4d %4d\n", michael@0: i, entry->mNode, entry->mIsValid ? 'V' : 'N', michael@0: entry->mIsInsertedText ? 'I' : 'B', michael@0: entry->mNodeOffset, entry->mStrOffset, entry->mLength); michael@0: } michael@0: michael@0: fflush(stdout); michael@0: } michael@0: michael@0: void michael@0: nsTextServicesDocument::PrintContentNode(nsIContent *aContent) michael@0: { michael@0: nsString tmpStr, str; michael@0: michael@0: aContent->Tag()->ToString(tmpStr); michael@0: printf("%s", NS_LossyConvertUTF16toASCII(tmpStr).get()); michael@0: michael@0: if (nsIDOMNode::TEXT_NODE == aContent->NodeType()) michael@0: { michael@0: aContent->AppendTextTo(str); michael@0: printf(": \"%s\"", NS_LossyConvertUTF16toASCII(str).get()); michael@0: } michael@0: michael@0: printf("\n"); michael@0: fflush(stdout); michael@0: } michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::WillInsertNode(nsIDOMNode *aNode, michael@0: nsIDOMNode *aParent, michael@0: int32_t aPosition) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::WillDeleteNode(nsIDOMNode *aChild) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::WillSplitNode(nsIDOMNode *aExistingRightNode, michael@0: int32_t aOffset) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::WillJoinNodes(nsIDOMNode *aLeftNode, michael@0: nsIDOMNode *aRightNode, michael@0: nsIDOMNode *aParent) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // ------------------------------- michael@0: // stubs for unused listen methods michael@0: // ------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, int32_t aPosition) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::DidCreateNode(const nsAString& aTag, nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::DidInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::DidDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::WillDeleteSelection(nsISelection *aSelection) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextServicesDocument::DidDeleteSelection(nsISelection *aSelection) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: