Bug 1250823 part 1 - Implement DocumentObserver as a nested class of IMEContentObserver for observing to begin and end update r=smaug

IMEContentObserver can reduce the number of times to compute node offsets with caching all added nodes as a range.  For that, it needs to know when it starts to update and ends updating the document.

nsIDocumentObserver is a subclass of nsIMutationObserver and IMEContentObserver is a mutation observer of editor's root content.  Therefore, if we change IMEContentObserver to a document observer, each mutation observer method needs to check if the change occurs in the observing editor.  Therefore, this patch implements document observer as its nested class and manages it as a member of IMEContentObserver.

MozReview-Commit-ID: HPSPfajxjnx

--HG--
extra : rebase_source : fd4bdcc24759040fb6c39317024049a3a924fc67
This commit is contained in:
Masayuki Nakano 2017-06-08 11:21:28 +09:00
parent 0dbc15d1bd
commit a5bc18e848
3 changed files with 225 additions and 3 deletions

View File

@ -132,6 +132,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditableNode)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditor)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndOfAddedTextCache.mContainerNode)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode)
@ -147,6 +148,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditableNode)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditor)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndOfAddedTextCache.mContainerNode)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
mStartOfRemovingTextRangeCache.mContainerNode)
@ -201,7 +203,6 @@ IMEContentObserver::Init(nsIWidget* aWidget,
// If this is now trying to initialize with new contents, all observers
// should be registered again for simpler implementation.
UnregisterObservers();
// Clear members which may not be initialized again.
Clear();
}
@ -352,6 +353,8 @@ IMEContentObserver::InitWithEditor(nsPresContext* aPresContext,
return false;
}
mDocumentObserver = new DocumentObserver(*this);
MOZ_ASSERT(!WasInitializedWithPlugin());
return true;
@ -383,6 +386,11 @@ IMEContentObserver::InitWithPlugin(nsPresContext* aPresContext,
mEditor = nullptr;
mEditableNode = aContent;
mRootContent = aContent;
// Should be safe to clear mDocumentObserver here even though it *might*
// grab this instance because this is called by Init() and the callers of
// it and MaybeReinitialize() grabs this instance with local RefPtr.
// So, this won't cause refcount of this instance become 0.
mDocumentObserver = nullptr;
mDocShell = aPresContext->GetDocShell();
if (NS_WARN_IF(!mDocShell)) {
@ -408,6 +416,12 @@ IMEContentObserver::Clear()
mEditableNode = nullptr;
mRootContent = nullptr;
mDocShell = nullptr;
// Should be safe to clear mDocumentObserver here even though it grabs
// this instance in most cases because this is called by Init() or Destroy().
// The callers of Init() grab this instance with local RefPtr.
// The caller of Destroy() also grabs this instance with local RefPtr.
// So, this won't cause refcount of this instance become 0.
mDocumentObserver = nullptr;
}
void
@ -446,6 +460,13 @@ IMEContentObserver::ObserveEditableNode()
// non-plugin content since we cannot detect text changes in
// plugins.
mRootContent->AddMutationObserver(this);
// If it's in a document (should be so), we can use document observer to
// reduce redundant computation of text change offsets.
nsIDocument* doc = mRootContent->GetComposedDoc();
if (doc) {
RefPtr<DocumentObserver> documentObserver = mDocumentObserver;
documentObserver->Observe(doc);
}
}
if (mDocShell) {
@ -519,6 +540,11 @@ IMEContentObserver::UnregisterObservers()
mRootContent->RemoveMutationObserver(this);
}
if (mDocumentObserver) {
RefPtr<DocumentObserver> documentObserver = mDocumentObserver;
documentObserver->StopObserving();
}
if (mDocShell) {
mDocShell->RemoveWeakScrollObserver(this);
mDocShell->RemoveWeakReflowObserver(this);
@ -1150,6 +1176,18 @@ IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
MaybeNotifyIMEOfTextChange(data);
}
void
IMEContentObserver::BeginDocumentUpdate()
{
// TODO: Implement this later.
}
void
IMEContentObserver::EndDocumentUpdate()
{
// TODO: Implement this later.
}
void
IMEContentObserver::SuppressNotifyingIME()
{
@ -1975,4 +2013,105 @@ IMEContentObserver::IMENotificationSender::SendCompositionEventHandled()
"NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED", this));
}
/******************************************************************************
* mozilla::IMEContentObserver::DocumentObservingHelper
******************************************************************************/
NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver::DocumentObserver)
// StopObserving() releases mIMEContentObserver and mDocument.
tmp->StopObserving();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMEContentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver::DocumentObserver)
NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver::DocumentObserver)
void
IMEContentObserver::DocumentObserver::Observe(nsIDocument* aDocument)
{
MOZ_ASSERT(aDocument);
// Guarantee that aDocument won't be destroyed during a call of
// StopObserving().
RefPtr<nsIDocument> newDocument = aDocument;
StopObserving();
mDocument = newDocument.forget();
mDocument->AddObserver(this);
}
void
IMEContentObserver::DocumentObserver::StopObserving()
{
if (!IsObserving()) {
return;
}
// Grab IMEContentObserver which could be destroyed during method calls.
RefPtr<IMEContentObserver> observer = mIMEContentObserver.forget();
// Stop observing the document first.
RefPtr<nsIDocument> document = mDocument.forget();
document->RemoveObserver(this);
// Notify IMEContentObserver of ending of document updates if this already
// notified it of beginning of document updates.
for (; IsUpdating(); --mDocumentUpdating) {
// FYI: IsUpdating() returns true until mDocumentUpdating becomes 0.
// However, IsObserving() returns false now because mDocument was
// already cleared above. Therefore, this method won't be called
// recursively.
observer->EndDocumentUpdate();
}
}
void
IMEContentObserver::DocumentObserver::Destroy()
{
StopObserving();
mIMEContentObserver = nullptr;
}
void
IMEContentObserver::DocumentObserver::BeginUpdate(nsIDocument* aDocument,
nsUpdateType aUpdateType)
{
if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving())) {
return;
}
if (!(aUpdateType & UPDATE_CONTENT_MODEL)) {
return;
}
mDocumentUpdating++;
mIMEContentObserver->BeginDocumentUpdate();
}
void
IMEContentObserver::DocumentObserver::EndUpdate(nsIDocument* aDocument,
nsUpdateType aUpdateType)
{
if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving()) ||
NS_WARN_IF(!IsUpdating())) {
return;
}
if (!(aUpdateType & UPDATE_CONTENT_MODEL)) {
return;
}
mDocumentUpdating--;
mIMEContentObserver->EndDocumentUpdate();
}
} // namespace mozilla

View File

@ -17,6 +17,7 @@
#include "nsISelectionListener.h"
#include "nsIScrollObserver.h"
#include "nsIWidget.h"
#include "nsStubDocumentObserver.h"
#include "nsStubMutationObserver.h"
#include "nsThreadUtils.h"
#include "nsWeakReference.h"
@ -73,9 +74,39 @@ public:
nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
/**
* Init() initializes the instance, i.e., retrieving necessary objects and
* starts to observe something.
* Be aware, callers of this method need to guarantee that the instance
* won't be released during calling this.
*
* @param aWidget The widget which can access native IME.
* @param aPresContext The PresContext which has aContent.
* @param aContent An editable element or a plugin host element which
* user may use IME in.
* Or nullptr if this will observe design mode
* document.
* @param aEditor When aContent is an editable element or nullptr,
* non-nullptr referring an editor instance which
* manages aContent.
* Otherwise, i.e., this will observe a plugin content,
* should be nullptr.
*/
void Init(nsIWidget* aWidget, nsPresContext* aPresContext,
nsIContent* aContent, nsIEditor* aEditor);
/**
* Destroy() finalizes the instance, i.e., stops observing contents and
* clearing the members.
* Be aware, callers of this method need to guarantee that the instance
* won't be released during calling this.
*/
void Destroy();
/**
* Returns false if the instance refers some objects and observing them.
* Otherwise, true.
*/
bool Destroyed() const;
/**
@ -84,10 +115,14 @@ public:
* storing the instance.
*/
void DisconnectFromEventStateManager();
/**
* MaybeReinitialize() tries to restart to observe the editor's root node.
* This is useful when the editor is reframed and all children are replaced
* with new node instances.
* Be aware, callers of this method need to guarantee that the instance
* won't be released during calling this.
*
* @return Returns true if the instance is managing the content.
* Otherwise, false.
*/
@ -95,6 +130,7 @@ public:
nsPresContext* aPresContext,
nsIContent* aContent,
nsIEditor* aEditor);
bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent) const;
bool IsManaging(const TextComposition* aTextComposition) const;
bool WasInitializedWithPlugin() const;
@ -146,6 +182,11 @@ private:
bool IsSafeToNotifyIME() const;
bool IsEditorComposing() const;
// Following methods are called by DocumentObserver when
// beginning to update the contents and ending updating the contents.
void BeginDocumentUpdate();
void EndDocumentUpdate();
void PostFocusSetNotification();
void MaybeNotifyIMEOfFocusSet();
void PostTextChangeNotification();
@ -278,6 +319,47 @@ private:
// mQueuedSender is, it was put into the event queue but not run yet.
RefPtr<IMENotificationSender> mQueuedSender;
/**
* IMEContentObserver is a mutation observer of mRootContent. However,
* it needs to know the beginning of content changes and end of it too for
* reducing redundant computation of text offset with ContentEventHandler.
* Therefore, it needs helper class to listen only them since if
* both mutations were observed by IMEContentObserver directly, each
* methods need to check if the changing node is in mRootContent but it's
* too expensive.
*/
class DocumentObserver final : public nsStubDocumentObserver
{
public:
explicit DocumentObserver(IMEContentObserver& aIMEContentObserver)
: mIMEContentObserver(&aIMEContentObserver)
, mDocumentUpdating(0)
{
}
NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE
NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
void Observe(nsIDocument* aDocument);
void StopObserving();
void Destroy();
bool Destroyed() const { return !mIMEContentObserver; }
bool IsObserving() const { return mDocument != nullptr; }
bool IsUpdating() const { return mDocumentUpdating != 0; }
private:
DocumentObserver() = delete;
virtual ~DocumentObserver() { Destroy(); }
RefPtr<IMEContentObserver> mIMEContentObserver;
nsCOMPtr<nsIDocument> mDocument;
uint32_t mDocumentUpdating;
};
RefPtr<DocumentObserver> mDocumentObserver;
/**
* FlatTextCache stores flat text length from start of the content to
* mNodeOffset of mContainerNode.

View File

@ -863,8 +863,9 @@ IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
MOZ_LOG(sISMLog, LogLevel::Debug,
(" UpdateIMEState(), try to reinitialize the "
"active IMEContentObserver"));
if (!sActiveIMEContentObserver->MaybeReinitialize(widget, sPresContext,
aContent, &aEditorBase)) {
RefPtr<IMEContentObserver> contentObserver = sActiveIMEContentObserver;
if (!contentObserver->MaybeReinitialize(widget, sPresContext,
aContent, &aEditorBase)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), failed to reinitialize the "
"active IMEContentObserver"));