diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /editor/txtsvc | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'editor/txtsvc')
-rw-r--r-- | editor/txtsvc/moz.build | 26 | ||||
-rw-r--r-- | editor/txtsvc/nsFilteredContentIterator.cpp | 422 | ||||
-rw-r--r-- | editor/txtsvc/nsFilteredContentIterator.h | 75 | ||||
-rw-r--r-- | editor/txtsvc/nsIInlineSpellChecker.idl | 50 | ||||
-rw-r--r-- | editor/txtsvc/nsISpellChecker.h | 122 | ||||
-rw-r--r-- | editor/txtsvc/nsITextService.h | 42 | ||||
-rw-r--r-- | editor/txtsvc/nsITextServicesDocument.h | 185 | ||||
-rw-r--r-- | editor/txtsvc/nsITextServicesFilter.idl | 21 | ||||
-rw-r--r-- | editor/txtsvc/nsTSAtomList.h | 51 | ||||
-rw-r--r-- | editor/txtsvc/nsTextServicesCID.h | 19 | ||||
-rw-r--r-- | editor/txtsvc/nsTextServicesDocument.cpp | 3499 | ||||
-rw-r--r-- | editor/txtsvc/nsTextServicesDocument.h | 236 | ||||
-rw-r--r-- | editor/txtsvc/nsTextServicesFactory.cpp | 31 |
13 files changed, 4779 insertions, 0 deletions
diff --git a/editor/txtsvc/moz.build b/editor/txtsvc/moz.build new file mode 100644 index 000000000..4c0b93419 --- /dev/null +++ b/editor/txtsvc/moz.build @@ -0,0 +1,26 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsIInlineSpellChecker.idl', + 'nsITextServicesFilter.idl', +] + +XPIDL_MODULE = 'txtsvc' + +EXPORTS += [ + 'nsISpellChecker.h', + 'nsITextService.h', + 'nsITextServicesDocument.h', + 'nsTextServicesCID.h', +] + +UNIFIED_SOURCES += [ + 'nsFilteredContentIterator.cpp', + 'nsTextServicesDocument.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/editor/txtsvc/nsFilteredContentIterator.cpp b/editor/txtsvc/nsFilteredContentIterator.cpp new file mode 100644 index 000000000..c8ea734c4 --- /dev/null +++ b/editor/txtsvc/nsFilteredContentIterator.cpp @@ -0,0 +1,422 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mozalloc.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsFilteredContentIterator.h" +#include "nsIAtom.h" +#include "nsIContent.h" +#include "nsIContentIterator.h" +#include "nsIDOMNode.h" +#include "nsINode.h" +#include "nsISupportsBase.h" +#include "nsISupportsUtils.h" +#include "nsITextServicesFilter.h" +#include "nsRange.h" + +//------------------------------------------------------------ +nsFilteredContentIterator::nsFilteredContentIterator(nsITextServicesFilter* aFilter) : + mFilter(aFilter), + mDidSkip(false), + mIsOutOfRange(false), + mDirection(eDirNotSet) +{ + mIterator = do_CreateInstance("@mozilla.org/content/post-content-iterator;1"); + mPreIterator = do_CreateInstance("@mozilla.org/content/pre-content-iterator;1"); +} + +//------------------------------------------------------------ +nsFilteredContentIterator::~nsFilteredContentIterator() +{ +} + +//------------------------------------------------------------ +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFilteredContentIterator) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFilteredContentIterator) + +NS_INTERFACE_MAP_BEGIN(nsFilteredContentIterator) + NS_INTERFACE_MAP_ENTRY(nsIContentIterator) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentIterator) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsFilteredContentIterator) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(nsFilteredContentIterator, + mCurrentIterator, + mIterator, + mPreIterator, + mFilter, + mRange) + +//------------------------------------------------------------ +nsresult +nsFilteredContentIterator::Init(nsINode* aRoot) +{ + NS_ENSURE_ARG_POINTER(aRoot); + NS_ENSURE_TRUE(mPreIterator, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); + mIsOutOfRange = false; + mDirection = eForward; + mCurrentIterator = mPreIterator; + + mRange = new nsRange(aRoot); + nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(aRoot)); + if (domNode) { + mRange->SelectNode(domNode); + } + + nsresult rv = mPreIterator->Init(mRange); + NS_ENSURE_SUCCESS(rv, rv); + return mIterator->Init(mRange); +} + +//------------------------------------------------------------ +nsresult +nsFilteredContentIterator::Init(nsIDOMRange* aRange) +{ + NS_ENSURE_TRUE(mPreIterator, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); + NS_ENSURE_ARG_POINTER(aRange); + mIsOutOfRange = false; + mDirection = eForward; + mCurrentIterator = mPreIterator; + + mRange = static_cast<nsRange*>(aRange)->CloneRange(); + + nsresult rv = mPreIterator->Init(mRange); + NS_ENSURE_SUCCESS(rv, rv); + return mIterator->Init(mRange); +} + +//------------------------------------------------------------ +nsresult +nsFilteredContentIterator::SwitchDirections(bool aChangeToForward) +{ + nsINode *node = mCurrentIterator->GetCurrentNode(); + + if (aChangeToForward) { + mCurrentIterator = mPreIterator; + mDirection = eForward; + } else { + mCurrentIterator = mIterator; + mDirection = eBackward; + } + + if (node) { + nsresult rv = mCurrentIterator->PositionAt(node); + if (NS_FAILED(rv)) { + mIsOutOfRange = true; + return rv; + } + } + return NS_OK; +} + +//------------------------------------------------------------ +void +nsFilteredContentIterator::First() +{ + if (!mCurrentIterator) { + NS_ERROR("Missing iterator!"); + + return; + } + + // If we are switching directions then + // we need to switch how we process the nodes + if (mDirection != eForward) { + mCurrentIterator = mPreIterator; + mDirection = eForward; + mIsOutOfRange = false; + } + + mCurrentIterator->First(); + + if (mCurrentIterator->IsDone()) { + return; + } + + nsINode *currentNode = mCurrentIterator->GetCurrentNode(); + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(currentNode)); + + bool didCross; + CheckAdvNode(node, didCross, eForward); +} + +//------------------------------------------------------------ +void +nsFilteredContentIterator::Last() +{ + if (!mCurrentIterator) { + NS_ERROR("Missing iterator!"); + + return; + } + + // If we are switching directions then + // we need to switch how we process the nodes + if (mDirection != eBackward) { + mCurrentIterator = mIterator; + mDirection = eBackward; + mIsOutOfRange = false; + } + + mCurrentIterator->Last(); + + if (mCurrentIterator->IsDone()) { + return; + } + + nsINode *currentNode = mCurrentIterator->GetCurrentNode(); + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(currentNode)); + + bool didCross; + CheckAdvNode(node, didCross, eBackward); +} + +/////////////////////////////////////////////////////////////////////////// +// ContentToParentOffset: returns the content node's parent and offset. +// +static void +ContentToParentOffset(nsIContent *aContent, nsIDOMNode **aParent, + int32_t *aOffset) +{ + if (!aParent || !aOffset) + return; + + *aParent = nullptr; + *aOffset = 0; + + if (!aContent) + return; + + nsIContent* parent = aContent->GetParent(); + + if (!parent) + return; + + *aOffset = parent->IndexOf(aContent); + + CallQueryInterface(parent, aParent); +} + +/////////////////////////////////////////////////////////////////////////// +// ContentIsInTraversalRange: returns true if content is visited during +// the traversal of the range in the specified mode. +// +static bool +ContentIsInTraversalRange(nsIContent *aContent, bool aIsPreMode, + nsIDOMNode *aStartNode, int32_t aStartOffset, + nsIDOMNode *aEndNode, int32_t aEndOffset) +{ + NS_ENSURE_TRUE(aStartNode && aEndNode && aContent, false); + + nsCOMPtr<nsIDOMNode> parentNode; + int32_t indx = 0; + + ContentToParentOffset(aContent, getter_AddRefs(parentNode), &indx); + + NS_ENSURE_TRUE(parentNode, false); + + if (!aIsPreMode) + ++indx; + + int32_t startRes = nsContentUtils::ComparePoints(aStartNode, aStartOffset, + parentNode, indx); + int32_t endRes = nsContentUtils::ComparePoints(aEndNode, aEndOffset, + parentNode, indx); + return (startRes <= 0) && (endRes >= 0); +} + +static bool +ContentIsInTraversalRange(nsRange* aRange, nsIDOMNode* aNextNode, bool aIsPreMode) +{ + nsCOMPtr<nsIContent> content(do_QueryInterface(aNextNode)); + NS_ENSURE_TRUE(content && aRange, false); + + nsCOMPtr<nsIDOMNode> sNode; + nsCOMPtr<nsIDOMNode> eNode; + int32_t sOffset; + int32_t eOffset; + aRange->GetStartContainer(getter_AddRefs(sNode)); + aRange->GetStartOffset(&sOffset); + aRange->GetEndContainer(getter_AddRefs(eNode)); + aRange->GetEndOffset(&eOffset); + return ContentIsInTraversalRange(content, aIsPreMode, sNode, sOffset, eNode, eOffset); +} + +//------------------------------------------------------------ +// Helper function to advance to the next or previous node +nsresult +nsFilteredContentIterator::AdvanceNode(nsIDOMNode* aNode, nsIDOMNode*& aNewNode, eDirectionType aDir) +{ + nsCOMPtr<nsIDOMNode> nextNode; + if (aDir == eForward) { + aNode->GetNextSibling(getter_AddRefs(nextNode)); + } else { + aNode->GetPreviousSibling(getter_AddRefs(nextNode)); + } + + if (nextNode) { + // If we got here, that means we found the nxt/prv node + // make sure it is in our DOMRange + bool intersects = ContentIsInTraversalRange(mRange, nextNode, aDir == eForward); + if (intersects) { + aNewNode = nextNode; + NS_ADDREF(aNewNode); + return NS_OK; + } + } else { + // The next node was null so we need to walk up the parent(s) + nsCOMPtr<nsIDOMNode> parent; + aNode->GetParentNode(getter_AddRefs(parent)); + NS_ASSERTION(parent, "parent can't be nullptr"); + + // Make sure the parent is in the DOMRange before going further + bool intersects = ContentIsInTraversalRange(mRange, nextNode, aDir == eForward); + if (intersects) { + // Now find the nxt/prv node after/before this node + nsresult rv = AdvanceNode(parent, aNewNode, aDir); + if (NS_SUCCEEDED(rv) && aNewNode) { + return NS_OK; + } + } + } + + // if we get here it pretty much means + // we went out of the DOM Range + mIsOutOfRange = true; + + return NS_ERROR_FAILURE; +} + +//------------------------------------------------------------ +// Helper function to see if the next/prev node should be skipped +void +nsFilteredContentIterator::CheckAdvNode(nsIDOMNode* aNode, bool& aDidSkip, eDirectionType aDir) +{ + aDidSkip = false; + mIsOutOfRange = false; + + if (aNode && mFilter) { + nsCOMPtr<nsIDOMNode> currentNode = aNode; + bool skipIt; + while (1) { + nsresult rv = mFilter->Skip(aNode, &skipIt); + if (NS_SUCCEEDED(rv) && skipIt) { + aDidSkip = true; + // Get the next/prev node and then + // see if we should skip that + nsCOMPtr<nsIDOMNode> advNode; + rv = AdvanceNode(aNode, *getter_AddRefs(advNode), aDir); + if (NS_SUCCEEDED(rv) && advNode) { + aNode = advNode; + } else { + return; // fell out of range + } + } else { + if (aNode != currentNode) { + nsCOMPtr<nsIContent> content(do_QueryInterface(aNode)); + mCurrentIterator->PositionAt(content); + } + return; // found something + } + } + } +} + +void +nsFilteredContentIterator::Next() +{ + if (mIsOutOfRange || !mCurrentIterator) { + NS_ASSERTION(mCurrentIterator, "Missing iterator!"); + + return; + } + + // If we are switching directions then + // we need to switch how we process the nodes + if (mDirection != eForward) { + nsresult rv = SwitchDirections(true); + if (NS_FAILED(rv)) { + return; + } + } + + mCurrentIterator->Next(); + + if (mCurrentIterator->IsDone()) { + return; + } + + // If we can't get the current node then + // don't check to see if we can skip it + nsINode *currentNode = mCurrentIterator->GetCurrentNode(); + + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(currentNode)); + CheckAdvNode(node, mDidSkip, eForward); +} + +void +nsFilteredContentIterator::Prev() +{ + if (mIsOutOfRange || !mCurrentIterator) { + NS_ASSERTION(mCurrentIterator, "Missing iterator!"); + + return; + } + + // If we are switching directions then + // we need to switch how we process the nodes + if (mDirection != eBackward) { + nsresult rv = SwitchDirections(false); + if (NS_FAILED(rv)) { + return; + } + } + + mCurrentIterator->Prev(); + + if (mCurrentIterator->IsDone()) { + return; + } + + // If we can't get the current node then + // don't check to see if we can skip it + nsINode *currentNode = mCurrentIterator->GetCurrentNode(); + + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(currentNode)); + CheckAdvNode(node, mDidSkip, eBackward); +} + +nsINode * +nsFilteredContentIterator::GetCurrentNode() +{ + if (mIsOutOfRange || !mCurrentIterator) { + return nullptr; + } + + return mCurrentIterator->GetCurrentNode(); +} + +bool +nsFilteredContentIterator::IsDone() +{ + if (mIsOutOfRange || !mCurrentIterator) { + return true; + } + + return mCurrentIterator->IsDone(); +} + +nsresult +nsFilteredContentIterator::PositionAt(nsINode* aCurNode) +{ + NS_ENSURE_TRUE(mCurrentIterator, NS_ERROR_FAILURE); + mIsOutOfRange = false; + return mCurrentIterator->PositionAt(aCurNode); +} diff --git a/editor/txtsvc/nsFilteredContentIterator.h b/editor/txtsvc/nsFilteredContentIterator.h new file mode 100644 index 000000000..9dde8132f --- /dev/null +++ b/editor/txtsvc/nsFilteredContentIterator.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsFilteredContentIterator_h__ +#define nsFilteredContentIterator_h__ + +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIContentIterator.h" +#include "nsISupportsImpl.h" +#include "nscore.h" + +class nsIAtom; +class nsIDOMNode; +class nsIDOMRange; +class nsINode; +class nsITextServicesFilter; +class nsRange; + +class nsFilteredContentIterator final : public nsIContentIterator +{ +public: + + // nsISupports interface... + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsFilteredContentIterator) + + explicit nsFilteredContentIterator(nsITextServicesFilter* aFilter); + + /* nsIContentIterator */ + virtual nsresult Init(nsINode* aRoot) override; + virtual nsresult Init(nsIDOMRange* aRange) override; + virtual void First() override; + virtual void Last() override; + virtual void Next() override; + virtual void Prev() override; + virtual nsINode *GetCurrentNode() override; + virtual bool IsDone() override; + virtual nsresult PositionAt(nsINode* aCurNode) override; + + /* Helpers */ + bool DidSkip() { return mDidSkip; } + void ClearDidSkip() { mDidSkip = false; } + +protected: + nsFilteredContentIterator() : mDidSkip(false), mIsOutOfRange(false) { } + + virtual ~nsFilteredContentIterator(); + + // enum to give us the direction + typedef enum {eDirNotSet, eForward, eBackward} eDirectionType; + nsresult AdvanceNode(nsIDOMNode* aNode, nsIDOMNode*& aNewNode, eDirectionType aDir); + void CheckAdvNode(nsIDOMNode* aNode, bool& aDidSkip, eDirectionType aDir); + nsresult SwitchDirections(bool aChangeToForward); + + nsCOMPtr<nsIContentIterator> mCurrentIterator; + nsCOMPtr<nsIContentIterator> mIterator; + nsCOMPtr<nsIContentIterator> mPreIterator; + + nsCOMPtr<nsIAtom> mBlockQuoteAtom; + nsCOMPtr<nsIAtom> mScriptAtom; + nsCOMPtr<nsIAtom> mTextAreaAtom; + nsCOMPtr<nsIAtom> mSelectAreaAtom; + nsCOMPtr<nsIAtom> mMapAtom; + + nsCOMPtr<nsITextServicesFilter> mFilter; + RefPtr<nsRange> mRange; + bool mDidSkip; + bool mIsOutOfRange; + eDirectionType mDirection; +}; + +#endif diff --git a/editor/txtsvc/nsIInlineSpellChecker.idl b/editor/txtsvc/nsIInlineSpellChecker.idl new file mode 100644 index 000000000..62ea06bfc --- /dev/null +++ b/editor/txtsvc/nsIInlineSpellChecker.idl @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "domstubs.idl" + +interface nsISelection; +interface nsIEditor; +interface nsIEditorSpellCheck; + +[scriptable, uuid(b7b7a77c-40c4-4196-b0b7-b0338243b3fe)] +interface nsIInlineSpellChecker : nsISupports +{ + readonly attribute nsIEditorSpellCheck spellChecker; + + void init(in nsIEditor aEditor); + void cleanup(in boolean aDestroyingFrames); + + attribute boolean enableRealTimeSpell; + + void spellCheckAfterEditorChange(in long aAction, + in nsISelection aSelection, + in nsIDOMNode aPreviousSelectedNode, + in long aPreviousSelectedOffset, + in nsIDOMNode aStartNode, + in long aStartOffset, + in nsIDOMNode aEndNode, + in long aEndOffset); + + void spellCheckRange(in nsIDOMRange aSelection); + + nsIDOMRange getMisspelledWord(in nsIDOMNode aNode, in long aOffset); + void replaceWord(in nsIDOMNode aNode, in long aOffset, in AString aNewword); + void addWordToDictionary(in AString aWord); + void removeWordFromDictionary(in AString aWord); + + void ignoreWord(in AString aWord); + void ignoreWords([array, size_is(aCount)] in wstring aWordsToIgnore, in unsigned long aCount); + void updateCurrentDictionary(); + + readonly attribute boolean spellCheckPending; +}; + +%{C++ + +#define MOZ_INLINESPELLCHECKER_CONTRACTID "@mozilla.org/spellchecker-inline;1" + +%} diff --git a/editor/txtsvc/nsISpellChecker.h b/editor/txtsvc/nsISpellChecker.h new file mode 100644 index 000000000..cafc725be --- /dev/null +++ b/editor/txtsvc/nsISpellChecker.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsISpellChecker_h__ +#define nsISpellChecker_h__ + +#include "nsISupports.h" +#include "nsTArray.h" + +#define NS_SPELLCHECKER_CONTRACTID "@mozilla.org/spellchecker;1" + +#define NS_ISPELLCHECKER_IID \ +{ /* 27bff957-b486-40ae-9f5d-af0cdd211868 */ \ +0x27bff957, 0xb486, 0x40ae, \ + { 0x9f, 0x5d, 0xaf, 0x0c, 0xdd, 0x21, 0x18, 0x68 } } + +class nsITextServicesDocument; +class nsString; + +/** + * A generic interface for a spelling checker. + */ +class nsISpellChecker : public nsISupports{ +public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISPELLCHECKER_IID) + + /** + * Tells the spellchecker what document to check. + * @param aDoc is the document to check. + * @param aFromStartOfDoc If true, start check from beginning of document, + * if false, start check from current cursor position. + */ + NS_IMETHOD SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc) = 0; + + /** + * Selects (hilites) the next misspelled word in the document. + * @param aWord will contain the misspelled word. + * @param aSuggestions is an array of nsStrings, that represent the + * suggested replacements for the misspelled word. + */ + NS_IMETHOD NextMisspelledWord(nsAString &aWord, nsTArray<nsString> *aSuggestions) = 0; + + /** + * Checks if a word is misspelled. No document is required to use this method. + * @param aWord is the word to check. + * @param aIsMisspelled will be set to true if the word is misspelled. + * @param aSuggestions is an array of nsStrings which represent the + * suggested replacements for the misspelled word. The array will be empty + * if there aren't any suggestions. + */ + NS_IMETHOD CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions) = 0; + + /** + * Replaces the old word with the specified new word. + * @param aOldWord is the word to be replaced. + * @param aNewWord is the word that is to replace old word. + * @param aAllOccurrences will replace all occurrences of old + * word, in the document, with new word when it is true. If + * false, it will replace the 1st occurrence only! + */ + NS_IMETHOD Replace(const nsAString &aOldWord, const nsAString &aNewWord, bool aAllOccurrences) = 0; + + /** + * Ignores all occurrences of the specified word in the document. + * @param aWord is the word to ignore. + */ + NS_IMETHOD IgnoreAll(const nsAString &aWord) = 0; + + /** + * Add a word to the user's personal dictionary. + * @param aWord is the word to add. + */ + NS_IMETHOD AddWordToPersonalDictionary(const nsAString &aWord) = 0; + + /** + * Remove a word from the user's personal dictionary. + * @param aWord is the word to remove. + */ + NS_IMETHOD RemoveWordFromPersonalDictionary(const nsAString &aWord) = 0; + + /** + * Returns the list of words in the user's personal dictionary. + * @param aWordList is an array of nsStrings that represent the + * list of words in the user's personal dictionary. + */ + NS_IMETHOD GetPersonalDictionary(nsTArray<nsString> *aWordList) = 0; + + /** + * Returns the list of strings representing the dictionaries + * the spellchecker supports. It was suggested that the strings + * returned be in the RFC 1766 format. This format looks something + * like <ISO 639 language code>-<ISO 3166 country code>. + * For example: en-US + * @param aDictionaryList is an array of nsStrings that represent the + * dictionaries supported by the spellchecker. + */ + NS_IMETHOD GetDictionaryList(nsTArray<nsString> *aDictionaryList) = 0; + + /** + * Returns a string representing the current dictionary. + * @param aDictionary will contain the name of the dictionary. + * This name is the same string that is in the list returned + * by GetDictionaryList(). + */ + NS_IMETHOD GetCurrentDictionary(nsAString &aDictionary) = 0; + + /** + * Tells the spellchecker to use a specific dictionary. + * @param aDictionary a string that is in the list returned + * by GetDictionaryList() or an empty string. If aDictionary is + * empty string, spellchecker will be disabled. + */ + NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsISpellChecker, NS_ISPELLCHECKER_IID) + +#endif // nsISpellChecker_h__ + diff --git a/editor/txtsvc/nsITextService.h b/editor/txtsvc/nsITextService.h new file mode 100644 index 000000000..13ffe3657 --- /dev/null +++ b/editor/txtsvc/nsITextService.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsITextService_h__ +#define nsITextService_h__ + +#include "nsISupports.h" + +class nsITextServicesDocument; + +/* +TextService interface to outside world +*/ + +#define NS_ITEXTSERVICE_IID \ +{ /* 019718E0-CDB5-11d2-8D3C-000000000000 */ \ +0x019718e0, 0xcdb5, 0x11d2, \ +{ 0x8d, 0x3c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } } + + +/** + * + */ +class nsITextService : public nsISupports{ +public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITEXTSERVICE_IID) + + /** + * + */ + NS_IMETHOD Init(nsITextServicesDocument *aDoc) = 0; + NS_IMETHOD Execute() = 0; + NS_IMETHOD GetMenuString(nsString &aString) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITextService, NS_ITEXTSERVICE_IID) + +#endif // nsITextService_h__ + diff --git a/editor/txtsvc/nsITextServicesDocument.h b/editor/txtsvc/nsITextServicesDocument.h new file mode 100644 index 000000000..8b9d9e506 --- /dev/null +++ b/editor/txtsvc/nsITextServicesDocument.h @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsITextServicesDocument_h__ +#define nsITextServicesDocument_h__ + +#include "nsISupports.h" + +class nsIDOMDocument; +class nsIDOMRange; +class nsIEditor; +class nsString; +class nsITextServicesFilter; + +/* +TextServicesDocument interface to outside world +*/ + +#define NS_ITEXTSERVICESDOCUMENT_IID \ +{ /* 019718E1-CDB5-11d2-8D3C-000000000000 */ \ +0x019718e1, 0xcdb5, 0x11d2, \ +{ 0x8d, 0x3c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } } + + +/** + * The nsITextServicesDocument presents the document in as a + * bunch of flattened text blocks. Each text block can be retrieved + * as an nsString (array of characters). + */ +class nsITextServicesDocument : public nsISupports{ +public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITEXTSERVICESDOCUMENT_IID) + + typedef enum { eDSNormal=0, eDSUndlerline } TSDDisplayStyle; + + typedef enum { eBlockNotFound=0, // There is no text block (TB) in or before the selection (S). + eBlockOutside, // No TB in S, but found one before/after S. + eBlockInside, // S extends beyond the start and end of TB. + eBlockContains, // TB contains entire S. + eBlockPartial // S begins or ends in TB but extends outside of TB. + } TSDBlockSelectionStatus; + + /** + * Get the DOM document for the document in use. + * @return aDocument the dom document [OUT] + */ + NS_IMETHOD GetDocument(nsIDOMDocument **aDocument) = 0; + + /** + * Initializes the text services document to use a particular + * editor. The text services document will use the DOM document + * and presentation shell used by the editor. + * @param aEditor is the editor to use. The editor is AddRef'd + * by this method. + */ + NS_IMETHOD InitWithEditor(nsIEditor *aEditor) = 0; + + /** + * Sets the range/extent over which the text services document + * will iterate. Note that InitWithEditor() should have been called prior to + * calling this method. If this method is never called, the text services + * defaults to iterating over the entire document. + * + * @param aDOMRange is the range to use. aDOMRange must point to a + * valid range object. + */ + NS_IMETHOD SetExtent(nsIDOMRange* aDOMRange) = 0; + + /** + * Expands the end points of the range so that it spans complete words. + * This call does not change any internal state of the text services document. + * + * @param aDOMRange the range to be expanded/adjusted. + */ + NS_IMETHOD ExpandRangeToWordBoundaries(nsIDOMRange *aRange) = 0; + + /** + * Sets the filter to be used while iterating over content. + * @param aFilter filter to be used while iterating over content. + */ + NS_IMETHOD SetFilter(nsITextServicesFilter *aFilter) = 0; + + /** + * Returns the text in the current text block. + * @param aStr will contain the text. + */ + + NS_IMETHOD GetCurrentTextBlock(nsString *aStr) = 0; + + /** + * Tells the document to point to the first text block + * in the document. This method does not adjust the current + * cursor position or selection. + */ + + NS_IMETHOD FirstBlock() = 0; + + /** + * Tells the document to point to the last text block that + * contains the current selection or caret. + * @param aSelectionStatus will contain the text block selection status + * @param aSelectionOffset will contain the offset into the + * string returned by GetCurrentTextBlock() where the selection + * begins. + * @param aLength will contain the number of characters that are + * selected in the string. + */ + + NS_IMETHOD LastSelectedBlock(TSDBlockSelectionStatus *aSelectionStatus, int32_t *aSelectionOffset, int32_t *aSelectionLength) = 0; + + /** + * Tells the document to point to the text block before + * the current one. This method will return NS_OK, even + * if there is no previous block. Callers should call IsDone() + * to check if we have gone beyond the first text block in + * the document. + */ + + NS_IMETHOD PrevBlock() = 0; + + /** + * Tells the document to point to the text block after + * the current one. This method will return NS_OK, even + * if there is no next block. Callers should call IsDone() + * to check if we have gone beyond the last text block + * in the document. + */ + + NS_IMETHOD NextBlock() = 0; + + /** + * IsDone() will always set aIsDone == false unless + * the document contains no text, PrevBlock() was called + * while the document was already pointing to the first + * text block in the document, or NextBlock() was called + * while the document was already pointing to the last + * text block in the document. + * @param aIsDone will contain the result. + */ + + NS_IMETHOD IsDone(bool *aIsDone) = 0; + + /** + * SetSelection() allows the caller to set the selection + * based on an offset into the string returned by + * GetCurrentTextBlock(). A length of zero places the cursor + * at that offset. A positive non-zero length "n" selects + * n characters in the string. + * @param aOffset offset into string returned by GetCurrentTextBlock(). + * @param aLength number characters selected. + */ + + NS_IMETHOD SetSelection(int32_t aOffset, int32_t aLength) = 0; + + /** + * Scrolls the document so that the current selection is visible. + */ + + NS_IMETHOD ScrollSelectionIntoView() = 0; + + /** + * Deletes the text selected by SetSelection(). Calling + * DeleteSelection with nothing selected, or with a collapsed + * selection (cursor) does nothing and returns NS_OK. + */ + + NS_IMETHOD DeleteSelection() = 0; + + /** + * Inserts the given text at the current cursor position. + * If there is a selection, it will be deleted before the + * text is inserted. + */ + + NS_IMETHOD InsertText(const nsString *aText) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITextServicesDocument, + NS_ITEXTSERVICESDOCUMENT_IID) + +#endif // nsITextServicesDocument_h__ + diff --git a/editor/txtsvc/nsITextServicesFilter.idl b/editor/txtsvc/nsITextServicesFilter.idl new file mode 100644 index 000000000..b2dba0e4a --- /dev/null +++ b/editor/txtsvc/nsITextServicesFilter.idl @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMNode; + +[scriptable, uuid(5BEC321F-59AC-413a-A4AD-8A8D7C50A0D0)] +interface nsITextServicesFilter : nsISupports +{ + + /** + * Indicates whether the content node should be skipped by the iterator + * @param aNode - node to skip + */ + boolean skip(in nsIDOMNode aNode); + +}; + diff --git a/editor/txtsvc/nsTSAtomList.h b/editor/txtsvc/nsTSAtomList.h new file mode 100644 index 000000000..13feb9f12 --- /dev/null +++ b/editor/txtsvc/nsTSAtomList.h @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/****** + + This file contains the list of all text services nsIAtoms and their values + + It is designed to be used as inline input to nsTextServicesDocument.cpp *only* + through the magic of C preprocessing. + + All entries must be enclosed in the macro TS_ATOM which will have cruel + and unusual things done to it + + It is recommended (but not strictly necessary) to keep all entries + in alphabetical order + + The first argument to TS_ATOM is the C++ identifier of the atom + The second argument is the string value of the atom + + ******/ + +// OUTPUT_CLASS=nsTextServicesDocument +// MACRO_NAME=TS_ATOM + +TS_ATOM(sAAtom, "a") +TS_ATOM(sAddressAtom, "address") +TS_ATOM(sBigAtom, "big") +TS_ATOM(sBAtom, "b") +TS_ATOM(sCiteAtom, "cite") +TS_ATOM(sCodeAtom, "code") +TS_ATOM(sDfnAtom, "dfn") +TS_ATOM(sEmAtom, "em") +TS_ATOM(sFontAtom, "font") +TS_ATOM(sIAtom, "i") +TS_ATOM(sKbdAtom, "kbd") +TS_ATOM(sKeygenAtom, "keygen") +TS_ATOM(sNobrAtom, "nobr") +TS_ATOM(sSAtom, "s") +TS_ATOM(sSampAtom, "samp") +TS_ATOM(sSmallAtom, "small") +TS_ATOM(sSpacerAtom, "spacer") +TS_ATOM(sSpanAtom, "span") +TS_ATOM(sStrikeAtom, "strike") +TS_ATOM(sStrongAtom, "strong") +TS_ATOM(sSubAtom, "sub") +TS_ATOM(sSupAtom, "sup") +TS_ATOM(sTtAtom, "tt") +TS_ATOM(sUAtom, "u") +TS_ATOM(sVarAtom, "var") +TS_ATOM(sWbrAtom, "wbr") diff --git a/editor/txtsvc/nsTextServicesCID.h b/editor/txtsvc/nsTextServicesCID.h new file mode 100644 index 000000000..62b282134 --- /dev/null +++ b/editor/txtsvc/nsTextServicesCID.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTextServicesCID_h__ +#define nsTextServicesCID_h__ + +#define NS_TEXTSERVICESDOCUMENTINTERNAL_CID \ +{ /* 019718E2-CDB5-11d2-8D3C-000000000000 */ \ +0x019718e2, 0xcdb5, 0x11d2, \ +{ 0x8d, 0x3c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } } + +#define NS_TEXTSERVICESDOCUMENT_CID \ +{ /* 019718E3-CDB5-11d2-8D3C-000000000000 */ \ +0x019718e3, 0xcdb5, 0x11d2, \ +{ 0x8d, 0x3c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } } + +#endif /* nsTextServicesCID_h__ */ diff --git a/editor/txtsvc/nsTextServicesDocument.cpp b/editor/txtsvc/nsTextServicesDocument.cpp new file mode 100644 index 000000000..e0c779683 --- /dev/null +++ b/editor/txtsvc/nsTextServicesDocument.cpp @@ -0,0 +1,3499 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stddef.h> // for nullptr + +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/dom/Selection.h" +#include "mozilla/mozalloc.h" // for operator new, etc +#include "nsAString.h" // for nsAString_internal::Length, etc +#include "nsContentUtils.h" // for nsContentUtils +#include "nsDebug.h" // for NS_ENSURE_TRUE, etc +#include "nsDependentSubstring.h" // for Substring +#include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc +#include "nsFilteredContentIterator.h" // for nsFilteredContentIterator +#include "nsIContent.h" // for nsIContent, etc +#include "nsIContentIterator.h" // for nsIContentIterator +#include "nsID.h" // for NS_GET_IID +#include "nsIDOMDocument.h" // for nsIDOMDocument +#include "nsIDOMElement.h" // for nsIDOMElement +#include "nsIDOMHTMLDocument.h" // for nsIDOMHTMLDocument +#include "nsIDOMHTMLElement.h" // for nsIDOMHTMLElement +#include "nsIDOMNode.h" // for nsIDOMNode, etc +#include "nsIDOMRange.h" // for nsIDOMRange, etc +#include "nsIEditor.h" // for nsIEditor, etc +#include "nsINode.h" // for nsINode +#include "nsIPlaintextEditor.h" // for nsIPlaintextEditor +#include "nsISelection.h" // for nsISelection +#include "nsISelectionController.h" // for nsISelectionController, etc +#include "nsISupportsBase.h" // for nsISupports +#include "nsISupportsUtils.h" // for NS_IF_ADDREF, NS_ADDREF, etc +#include "nsITextServicesFilter.h" // for nsITextServicesFilter +#include "nsIWordBreaker.h" // for nsWordRange, nsIWordBreaker +#include "nsRange.h" // for nsRange +#include "nsStaticAtom.h" // for NS_STATIC_ATOM, etc +#include "nsString.h" // for nsString, nsAutoString +#include "nsTextServicesDocument.h" +#include "nscore.h" // for nsresult, NS_IMETHODIMP, etc + +#define LOCK_DOC(doc) +#define UNLOCK_DOC(doc) + +using namespace mozilla; +using namespace mozilla::dom; + +class OffsetEntry +{ +public: + OffsetEntry(nsIDOMNode *aNode, int32_t aOffset, int32_t aLength) + : mNode(aNode), mNodeOffset(0), mStrOffset(aOffset), mLength(aLength), + mIsInsertedText(false), mIsValid(true) + { + if (mStrOffset < 1) { + mStrOffset = 0; + } + if (mLength < 1) { + mLength = 0; + } + } + + virtual ~OffsetEntry() + { + mNode = 0; + mNodeOffset = 0; + mStrOffset = 0; + mLength = 0; + mIsValid = false; + } + + nsIDOMNode *mNode; + int32_t mNodeOffset; + int32_t mStrOffset; + int32_t mLength; + bool mIsInsertedText; + bool mIsValid; +}; + +#define TS_ATOM(name_, value_) nsIAtom* nsTextServicesDocument::name_ = 0; +#include "nsTSAtomList.h" // IWYU pragma: keep +#undef TS_ATOM + +nsTextServicesDocument::nsTextServicesDocument() +{ + mSelStartIndex = -1; + mSelStartOffset = -1; + mSelEndIndex = -1; + mSelEndOffset = -1; + + mIteratorStatus = eIsDone; +} + +nsTextServicesDocument::~nsTextServicesDocument() +{ + ClearOffsetTable(&mOffsetTable); +} + +#define TS_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_) +#include "nsTSAtomList.h" // IWYU pragma: keep +#undef TS_ATOM + +/* static */ +void +nsTextServicesDocument::RegisterAtoms() +{ + static const nsStaticAtom ts_atoms[] = { +#define TS_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &name_), +#include "nsTSAtomList.h" // IWYU pragma: keep +#undef TS_ATOM + }; + + NS_RegisterStaticAtoms(ts_atoms); +} + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextServicesDocument) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextServicesDocument) + +NS_INTERFACE_MAP_BEGIN(nsTextServicesDocument) + NS_INTERFACE_MAP_ENTRY(nsITextServicesDocument) + NS_INTERFACE_MAP_ENTRY(nsIEditActionListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITextServicesDocument) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsTextServicesDocument) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(nsTextServicesDocument, + mDOMDocument, + mSelCon, + mIterator, + mPrevTextBlock, + mNextTextBlock, + mExtent, + mTxtSvcFilter) + +NS_IMETHODIMP +nsTextServicesDocument::InitWithEditor(nsIEditor *aEditor) +{ + nsCOMPtr<nsISelectionController> selCon; + nsCOMPtr<nsIDOMDocument> doc; + + NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER); + + LOCK_DOC(this); + + // Check to see if we already have an mSelCon. If we do, it + // better be the same one the editor uses! + + nsresult rv = aEditor->GetSelectionController(getter_AddRefs(selCon)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (!selCon || (mSelCon && selCon != mSelCon)) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + if (!mSelCon) { + mSelCon = selCon; + } + + // Check to see if we already have an mDOMDocument. If we do, it + // better be the same one the editor uses! + + rv = aEditor->GetDocument(getter_AddRefs(doc)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (!doc || (mDOMDocument && doc != mDOMDocument)) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + if (!mDOMDocument) { + mDOMDocument = doc; + + rv = CreateDocumentContentIterator(getter_AddRefs(mIterator)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + mIteratorStatus = nsTextServicesDocument::eIsDone; + + rv = FirstBlock(); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + } + + mEditor = do_GetWeakReference(aEditor); + + rv = aEditor->AddEditActionListener(this); + + UNLOCK_DOC(this); + + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::GetDocument(nsIDOMDocument **aDoc) +{ + NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); + + *aDoc = nullptr; // init out param + NS_ENSURE_TRUE(mDOMDocument, NS_ERROR_NOT_INITIALIZED); + + *aDoc = mDOMDocument; + NS_ADDREF(*aDoc); + + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::SetExtent(nsIDOMRange* aDOMRange) +{ + NS_ENSURE_ARG_POINTER(aDOMRange); + NS_ENSURE_TRUE(mDOMDocument, NS_ERROR_FAILURE); + + LOCK_DOC(this); + + // We need to store a copy of aDOMRange since we don't + // know where it came from. + + mExtent = static_cast<nsRange*>(aDOMRange)->CloneRange(); + + // Create a new iterator based on our new extent range. + + nsresult rv = CreateContentIterator(mExtent, getter_AddRefs(mIterator)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + // Now position the iterator at the start of the first block + // in the range. + + mIteratorStatus = nsTextServicesDocument::eIsDone; + + rv = FirstBlock(); + + UNLOCK_DOC(this); + + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::ExpandRangeToWordBoundaries(nsIDOMRange *aRange) +{ + NS_ENSURE_ARG_POINTER(aRange); + RefPtr<nsRange> range = static_cast<nsRange*>(aRange); + + // Get the end points of the range. + + nsCOMPtr<nsIDOMNode> rngStartNode, rngEndNode; + int32_t rngStartOffset, rngEndOffset; + + nsresult rv = GetRangeEndPoints(range, getter_AddRefs(rngStartNode), + &rngStartOffset, + getter_AddRefs(rngEndNode), + &rngEndOffset); + + NS_ENSURE_SUCCESS(rv, rv); + + // Create a content iterator based on the range. + + nsCOMPtr<nsIContentIterator> iter; + rv = CreateContentIterator(range, getter_AddRefs(iter)); + + NS_ENSURE_SUCCESS(rv, rv); + + // Find the first text node in the range. + + TSDIteratorStatus iterStatus; + + rv = FirstTextNode(iter, &iterStatus); + NS_ENSURE_SUCCESS(rv, rv); + + if (iterStatus == nsTextServicesDocument::eIsDone) { + // No text was found so there's no adjustment necessary! + return NS_OK; + } + + nsINode *firstText = iter->GetCurrentNode(); + NS_ENSURE_TRUE(firstText, NS_ERROR_FAILURE); + + // Find the last text node in the range. + + rv = LastTextNode(iter, &iterStatus); + NS_ENSURE_SUCCESS(rv, rv); + + if (iterStatus == nsTextServicesDocument::eIsDone) { + // We should never get here because a first text block + // was found above. + NS_ASSERTION(false, "Found a first without a last!"); + return NS_ERROR_FAILURE; + } + + nsINode *lastText = iter->GetCurrentNode(); + NS_ENSURE_TRUE(lastText, NS_ERROR_FAILURE); + + // Now make sure our end points are in terms of text nodes in the range! + + nsCOMPtr<nsIDOMNode> firstTextNode = do_QueryInterface(firstText); + NS_ENSURE_TRUE(firstTextNode, NS_ERROR_FAILURE); + + if (rngStartNode != firstTextNode) { + // The range includes the start of the first text node! + rngStartNode = firstTextNode; + rngStartOffset = 0; + } + + nsCOMPtr<nsIDOMNode> lastTextNode = do_QueryInterface(lastText); + NS_ENSURE_TRUE(lastTextNode, NS_ERROR_FAILURE); + + if (rngEndNode != lastTextNode) { + // The range includes the end of the last text node! + rngEndNode = lastTextNode; + nsAutoString str; + lastTextNode->GetNodeValue(str); + rngEndOffset = str.Length(); + } + + // Create a doc iterator so that we can scan beyond + // the bounds of the extent range. + + nsCOMPtr<nsIContentIterator> docIter; + rv = CreateDocumentContentIterator(getter_AddRefs(docIter)); + NS_ENSURE_SUCCESS(rv, rv); + + // Grab all the text in the block containing our + // first text node. + + rv = docIter->PositionAt(firstText); + NS_ENSURE_SUCCESS(rv, rv); + + iterStatus = nsTextServicesDocument::eValid; + + nsTArray<OffsetEntry*> offsetTable; + nsAutoString blockStr; + + rv = CreateOffsetTable(&offsetTable, docIter, &iterStatus, + nullptr, &blockStr); + if (NS_FAILED(rv)) { + ClearOffsetTable(&offsetTable); + return rv; + } + + nsCOMPtr<nsIDOMNode> wordStartNode, wordEndNode; + int32_t wordStartOffset, wordEndOffset; + + rv = FindWordBounds(&offsetTable, &blockStr, + rngStartNode, rngStartOffset, + getter_AddRefs(wordStartNode), &wordStartOffset, + getter_AddRefs(wordEndNode), &wordEndOffset); + + ClearOffsetTable(&offsetTable); + + NS_ENSURE_SUCCESS(rv, rv); + + rngStartNode = wordStartNode; + rngStartOffset = wordStartOffset; + + // Grab all the text in the block containing our + // last text node. + + rv = docIter->PositionAt(lastText); + NS_ENSURE_SUCCESS(rv, rv); + + iterStatus = nsTextServicesDocument::eValid; + + rv = CreateOffsetTable(&offsetTable, docIter, &iterStatus, + nullptr, &blockStr); + if (NS_FAILED(rv)) { + ClearOffsetTable(&offsetTable); + return rv; + } + + rv = FindWordBounds(&offsetTable, &blockStr, + rngEndNode, rngEndOffset, + getter_AddRefs(wordStartNode), &wordStartOffset, + getter_AddRefs(wordEndNode), &wordEndOffset); + + ClearOffsetTable(&offsetTable); + + NS_ENSURE_SUCCESS(rv, rv); + + // To prevent expanding the range too much, we only change + // rngEndNode and rngEndOffset if it isn't already at the start of the + // word and isn't equivalent to rngStartNode and rngStartOffset. + + if (rngEndNode != wordStartNode || + rngEndOffset != wordStartOffset || + (rngEndNode == rngStartNode && rngEndOffset == rngStartOffset)) { + rngEndNode = wordEndNode; + rngEndOffset = wordEndOffset; + } + + // Now adjust the range so that it uses our new + // end points. + + rv = range->SetEnd(rngEndNode, rngEndOffset); + NS_ENSURE_SUCCESS(rv, rv); + + return range->SetStart(rngStartNode, rngStartOffset); +} + +NS_IMETHODIMP +nsTextServicesDocument::SetFilter(nsITextServicesFilter *aFilter) +{ + // Hang on to the filter so we can set it into the filtered iterator. + mTxtSvcFilter = aFilter; + + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::GetCurrentTextBlock(nsString *aStr) +{ + NS_ENSURE_TRUE(aStr, NS_ERROR_NULL_POINTER); + + aStr->Truncate(); + + NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); + + LOCK_DOC(this); + + nsresult rv = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, + mExtent, aStr); + + UNLOCK_DOC(this); + + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::FirstBlock() +{ + NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); + + LOCK_DOC(this); + + nsresult rv = FirstTextNode(mIterator, &mIteratorStatus); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + // Keep track of prev and next blocks, just in case + // the text service blows away the current block. + + if (mIteratorStatus == nsTextServicesDocument::eValid) { + mPrevTextBlock = nullptr; + rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); + } else { + // There's no text block in the document! + + mPrevTextBlock = nullptr; + mNextTextBlock = nullptr; + } + + UNLOCK_DOC(this); + + // XXX Result of FirstTextNode() or GetFirstTextNodeInNextBlock(). + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::LastSelectedBlock(TSDBlockSelectionStatus *aSelStatus, + int32_t *aSelOffset, + int32_t *aSelLength) +{ + NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER); + + LOCK_DOC(this); + + mIteratorStatus = nsTextServicesDocument::eIsDone; + + *aSelStatus = nsITextServicesDocument::eBlockNotFound; + *aSelOffset = *aSelLength = -1; + + if (!mSelCon || !mIterator) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsISelection> domSelection; + nsresult rv = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSelection)); + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + RefPtr<Selection> selection = domSelection->AsSelection(); + + bool isCollapsed = selection->IsCollapsed(); + + nsCOMPtr<nsIContentIterator> iter; + RefPtr<nsRange> range; + nsCOMPtr<nsIDOMNode> parent; + int32_t rangeCount, offset; + + if (isCollapsed) { + // We have a caret. Check if the caret is in a text node. + // If it is, make the text node's block the current block. + // If the caret isn't in a text node, search forwards in + // the document, till we find a text node. + + range = selection->GetRangeAt(0); + + if (!range) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + rv = range->GetStartContainer(getter_AddRefs(parent)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (!parent) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + rv = range->GetStartOffset(&offset); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (IsTextNode(parent)) { + // The caret is in a text node. Find the beginning + // of the text block containing this text node and + // return. + + nsCOMPtr<nsIContent> content(do_QueryInterface(parent)); + + if (!content) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + rv = mIterator->PositionAt(content); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = FirstTextNodeInCurrentBlock(mIterator); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + mIteratorStatus = nsTextServicesDocument::eValid; + + rv = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, + mExtent, nullptr); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = GetSelection(aSelStatus, aSelOffset, aSelLength); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (*aSelStatus == nsITextServicesDocument::eBlockContains) { + rv = SetSelectionInternal(*aSelOffset, *aSelLength, false); + } + } else { + // The caret isn't in a text node. Create an iterator + // based on a range that extends from the current caret + // position to the end of the document, then walk forwards + // till you find a text node, then find the beginning of it's block. + + rv = CreateDocumentContentRootToNodeOffsetRange(parent, offset, false, + getter_AddRefs(range)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = range->GetCollapsed(&isCollapsed); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (isCollapsed) { + // If we get here, the range is collapsed because there is nothing after + // the caret! Just return NS_OK; + + UNLOCK_DOC(this); + return NS_OK; + } + + rv = CreateContentIterator(range, getter_AddRefs(iter)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + iter->First(); + + nsCOMPtr<nsIContent> content; + while (!iter->IsDone()) { + content = do_QueryInterface(iter->GetCurrentNode()); + + if (IsTextNode(content)) { + break; + } + + content = nullptr; + + iter->Next(); + } + + if (!content) { + UNLOCK_DOC(this); + return NS_OK; + } + + rv = mIterator->PositionAt(content); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = FirstTextNodeInCurrentBlock(mIterator); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + mIteratorStatus = nsTextServicesDocument::eValid; + + rv = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, + mExtent, nullptr); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = GetSelection(aSelStatus, aSelOffset, aSelLength); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + } + + UNLOCK_DOC(this); + + // Result of SetSelectionInternal() in the |if| block or NS_OK. + return rv; + } + + // If we get here, we have an uncollapsed selection! + // Look backwards through each range in the selection till you + // find the first text node. If you find one, find the + // beginning of its text block, and make it the current + // block. + + rv = selection->GetRangeCount(&rangeCount); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + NS_ASSERTION(rangeCount > 0, "Unexpected range count!"); + + if (rangeCount <= 0) { + UNLOCK_DOC(this); + return NS_OK; + } + + // XXX: We may need to add some code here to make sure + // the ranges are sorted in document appearance order! + + for (int32_t i = rangeCount - 1; i >= 0; i--) { + // Get the i'th range from the selection. + + range = selection->GetRangeAt(i); + + if (!range) { + UNLOCK_DOC(this); + return NS_OK; // XXX Really? + } + + // Create an iterator for the range. + + rv = CreateContentIterator(range, getter_AddRefs(iter)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + iter->Last(); + + // Now walk through the range till we find a text node. + + while (!iter->IsDone()) { + if (iter->GetCurrentNode()->NodeType() == nsIDOMNode::TEXT_NODE) { + // We found a text node, so position the document's + // iterator at the beginning of the block, then get + // the selection in terms of the string offset. + nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->AsContent(); + + rv = mIterator->PositionAt(content); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = FirstTextNodeInCurrentBlock(mIterator); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + mIteratorStatus = nsTextServicesDocument::eValid; + + rv = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, + mExtent, nullptr); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = GetSelection(aSelStatus, aSelOffset, aSelLength); + + UNLOCK_DOC(this); + + return rv; + + } + + iter->Prev(); + } + } + + // If we get here, we didn't find any text node in the selection! + // Create a range that extends from the end of the selection, + // to the end of the document, then iterate forwards through + // it till you find a text node! + + range = selection->GetRangeAt(rangeCount - 1); + + if (!range) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + rv = range->GetEndContainer(getter_AddRefs(parent)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (!parent) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + rv = range->GetEndOffset(&offset); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = CreateDocumentContentRootToNodeOffsetRange(parent, offset, false, + getter_AddRefs(range)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = range->GetCollapsed(&isCollapsed); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (isCollapsed) { + // If we get here, the range is collapsed because there is nothing after + // the current selection! Just return NS_OK; + + UNLOCK_DOC(this); + return NS_OK; + } + + rv = CreateContentIterator(range, getter_AddRefs(iter)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + iter->First(); + + while (!iter->IsDone()) { + if (iter->GetCurrentNode()->NodeType() == nsIDOMNode::TEXT_NODE) { + // We found a text node! Adjust the document's iterator to point + // to the beginning of its text block, then get the current selection. + nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->AsContent(); + + rv = mIterator->PositionAt(content); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = FirstTextNodeInCurrentBlock(mIterator); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + + mIteratorStatus = nsTextServicesDocument::eValid; + + rv = CreateOffsetTable(&mOffsetTable, mIterator, &mIteratorStatus, + mExtent, nullptr); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + rv = GetSelection(aSelStatus, aSelOffset, aSelLength); + + UNLOCK_DOC(this); + + return rv; + } + + iter->Next(); + } + + // If we get here, we didn't find any block before or inside + // the selection! Just return OK. + + UNLOCK_DOC(this); + + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::PrevBlock() +{ + NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); + + LOCK_DOC(this); + + if (mIteratorStatus == nsTextServicesDocument::eIsDone) { + return NS_OK; + } + + switch (mIteratorStatus) { + case nsTextServicesDocument::eValid: + case nsTextServicesDocument::eNext: { + + nsresult rv = FirstTextNodeInPrevBlock(mIterator); + + if (NS_FAILED(rv)) { + mIteratorStatus = nsTextServicesDocument::eIsDone; + UNLOCK_DOC(this); + return rv; + } + + if (mIterator->IsDone()) { + mIteratorStatus = nsTextServicesDocument::eIsDone; + UNLOCK_DOC(this); + return NS_OK; + } + + mIteratorStatus = nsTextServicesDocument::eValid; + break; + } + case nsTextServicesDocument::ePrev: + + // The iterator already points to the previous + // block, so don't do anything. + + mIteratorStatus = nsTextServicesDocument::eValid; + break; + + default: + + mIteratorStatus = nsTextServicesDocument::eIsDone; + break; + } + + // Keep track of prev and next blocks, just in case + // the text service blows away the current block. + nsresult rv = NS_OK; + if (mIteratorStatus == nsTextServicesDocument::eValid) { + GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock)); + rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); + } else { + // We must be done! + mPrevTextBlock = nullptr; + mNextTextBlock = nullptr; + } + + UNLOCK_DOC(this); + + // XXX The result of GetFirstTextNodeInNextBlock() or NS_OK. + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::NextBlock() +{ + NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); + + LOCK_DOC(this); + + if (mIteratorStatus == nsTextServicesDocument::eIsDone) { + return NS_OK; + } + + switch (mIteratorStatus) { + case nsTextServicesDocument::eValid: { + + // Advance the iterator to the next text block. + + nsresult rv = FirstTextNodeInNextBlock(mIterator); + + if (NS_FAILED(rv)) { + mIteratorStatus = nsTextServicesDocument::eIsDone; + UNLOCK_DOC(this); + return rv; + } + + if (mIterator->IsDone()) { + mIteratorStatus = nsTextServicesDocument::eIsDone; + UNLOCK_DOC(this); + return NS_OK; + } + + mIteratorStatus = nsTextServicesDocument::eValid; + break; + } + case nsTextServicesDocument::eNext: + + // The iterator already points to the next block, + // so don't do anything to it! + + mIteratorStatus = nsTextServicesDocument::eValid; + break; + + case nsTextServicesDocument::ePrev: + + // If the iterator is pointing to the previous block, + // we know that there is no next text block! Just + // fall through to the default case! + + default: + + mIteratorStatus = nsTextServicesDocument::eIsDone; + break; + } + + // Keep track of prev and next blocks, just in case + // the text service blows away the current block. + nsresult rv = NS_OK; + if (mIteratorStatus == nsTextServicesDocument::eValid) { + GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock)); + rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); + } else { + // We must be done. + mPrevTextBlock = nullptr; + mNextTextBlock = nullptr; + } + + UNLOCK_DOC(this); + + // The result of GetFirstTextNodeInNextBlock() or NS_OK. + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::IsDone(bool *aIsDone) +{ + NS_ENSURE_TRUE(aIsDone, NS_ERROR_NULL_POINTER); + + *aIsDone = false; + + NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); + + LOCK_DOC(this); + + *aIsDone = (mIteratorStatus == nsTextServicesDocument::eIsDone) ? true : false; + + UNLOCK_DOC(this); + + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::SetSelection(int32_t aOffset, int32_t aLength) +{ + NS_ENSURE_TRUE(mSelCon && aOffset >= 0 && aLength >= 0, NS_ERROR_FAILURE); + + LOCK_DOC(this); + + nsresult rv = SetSelectionInternal(aOffset, aLength, true); + + UNLOCK_DOC(this); + + //**** KDEBUG **** + // printf("\n * Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); + //**** KDEBUG **** + + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::ScrollSelectionIntoView() +{ + NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE); + + LOCK_DOC(this); + + // After ScrollSelectionIntoView(), the pending notifications might be flushed + // and PresShell/PresContext/Frames may be dead. See bug 418470. + nsresult rv = + mSelCon->ScrollSelectionIntoView( + nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_FOCUS_REGION, + nsISelectionController::SCROLL_SYNCHRONOUS); + + UNLOCK_DOC(this); + + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::DeleteSelection() +{ + // We don't allow deletion during a collapsed selection! + nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); + NS_ASSERTION(editor, "DeleteSelection called without an editor present!"); + NS_ASSERTION(SelectionIsValid(), "DeleteSelection called without a valid selection!"); + + if (!editor || !SelectionIsValid()) { + return NS_ERROR_FAILURE; + } + if (SelectionIsCollapsed()) { + return NS_OK; + } + + LOCK_DOC(this); + + //**** KDEBUG **** + // printf("\n---- Before Delete\n"); + // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); + // PrintOffsetTable(); + //**** KDEBUG **** + + // If we have an mExtent, save off its current set of + // end points so we can compare them against mExtent's + // set after the deletion of the content. + + nsCOMPtr<nsIDOMNode> origStartNode, origEndNode; + int32_t origStartOffset = 0, origEndOffset = 0; + + if (mExtent) { + nsresult rv = + GetRangeEndPoints(mExtent, + getter_AddRefs(origStartNode), &origStartOffset, + getter_AddRefs(origEndNode), &origEndOffset); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + } + + int32_t selLength; + OffsetEntry *entry, *newEntry; + + for (int32_t i = mSelStartIndex; i <= mSelEndIndex; i++) { + entry = mOffsetTable[i]; + + if (i == mSelStartIndex) { + // Calculate the length of the selection. Note that the + // selection length can be zero if the start of the selection + // is at the very end of a text node entry. + + if (entry->mIsInsertedText) { + // Inserted text offset entries have no width when + // talking in terms of string offsets! If the beginning + // of the selection is in an inserted text offset entry, + // the caret is always at the end of the entry! + + selLength = 0; + } else { + selLength = entry->mLength - (mSelStartOffset - entry->mStrOffset); + } + + if (selLength > 0 && mSelStartOffset > entry->mStrOffset) { + // Selection doesn't start at the beginning of the + // text node entry. We need to split this entry into + // two pieces, the piece before the selection, and + // the piece inside the selection. + + nsresult rv = SplitOffsetEntry(i, selLength); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + // Adjust selection indexes to account for new entry: + + ++mSelStartIndex; + ++mSelEndIndex; + ++i; + + entry = mOffsetTable[i]; + } + + + if (selLength > 0 && mSelStartIndex < mSelEndIndex) { + // The entire entry is contained in the selection. Mark the + // entry invalid. + entry->mIsValid = false; + } + } + + //**** KDEBUG **** + // printf("\n---- Middle Delete\n"); + // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); + // PrintOffsetTable(); + //**** KDEBUG **** + + if (i == mSelEndIndex) { + if (entry->mIsInsertedText) { + // Inserted text offset entries have no width when + // talking in terms of string offsets! If the end + // of the selection is in an inserted text offset entry, + // the selection includes the entire entry! + + entry->mIsValid = false; + } else { + // Calculate the length of the selection. Note that the + // selection length can be zero if the end of the selection + // is at the very beginning of a text node entry. + + selLength = mSelEndOffset - entry->mStrOffset; + + if (selLength > 0 && + mSelEndOffset < entry->mStrOffset + entry->mLength) { + // mStrOffset is guaranteed to be inside the selection, even + // when mSelStartIndex == mSelEndIndex. + + nsresult rv = SplitOffsetEntry(i, entry->mLength - selLength); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + // Update the entry fields: + + newEntry = mOffsetTable[i+1]; + newEntry->mNodeOffset = entry->mNodeOffset; + } + + + if (selLength > 0 && + mSelEndOffset == entry->mStrOffset + entry->mLength) { + // The entire entry is contained in the selection. Mark the + // entry invalid. + entry->mIsValid = false; + } + } + } + + if (i != mSelStartIndex && i != mSelEndIndex) { + // The entire entry is contained in the selection. Mark the + // entry invalid. + entry->mIsValid = false; + } + } + + // Make sure mIterator always points to something valid! + + AdjustContentIterator(); + + // Now delete the actual content! + + nsresult rv = + editor->DeleteSelection(nsIEditor::ePrevious, nsIEditor::eStrip); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + // Now that we've actually deleted the selected content, + // check to see if our mExtent has changed, if so, then + // we have to create a new content iterator! + + if (origStartNode && origEndNode) { + nsCOMPtr<nsIDOMNode> curStartNode, curEndNode; + int32_t curStartOffset = 0, curEndOffset = 0; + + rv = GetRangeEndPoints(mExtent, + getter_AddRefs(curStartNode), &curStartOffset, + getter_AddRefs(curEndNode), &curEndOffset); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (origStartNode != curStartNode || origEndNode != curEndNode) { + // The range has changed, so we need to create a new content + // iterator based on the new range. + + nsCOMPtr<nsIContent> curContent; + + if (mIteratorStatus != nsTextServicesDocument::eIsDone) { + // The old iterator is still pointing to something valid, + // so get its current node so we can restore it after we + // create the new iterator! + + curContent = mIterator->GetCurrentNode() + ? mIterator->GetCurrentNode()->AsContent() + : nullptr; + } + + // Create the new iterator. + + rv = CreateContentIterator(mExtent, getter_AddRefs(mIterator)); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + // Now make the new iterator point to the content node + // the old one was pointing at. + + if (curContent) { + rv = mIterator->PositionAt(curContent); + + if (NS_FAILED(rv)) { + mIteratorStatus = eIsDone; + } else { + mIteratorStatus = eValid; + } + } + } + } + + entry = 0; + + // Move the caret to the end of the first valid entry. + // Start with mSelStartIndex since it may still be valid. + + for (int32_t i = mSelStartIndex; !entry && i >= 0; i--) { + entry = mOffsetTable[i]; + + if (!entry->mIsValid) { + entry = 0; + } else { + mSelStartIndex = mSelEndIndex = i; + mSelStartOffset = mSelEndOffset = entry->mStrOffset + entry->mLength; + } + } + + // If we still don't have a valid entry, move the caret + // to the next valid entry after the selection: + + for (int32_t i = mSelEndIndex; + !entry && i < static_cast<int32_t>(mOffsetTable.Length()); i++) { + entry = mOffsetTable[i]; + + if (!entry->mIsValid) { + entry = 0; + } else { + mSelStartIndex = mSelEndIndex = i; + mSelStartOffset = mSelEndOffset = entry->mStrOffset; + } + } + + if (entry) { + SetSelection(mSelStartOffset, 0); + } else { + // Uuughh we have no valid offset entry to place our + // caret ... just mark the selection invalid. + mSelStartIndex = mSelEndIndex = -1; + mSelStartOffset = mSelEndOffset = -1; + } + + // Now remove any invalid entries from the offset table. + + rv = RemoveInvalidOffsetEntries(); + + //**** KDEBUG **** + // printf("\n---- After Delete\n"); + // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); + // PrintOffsetTable(); + //**** KDEBUG **** + + UNLOCK_DOC(this); + + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::InsertText(const nsString *aText) +{ + nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); + NS_ASSERTION(editor, "InsertText called without an editor present!"); + + if (!editor || !SelectionIsValid()) { + return NS_ERROR_FAILURE; + } + + NS_ENSURE_TRUE(aText, NS_ERROR_NULL_POINTER); + + // If the selection is not collapsed, we need to save + // off the selection offsets so we can restore the + // selection and delete the selected content after we've + // inserted the new text. This is necessary to try and + // retain as much of the original style of the content + // being deleted. + + bool collapsedSelection = SelectionIsCollapsed(); + int32_t savedSelOffset = mSelStartOffset; + int32_t savedSelLength = mSelEndOffset - mSelStartOffset; + + if (!collapsedSelection) { + // Collapse to the start of the current selection + // for the insert! + + nsresult rv = SetSelection(mSelStartOffset, 0); + + NS_ENSURE_SUCCESS(rv, rv); + } + + + LOCK_DOC(this); + + nsresult rv = editor->BeginTransaction(); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(editor, &rv)); + if (textEditor) { + rv = textEditor->InsertText(*aText); + } + + if (NS_FAILED(rv)) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return rv; + } + + //**** KDEBUG **** + // printf("\n---- Before Insert\n"); + // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); + // PrintOffsetTable(); + //**** KDEBUG **** + + int32_t strLength = aText->Length(); + + nsCOMPtr<nsISelection> selection; + OffsetEntry *itEntry; + OffsetEntry *entry = mOffsetTable[mSelStartIndex]; + void *node = entry->mNode; + + NS_ASSERTION((entry->mIsValid), "Invalid insertion point!"); + + if (entry->mStrOffset == mSelStartOffset) { + if (entry->mIsInsertedText) { + // If the caret is in an inserted text offset entry, + // we simply insert the text at the end of the entry. + + entry->mLength += strLength; + } else { + // Insert an inserted text offset entry before the current + // entry! + + itEntry = new OffsetEntry(entry->mNode, entry->mStrOffset, strLength); + + if (!itEntry) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return NS_ERROR_OUT_OF_MEMORY; + } + + itEntry->mIsInsertedText = true; + itEntry->mNodeOffset = entry->mNodeOffset; + + if (!mOffsetTable.InsertElementAt(mSelStartIndex, itEntry)) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + } + } else if (entry->mStrOffset + entry->mLength == mSelStartOffset) { + // We are inserting text at the end of the current offset entry. + // Look at the next valid entry in the table. If it's an inserted + // text entry, add to its length and adjust its node offset. If + // it isn't, add a new inserted text entry. + + // XXX Rename this! + uint32_t i = mSelStartIndex + 1; + itEntry = 0; + + if (mOffsetTable.Length() > i) { + itEntry = mOffsetTable[i]; + + if (!itEntry) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + // Check if the entry is a match. If it isn't, set + // iEntry to zero. + + if (!itEntry->mIsInsertedText || itEntry->mStrOffset != mSelStartOffset) { + itEntry = 0; + } + } + + if (!itEntry) { + // We didn't find an inserted text offset entry, so + // create one. + + itEntry = new OffsetEntry(entry->mNode, mSelStartOffset, 0); + + if (!itEntry) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return NS_ERROR_OUT_OF_MEMORY; + } + + itEntry->mNodeOffset = entry->mNodeOffset + entry->mLength; + itEntry->mIsInsertedText = true; + + if (!mOffsetTable.InsertElementAt(i, itEntry)) { + delete itEntry; + return NS_ERROR_FAILURE; + } + } + + // We have a valid inserted text offset entry. Update its + // length, adjust the selection indexes, and make sure the + // caret is properly placed! + + itEntry->mLength += strLength; + + mSelStartIndex = mSelEndIndex = i; + + rv = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + + if (NS_FAILED(rv)) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return rv; + } + + rv = selection->Collapse(itEntry->mNode, + itEntry->mNodeOffset + itEntry->mLength); + + if (NS_FAILED(rv)) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return rv; + } + } else if (entry->mStrOffset + entry->mLength > mSelStartOffset) { + // We are inserting text into the middle of the current offset entry. + // split the current entry into two parts, then insert an inserted text + // entry between them! + + // XXX Rename this! + uint32_t i = entry->mLength - (mSelStartOffset - entry->mStrOffset); + + rv = SplitOffsetEntry(mSelStartIndex, i); + + if (NS_FAILED(rv)) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return rv; + } + + itEntry = new OffsetEntry(entry->mNode, mSelStartOffset, strLength); + + if (!itEntry) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return NS_ERROR_OUT_OF_MEMORY; + } + + itEntry->mIsInsertedText = true; + itEntry->mNodeOffset = entry->mNodeOffset + entry->mLength; + + if (!mOffsetTable.InsertElementAt(mSelStartIndex + 1, itEntry)) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + mSelEndIndex = ++mSelStartIndex; + } + + // We've just finished inserting an inserted text offset entry. + // update all entries with the same mNode pointer that follow + // it in the table! + + for (size_t i = mSelStartIndex + 1; i < mOffsetTable.Length(); i++) { + entry = mOffsetTable[i]; + if (entry->mNode != node) { + break; + } + if (entry->mIsValid) { + entry->mNodeOffset += strLength; + } + } + + //**** KDEBUG **** + // printf("\n---- After Insert\n"); + // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); + // PrintOffsetTable(); + //**** KDEBUG **** + + if (!collapsedSelection) { + rv = SetSelection(savedSelOffset, savedSelLength); + + if (NS_FAILED(rv)) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return rv; + } + + rv = DeleteSelection(); + + if (NS_FAILED(rv)) { + editor->EndTransaction(); + UNLOCK_DOC(this); + return rv; + } + } + + rv = editor->EndTransaction(); + + UNLOCK_DOC(this); + + return rv; +} + +NS_IMETHODIMP +nsTextServicesDocument::DidInsertNode(nsIDOMNode *aNode, + nsIDOMNode *aParent, + int32_t aPosition, + nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) +{ + NS_ENSURE_SUCCESS(aResult, NS_OK); + + NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); + + //**** KDEBUG **** + // printf("** DeleteNode: 0x%.8x\n", aChild); + // fflush(stdout); + //**** KDEBUG **** + + LOCK_DOC(this); + + int32_t nodeIndex = 0; + bool hasEntry = false; + OffsetEntry *entry; + + nsresult rv = + NodeHasOffsetEntry(&mOffsetTable, aChild, &hasEntry, &nodeIndex); + + if (NS_FAILED(rv)) { + UNLOCK_DOC(this); + return rv; + } + + if (!hasEntry) { + // It's okay if the node isn't in the offset table, the + // editor could be cleaning house. + UNLOCK_DOC(this); + return NS_OK; + } + + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(mIterator->GetCurrentNode()); + + if (node && node == aChild && + mIteratorStatus != nsTextServicesDocument::eIsDone) { + // XXX: This should never really happen because + // AdjustContentIterator() should have been called prior + // to the delete to try and position the iterator on the + // next valid text node in the offset table, and if there + // wasn't a next, it would've set mIteratorStatus to eIsDone. + + NS_ERROR("DeleteNode called for current iterator node."); + } + + int32_t tcount = mOffsetTable.Length(); + + while (nodeIndex < tcount) { + entry = mOffsetTable[nodeIndex]; + + if (!entry) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + if (entry->mNode == aChild) { + entry->mIsValid = false; + } + + nodeIndex++; + } + + UNLOCK_DOC(this); + + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::DidSplitNode(nsIDOMNode *aExistingRightNode, + int32_t aOffset, + nsIDOMNode *aNewLeftNode, + nsresult aResult) +{ + //**** KDEBUG **** + // printf("** SplitNode: 0x%.8x %d 0x%.8x\n", aExistingRightNode, aOffset, aNewLeftNode); + // fflush(stdout); + //**** KDEBUG **** + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::DidJoinNodes(nsIDOMNode *aLeftNode, + nsIDOMNode *aRightNode, + nsIDOMNode *aParent, + nsresult aResult) +{ + NS_ENSURE_SUCCESS(aResult, NS_OK); + + //**** KDEBUG **** + // printf("** JoinNodes: 0x%.8x 0x%.8x 0x%.8x\n", aLeftNode, aRightNode, aParent); + // fflush(stdout); + //**** KDEBUG **** + + // Make sure that both nodes are text nodes -- otherwise we don't care. + + uint16_t type; + nsresult rv = aLeftNode->GetNodeType(&type); + NS_ENSURE_SUCCESS(rv, NS_OK); + if (nsIDOMNode::TEXT_NODE != type) { + return NS_OK; + } + + rv = aRightNode->GetNodeType(&type); + NS_ENSURE_SUCCESS(rv, NS_OK); + if (nsIDOMNode::TEXT_NODE != type) { + return NS_OK; + } + + // Note: The editor merges the contents of the left node into the + // contents of the right. + + int32_t leftIndex = 0; + int32_t rightIndex = 0; + bool leftHasEntry = false; + bool rightHasEntry = false; + + rv = NodeHasOffsetEntry(&mOffsetTable, aLeftNode, &leftHasEntry, &leftIndex); + + NS_ENSURE_SUCCESS(rv, rv); + + if (!leftHasEntry) { + // It's okay if the node isn't in the offset table, the + // editor could be cleaning house. + return NS_OK; + } + + rv = NodeHasOffsetEntry(&mOffsetTable, aRightNode, + &rightHasEntry, &rightIndex); + + NS_ENSURE_SUCCESS(rv, rv); + + if (!rightHasEntry) { + // It's okay if the node isn't in the offset table, the + // editor could be cleaning house. + return NS_OK; + } + + NS_ASSERTION(leftIndex < rightIndex, "Indexes out of order."); + + if (leftIndex > rightIndex) { + // Don't know how to handle this situation. + return NS_ERROR_FAILURE; + } + + LOCK_DOC(this); + + OffsetEntry *entry = mOffsetTable[rightIndex]; + NS_ASSERTION(entry->mNodeOffset == 0, "Unexpected offset value for rightIndex."); + + // Run through the table and change all entries referring to + // the left node so that they now refer to the right node: + + nsAutoString str; + aLeftNode->GetNodeValue(str); + int32_t nodeLength = str.Length(); + + for (int32_t i = leftIndex; i < rightIndex; i++) { + entry = mOffsetTable[i]; + if (entry->mNode != aLeftNode) { + break; + } + if (entry->mIsValid) { + entry->mNode = aRightNode; + } + } + + // Run through the table and adjust the node offsets + // for all entries referring to the right node. + + for (int32_t i = rightIndex; + i < static_cast<int32_t>(mOffsetTable.Length()); i++) { + entry = mOffsetTable[i]; + if (entry->mNode != aRightNode) { + break; + } + if (entry->mIsValid) { + entry->mNodeOffset += nodeLength; + } + } + + // Now check to see if the iterator is pointing to the + // left node. If it is, make it point to the right node! + + nsCOMPtr<nsIContent> leftContent = do_QueryInterface(aLeftNode); + nsCOMPtr<nsIContent> rightContent = do_QueryInterface(aRightNode); + + if (!leftContent || !rightContent) { + UNLOCK_DOC(this); + return NS_ERROR_FAILURE; + } + + if (mIterator->GetCurrentNode() == leftContent) { + mIterator->PositionAt(rightContent); + } + + UNLOCK_DOC(this); + + return NS_OK; +} + +nsresult +nsTextServicesDocument::CreateContentIterator(nsRange* aRange, + nsIContentIterator** aIterator) +{ + NS_ENSURE_TRUE(aRange && aIterator, NS_ERROR_NULL_POINTER); + + *aIterator = nullptr; + + // Create a nsFilteredContentIterator + // This class wraps the ContentIterator in order to give itself a chance + // to filter out certain content nodes + RefPtr<nsFilteredContentIterator> filter = new nsFilteredContentIterator(mTxtSvcFilter); + + nsresult rv = filter->Init(aRange); + if (NS_FAILED(rv)) { + return rv; + } + + filter.forget(aIterator); + return NS_OK; +} + +nsresult +nsTextServicesDocument::GetDocumentContentRootNode(nsIDOMNode **aNode) +{ + NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); + + *aNode = 0; + + NS_ENSURE_TRUE(mDOMDocument, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(mDOMDocument); + + if (htmlDoc) { + // For HTML documents, the content root node is the body. + + nsCOMPtr<nsIDOMHTMLElement> bodyElement; + + nsresult rv = htmlDoc->GetBody(getter_AddRefs(bodyElement)); + + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(bodyElement, NS_ERROR_FAILURE); + + bodyElement.forget(aNode); + } else { + // For non-HTML documents, the content root node will be the document element. + + nsCOMPtr<nsIDOMElement> docElement; + + nsresult rv = mDOMDocument->GetDocumentElement(getter_AddRefs(docElement)); + + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(docElement, NS_ERROR_FAILURE); + + docElement.forget(aNode); + } + + return NS_OK; +} + +nsresult +nsTextServicesDocument::CreateDocumentContentRange(nsRange** aRange) +{ + *aRange = nullptr; + + nsCOMPtr<nsIDOMNode> node; + nsresult rv = GetDocumentContentRootNode(getter_AddRefs(node)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); + + nsCOMPtr<nsINode> nativeNode = do_QueryInterface(node); + NS_ENSURE_STATE(nativeNode); + + RefPtr<nsRange> range = new nsRange(nativeNode); + + rv = range->SelectNodeContents(node); + NS_ENSURE_SUCCESS(rv, rv); + + range.forget(aRange); + return NS_OK; +} + +nsresult +nsTextServicesDocument::CreateDocumentContentRootToNodeOffsetRange( + nsIDOMNode* aParent, int32_t aOffset, bool aToStart, nsRange** aRange) +{ + NS_ENSURE_TRUE(aParent && aRange, NS_ERROR_NULL_POINTER); + + *aRange = 0; + + NS_ASSERTION(aOffset >= 0, "Invalid offset!"); + + if (aOffset < 0) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIDOMNode> bodyNode; + nsresult rv = GetDocumentContentRootNode(getter_AddRefs(bodyNode)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(bodyNode, NS_ERROR_NULL_POINTER); + + nsCOMPtr<nsIDOMNode> startNode; + nsCOMPtr<nsIDOMNode> endNode; + int32_t startOffset, endOffset; + + if (aToStart) { + // The range should begin at the start of the document + // and extend up until (aParent, aOffset). + + startNode = bodyNode; + startOffset = 0; + endNode = aParent; + endOffset = aOffset; + } else { + // The range should begin at (aParent, aOffset) and + // extend to the end of the document. + + startNode = aParent; + startOffset = aOffset; + endNode = bodyNode; + + nsCOMPtr<nsINode> body = do_QueryInterface(bodyNode); + endOffset = body ? int32_t(body->GetChildCount()) : 0; + } + + return nsRange::CreateRange(startNode, startOffset, endNode, endOffset, + aRange); +} + +nsresult +nsTextServicesDocument::CreateDocumentContentIterator(nsIContentIterator **aIterator) +{ + NS_ENSURE_TRUE(aIterator, NS_ERROR_NULL_POINTER); + + RefPtr<nsRange> range; + + nsresult rv = CreateDocumentContentRange(getter_AddRefs(range)); + + NS_ENSURE_SUCCESS(rv, rv); + + return CreateContentIterator(range, aIterator); +} + +nsresult +nsTextServicesDocument::AdjustContentIterator() +{ + NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mIterator->GetCurrentNode())); + + NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); + + nsIDOMNode *nodePtr = node.get(); + int32_t tcount = mOffsetTable.Length(); + + nsIDOMNode *prevValidNode = 0; + nsIDOMNode *nextValidNode = 0; + bool foundEntry = false; + OffsetEntry *entry; + + for (int32_t i = 0; i < tcount && !nextValidNode; i++) { + entry = mOffsetTable[i]; + + NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); + + if (entry->mNode == nodePtr) { + if (entry->mIsValid) { + // The iterator is still pointing to something valid! + // Do nothing! + return NS_OK; + } + // We found an invalid entry that points to + // the current iterator node. Stop looking for + // a previous valid node! + foundEntry = true; + } + + if (entry->mIsValid) { + if (!foundEntry) { + prevValidNode = entry->mNode; + } else { + nextValidNode = entry->mNode; + } + } + } + + nsCOMPtr<nsIContent> content; + + if (prevValidNode) { + content = do_QueryInterface(prevValidNode); + } else if (nextValidNode) { + content = do_QueryInterface(nextValidNode); + } + + if (content) { + nsresult rv = mIterator->PositionAt(content); + + if (NS_FAILED(rv)) { + mIteratorStatus = eIsDone; + } else { + mIteratorStatus = eValid; + } + return rv; + } + + // If we get here, there aren't any valid entries + // in the offset table! Try to position the iterator + // on the next text block first, then previous if + // one doesn't exist! + + if (mNextTextBlock) { + nsresult rv = mIterator->PositionAt(mNextTextBlock); + + if (NS_FAILED(rv)) { + mIteratorStatus = eIsDone; + return rv; + } + + mIteratorStatus = eNext; + } else if (mPrevTextBlock) { + nsresult rv = mIterator->PositionAt(mPrevTextBlock); + + if (NS_FAILED(rv)) { + mIteratorStatus = eIsDone; + return rv; + } + + mIteratorStatus = ePrev; + } else { + mIteratorStatus = eIsDone; + } + return NS_OK; +} + +bool +nsTextServicesDocument::DidSkip(nsIContentIterator* aFilteredIter) +{ + // We can assume here that the Iterator is a nsFilteredContentIterator because + // all the iterator are created in CreateContentIterator which create a + // nsFilteredContentIterator + // So if the iterator bailed on one of the "filtered" content nodes then we + // consider that to be a block and bail with true + if (aFilteredIter) { + nsFilteredContentIterator* filter = static_cast<nsFilteredContentIterator *>(aFilteredIter); + if (filter && filter->DidSkip()) { + return true; + } + } + return false; +} + +void +nsTextServicesDocument::ClearDidSkip(nsIContentIterator* aFilteredIter) +{ + // Clear filter's skip flag + if (aFilteredIter) { + nsFilteredContentIterator* filter = static_cast<nsFilteredContentIterator *>(aFilteredIter); + filter->ClearDidSkip(); + } +} + +bool +nsTextServicesDocument::IsBlockNode(nsIContent *aContent) +{ + if (!aContent) { + NS_ERROR("How did a null pointer get passed to IsBlockNode?"); + return false; + } + + nsIAtom *atom = aContent->NodeInfo()->NameAtom(); + + return (sAAtom != atom && + sAddressAtom != atom && + sBigAtom != atom && + sBAtom != atom && + sCiteAtom != atom && + sCodeAtom != atom && + sDfnAtom != atom && + sEmAtom != atom && + sFontAtom != atom && + sIAtom != atom && + sKbdAtom != atom && + sKeygenAtom != atom && + sNobrAtom != atom && + sSAtom != atom && + sSampAtom != atom && + sSmallAtom != atom && + sSpacerAtom != atom && + sSpanAtom != atom && + sStrikeAtom != atom && + sStrongAtom != atom && + sSubAtom != atom && + sSupAtom != atom && + sTtAtom != atom && + sUAtom != atom && + sVarAtom != atom && + sWbrAtom != atom); +} + +bool +nsTextServicesDocument::HasSameBlockNodeParent(nsIContent *aContent1, nsIContent *aContent2) +{ + nsIContent* p1 = aContent1->GetParent(); + nsIContent* p2 = aContent2->GetParent(); + + // Quick test: + + if (p1 == p2) { + return true; + } + + // Walk up the parent hierarchy looking for closest block boundary node: + + while (p1 && !IsBlockNode(p1)) { + p1 = p1->GetParent(); + } + + while (p2 && !IsBlockNode(p2)) { + p2 = p2->GetParent(); + } + + return p1 == p2; +} + +bool +nsTextServicesDocument::IsTextNode(nsIContent *aContent) +{ + NS_ENSURE_TRUE(aContent, false); + return nsIDOMNode::TEXT_NODE == aContent->NodeType(); +} + +bool +nsTextServicesDocument::IsTextNode(nsIDOMNode *aNode) +{ + NS_ENSURE_TRUE(aNode, false); + + nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); + return IsTextNode(content); +} + +nsresult +nsTextServicesDocument::SetSelectionInternal(int32_t aOffset, int32_t aLength, bool aDoUpdate) +{ + NS_ENSURE_TRUE(mSelCon && aOffset >= 0 && aLength >= 0, NS_ERROR_FAILURE); + + nsIDOMNode *sNode = 0, *eNode = 0; + int32_t sOffset = 0, eOffset = 0; + OffsetEntry *entry; + + // Find start of selection in node offset terms: + + for (size_t i = 0; !sNode && i < mOffsetTable.Length(); i++) { + entry = mOffsetTable[i]; + if (entry->mIsValid) { + if (entry->mIsInsertedText) { + // Caret can only be placed at the end of an + // inserted text offset entry, if the offsets + // match exactly! + + if (entry->mStrOffset == aOffset) { + sNode = entry->mNode; + sOffset = entry->mNodeOffset + entry->mLength; + } + } else if (aOffset >= entry->mStrOffset) { + bool foundEntry = false; + int32_t strEndOffset = entry->mStrOffset + entry->mLength; + + if (aOffset < strEndOffset) { + foundEntry = true; + } else if (aOffset == strEndOffset) { + // Peek after this entry to see if we have any + // inserted text entries belonging to the same + // entry->mNode. If so, we have to place the selection + // after it! + + if (i + 1 < mOffsetTable.Length()) { + OffsetEntry *nextEntry = mOffsetTable[i+1]; + + if (!nextEntry->mIsValid || nextEntry->mStrOffset != aOffset) { + // Next offset entry isn't an exact match, so we'll + // just use the current entry. + foundEntry = true; + } + } + } + + if (foundEntry) { + sNode = entry->mNode; + sOffset = entry->mNodeOffset + aOffset - entry->mStrOffset; + } + } + + if (sNode) { + mSelStartIndex = static_cast<int32_t>(i); + mSelStartOffset = aOffset; + } + } + } + + NS_ENSURE_TRUE(sNode, NS_ERROR_FAILURE); + + // XXX: If we ever get a SetSelection() method in nsIEditor, we should + // use it. + + nsCOMPtr<nsISelection> selection; + + if (aDoUpdate) { + nsresult rv = + mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = selection->Collapse(sNode, sOffset); + + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aLength <= 0) { + // We have a collapsed selection. (Caret) + + mSelEndIndex = mSelStartIndex; + mSelEndOffset = mSelStartOffset; + + //**** KDEBUG **** + // printf("\n* Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); + //**** KDEBUG **** + + return NS_OK; + } + + // Find the end of the selection in node offset terms: + int32_t endOffset = aOffset + aLength; + for (int32_t i = mOffsetTable.Length() - 1; !eNode && i >= 0; i--) { + entry = mOffsetTable[i]; + + if (entry->mIsValid) { + if (entry->mIsInsertedText) { + if (entry->mStrOffset == eOffset) { + // If the selection ends on an inserted text offset entry, + // the selection includes the entire entry! + + eNode = entry->mNode; + eOffset = entry->mNodeOffset + entry->mLength; + } + } else if (endOffset >= entry->mStrOffset && + endOffset <= entry->mStrOffset + entry->mLength) { + eNode = entry->mNode; + eOffset = entry->mNodeOffset + endOffset - entry->mStrOffset; + } + + if (eNode) { + mSelEndIndex = i; + mSelEndOffset = endOffset; + } + } + } + + if (aDoUpdate && eNode) { + nsresult rv = selection->Extend(eNode, eOffset); + + NS_ENSURE_SUCCESS(rv, rv); + } + + //**** KDEBUG **** + // printf("\n * Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset); + //**** KDEBUG **** + + return NS_OK; +} + +nsresult +nsTextServicesDocument::GetSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength) +{ + NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER); + + *aSelStatus = nsITextServicesDocument::eBlockNotFound; + *aSelOffset = -1; + *aSelLength = -1; + + NS_ENSURE_TRUE(mDOMDocument && mSelCon, NS_ERROR_FAILURE); + + if (mIteratorStatus == nsTextServicesDocument::eIsDone) { + return NS_OK; + } + + nsCOMPtr<nsISelection> selection; + bool isCollapsed; + + nsresult rv = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + rv = selection->GetIsCollapsed(&isCollapsed); + + NS_ENSURE_SUCCESS(rv, rv); + + // XXX: If we expose this method publicly, we need to + // add LOCK_DOC/UNLOCK_DOC calls! + + // LOCK_DOC(this); + + if (isCollapsed) { + rv = GetCollapsedSelection(aSelStatus, aSelOffset, aSelLength); + } else { + rv = GetUncollapsedSelection(aSelStatus, aSelOffset, aSelLength); + } + + // UNLOCK_DOC(this); + + // XXX The result of GetCollapsedSelection() or GetUncollapsedSelection(). + return rv; +} + +nsresult +nsTextServicesDocument::GetCollapsedSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength) +{ + nsCOMPtr<nsISelection> domSelection; + nsresult rv = + mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSelection)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(domSelection, NS_ERROR_FAILURE); + + RefPtr<Selection> selection = domSelection->AsSelection(); + + // The calling function should have done the GetIsCollapsed() + // check already. Just assume it's collapsed! + *aSelStatus = nsITextServicesDocument::eBlockOutside; + *aSelOffset = *aSelLength = -1; + + int32_t tableCount = mOffsetTable.Length(); + + if (!tableCount) { + return NS_OK; + } + + // Get pointers to the first and last offset entries + // in the table. + + OffsetEntry* eStart = mOffsetTable[0]; + OffsetEntry* eEnd; + if (tableCount > 1) { + eEnd = mOffsetTable[tableCount - 1]; + } else { + eEnd = eStart; + } + + int32_t eStartOffset = eStart->mNodeOffset; + int32_t eEndOffset = eEnd->mNodeOffset + eEnd->mLength; + + RefPtr<nsRange> range = selection->GetRangeAt(0); + NS_ENSURE_STATE(range); + + nsCOMPtr<nsIDOMNode> domParent; + rv = range->GetStartContainer(getter_AddRefs(domParent)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsINode> parent = do_QueryInterface(domParent); + MOZ_ASSERT(parent); + + int32_t offset; + rv = range->GetStartOffset(&offset); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t e1s1 = nsContentUtils::ComparePoints(eStart->mNode, eStartOffset, + domParent, offset); + int32_t e2s1 = nsContentUtils::ComparePoints(eEnd->mNode, eEndOffset, + domParent, offset); + + if (e1s1 > 0 || e2s1 < 0) { + // We're done if the caret is outside the current text block. + return NS_OK; + } + + if (parent->NodeType() == nsIDOMNode::TEXT_NODE) { + // Good news, the caret is in a text node. Look + // through the offset table for the entry that + // matches its parent and offset. + + for (int32_t i = 0; i < tableCount; i++) { + OffsetEntry* entry = mOffsetTable[i]; + NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); + + if (entry->mNode == domParent.get() && + entry->mNodeOffset <= offset && + offset <= entry->mNodeOffset + entry->mLength) { + *aSelStatus = nsITextServicesDocument::eBlockContains; + *aSelOffset = entry->mStrOffset + (offset - entry->mNodeOffset); + *aSelLength = 0; + + return NS_OK; + } + } + + // If we get here, we didn't find a text node entry + // in our offset table that matched. + + return NS_ERROR_FAILURE; + } + + // The caret is in our text block, but it's positioned in some + // non-text node (ex. <b>). Create a range based on the start + // and end of the text block, then create an iterator based on + // this range, with its initial position set to the closest + // child of this non-text node. Then look for the closest text + // node. + + rv = CreateRange(eStart->mNode, eStartOffset, eEnd->mNode, eEndOffset, + getter_AddRefs(range)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContentIterator> iter; + rv = CreateContentIterator(range, getter_AddRefs(iter)); + NS_ENSURE_SUCCESS(rv, rv); + + nsIContent* saveNode; + if (parent->HasChildren()) { + // XXX: We need to make sure that all of parent's + // children are in the text block. + + // If the parent has children, position the iterator + // on the child that is to the left of the offset. + + uint32_t childIndex = (uint32_t)offset; + + if (childIndex > 0) { + uint32_t numChildren = parent->GetChildCount(); + NS_ASSERTION(childIndex <= numChildren, "Invalid selection offset!"); + + if (childIndex > numChildren) { + childIndex = numChildren; + } + + childIndex -= 1; + } + + nsIContent* content = parent->GetChildAt(childIndex); + NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); + + rv = iter->PositionAt(content); + NS_ENSURE_SUCCESS(rv, rv); + + saveNode = content; + } else { + // The parent has no children, so position the iterator + // on the parent. + NS_ENSURE_TRUE(parent->IsContent(), NS_ERROR_FAILURE); + nsCOMPtr<nsIContent> content = parent->AsContent(); + + rv = iter->PositionAt(content); + NS_ENSURE_SUCCESS(rv, rv); + + saveNode = content; + } + + // Now iterate to the left, towards the beginning of + // the text block, to find the first text node you + // come across. + + nsIContent* node = nullptr; + while (!iter->IsDone()) { + nsINode* current = iter->GetCurrentNode(); + if (current->NodeType() == nsIDOMNode::TEXT_NODE) { + node = static_cast<nsIContent*>(current); + break; + } + + iter->Prev(); + } + + if (node) { + // We found a node, now set the offset to the end + // of the text node. + offset = node->TextLength(); + } else { + // We should never really get here, but I'm paranoid. + + // We didn't find a text node above, so iterate to + // the right, towards the end of the text block, looking + // for a text node. + + rv = iter->PositionAt(saveNode); + NS_ENSURE_SUCCESS(rv, rv); + + node = nullptr; + while (!iter->IsDone()) { + nsINode* current = iter->GetCurrentNode(); + + if (current->NodeType() == nsIDOMNode::TEXT_NODE) { + node = static_cast<nsIContent*>(current); + break; + } + + iter->Next(); + } + + NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); + + // We found a text node, so set the offset to + // the beginning of the node. + + offset = 0; + } + + for (int32_t i = 0; i < tableCount; i++) { + OffsetEntry* entry = mOffsetTable[i]; + NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); + + if (entry->mNode == node->AsDOMNode() && + entry->mNodeOffset <= offset && + offset <= entry->mNodeOffset + entry->mLength) { + *aSelStatus = nsITextServicesDocument::eBlockContains; + *aSelOffset = entry->mStrOffset + (offset - entry->mNodeOffset); + *aSelLength = 0; + + // Now move the caret so that it is actually in the text node. + // We do this to keep things in sync. + // + // In most cases, the user shouldn't see any movement in the caret + // on screen. + + return SetSelectionInternal(*aSelOffset, *aSelLength, true); + } + } + + return NS_ERROR_FAILURE; +} + +nsresult +nsTextServicesDocument::GetUncollapsedSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength) +{ + RefPtr<nsRange> range; + OffsetEntry *entry; + + nsCOMPtr<nsISelection> domSelection; + nsresult rv = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSelection)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(domSelection, NS_ERROR_FAILURE); + + RefPtr<Selection> selection = domSelection->AsSelection(); + + // It is assumed that the calling function has made sure that the + // selection is not collapsed, and that the input params to this + // method are initialized to some defaults. + + nsCOMPtr<nsIDOMNode> startParent, endParent; + int32_t startOffset, endOffset; + int32_t rangeCount, tableCount; + int32_t e1s1 = 0, e1s2 = 0, e2s1 = 0, e2s2 = 0; + + OffsetEntry *eStart, *eEnd; + int32_t eStartOffset, eEndOffset; + + tableCount = mOffsetTable.Length(); + + // Get pointers to the first and last offset entries + // in the table. + + eStart = mOffsetTable[0]; + + if (tableCount > 1) { + eEnd = mOffsetTable[tableCount - 1]; + } else { + eEnd = eStart; + } + + eStartOffset = eStart->mNodeOffset; + eEndOffset = eEnd->mNodeOffset + eEnd->mLength; + + rv = selection->GetRangeCount(&rangeCount); + + NS_ENSURE_SUCCESS(rv, rv); + + // Find the first range in the selection that intersects + // the current text block. + + for (int32_t i = 0; i < rangeCount; i++) { + range = selection->GetRangeAt(i); + NS_ENSURE_STATE(range); + + rv = GetRangeEndPoints(range, + getter_AddRefs(startParent), &startOffset, + getter_AddRefs(endParent), &endOffset); + + NS_ENSURE_SUCCESS(rv, rv); + + e1s2 = nsContentUtils::ComparePoints(eStart->mNode, eStartOffset, + endParent, endOffset); + e2s1 = nsContentUtils::ComparePoints(eEnd->mNode, eEndOffset, + startParent, startOffset); + + // Break out of the loop if the text block intersects the current range. + + if (e1s2 <= 0 && e2s1 >= 0) { + break; + } + } + + // We're done if we didn't find an intersecting range. + + if (rangeCount < 1 || e1s2 > 0 || e2s1 < 0) { + *aSelStatus = nsITextServicesDocument::eBlockOutside; + *aSelOffset = *aSelLength = -1; + return NS_OK; + } + + // Now that we have an intersecting range, find out more info: + + e1s1 = nsContentUtils::ComparePoints(eStart->mNode, eStartOffset, + startParent, startOffset); + e2s2 = nsContentUtils::ComparePoints(eEnd->mNode, eEndOffset, + endParent, endOffset); + + if (rangeCount > 1) { + // There are multiple selection ranges, we only deal + // with the first one that intersects the current, + // text block, so mark this a as a partial. + *aSelStatus = nsITextServicesDocument::eBlockPartial; + } else if (e1s1 > 0 && e2s2 < 0) { + // The range extends beyond the start and + // end of the current text block. + *aSelStatus = nsITextServicesDocument::eBlockInside; + } else if (e1s1 <= 0 && e2s2 >= 0) { + // The current text block contains the entire + // range. + *aSelStatus = nsITextServicesDocument::eBlockContains; + } else { + // The range partially intersects the block. + *aSelStatus = nsITextServicesDocument::eBlockPartial; + } + + // Now create a range based on the intersection of the + // text block and range: + + nsCOMPtr<nsIDOMNode> p1, p2; + int32_t o1, o2; + + // The start of the range will be the rightmost + // start node. + + if (e1s1 >= 0) { + p1 = do_QueryInterface(eStart->mNode); + o1 = eStartOffset; + } else { + p1 = startParent; + o1 = startOffset; + } + + // The end of the range will be the leftmost + // end node. + + if (e2s2 <= 0) { + p2 = do_QueryInterface(eEnd->mNode); + o2 = eEndOffset; + } else { + p2 = endParent; + o2 = endOffset; + } + + rv = CreateRange(p1, o1, p2, o2, getter_AddRefs(range)); + + NS_ENSURE_SUCCESS(rv, rv); + + // Now iterate over this range to figure out the selection's + // block offset and length. + + nsCOMPtr<nsIContentIterator> iter; + + rv = CreateContentIterator(range, getter_AddRefs(iter)); + + NS_ENSURE_SUCCESS(rv, rv); + + // Find the first text node in the range. + + bool found; + nsCOMPtr<nsIContent> content; + + iter->First(); + + if (!IsTextNode(p1)) { + found = false; + + while (!iter->IsDone()) { + content = do_QueryInterface(iter->GetCurrentNode()); + + if (IsTextNode(content)) { + p1 = do_QueryInterface(content); + + NS_ENSURE_TRUE(p1, NS_ERROR_FAILURE); + + o1 = 0; + found = true; + + break; + } + + iter->Next(); + } + + NS_ENSURE_TRUE(found, NS_ERROR_FAILURE); + } + + // Find the last text node in the range. + + iter->Last(); + + if (!IsTextNode(p2)) { + found = false; + while (!iter->IsDone()) { + content = do_QueryInterface(iter->GetCurrentNode()); + if (IsTextNode(content)) { + p2 = do_QueryInterface(content); + + NS_ENSURE_TRUE(p2, NS_ERROR_FAILURE); + + nsString str; + + rv = p2->GetNodeValue(str); + + NS_ENSURE_SUCCESS(rv, rv); + + o2 = str.Length(); + found = true; + + break; + } + + iter->Prev(); + } + + NS_ENSURE_TRUE(found, NS_ERROR_FAILURE); + } + + found = false; + *aSelLength = 0; + + for (int32_t i = 0; i < tableCount; i++) { + entry = mOffsetTable[i]; + NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); + if (!found) { + if (entry->mNode == p1.get() && + entry->mNodeOffset <= o1 && + o1 <= entry->mNodeOffset + entry->mLength) { + *aSelOffset = entry->mStrOffset + (o1 - entry->mNodeOffset); + if (p1 == p2 && + entry->mNodeOffset <= o2 && + o2 <= entry->mNodeOffset + entry->mLength) { + // The start and end of the range are in the same offset + // entry. Calculate the length of the range then we're done. + *aSelLength = o2 - o1; + break; + } + // Add the length of the sub string in this offset entry + // that follows the start of the range. + *aSelLength = entry->mLength - (o1 - entry->mNodeOffset); + found = true; + } + } else { // Found. + if (entry->mNode == p2.get() && + entry->mNodeOffset <= o2 && + o2 <= entry->mNodeOffset + entry->mLength) { + // We found the end of the range. Calculate the length of the + // sub string that is before the end of the range, then we're done. + *aSelLength += o2 - entry->mNodeOffset; + break; + } + // The entire entry must be in the range. + *aSelLength += entry->mLength; + } + } + + return NS_OK; +} + +bool +nsTextServicesDocument::SelectionIsCollapsed() +{ + return(mSelStartIndex == mSelEndIndex && mSelStartOffset == mSelEndOffset); +} + +bool +nsTextServicesDocument::SelectionIsValid() +{ + return(mSelStartIndex >= 0); +} + +nsresult +nsTextServicesDocument::GetRangeEndPoints(nsRange* aRange, + nsIDOMNode **aStartParent, int32_t *aStartOffset, + nsIDOMNode **aEndParent, int32_t *aEndOffset) +{ + NS_ENSURE_TRUE(aRange && aStartParent && aStartOffset && aEndParent && aEndOffset, NS_ERROR_NULL_POINTER); + + nsresult rv = aRange->GetStartContainer(aStartParent); + + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(aStartParent, NS_ERROR_FAILURE); + + rv = aRange->GetStartOffset(aStartOffset); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = aRange->GetEndContainer(aEndParent); + + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(aEndParent, NS_ERROR_FAILURE); + + return aRange->GetEndOffset(aEndOffset); +} + +nsresult +nsTextServicesDocument::CreateRange(nsIDOMNode *aStartParent, int32_t aStartOffset, + nsIDOMNode *aEndParent, int32_t aEndOffset, + nsRange** aRange) +{ + return nsRange::CreateRange(aStartParent, aStartOffset, aEndParent, + aEndOffset, aRange); +} + +nsresult +nsTextServicesDocument::FirstTextNode(nsIContentIterator *aIterator, + TSDIteratorStatus *aIteratorStatus) +{ + if (aIteratorStatus) { + *aIteratorStatus = nsTextServicesDocument::eIsDone; + } + + aIterator->First(); + + while (!aIterator->IsDone()) { + if (aIterator->GetCurrentNode()->NodeType() == nsIDOMNode::TEXT_NODE) { + if (aIteratorStatus) { + *aIteratorStatus = nsTextServicesDocument::eValid; + } + break; + } + aIterator->Next(); + } + + return NS_OK; +} + +nsresult +nsTextServicesDocument::LastTextNode(nsIContentIterator *aIterator, + TSDIteratorStatus *aIteratorStatus) +{ + if (aIteratorStatus) { + *aIteratorStatus = nsTextServicesDocument::eIsDone; + } + + aIterator->Last(); + + while (!aIterator->IsDone()) { + if (aIterator->GetCurrentNode()->NodeType() == nsIDOMNode::TEXT_NODE) { + if (aIteratorStatus) { + *aIteratorStatus = nsTextServicesDocument::eValid; + } + break; + } + aIterator->Prev(); + } + + return NS_OK; +} + +nsresult +nsTextServicesDocument::FirstTextNodeInCurrentBlock(nsIContentIterator *iter) +{ + NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); + + ClearDidSkip(iter); + + nsCOMPtr<nsIContent> last; + + // Walk backwards over adjacent text nodes until + // we hit a block boundary: + + while (!iter->IsDone()) { + nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->IsContent() + ? iter->GetCurrentNode()->AsContent() + : nullptr; + if (last && IsBlockNode(content)) { + break; + } + if (IsTextNode(content)) { + if (last && !HasSameBlockNodeParent(content, last)) { + // We're done, the current text node is in a + // different block. + break; + } + last = content; + } + + iter->Prev(); + + if (DidSkip(iter)) { + break; + } + } + + if (last) { + iter->PositionAt(last); + } + + // XXX: What should we return if last is null? + + return NS_OK; +} + +nsresult +nsTextServicesDocument::FirstTextNodeInPrevBlock(nsIContentIterator *aIterator) +{ + NS_ENSURE_TRUE(aIterator, NS_ERROR_NULL_POINTER); + + // XXX: What if mIterator is not currently on a text node? + + // Make sure mIterator is pointing to the first text node in the + // current block: + + nsresult rv = FirstTextNodeInCurrentBlock(aIterator); + + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Point mIterator to the first node before the first text node: + + aIterator->Prev(); + + if (aIterator->IsDone()) { + return NS_ERROR_FAILURE; + } + + // Now find the first text node of the next block: + + return FirstTextNodeInCurrentBlock(aIterator); +} + +nsresult +nsTextServicesDocument::FirstTextNodeInNextBlock(nsIContentIterator *aIterator) +{ + nsCOMPtr<nsIContent> prev; + bool crossedBlockBoundary = false; + + NS_ENSURE_TRUE(aIterator, NS_ERROR_NULL_POINTER); + + ClearDidSkip(aIterator); + + while (!aIterator->IsDone()) { + nsCOMPtr<nsIContent> content = aIterator->GetCurrentNode()->IsContent() + ? aIterator->GetCurrentNode()->AsContent() + : nullptr; + + if (IsTextNode(content)) { + if (crossedBlockBoundary || + (prev && !HasSameBlockNodeParent(prev, content))) { + break; + } + prev = content; + } else if (!crossedBlockBoundary && IsBlockNode(content)) { + crossedBlockBoundary = true; + } + + aIterator->Next(); + + if (!crossedBlockBoundary && DidSkip(aIterator)) { + crossedBlockBoundary = true; + } + } + + return NS_OK; +} + +nsresult +nsTextServicesDocument::GetFirstTextNodeInPrevBlock(nsIContent **aContent) +{ + NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER); + + *aContent = 0; + + // Save the iterator's current content node so we can restore + // it when we are done: + + nsINode* node = mIterator->GetCurrentNode(); + + nsresult rv = FirstTextNodeInPrevBlock(mIterator); + + if (NS_FAILED(rv)) { + // Try to restore the iterator before returning. + mIterator->PositionAt(node); + return rv; + } + + if (!mIterator->IsDone()) { + nsCOMPtr<nsIContent> current = mIterator->GetCurrentNode()->IsContent() + ? mIterator->GetCurrentNode()->AsContent() + : nullptr; + current.forget(aContent); + } + + // Restore the iterator: + + return mIterator->PositionAt(node); +} + +nsresult +nsTextServicesDocument::GetFirstTextNodeInNextBlock(nsIContent **aContent) +{ + NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER); + + *aContent = 0; + + // Save the iterator's current content node so we can restore + // it when we are done: + + nsINode* node = mIterator->GetCurrentNode(); + + nsresult rv = FirstTextNodeInNextBlock(mIterator); + + if (NS_FAILED(rv)) { + // Try to restore the iterator before returning. + mIterator->PositionAt(node); + return rv; + } + + if (!mIterator->IsDone()) { + nsCOMPtr<nsIContent> current = mIterator->GetCurrentNode()->IsContent() + ? mIterator->GetCurrentNode()->AsContent() + : nullptr; + current.forget(aContent); + } + + // Restore the iterator: + return mIterator->PositionAt(node); +} + +nsresult +nsTextServicesDocument::CreateOffsetTable(nsTArray<OffsetEntry*> *aOffsetTable, + nsIContentIterator *aIterator, + TSDIteratorStatus *aIteratorStatus, + nsRange* aIterRange, nsString* aStr) +{ + nsCOMPtr<nsIContent> first; + nsCOMPtr<nsIContent> prev; + + NS_ENSURE_TRUE(aIterator, NS_ERROR_NULL_POINTER); + + ClearOffsetTable(aOffsetTable); + + if (aStr) { + aStr->Truncate(); + } + + if (*aIteratorStatus == nsTextServicesDocument::eIsDone) { + return NS_OK; + } + + // If we have an aIterRange, retrieve the endpoints so + // they can be used in the while loop below to trim entries + // for text nodes that are partially selected by aIterRange. + + nsCOMPtr<nsIDOMNode> rngStartNode, rngEndNode; + int32_t rngStartOffset = 0, rngEndOffset = 0; + + if (aIterRange) { + nsresult rv = + GetRangeEndPoints(aIterRange, + getter_AddRefs(rngStartNode), &rngStartOffset, + getter_AddRefs(rngEndNode), &rngEndOffset); + + NS_ENSURE_SUCCESS(rv, rv); + } + + // The text service could have added text nodes to the beginning + // of the current block and called this method again. Make sure + // we really are at the beginning of the current block: + + nsresult rv = FirstTextNodeInCurrentBlock(aIterator); + + NS_ENSURE_SUCCESS(rv, rv); + + int32_t offset = 0; + + ClearDidSkip(aIterator); + + while (!aIterator->IsDone()) { + nsCOMPtr<nsIContent> content = aIterator->GetCurrentNode()->IsContent() + ? aIterator->GetCurrentNode()->AsContent() + : nullptr; + if (IsTextNode(content)) { + if (prev && !HasSameBlockNodeParent(prev, content)) { + break; + } + + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(content); + if (node) { + nsString str; + + rv = node->GetNodeValue(str); + + NS_ENSURE_SUCCESS(rv, rv); + + // Add an entry for this text node into the offset table: + + OffsetEntry *entry = new OffsetEntry(node, offset, str.Length()); + aOffsetTable->AppendElement(entry); + + // If one or both of the endpoints of the iteration range + // are in the text node for this entry, make sure the entry + // only accounts for the portion of the text node that is + // in the range. + + int32_t startOffset = 0; + int32_t endOffset = str.Length(); + bool adjustStr = false; + + if (entry->mNode == rngStartNode) { + entry->mNodeOffset = startOffset = rngStartOffset; + adjustStr = true; + } + + if (entry->mNode == rngEndNode) { + endOffset = rngEndOffset; + adjustStr = true; + } + + if (adjustStr) { + entry->mLength = endOffset - startOffset; + str = Substring(str, startOffset, entry->mLength); + } + + offset += str.Length(); + + if (aStr) { + // Append the text node's string to the output string: + if (!first) { + *aStr = str; + } else { + *aStr += str; + } + } + } + + prev = content; + + if (!first) { + first = content; + } + } + // XXX This should be checked before IsTextNode(), but IsBlockNode() returns + // true even if content is a text node. See bug 1311934. + else if (IsBlockNode(content)) { + break; + } + + aIterator->Next(); + + if (DidSkip(aIterator)) { + break; + } + } + + if (first) { + // Always leave the iterator pointing at the first + // text node of the current block! + aIterator->PositionAt(first); + } else { + // If we never ran across a text node, the iterator + // might have been pointing to something invalid to + // begin with. + *aIteratorStatus = nsTextServicesDocument::eIsDone; + } + + return NS_OK; +} + +nsresult +nsTextServicesDocument::RemoveInvalidOffsetEntries() +{ + for (size_t i = 0; i < mOffsetTable.Length(); ) { + OffsetEntry* entry = mOffsetTable[i]; + if (!entry->mIsValid) { + mOffsetTable.RemoveElementAt(i); + if (mSelStartIndex >= 0 && static_cast<size_t>(mSelStartIndex) >= i) { + // We are deleting an entry that comes before + // mSelStartIndex, decrement mSelStartIndex so + // that it points to the correct entry! + + NS_ASSERTION(i != static_cast<size_t>(mSelStartIndex), + "Invalid selection index."); + + --mSelStartIndex; + --mSelEndIndex; + } + } else { + i++; + } + } + + return NS_OK; +} + +nsresult +nsTextServicesDocument::ClearOffsetTable(nsTArray<OffsetEntry*> *aOffsetTable) +{ + for (size_t i = 0; i < aOffsetTable->Length(); i++) { + delete aOffsetTable->ElementAt(i); + } + + aOffsetTable->Clear(); + + return NS_OK; +} + +nsresult +nsTextServicesDocument::SplitOffsetEntry(int32_t aTableIndex, int32_t aNewEntryLength) +{ + OffsetEntry *entry = mOffsetTable[aTableIndex]; + + NS_ASSERTION((aNewEntryLength > 0), "aNewEntryLength <= 0"); + NS_ASSERTION((aNewEntryLength < entry->mLength), "aNewEntryLength >= mLength"); + + if (aNewEntryLength < 1 || aNewEntryLength >= entry->mLength) { + return NS_ERROR_FAILURE; + } + + int32_t oldLength = entry->mLength - aNewEntryLength; + + OffsetEntry *newEntry = new OffsetEntry(entry->mNode, + entry->mStrOffset + oldLength, + aNewEntryLength); + + if (!mOffsetTable.InsertElementAt(aTableIndex + 1, newEntry)) { + delete newEntry; + return NS_ERROR_FAILURE; + } + + // Adjust entry fields: + + entry->mLength = oldLength; + newEntry->mNodeOffset = entry->mNodeOffset + oldLength; + + return NS_OK; +} + +nsresult +nsTextServicesDocument::NodeHasOffsetEntry(nsTArray<OffsetEntry*> *aOffsetTable, nsIDOMNode *aNode, bool *aHasEntry, int32_t *aEntryIndex) +{ + NS_ENSURE_TRUE(aNode && aHasEntry && aEntryIndex, NS_ERROR_NULL_POINTER); + + for (size_t i = 0; i < aOffsetTable->Length(); i++) { + OffsetEntry* entry = (*aOffsetTable)[i]; + + NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE); + + if (entry->mNode == aNode) { + *aHasEntry = true; + *aEntryIndex = i; + return NS_OK; + } + } + + *aHasEntry = false; + *aEntryIndex = -1; + return NS_OK; +} + +// Spellchecker code has this. See bug 211343 +#define IS_NBSP_CHAR(c) (((unsigned char)0xa0)==(c)) + +nsresult +nsTextServicesDocument::FindWordBounds(nsTArray<OffsetEntry*> *aOffsetTable, + nsString *aBlockStr, + nsIDOMNode *aNode, + int32_t aNodeOffset, + nsIDOMNode **aWordStartNode, + int32_t *aWordStartOffset, + nsIDOMNode **aWordEndNode, + int32_t *aWordEndOffset) +{ + // Initialize return values. + + if (aWordStartNode) { + *aWordStartNode = nullptr; + } + if (aWordStartOffset) { + *aWordStartOffset = 0; + } + if (aWordEndNode) { + *aWordEndNode = nullptr; + } + if (aWordEndOffset) { + *aWordEndOffset = 0; + } + + int32_t entryIndex = 0; + bool hasEntry = false; + + // It's assumed that aNode is a text node. The first thing + // we do is get its index in the offset table so we can + // calculate the dom point's string offset. + + nsresult rv = NodeHasOffsetEntry(aOffsetTable, aNode, &hasEntry, &entryIndex); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(hasEntry, NS_ERROR_FAILURE); + + // Next we map aNodeOffset into a string offset. + + OffsetEntry *entry = (*aOffsetTable)[entryIndex]; + uint32_t strOffset = entry->mStrOffset + aNodeOffset - entry->mNodeOffset; + + // Now we use the word breaker to find the beginning and end + // of the word from our calculated string offset. + + const char16_t *str = aBlockStr->get(); + uint32_t strLen = aBlockStr->Length(); + + nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker(); + nsWordRange res = wordBreaker->FindWord(str, strLen, strOffset); + if (res.mBegin > strLen) { + return str ? NS_ERROR_ILLEGAL_VALUE : NS_ERROR_NULL_POINTER; + } + + // Strip out the NBSPs at the ends + while (res.mBegin <= res.mEnd && IS_NBSP_CHAR(str[res.mBegin])) { + res.mBegin++; + } + if (str[res.mEnd] == (unsigned char)0x20) { + uint32_t realEndWord = res.mEnd - 1; + while (realEndWord > res.mBegin && IS_NBSP_CHAR(str[realEndWord])) { + realEndWord--; + } + if (realEndWord < res.mEnd - 1) { + res.mEnd = realEndWord + 1; + } + } + + // Now that we have the string offsets for the beginning + // and end of the word, run through the offset table and + // convert them back into dom points. + + size_t lastIndex = aOffsetTable->Length() - 1; + for (size_t i = 0; i <= lastIndex; i++) { + entry = (*aOffsetTable)[i]; + + int32_t strEndOffset = entry->mStrOffset + entry->mLength; + + // Check to see if res.mBegin is within the range covered + // by this entry. Note that if res.mBegin is after the last + // character covered by this entry, we will use the next + // entry if there is one. + + if (uint32_t(entry->mStrOffset) <= res.mBegin && + (res.mBegin < static_cast<uint32_t>(strEndOffset) || + (res.mBegin == static_cast<uint32_t>(strEndOffset) && + i == lastIndex))) { + if (aWordStartNode) { + *aWordStartNode = entry->mNode; + NS_IF_ADDREF(*aWordStartNode); + } + + if (aWordStartOffset) { + *aWordStartOffset = entry->mNodeOffset + res.mBegin - entry->mStrOffset; + } + + if (!aWordEndNode && !aWordEndOffset) { + // We've found our start entry, but if we're not looking + // for end entries, we're done. + break; + } + } + + // Check to see if res.mEnd is within the range covered + // by this entry. + + if (static_cast<uint32_t>(entry->mStrOffset) <= res.mEnd && + res.mEnd <= static_cast<uint32_t>(strEndOffset)) { + if (res.mBegin == res.mEnd && + res.mEnd == static_cast<uint32_t>(strEndOffset) && + i != lastIndex) { + // Wait for the next round so that we use the same entry + // we did for aWordStartNode. + continue; + } + + if (aWordEndNode) { + *aWordEndNode = entry->mNode; + NS_IF_ADDREF(*aWordEndNode); + } + + if (aWordEndOffset) { + *aWordEndOffset = entry->mNodeOffset + res.mEnd - entry->mStrOffset; + } + break; + } + } + + + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::WillInsertNode(nsIDOMNode *aNode, + nsIDOMNode *aParent, + int32_t aPosition) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::WillDeleteNode(nsIDOMNode *aChild) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::WillSplitNode(nsIDOMNode *aExistingRightNode, + int32_t aOffset) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::WillJoinNodes(nsIDOMNode *aLeftNode, + nsIDOMNode *aRightNode, + nsIDOMNode *aParent) +{ + return NS_OK; +} + +// ------------------------------- +// stubs for unused listen methods +// ------------------------------- + +NS_IMETHODIMP +nsTextServicesDocument::WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, int32_t aPosition) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::DidCreateNode(const nsAString& aTag, nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition, nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::DidInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString, nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::DidDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength, nsresult aResult) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::WillDeleteSelection(nsISelection *aSelection) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextServicesDocument::DidDeleteSelection(nsISelection *aSelection) +{ + return NS_OK; +} diff --git a/editor/txtsvc/nsTextServicesDocument.h b/editor/txtsvc/nsTextServicesDocument.h new file mode 100644 index 000000000..d4e034546 --- /dev/null +++ b/editor/txtsvc/nsTextServicesDocument.h @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTextServicesDocument_h__ +#define nsTextServicesDocument_h__ + +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIEditActionListener.h" +#include "nsISupportsImpl.h" +#include "nsITextServicesDocument.h" +#include "nsIWeakReferenceUtils.h" +#include "nsTArray.h" +#include "nscore.h" + +class OffsetEntry; +class nsIAtom; +class nsIContent; +class nsIContentIterator; +class nsIDOMCharacterData; +class nsIDOMDocument; +class nsIDOMNode; +class nsIDOMRange; +class nsIEditor; +class nsISelection; +class nsISelectionController; +class nsITextServicesFilter; +class nsString; + +/** implementation of a text services object. + * + */ +class nsTextServicesDocument final : public nsITextServicesDocument, + public nsIEditActionListener +{ +private: + static nsIAtom *sAAtom; + static nsIAtom *sAddressAtom; + static nsIAtom *sBigAtom; + static nsIAtom *sBAtom; + static nsIAtom *sCiteAtom; + static nsIAtom *sCodeAtom; + static nsIAtom *sDfnAtom; + static nsIAtom *sEmAtom; + static nsIAtom *sFontAtom; + static nsIAtom *sIAtom; + static nsIAtom *sKbdAtom; + static nsIAtom *sKeygenAtom; + static nsIAtom *sNobrAtom; + static nsIAtom *sSAtom; + static nsIAtom *sSampAtom; + static nsIAtom *sSmallAtom; + static nsIAtom *sSpacerAtom; + static nsIAtom *sSpanAtom; + static nsIAtom *sStrikeAtom; + static nsIAtom *sStrongAtom; + static nsIAtom *sSubAtom; + static nsIAtom *sSupAtom; + static nsIAtom *sTtAtom; + static nsIAtom *sUAtom; + static nsIAtom *sVarAtom; + static nsIAtom *sWbrAtom; + + typedef enum { eIsDone=0, // No iterator (I), or iterator doesn't point to anything valid. + eValid, // I points to first text node (TN) in current block (CB). + ePrev, // No TN in CB, I points to first TN in prev block. + eNext // No TN in CB, I points to first TN in next block. + } TSDIteratorStatus; + + nsCOMPtr<nsIDOMDocument> mDOMDocument; + nsCOMPtr<nsISelectionController>mSelCon; + nsWeakPtr mEditor; // avoid a cycle with the spell checker and editor + nsCOMPtr<nsIContentIterator> mIterator; + TSDIteratorStatus mIteratorStatus; + nsCOMPtr<nsIContent> mPrevTextBlock; + nsCOMPtr<nsIContent> mNextTextBlock; + nsTArray<OffsetEntry*> mOffsetTable; + + int32_t mSelStartIndex; + int32_t mSelStartOffset; + int32_t mSelEndIndex; + int32_t mSelEndOffset; + + RefPtr<nsRange> mExtent; + + nsCOMPtr<nsITextServicesFilter> mTxtSvcFilter; + +protected: + /** The default destructor. + */ + virtual ~nsTextServicesDocument(); + +public: + + /** The default constructor. + */ + nsTextServicesDocument(); + + /** To be called at module init + */ + static void RegisterAtoms(); + + /* Macro for AddRef(), Release(), and QueryInterface() */ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTextServicesDocument, nsITextServicesDocument) + + /* nsITextServicesDocument method implementations. */ + NS_IMETHOD InitWithEditor(nsIEditor *aEditor) override; + NS_IMETHOD GetDocument(nsIDOMDocument **aDoc) override; + NS_IMETHOD SetExtent(nsIDOMRange* aDOMRange) override; + NS_IMETHOD ExpandRangeToWordBoundaries(nsIDOMRange *aRange) override; + NS_IMETHOD SetFilter(nsITextServicesFilter *aFilter) override; + NS_IMETHOD GetCurrentTextBlock(nsString *aStr) override; + NS_IMETHOD FirstBlock() override; + NS_IMETHOD LastSelectedBlock(TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength) override; + NS_IMETHOD PrevBlock() override; + NS_IMETHOD NextBlock() override; + NS_IMETHOD IsDone(bool *aIsDone) override; + NS_IMETHOD SetSelection(int32_t aOffset, int32_t aLength) override; + NS_IMETHOD ScrollSelectionIntoView() override; + NS_IMETHOD DeleteSelection() override; + NS_IMETHOD InsertText(const nsString *aText) override; + + /* nsIEditActionListener method implementations. */ + NS_IMETHOD WillInsertNode(nsIDOMNode *aNode, + nsIDOMNode *aParent, + int32_t aPosition) override; + NS_IMETHOD DidInsertNode(nsIDOMNode *aNode, + nsIDOMNode *aParent, + int32_t aPosition, + nsresult aResult) override; + + NS_IMETHOD WillDeleteNode(nsIDOMNode *aChild) override; + NS_IMETHOD DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) override; + + NS_IMETHOD WillSplitNode(nsIDOMNode * aExistingRightNode, + int32_t aOffset) override; + NS_IMETHOD DidSplitNode(nsIDOMNode *aExistingRightNode, + int32_t aOffset, + nsIDOMNode *aNewLeftNode, + nsresult aResult) override; + + NS_IMETHOD WillJoinNodes(nsIDOMNode *aLeftNode, + nsIDOMNode *aRightNode, + nsIDOMNode *aParent) override; + NS_IMETHOD DidJoinNodes(nsIDOMNode *aLeftNode, + nsIDOMNode *aRightNode, + nsIDOMNode *aParent, + nsresult aResult) override; + // these listen methods are unused: + NS_IMETHOD WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, int32_t aPosition) override; + NS_IMETHOD DidCreateNode(const nsAString& aTag, nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition, nsresult aResult) override; + NS_IMETHOD WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString) override; + NS_IMETHOD DidInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString, nsresult aResult) override; + NS_IMETHOD WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) override; + NS_IMETHOD DidDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength, nsresult aResult) override; + NS_IMETHOD WillDeleteSelection(nsISelection *aSelection) override; + NS_IMETHOD DidDeleteSelection(nsISelection *aSelection) override; + + /* Helper functions */ + static nsresult GetRangeEndPoints(nsRange* aRange, nsIDOMNode** aParent1, + int32_t* aOffset1, nsIDOMNode** aParent2, + int32_t* aOffset2); + static nsresult CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset, + nsIDOMNode* aEndParent, int32_t aEndOffset, + nsRange** aRange); + +private: + /* nsTextServicesDocument private methods. */ + + nsresult CreateContentIterator(nsRange* aRange, + nsIContentIterator** aIterator); + + nsresult GetDocumentContentRootNode(nsIDOMNode **aNode); + nsresult CreateDocumentContentRange(nsRange** aRange); + nsresult CreateDocumentContentRootToNodeOffsetRange(nsIDOMNode* aParent, + int32_t aOffset, + bool aToStart, + nsRange** aRange); + nsresult CreateDocumentContentIterator(nsIContentIterator **aIterator); + + nsresult AdjustContentIterator(); + + static nsresult FirstTextNode(nsIContentIterator *aIterator, TSDIteratorStatus *IteratorStatus); + static nsresult LastTextNode(nsIContentIterator *aIterator, TSDIteratorStatus *IteratorStatus); + + static nsresult FirstTextNodeInCurrentBlock(nsIContentIterator *aIterator); + static nsresult FirstTextNodeInPrevBlock(nsIContentIterator *aIterator); + static nsresult FirstTextNodeInNextBlock(nsIContentIterator *aIterator); + + nsresult GetFirstTextNodeInPrevBlock(nsIContent **aContent); + nsresult GetFirstTextNodeInNextBlock(nsIContent **aContent); + + static bool IsBlockNode(nsIContent *aContent); + static bool IsTextNode(nsIContent *aContent); + static bool IsTextNode(nsIDOMNode *aNode); + + static bool DidSkip(nsIContentIterator* aFilteredIter); + static void ClearDidSkip(nsIContentIterator* aFilteredIter); + + static bool HasSameBlockNodeParent(nsIContent *aContent1, nsIContent *aContent2); + + nsresult SetSelectionInternal(int32_t aOffset, int32_t aLength, bool aDoUpdate); + nsresult GetSelection(TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength); + nsresult GetCollapsedSelection(TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength); + nsresult GetUncollapsedSelection(TSDBlockSelectionStatus *aSelStatus, int32_t *aSelOffset, int32_t *aSelLength); + + bool SelectionIsCollapsed(); + bool SelectionIsValid(); + + static nsresult CreateOffsetTable(nsTArray<OffsetEntry*> *aOffsetTable, + nsIContentIterator *aIterator, + TSDIteratorStatus *aIteratorStatus, + nsRange* aIterRange, nsString* aStr); + static nsresult ClearOffsetTable(nsTArray<OffsetEntry*> *aOffsetTable); + + static nsresult NodeHasOffsetEntry(nsTArray<OffsetEntry*> *aOffsetTable, + nsIDOMNode *aNode, + bool *aHasEntry, + int32_t *aEntryIndex); + + nsresult RemoveInvalidOffsetEntries(); + nsresult SplitOffsetEntry(int32_t aTableIndex, int32_t aOffsetIntoEntry); + + static nsresult FindWordBounds(nsTArray<OffsetEntry*> *offsetTable, + nsString *blockStr, + nsIDOMNode *aNode, int32_t aNodeOffset, + nsIDOMNode **aWordStartNode, + int32_t *aWordStartOffset, + nsIDOMNode **aWordEndNode, + int32_t *aWordEndOffset); +}; + +#endif // nsTextServicesDocument_h__ diff --git a/editor/txtsvc/nsTextServicesFactory.cpp b/editor/txtsvc/nsTextServicesFactory.cpp new file mode 100644 index 000000000..845fb344d --- /dev/null +++ b/editor/txtsvc/nsTextServicesFactory.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIGenericFactory.h" + +#include "nsTextServicesDocument.h" +#include "nsTextServicesCID.h" + +//////////////////////////////////////////////////////////////////////// +// Define the contructor function for the objects +// +// NOTE: This creates an instance of objects by using the default constructor +// +NS_GENERIC_FACTORY_CONSTRUCTOR(nsTextServicesDocument) + +//////////////////////////////////////////////////////////////////////// +// Define a table of CIDs implemented by this module along with other +// information like the function to create an instance, contractid, and +// class name. +// +static const nsModuleComponentInfo components[] = { + { nullptr, NS_TEXTSERVICESDOCUMENT_CID, "@mozilla.org/textservices/textservicesdocument;1", nsTextServicesDocumentConstructor }, +}; + +//////////////////////////////////////////////////////////////////////// +// Implement the NSGetModule() exported function for your module +// and the entire implementation of the module object. +// +NS_IMPL_NSGETMODULE(nsTextServicesModule, components) |