Bug 1430982 - part 10: Make EditorBase store TextServicesDocument for its mInlineSpellChecker for avoiding to run for loops to call nsIEditActionListener methods r=m_kato

Currently, first edit action listener is always TextServicesDocument for
EditorBase::mInlineSpellChecker if spell check is enabled and it requires
only DidDeleteNode() and DidJoinNodes().  So, in most cases, EditorBase
should call only them to avoid running redundant for loops for
nsIEditActionListener methods.

This patch makes that EditorBase::AddEditActionListener() and
RemoveEditActionListener() check whether coming nsIEditActionListener is
TextServicesDocument for mInlineSpellChecker.  If so, EditorBase should
store it as reference to TextServicesDocument.  And when edit action occurs,
its DidDeleteNode() and DidJoinNodes() should be called directly.

Unfortunately, this patch makes TextServiceDocument's maintaining rules
complicated.  However, it must be really rare case to add new edit action
listener because it's really old component.

Note that EditorSpellCheck may be created multiple instances for an editor
from chrome JS.  Therefore, we need to keep TextServicesDocument being
derived from nsIEditActionListener because in such case, TextServicesDocument
for other EditorSpellCheck instances should be supported via
nsIEditActionListener even though such case makes EditorBase slower.

MozReview-Commit-ID: KtlXsBCOzKL

--HG--
extra : rebase_source : 6827ed09a028f2049fd7afba2f5116d092bd14e5
This commit is contained in:
Masayuki Nakano 2018-01-18 23:46:03 +09:00
parent 0c82cf40da
commit 2020349542
5 changed files with 160 additions and 88 deletions

View File

@ -399,6 +399,16 @@ EditorSpellCheck::InitSpellChecker(nsIEditor* aEditor,
new TextServicesDocument();
textServicesDocument->SetFilter(mTxtSrvFilter);
// EditorBase::AddEditActionListener() needs to access mSpellChecker and
// mSpellChecker->GetTextServicesDocument(). Therefore, we need to
// initialize them before calling TextServicesDocument::InitWithEditor()
// since it calls EditorBase::AddEditActionListener().
mSpellChecker = new mozSpellChecker();
rv = mSpellChecker->SetDocument(textServicesDocument, true);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Pass the editor to the text services document
rv = textServicesDocument->InitWithEditor(aEditor);
NS_ENSURE_SUCCESS(rv, rv);
@ -438,12 +448,8 @@ EditorSpellCheck::InitSpellChecker(nsIEditor* aEditor,
}
}
mSpellChecker = new mozSpellChecker();
rv = mSpellChecker->Init();
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = mSpellChecker->SetDocument(textServicesDocument, true);
NS_ENSURE_SUCCESS(rv, rv);
// do not fail if UpdateCurrentDictionary fails because this method may
// succeed later.
rv = UpdateCurrentDictionary(aCallback);

View File

@ -27,26 +27,28 @@
#include "SplitNodeTransaction.h" // for SplitNodeTransaction
#include "StyleSheetTransactions.h" // for AddStyleSheetTransaction, etc.
#include "TextEditUtils.h" // for TextEditUtils
#include "mozInlineSpellChecker.h" // for mozInlineSpellChecker
#include "mozilla/CheckedInt.h" // for CheckedInt
#include "mozilla/EditAction.h" // for EditAction
#include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
#include "mozilla/EditorSpellCheck.h" // for EditorSpellCheck
#include "mozilla/EditorUtils.h" // for AutoRules, etc.
#include "mozilla/EditTransactionBase.h" // for EditTransactionBase
#include "mozilla/FlushType.h" // for FlushType::Frames
#include "mozilla/IMEStateManager.h" // for IMEStateManager
#include "mozilla/mozalloc.h" // for operator new, etc.
#include "mozilla/mozInlineSpellChecker.h" // for mozInlineSpellChecker
#include "mozilla/mozSpellChecker.h" // for mozSpellChecker
#include "mozilla/Preferences.h" // for Preferences
#include "mozilla/RangeBoundary.h" // for RawRangeBoundary, RangeBoundary
#include "mozilla/dom/Selection.h" // for Selection, etc.
#include "mozilla/Services.h" // for GetObserverService
#include "mozilla/TextComposition.h" // for TextComposition
#include "mozilla/TextServicesDocument.h" // for TextServicesDocument
#include "mozilla/TextEvents.h"
#include "mozilla/dom/Element.h" // for Element, nsINode::AsElement
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/Text.h"
#include "mozilla/dom/Event.h"
#include "mozilla/mozalloc.h" // for operator new, etc.
#include "nsAString.h" // for nsAString::Length, etc.
#include "nsCCUncollectableMarker.h" // for nsCCUncollectableMarker
#include "nsCaret.h" // for nsCaret
@ -166,6 +168,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EditorBase)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionController)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mInlineSpellChecker)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextServicesDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTxnMgr)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionListeners)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorObservers)
@ -188,6 +191,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EditorBase)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionController)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineSpellChecker)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextServicesDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTxnMgr)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionListeners)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorObservers)
@ -466,6 +470,7 @@ EditorBase::PreDestroy(bool aDestroyingFrames)
mEditorObservers.Clear();
mDocStateListeners.Clear();
mInlineSpellChecker = nullptr;
mTextServicesDocument = nullptr;
mSpellcheckCheckboxState = eTriUnset;
mRootElement = nullptr;
@ -1634,6 +1639,11 @@ EditorBase::JoinNodes(nsINode& aLeftNode,
htmlEditRules->DidJoinNodes(aLeftNode, aRightNode);
}
if (mTextServicesDocument && NS_SUCCEEDED(rv)) {
RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
textServicesDocument->DidJoinNodes(aLeftNode, aRightNode);
}
if (!mActionListeners.IsEmpty()) {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
@ -1673,6 +1683,11 @@ EditorBase::DeleteNode(nsINode* aNode)
nsresult rv = deleteNodeTransaction ? DoTransaction(deleteNodeTransaction) :
NS_ERROR_FAILURE;
if (mTextServicesDocument && NS_SUCCEEDED(rv)) {
RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
textServicesDocument->DidDeleteNode(aNode);
}
if (!mActionListeners.IsEmpty()) {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
@ -2175,6 +2190,26 @@ EditorBase::AddEditActionListener(nsIEditActionListener* aListener)
{
NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
// If given edit action listener is text services document for the inline
// spell checker, store it as reference of concrete class for performance
// reason.
if (mInlineSpellChecker) {
EditorSpellCheck* editorSpellCheck =
mInlineSpellChecker->GetEditorSpellCheck();
if (editorSpellCheck) {
mozSpellChecker* spellChecker = editorSpellCheck->GetSpellChecker();
if (spellChecker) {
TextServicesDocument* textServicesDocument =
spellChecker->GetTextServicesDocument();
if (static_cast<nsIEditActionListener*>(textServicesDocument) ==
aListener) {
mTextServicesDocument = textServicesDocument;
return NS_OK;
}
}
}
}
// Make sure the listener isn't already on the list
if (!mActionListeners.Contains(aListener)) {
mActionListeners.AppendElement(*aListener);
@ -2188,6 +2223,11 @@ EditorBase::RemoveEditActionListener(nsIEditActionListener* aListener)
{
NS_ENSURE_TRUE(aListener, NS_ERROR_FAILURE);
if (static_cast<nsIEditActionListener*>(mTextServicesDocument) == aListener) {
mTextServicesDocument = nullptr;
return NS_OK;
}
mActionListeners.RemoveElement(aListener);
return NS_OK;
@ -4392,6 +4432,12 @@ EditorBase::DeleteSelectionImpl(EDirection aAction,
htmlEditRules->DidDeleteText(deleteNode, deleteCharOffset, 1);
}
if (mTextServicesDocument && NS_SUCCEEDED(rv) &&
deleteNode && !deleteCharData) {
RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
textServicesDocument->DidDeleteNode(deleteNode);
}
// Notify nsIEditActionListener::DidDelete[Selection|Text|Node]
{
AutoActionListenerArray listeners(mActionListeners);

View File

@ -76,6 +76,7 @@ class SplitNodeResult;
class SplitNodeTransaction;
class TextComposition;
class TextEditor;
class TextServicesDocument;
enum class EditAction : int32_t;
namespace dom {
@ -1385,6 +1386,8 @@ protected:
nsCString mContentMIMEType;
RefPtr<mozInlineSpellChecker> mInlineSpellChecker;
// Reference to text services document for mInlineSpellChecker.
RefPtr<TextServicesDocument> mTextServicesDocument;
RefPtr<nsTransactionManager> mTxnMgr;
// Cached root node.

View File

@ -1477,51 +1477,35 @@ TextServicesDocument::InsertText(const nsString* aText)
return rv;
}
NS_IMETHODIMP
TextServicesDocument::DidInsertNode(nsIDOMNode* aNode,
nsresult aResult)
void
TextServicesDocument::DidDeleteNode(nsINode* aChild)
{
return NS_OK;
}
NS_IMETHODIMP
TextServicesDocument::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 ****
if (NS_WARN_IF(!mIterator)) {
return;
}
LOCK_DOC(this);
int32_t nodeIndex = 0;
bool hasEntry = false;
OffsetEntry *entry;
nsCOMPtr<nsINode> child = do_QueryInterface(aChild);
nsresult rv =
NodeHasOffsetEntry(&mOffsetTable, child, &hasEntry, &nodeIndex);
NodeHasOffsetEntry(&mOffsetTable, aChild, &hasEntry, &nodeIndex);
if (NS_FAILED(rv)) {
UNLOCK_DOC(this);
return rv;
return;
}
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;
return;
}
nsINode* node = mIterator->GetCurrentNode();
if (node && node == child &&
if (node && node == aChild &&
mIteratorStatus != IteratorStatus::eDone) {
// XXX: This should never really happen because
// AdjustContentIterator() should have been called prior
@ -1533,16 +1517,15 @@ TextServicesDocument::DidDeleteNode(nsIDOMNode* aChild,
}
int32_t tcount = mOffsetTable.Length();
while (nodeIndex < tcount) {
entry = mOffsetTable[nodeIndex];
if (!entry) {
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
return;
}
if (entry->mNode == child) {
if (entry->mNode == aChild) {
entry->mIsValid = false;
}
@ -1550,41 +1533,16 @@ TextServicesDocument::DidDeleteNode(nsIDOMNode* aChild,
}
UNLOCK_DOC(this);
return NS_OK;
}
NS_IMETHODIMP
TextServicesDocument::DidSplitNode(nsIDOMNode* aExistingRightNode,
nsIDOMNode* aNewLeftNode)
void
TextServicesDocument::DidJoinNodes(nsINode& aLeftNode,
nsINode& aRightNode)
{
return NS_OK;
}
NS_IMETHODIMP
TextServicesDocument::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.
nsCOMPtr<nsINode> leftNode = do_QueryInterface(aLeftNode);
nsCOMPtr<nsINode> rightNode = do_QueryInterface(aRightNode);
if (!leftNode || !leftNode->IsNodeOfType(nsINode::eTEXT)) {
return NS_OK;
}
if (!rightNode || !rightNode->IsNodeOfType(nsINode::eTEXT)) {
return NS_OK;
if (!aLeftNode.IsNodeOfType(nsINode::eTEXT) ||
!aRightNode.IsNodeOfType(nsINode::eTEXT)) {
return;
}
// Note: The editor merges the contents of the left node into the
@ -1596,31 +1554,34 @@ TextServicesDocument::DidJoinNodes(nsIDOMNode* aLeftNode,
bool rightHasEntry = false;
nsresult rv =
NodeHasOffsetEntry(&mOffsetTable, leftNode, &leftHasEntry, &leftIndex);
NS_ENSURE_SUCCESS(rv, rv);
NodeHasOffsetEntry(&mOffsetTable, &aLeftNode, &leftHasEntry, &leftIndex);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (!leftHasEntry) {
// It's okay if the node isn't in the offset table, the
// editor could be cleaning house.
return NS_OK;
return;
}
rv = NodeHasOffsetEntry(&mOffsetTable, rightNode,
rv = NodeHasOffsetEntry(&mOffsetTable, &aRightNode,
&rightHasEntry, &rightIndex);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (!rightHasEntry) {
// It's okay if the node isn't in the offset table, the
// editor could be cleaning house.
return NS_OK;
return;
}
NS_ASSERTION(leftIndex < rightIndex, "Indexes out of order.");
if (leftIndex > rightIndex) {
// Don't know how to handle this situation.
return NS_ERROR_FAILURE;
return;
}
LOCK_DOC(this);
@ -1630,26 +1591,23 @@ TextServicesDocument::DidJoinNodes(nsIDOMNode* aLeftNode,
// Run through the table and change all entries referring to
// the left node so that they now refer to the right node:
uint32_t nodeLength = leftNode->Length();
uint32_t nodeLength = aLeftNode.Length();
for (int32_t i = leftIndex; i < rightIndex; i++) {
entry = mOffsetTable[i];
if (entry->mNode != leftNode) {
if (entry->mNode != &aLeftNode) {
break;
}
if (entry->mIsValid) {
entry->mNode = rightNode;
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 != rightNode) {
if (entry->mNode != &aRightNode) {
break;
}
if (entry->mIsValid) {
@ -1660,13 +1618,11 @@ TextServicesDocument::DidJoinNodes(nsIDOMNode* aLeftNode,
// Now check to see if the iterator is pointing to the
// left node. If it is, make it point to the right node!
if (mIterator->GetCurrentNode() == leftNode) {
mIterator->PositionAt(rightNode);
if (mIterator->GetCurrentNode() == &aLeftNode) {
mIterator->PositionAt(&aRightNode);
}
UNLOCK_DOC(this);
return NS_OK;
}
nsresult
@ -3249,9 +3205,57 @@ TextServicesDocument::FindWordBounds(nsTArray<OffsetEntry*>* aOffsetTable,
return NS_OK;
}
// -------------------------------
// stubs for unused listen methods
// -------------------------------
/**
* nsIEditActionListener implementation:
* Don't implement the behavior directly here. The methods won't be called
* if the instance is created for inline spell checker created for editor.
* If you need to listen a new edit action, you need to add similar
* non-virtual method and you need to call it from EditorBase directly.
*/
NS_IMETHODIMP
TextServicesDocument::DidInsertNode(nsIDOMNode* aNode,
nsresult aResult)
{
return NS_OK;
}
NS_IMETHODIMP
TextServicesDocument::DidDeleteNode(nsIDOMNode* aChild,
nsresult aResult)
{
if (NS_WARN_IF(NS_FAILED(aResult))) {
return NS_OK;
}
nsCOMPtr<nsINode> child = do_QueryInterface(aChild);
DidDeleteNode(child);
return NS_OK;
}
NS_IMETHODIMP
TextServicesDocument::DidSplitNode(nsIDOMNode* aExistingRightNode,
nsIDOMNode* aNewLeftNode)
{
return NS_OK;
}
NS_IMETHODIMP
TextServicesDocument::DidJoinNodes(nsIDOMNode* aLeftNode,
nsIDOMNode* aRightNode,
nsIDOMNode* aParent,
nsresult aResult)
{
if (NS_WARN_IF(NS_FAILED(aResult))) {
return NS_OK;
}
nsCOMPtr<nsINode> leftNode = do_QueryInterface(aLeftNode);
nsCOMPtr<nsINode> rightNode = do_QueryInterface(aRightNode);
if (NS_WARN_IF(!leftNode) || NS_WARN_IF(!rightNode)) {
return NS_OK;
}
DidJoinNodes(*leftNode, *rightNode);
return NS_OK;
}
NS_IMETHODIMP
TextServicesDocument::DidCreateNode(const nsAString& aTag,

View File

@ -218,9 +218,22 @@ public:
*/
nsresult InsertText(const nsString* aText);
/* nsIEditActionListener method implementations. */
/**
* nsIEditActionListener method implementations.
*/
NS_DECL_NSIEDITACTIONLISTENER
/**
* Actual edit action listeners. When you add new method here for listening
* to new edit action, you need to make it called by EditorBase.
* Additionally, you need to call it from proper method of
* nsIEditActionListener too because if this is created not for inline
* spell checker of the editor, edit actions will be notified via
* nsIEditActionListener (slow path, though).
*/
void DidDeleteNode(nsINode* aChild);
void DidJoinNodes(nsINode& aLeftNode, nsINode& aRightNode);
static nsresult GetRangeEndPoints(nsRange* aRange,
nsINode** aStartContainer,
int32_t* aStartOffset,