# HG changeset patch # User Masayuki Nakano # Date 1516955145 -32400 # Node ID 1bb95c1b49c8aab525e6f6f45d954cc68bdb9b60 # Parent 071bafccef6b335d4fc44244eeb5be4fdc52786c Bug 1433345 - part 4: Make EditorBase derived from nsISelectionListener and notify its owning classes of selection change r=m_kato This patch makes EditorBase derived from nsISelectionListener. Then, we can make IMEContentObserver, TextInputListener, ComposerCommandsUpdater, TypeInState not derived from nsISelectionListener since EditorBase or HTMLEditor can notify them of selection change directly. Additionally, ResizerSelectionListener is not necessary anymore since it just implements nsISelectionListener and calls only a method of HTMLEditor. So, HTMLEditor can call it directly. Note that the order of selection listeners may be different. However, according to what each selection listener does, changing the order isn't problem. MozReview-Commit-ID: 1JXZxQcS0tP diff --git a/dom/events/IMEContentObserver.cpp b/dom/events/IMEContentObserver.cpp --- a/dom/events/IMEContentObserver.cpp +++ b/dom/events/IMEContentObserver.cpp @@ -181,22 +181,21 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN( NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorBase) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndOfAddedTextCache.mContainerNode) NS_IMPL_CYCLE_COLLECTION_TRAVERSE( mStartOfRemovingTextRangeCache.mContainerNode) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver) - NS_INTERFACE_MAP_ENTRY(nsISelectionListener) NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) NS_INTERFACE_MAP_ENTRY(nsIReflowObserver) NS_INTERFACE_MAP_ENTRY(nsIScrollObserver) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelectionListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIReflowObserver) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver) NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver) IMEContentObserver::IMEContentObserver() : mESM(nullptr) , mIMENotificationRequests(nullptr) @@ -400,18 +399,16 @@ IMEContentObserver::ObserveEditableNode( return; } mIsObserving = true; if (mEditorBase) { mEditorBase->SetIMEContentObserver(this); } - nsresult rv = mSelection->AddSelectionListener(this); - NS_ENSURE_SUCCESS_VOID(rv); 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 = mDocumentObserver; documentObserver->Observe(doc); } @@ -470,17 +467,16 @@ IMEContentObserver::UnregisterObservers( } mIsObserving = false; if (mEditorBase) { mEditorBase->SetIMEContentObserver(nullptr); } if (mSelection) { - mSelection->RemoveSelectionListener(this); mSelectionData.Clear(); mFocusedWidget = nullptr; } if (mRootContent) { mRootContent->RemoveMutationObserver(this); } @@ -630,33 +626,31 @@ IMEContentObserver::GetSelectionAndRoot( } NS_ASSERTION(mSelection && mRootContent, "uninitialized content observer"); NS_ADDREF(*aSelection = mSelection); NS_ADDREF(*aRootContent = mRootContent); return NS_OK; } -nsresult -IMEContentObserver::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, - nsISelection* aSelection, - int16_t aReason) +void +IMEContentObserver::OnSelectionChange(Selection& aSelection) { - int32_t count = 0; - nsresult rv = aSelection->GetRangeCount(&count); - NS_ENSURE_SUCCESS(rv, rv); - if (count > 0 && mWidget) { + if (!mIsObserving) { + return; + } + + if (aSelection.RangeCount() && mWidget) { bool causedByComposition = IsEditorHandlingEventForComposition(); bool causedBySelectionEvent = TextComposition::IsHandlingSelectionEvent(); bool duringComposition = IsEditorComposing(); MaybeNotifyIMEOfSelectionChange(causedByComposition, causedBySelectionEvent, duringComposition); } - return NS_OK; } void IMEContentObserver::ScrollPositionChanged() { if (!NeedsPositionChangeNotification()) { return; } diff --git a/dom/events/IMEContentObserver.h b/dom/events/IMEContentObserver.h --- a/dom/events/IMEContentObserver.h +++ b/dom/events/IMEContentObserver.h @@ -9,17 +9,16 @@ #include "mozilla/Attributes.h" #include "mozilla/EditorBase.h" #include "mozilla/dom/Selection.h" #include "nsCOMPtr.h" #include "nsCycleCollectionParticipant.h" #include "nsIDocShell.h" // XXX Why does only this need to be included here? #include "nsIReflowObserver.h" -#include "nsISelectionListener.h" #include "nsIScrollObserver.h" #include "nsIWidget.h" #include "nsStubDocumentObserver.h" #include "nsStubMutationObserver.h" #include "nsThreadUtils.h" #include "nsWeakReference.h" class nsIContent; @@ -27,49 +26,56 @@ class nsINode; class nsISelection; class nsPresContext; namespace mozilla { class EventStateManager; class TextComposition; +namespace dom { +class Selection; +} // namespace dom + // IMEContentObserver notifies widget of any text and selection changes // in the currently focused editor -class IMEContentObserver final : public nsISelectionListener - , public nsStubMutationObserver +class IMEContentObserver final : public nsStubMutationObserver , public nsIReflowObserver , public nsIScrollObserver , public nsSupportsWeakReference { public: typedef widget::IMENotification::SelectionChangeData SelectionChangeData; typedef widget::IMENotification::TextChangeData TextChangeData; typedef widget::IMENotification::TextChangeDataBase TextChangeDataBase; typedef widget::IMENotificationRequests IMENotificationRequests; typedef widget::IMEMessage IMEMessage; IMEContentObserver(); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver, - nsISelectionListener) - NS_DECL_NSISELECTIONLISTENER + nsIReflowObserver) NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED NS_DECL_NSIREFLOWOBSERVER // nsIScrollObserver virtual void ScrollPositionChanged() override; + /** + * OnSelectionChange() is called when selection is changed in the editor. + */ + void OnSelectionChange(dom::Selection& aSelection); + bool OnMouseButtonEvent(nsPresContext* aPresContext, WidgetMouseEvent* aMouseEvent); nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent); /** * Init() initializes the instance, i.e., retrieving necessary objects and * starts to observe something. @@ -330,24 +336,24 @@ private: eChangeEventType_CompositionEventHandled }; explicit AChangeEvent(const char* aName, IMEContentObserver* aIMEContentObserver) : Runnable(aName) , mIMEContentObserver( do_GetWeakReference( - static_cast(aIMEContentObserver))) + static_cast(aIMEContentObserver))) { MOZ_ASSERT(aIMEContentObserver); } already_AddRefed GetObserver() const { - nsCOMPtr observer = + nsCOMPtr observer = do_QueryReferent(mIMEContentObserver); return observer.forget().downcast(); } nsWeakPtr mIMEContentObserver; /** * CanNotifyIME() checks if mIMEContentObserver can and should notify IME. diff --git a/dom/html/TextInputListener.h b/dom/html/TextInputListener.h --- a/dom/html/TextInputListener.h +++ b/dom/html/TextInputListener.h @@ -4,29 +4,27 @@ * 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 mozilla_TextInputListener_h #define mozilla_TextInputListener_h #include "nsCycleCollectionParticipant.h" #include "nsIDOMEventListener.h" -#include "nsISelectionListener.h" #include "nsStringFwd.h" #include "nsWeakReference.h" class nsIFrame; class nsISelection; class nsITextControlElement; class nsTextControlFrame; namespace mozilla { -class TextInputListener final : public nsISelectionListener - , public nsIDOMEventListener +class TextInputListener final : public nsIDOMEventListener , public nsSupportsWeakReference { public: explicit TextInputListener(nsITextControlElement* aTextControlElement); void SetFrame(nsIFrame* aTextControlFrame) { mFrame = aTextControlFrame; @@ -46,20 +44,36 @@ public: */ void HandleValueChanged(nsTextControlFrame* aFrame = nullptr); /** * OnEditActionHandled() is called when the editor handles each edit action. */ void OnEditActionHandled(); + /** + * OnSelectionChange() is called when selection is changed in the editor. + */ + void OnSelectionChange(Selection& aSelection, int16_t aReason); + + /** + * Start to listen or end listening to selection change in the editor. + */ + void StartToListenToSelectionChange() + { + mListeningToSelectionChange = true; + } + void EndListeningToSelectionChange() + { + mListeningToSelectionChange = false; + } + NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TextInputListener, - nsISelectionListener) - NS_DECL_NSISELECTIONLISTENER + nsIDOMEventListener) NS_DECL_NSIDOMEVENTLISTENER protected: virtual ~TextInputListener() = default; nsresult UpdateTextInputCommands(const nsAString& aCommandsToUpdate, nsISelection* aSelection = nullptr, int16_t aReason = 0); @@ -85,13 +99,17 @@ protected: * refrain from calling OnValueChanged. */ bool mSettingValue; /** * Whether we are in the process of a SetValue call that doesn't want * |SetValueChanged| to be called. */ bool mSetValueChanged; + /** + * Whether we're listening to selection change in the editor. + */ + bool mListeningToSelectionChange; }; } // namespace mozilla #endif // #ifndef mozilla_TextInputListener_h diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp --- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -796,61 +796,57 @@ nsTextInputSelectionImpl::CheckVisibilit TextInputListener::TextInputListener(nsITextControlElement* aTxtCtrlElement) : mFrame(nullptr) , mTxtCtrlElement(aTxtCtrlElement) , mSelectionWasCollapsed(true) , mHadUndoItems(false) , mHadRedoItems(false) , mSettingValue(false) , mSetValueChanged(true) + , mListeningToSelectionChange(false) { } NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputListener) NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener) NS_INTERFACE_MAP_BEGIN(TextInputListener) - NS_INTERFACE_MAP_ENTRY(nsISelectionListener) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelectionListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener) NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextInputListener) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_0(TextInputListener) -// BEGIN nsIDOMSelectionListener - -NS_IMETHODIMP -TextInputListener::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, - nsISelection* aSelection, - int16_t aReason) +void +TextInputListener::OnSelectionChange(Selection& aSelection, + int16_t aReason) { - bool collapsed; - AutoWeakFrame weakFrame = mFrame; + if (!mListeningToSelectionChange) { + return; + } - if (!aDOMDocument || !aSelection || - NS_FAILED(aSelection->GetIsCollapsed(&collapsed))) { - return NS_OK; - } + AutoWeakFrame weakFrame = mFrame; // Fire the select event // The specs don't exactly say when we should fire the select event. // IE: Whenever you add/remove a character to/from the selection. Also // each time for select all. Also if you get to the end of the text // field you will get new event for each keypress or a continuous // stream of events if you use the mouse. IE will fire select event // when the selection collapses to nothing if you are holding down // the shift or mouse button. // Mozilla: If we have non-empty selection we will fire a new event for each // keypress (or mouseup) if the selection changed. Mozilla will also // create the event each time select all is called, even if everything // was previously selected, becase technically select all will first collapse // and then extend. Mozilla will never create an event if the selection // collapses to nothing. + bool collapsed = aSelection.IsCollapsed(); if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON | nsISelectionListener::KEYPRESS_REASON | nsISelectionListener::SELECTALL_REASON))) { nsIContent* content = mFrame->GetContent(); if (content) { nsCOMPtr doc = content->GetComposedDoc(); if (doc) { nsCOMPtr presShell = doc->GetShell(); @@ -861,32 +857,30 @@ TextInputListener::NotifySelectionChange presShell->HandleEventWithTarget(&event, mFrame, content, &status); } } } } // if the collapsed state did not change, don't fire notifications if (collapsed == mSelectionWasCollapsed) { - return NS_OK; + return; } mSelectionWasCollapsed = collapsed; if (!weakFrame.IsAlive() || !mFrame || !nsContentUtils::IsFocusedContent(mFrame->GetContent())) { - return NS_OK; + return; } - return UpdateTextInputCommands(NS_LITERAL_STRING("select"), - aSelection, aReason); + UpdateTextInputCommands(NS_LITERAL_STRING("select"), + &aSelection, aReason); } -// END nsIDOMSelectionListener - static void DoCommandCallback(Command aCommand, void* aData) { nsTextControlFrame *frame = static_cast(aData); nsIContent *content = frame->GetContent(); nsCOMPtr controllers; nsCOMPtr input = do_QueryInterface(content); @@ -1257,17 +1251,17 @@ nsTextEditorState::BindToFrame(nsTextCon // Selection::AddSelectionListner() because it only appends the listener // to its internal array. Selection* selection = mSelCon->GetSelection(SelectionType::eNormal); if (selection) { RefPtr caret = shell->GetCaret(); if (caret) { selection->AddSelectionListener(caret); } - selection->AddSelectionListener(mTextListener); + mTextListener->StartToListenToSelectionChange(); } // If an editor exists from before, prepare it for usage if (mTextEditor) { nsCOMPtr content = do_QueryInterface(mTextCtrlElement); NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); // Set the correct direction on the newly created root node @@ -2108,24 +2102,17 @@ nsTextEditorState::UnbindFromFrame(nsTex } } } } } if (mSelCon) { if (mTextListener) { - // FYI: It's safe to use raw pointer for calling - // Selection::RemoveSelectionListener() because it only removes the - // listener from its array. - Selection* selection = - mSelCon->GetSelection(SelectionType::eNormal); - if (selection) { - selection->RemoveSelectionListener(mTextListener); - } + mTextListener->EndListeningToSelectionChange(); } mSelCon->SetScrollableFrame(nullptr); mSelCon = nullptr; } if (mTextListener) { diff --git a/editor/composer/ComposerCommandsUpdater.cpp b/editor/composer/ComposerCommandsUpdater.cpp --- a/editor/composer/ComposerCommandsUpdater.cpp +++ b/editor/composer/ComposerCommandsUpdater.cpp @@ -18,17 +18,16 @@ #include "nsIDocShell.h" // for nsIDocShell #include "nsIInterfaceRequestorUtils.h" // for do_GetInterface #include "nsISelection.h" // for nsISelection #include "nsITransactionManager.h" // for nsITransactionManager #include "nsLiteralString.h" // for NS_LITERAL_STRING #include "nsPICommandUpdater.h" // for nsPICommandUpdater #include "nsPIDOMWindow.h" // for nsPIDOMWindow -class nsIDOMDocument; class nsITransaction; namespace mozilla { ComposerCommandsUpdater::ComposerCommandsUpdater() : mDirtyState(eStateUninitialized) , mSelectionCollapsed(eStateUninitialized) , mFirstDoOfFirstUndo(true) @@ -42,17 +41,16 @@ ComposerCommandsUpdater::~ComposerComman mUpdateTimer->Cancel(); } } NS_IMPL_CYCLE_COLLECTING_ADDREF(ComposerCommandsUpdater) NS_IMPL_CYCLE_COLLECTING_RELEASE(ComposerCommandsUpdater) NS_INTERFACE_MAP_BEGIN(ComposerCommandsUpdater) - NS_INTERFACE_MAP_ENTRY(nsISelectionListener) NS_INTERFACE_MAP_ENTRY(nsIDocumentStateListener) NS_INTERFACE_MAP_ENTRY(nsITransactionListener) NS_INTERFACE_MAP_ENTRY(nsITimerCallback) NS_INTERFACE_MAP_ENTRY(nsINamed) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentStateListener) NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ComposerCommandsUpdater) NS_INTERFACE_MAP_END @@ -94,24 +92,16 @@ ComposerCommandsUpdater::NotifyDocumentW NS_IMETHODIMP ComposerCommandsUpdater::NotifyDocumentStateChanged(bool aNowDirty) { // update document modified. We should have some other notifications for this too. return UpdateDirtyState(aNowDirty); } -NS_IMETHODIMP -ComposerCommandsUpdater::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, - nsISelection* aSelection, - int16_t aReason) -{ - return PrimeUpdateTimer(); -} - #if 0 #pragma mark - #endif NS_IMETHODIMP ComposerCommandsUpdater::WillDo(nsITransactionManager* aManager, nsITransaction* aTransaction, bool* aInterrupt) diff --git a/editor/composer/ComposerCommandsUpdater.h b/editor/composer/ComposerCommandsUpdater.h --- a/editor/composer/ComposerCommandsUpdater.h +++ b/editor/composer/ComposerCommandsUpdater.h @@ -6,62 +6,65 @@ #ifndef mozilla_ComposerCommandsUpdater_h #define mozilla_ComposerCommandsUpdater_h #include "nsCOMPtr.h" // for already_AddRefed, nsCOMPtr #include "nsCycleCollectionParticipant.h" #include "nsIDocumentStateListener.h" #include "nsINamed.h" -#include "nsISelectionListener.h" #include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS #include "nsITimer.h" // for NS_DECL_NSITIMERCALLBACK, etc #include "nsITransactionListener.h" // for nsITransactionListener #include "nsIWeakReferenceUtils.h" // for nsWeakPtr #include "nscore.h" // for NS_IMETHOD, nsresult, etc class nsIDocShell; class nsITransaction; class nsITransactionManager; class nsPIDOMWindowOuter; class nsPICommandUpdater; namespace mozilla { -class ComposerCommandsUpdater final : public nsISelectionListener - , public nsIDocumentStateListener +class ComposerCommandsUpdater final : public nsIDocumentStateListener , public nsITransactionListener , public nsITimerCallback , public nsINamed { public: ComposerCommandsUpdater(); // nsISupports NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ComposerCommandsUpdater, nsIDocumentStateListener) - // nsISelectionListener - NS_DECL_NSISELECTIONLISTENER - // nsIDocumentStateListener NS_DECL_NSIDOCUMENTSTATELISTENER // nsITimerCallback NS_DECL_NSITIMERCALLBACK // nsINamed NS_DECL_NSINAMED // nsITransactionListener NS_DECL_NSITRANSACTIONLISTENER nsresult Init(nsPIDOMWindowOuter* aDOMWindow); + /** + * OnSelectionChange() is called when selection is changed in the editor. + */ + void OnSelectionChange() + { + PrimeUpdateTimer(); + } + protected: virtual ~ComposerCommandsUpdater(); enum { eStateUninitialized = -1, eStateOff = 0, eStateOn = 1, diff --git a/editor/composer/nsEditingSession.cpp b/editor/composer/nsEditingSession.cpp --- a/editor/composer/nsEditingSession.cpp +++ b/editor/composer/nsEditingSession.cpp @@ -462,20 +462,16 @@ nsEditingSession::SetupEditorOnWindow(mo nullptr, mEditorFlags, EmptyString()); NS_ENSURE_SUCCESS(rv, rv); RefPtr selection = htmlEditor->GetSelection(); if (NS_WARN_IF(!selection)) { return NS_ERROR_FAILURE; } - rv = selection->AddSelectionListener(mComposerCommandsUpdater); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } htmlEditor->SetComposerCommandsUpdater(mComposerCommandsUpdater); // and as a transaction listener nsCOMPtr txnMgr; htmlEditor->GetTransactionManager(getter_AddRefs(txnMgr)); if (txnMgr) { txnMgr->AddListener(mComposerCommandsUpdater); } @@ -496,20 +492,16 @@ void nsEditingSession::RemoveListenersAndControllers(nsPIDOMWindowOuter* aWindow, HTMLEditor* aHTMLEditor) { if (!mComposerCommandsUpdater || !aHTMLEditor) { return; } // Remove all the listeners - RefPtr selection = aHTMLEditor->GetSelection(); - if (selection) { - selection->RemoveSelectionListener(mComposerCommandsUpdater); - } aHTMLEditor->SetComposerCommandsUpdater(nullptr); aHTMLEditor->RemoveDocumentStateListener(mComposerCommandsUpdater); nsCOMPtr txnMgr; aHTMLEditor->GetTransactionManager(getter_AddRefs(txnMgr)); if (txnMgr) { txnMgr->RemoveListener(mComposerCommandsUpdater); diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp --- a/editor/libeditor/EditorBase.cpp +++ b/editor/libeditor/EditorBase.cpp @@ -210,16 +210,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN( NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventListener) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaceholderTransaction) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSavedSel); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRangeUpdater); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditorBase) + NS_INTERFACE_MAP_ENTRY(nsISelectionListener) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIEditor) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditor) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(EditorBase) NS_IMPL_CYCLE_COLLECTING_RELEASE(EditorBase) @@ -283,16 +284,21 @@ EditorBase::Init(nsIDOMDocument* aDOMDoc selectionController->SetCaretReadOnly(false); selectionController->SetDisplaySelection( nsISelectionController::SELECTION_ON); // Show all the selection reflected to user. selectionController->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL); MOZ_ASSERT(IsInitialized()); + Selection* selection = GetSelection(); + if (selection) { + selection->AddSelectionListener(this); + } + // Make sure that the editor will be destroyed properly mDidPreDestroy = false; // Make sure that the ediotr will be created properly mDidPostCreate = false; return NS_OK; } @@ -470,16 +476,21 @@ EditorBase::GetDesiredSpellCheckState() } NS_IMETHODIMP EditorBase::PreDestroy(bool aDestroyingFrames) { if (mDidPreDestroy) return NS_OK; + Selection* selection = GetSelection(); + if (selection) { + selection->RemoveSelectionListener(this); + } + IMEStateManager::OnEditorDestroying(*this); // Let spellchecker clean up its observers etc. It is important not to // actually free the spellchecker here, since the spellchecker could have // caused flush notifications, which could have gotten here if a textbox // is being removed. Setting the spellchecker to nullptr could free the // object that is still in use! It will be freed when the editor is // destroyed. @@ -2115,16 +2126,43 @@ EditorBase::RemoveEditorObserver(nsIEdit NS_WARNING_ASSERTION(mEditorObservers.Length() != 1, "All nsIEditorObservers have been removed, this editor becomes faster"); mEditorObservers.RemoveElement(aObserver); return NS_OK; } +NS_IMETHODIMP +EditorBase::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, + nsISelection* aSelection, + int16_t aReason) +{ + if (NS_WARN_IF(!aDOMDocument) || NS_WARN_IF(!aSelection)) { + return NS_ERROR_INVALID_ARG; + } + + RefPtr selection = aSelection->AsSelection(); + if (NS_WARN_IF(!selection)) { + return NS_ERROR_UNEXPECTED; + } + + if (mTextInputListener) { + RefPtr textInputListener = mTextInputListener; + textInputListener->OnSelectionChange(*selection, aReason); + } + + if (mIMEContentObserver) { + RefPtr observer = mIMEContentObserver; + observer->OnSelectionChange(*selection); + } + + return NS_OK; +} + class EditorInputEventDispatcher final : public Runnable { public: EditorInputEventDispatcher(EditorBase* aEditorBase, nsIContent* aTarget, bool aIsComposing) : Runnable("EditorInputEventDispatcher") , mEditorBase(aEditorBase) diff --git a/editor/libeditor/EditorBase.h b/editor/libeditor/EditorBase.h --- a/editor/libeditor/EditorBase.h +++ b/editor/libeditor/EditorBase.h @@ -21,16 +21,17 @@ #include "nsCOMPtr.h" // for already_AddRefed, nsCOMPtr #include "nsCycleCollectionParticipant.h" #include "nsGkAtoms.h" #include "nsIDocument.h" // for nsIDocument #include "nsIEditor.h" // for nsIEditor, etc. #include "nsIObserver.h" // for NS_DECL_NSIOBSERVER, etc. #include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc. #include "nsISelectionController.h" // for nsISelectionController constants +#include "nsISelectionListener.h" // for nsISelectionListener #include "nsISupportsImpl.h" // for EditorBase::Release, etc. #include "nsIWeakReferenceUtils.h" // for nsWeakPtr #include "nsLiteralString.h" // for NS_LITERAL_STRING #include "nsString.h" // for nsCString #include "nsTArray.h" // for nsTArray and nsAutoTArray #include "nsWeakReference.h" // for nsSupportsWeakReference #include "nscore.h" // for nsresult, nsAString, etc. @@ -183,16 +184,17 @@ enum class SplitAtEdges /** * Implementation of an editor object. it will be the controller/focal point * for the main editor services. i.e. the GUIManager, publishing, transaction * manager, event interfaces. the idea for the event interfaces is to have them * delegate the actual commands to the editor independent of the XPFE * implementation. */ class EditorBase : public nsIEditor + , public nsISelectionListener , public nsSupportsWeakReference { public: typedef dom::Element Element; typedef dom::Selection Selection; typedef dom::Text Text; enum IterDirection @@ -249,16 +251,19 @@ public: eNotifyEditorObserversOfBefore, eNotifyEditorObserversOfCancel }; void NotifyEditorObservers(NotificationForEditorObservers aNotification); // nsIEditor methods NS_DECL_NSIEDITOR + // nsISelectionListener method + NS_DECL_NSISELECTIONLISTENER + /** * Set or unset TextInputListener. If setting non-nullptr when the editor * already has a TextInputListener, this will crash in debug build. */ void SetTextInputListener(TextInputListener* aTextInputListener); /** * Set or unset IMEContentObserver. If setting non-nullptr when the editor diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp --- a/editor/libeditor/HTMLEditor.cpp +++ b/editor/libeditor/HTMLEditor.cpp @@ -137,31 +137,17 @@ HTMLEditor::HTMLEditor() } HTMLEditor::~HTMLEditor() { if (mRules && mRules->AsHTMLEditRules()) { mRules->AsHTMLEditRules()->EndListeningToEditActions(); } - //the autopointers will clear themselves up. - //but we need to also remove the listeners or we have a leak - RefPtr selection = GetSelection(); - // if we don't get the selection, just skip this - if (selection) { - if (mTypeInState) { - selection->RemoveSelectionListener(mTypeInState); - } - if (mResizerSelectionListener) { - selection->RemoveSelectionListener(mResizerSelectionListener); - } - } - mTypeInState = nullptr; - mResizerSelectionListener = nullptr; if (mLinkHandler && IsInitialized()) { nsCOMPtr ps = GetPresShell(); if (ps && ps->GetPresContext()) { ps->GetPresContext()->SetLinkHandler(mLinkHandler); } } @@ -195,17 +181,16 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN tmp->HideAnonymousEditingUIs(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinkHandler) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLEditor, TextEditor) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTypeInState) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mComposerCommandsUpdater) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizerSelectionListener) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopLeftHandle) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopHandle) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopRightHandle) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeftHandle) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRightHandle) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomLeftHandle) @@ -295,33 +280,20 @@ HTMLEditor::Init(nsIDOMDocument* aDoc, if (!IsPlaintextEditor() && !IsInteractionAllowed()) { mLinkHandler = context->GetLinkHandler(); context->SetLinkHandler(nullptr); } // init the type-in state mTypeInState = new TypeInState(); - // init the selection listener for image resizing - mResizerSelectionListener = new ResizerSelectionListener(*this); - if (!IsInteractionAllowed()) { // ignore any errors from this in case the file is missing AddOverrideStyleSheet(NS_LITERAL_STRING("resource://gre/res/EditorOverride.css")); } - - RefPtr selection = GetSelection(); - if (selection) { - if (mTypeInState) { - selection->AddSelectionListener(mTypeInState); - } - if (mResizerSelectionListener) { - selection->AddSelectionListener(mResizerSelectionListener); - } - } } NS_ENSURE_SUCCESS(rulesRv, rulesRv); return NS_OK; } NS_IMETHODIMP HTMLEditor::PreDestroy(bool aDestroyingFrames) @@ -341,16 +313,58 @@ HTMLEditor::PreDestroy(bool aDestroyingF // Clean up after our anonymous content -- we don't want these nodes to // stay around (which they would, since the frames have an owning reference). HideAnonymousEditingUIs(); return TextEditor::PreDestroy(aDestroyingFrames); } +NS_IMETHODIMP +HTMLEditor::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, + nsISelection* aSelection, + int16_t aReason) +{ + if (NS_WARN_IF(!aDOMDocument) || NS_WARN_IF(!aSelection)) { + return NS_ERROR_INVALID_ARG; + } + + RefPtr selection = aSelection->AsSelection(); + if (NS_WARN_IF(!selection)) { + return NS_ERROR_UNEXPECTED; + } + + if (mTypeInState) { + RefPtr typeInState = mTypeInState; + typeInState->OnSelectionChange(*selection); + + // We used a class which derived from nsISelectionListener to call + // HTMLEditor::CheckSelectionStateForAnonymousButtons(). The lifetime of + // the class was exactly same as mTypeInState. So, call it only when + // mTypeInState is not nullptr. + if ((aReason & (nsISelectionListener::MOUSEDOWN_REASON | + nsISelectionListener::KEYPRESS_REASON | + nsISelectionListener::SELECTALL_REASON)) && aSelection) { + // the selection changed and we need to check if we have to + // hide and/or redisplay resizing handles + // FYI: This is an XPCOM method. So, the caller, Selection, guarantees + // the lifetime of this instance. So, don't need to grab this with + // local variable. + CheckSelectionStateForAnonymousButtons(selection); + } + } + + if (mComposerCommandsUpdater) { + RefPtr updater = mComposerCommandsUpdater; + updater->OnSelectionChange(); + } + + return EditorBase::NotifySelectionChanged(aDOMDocument, aSelection, aReason); +} + void HTMLEditor::UpdateRootElement() { // Use the HTML documents body element as the editor root if we didn't // get a root element during initialization. nsCOMPtr rootElement; nsCOMPtr bodyElement; diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h --- a/editor/libeditor/HTMLEditor.h +++ b/editor/libeditor/HTMLEditor.h @@ -25,30 +25,31 @@ #include "nsIDOMEventListener.h" #include "nsIEditorMailSupport.h" #include "nsIEditorStyleSheets.h" #include "nsIEditorUtils.h" #include "nsIHTMLAbsPosEditor.h" #include "nsIHTMLEditor.h" #include "nsIHTMLInlineTableEditor.h" #include "nsIHTMLObjectResizer.h" -#include "nsISelectionListener.h" #include "nsITableEditor.h" #include "nsPoint.h" #include "nsStubMutationObserver.h" #include "nsTArray.h" class nsDocumentFragment; class nsITransferable; class nsIClipboard; +class nsIDOMDocument; class nsIDOMMouseEvent; class nsILinkHandler; class nsTableWrapperFrame; class nsIDOMRange; class nsRange; +class nsISelection; namespace mozilla { class AutoSelectionSetterAfterTableEdit; class HTMLEditorEventListener; class HTMLEditRules; class ResizerSelectionListener; class TypeInState; class WSRunObject; @@ -103,16 +104,21 @@ public: HTMLEditor(); bool GetReturnInParagraphCreatesNewParagraph(); Element* GetSelectionContainer(); // nsIEditor overrides NS_IMETHOD GetPreferredIMEState(widget::IMEState* aState) override; + // nsISelectionListener overrides + NS_IMETHOD NotifySelectionChanged(nsIDOMDocument* aDOMDocument, + nsISelection* aSelection, + int16_t aReason) override; + // TextEditor overrides NS_IMETHOD BeginningOfDocument() override; virtual nsresult HandleKeyPressEvent( WidgetKeyboardEvent* aKeyboardEvent) override; virtual nsIContent* GetFocusedContent() override; virtual already_AddRefed GetFocusedContentForIME() override; virtual bool IsActiveInDOMWindow() override; virtual dom::EventTarget* GetDOMEventTarget() override; @@ -985,17 +991,16 @@ protected: */ void SetSelectionAfterTableEdit(nsIDOMElement* aTable, int32_t aRow, int32_t aCol, int32_t aDirection, bool aSelected); protected: RefPtr mTypeInState; RefPtr mComposerCommandsUpdater; - RefPtr mResizerSelectionListener; bool mCRInParagraphCreatesParagraph; bool mCSSAware; UniquePtr mCSSEditUtils; // Used by GetFirstSelectedCell and GetNextSelectedCell int32_t mSelectedCellIndex; diff --git a/editor/libeditor/HTMLEditorObjectResizer.cpp b/editor/libeditor/HTMLEditorObjectResizer.cpp --- a/editor/libeditor/HTMLEditorObjectResizer.cpp +++ b/editor/libeditor/HTMLEditorObjectResizer.cpp @@ -63,59 +63,16 @@ DocumentResizeEventListener::HandleEvent RefPtr htmlEditor = mHTMLEditorWeak.get(); if (htmlEditor) { return htmlEditor->RefreshResizers(); } return NS_OK; } /****************************************************************************** - * mozilla::ResizerSelectionListener - ******************************************************************************/ - -NS_IMPL_CYCLE_COLLECTING_ADDREF(ResizerSelectionListener) -NS_IMPL_CYCLE_COLLECTING_RELEASE(ResizerSelectionListener) - -NS_INTERFACE_MAP_BEGIN(ResizerSelectionListener) - NS_INTERFACE_MAP_ENTRY(nsISelectionListener) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelectionListener) - NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ResizerSelectionListener) -NS_INTERFACE_MAP_END - -NS_IMPL_CYCLE_COLLECTION(ResizerSelectionListener, - mHTMLEditor) - -ResizerSelectionListener::ResizerSelectionListener(HTMLEditor& aHTMLEditor) - : mHTMLEditor(&aHTMLEditor) -{ -} - -NS_IMETHODIMP -ResizerSelectionListener::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, - nsISelection* aSelection, - int16_t aReason) -{ - if (!mHTMLEditor) { - return NS_OK; - } - if ((aReason & (nsISelectionListener::MOUSEDOWN_REASON | - nsISelectionListener::KEYPRESS_REASON | - nsISelectionListener::SELECTALL_REASON)) && aSelection) { - // the selection changed and we need to check if we have to - // hide and/or redisplay resizing handles - RefPtr htmlEditor = mHTMLEditor; - if (htmlEditor) { - htmlEditor->CheckSelectionStateForAnonymousButtons(aSelection); - } - } - - return NS_OK; -} - -/****************************************************************************** * mozilla::ResizerMouseMotionListener ******************************************************************************/ NS_IMPL_ISUPPORTS(ResizerMouseMotionListener, nsIDOMEventListener) ResizerMouseMotionListener::ResizerMouseMotionListener(HTMLEditor& aHTMLEditor) : mHTMLEditorWeak(&aHTMLEditor) { diff --git a/editor/libeditor/HTMLEditorObjectResizerUtils.h b/editor/libeditor/HTMLEditorObjectResizerUtils.h --- a/editor/libeditor/HTMLEditorObjectResizerUtils.h +++ b/editor/libeditor/HTMLEditorObjectResizerUtils.h @@ -4,17 +4,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef HTMLEditorObjectResizerUtils_h #define HTMLEditorObjectResizerUtils_h #include "mozilla/HTMLEditor.h" #include "nsCycleCollectionParticipant.h" #include "nsIDOMEventListener.h" -#include "nsISelectionListener.h" #include "nsISupportsImpl.h" #include "nsIWeakReferenceUtils.h" #include "nsLiteralString.h" class nsIHTMLEditor; namespace mozilla { @@ -23,37 +22,16 @@ namespace mozilla { #define kTopRight NS_LITERAL_STRING("ne") #define kLeft NS_LITERAL_STRING("w") #define kRight NS_LITERAL_STRING("e") #define kBottomLeft NS_LITERAL_STRING("sw") #define kBottom NS_LITERAL_STRING("s") #define kBottomRight NS_LITERAL_STRING("se") /****************************************************************************** - * mozilla::ResizerSelectionListener - ******************************************************************************/ - -class ResizerSelectionListener final : public nsISelectionListener -{ -public: - explicit ResizerSelectionListener(HTMLEditor& aHTMLEditor); - void Reset(); - - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ResizerSelectionListener, - nsISelectionListener) - - NS_DECL_NSISELECTIONLISTENER - -protected: - virtual ~ResizerSelectionListener() = default; - RefPtr mHTMLEditor; -}; - -/****************************************************************************** * mozilla::ResizerMouseMotionListener ******************************************************************************/ class ResizerMouseMotionListener final : public nsIDOMEventListener { public: explicit ResizerMouseMotionListener(HTMLEditor& aHTMLEditor); diff --git a/editor/libeditor/TypeInState.cpp b/editor/libeditor/TypeInState.cpp --- a/editor/libeditor/TypeInState.cpp +++ b/editor/libeditor/TypeInState.cpp @@ -31,23 +31,28 @@ class nsIDOMDocument; namespace mozilla { using namespace dom; /******************************************************************** * mozilla::TypeInState *******************************************************************/ -NS_IMPL_CYCLE_COLLECTION(TypeInState, mLastSelectionContainer) -NS_IMPL_CYCLE_COLLECTING_ADDREF(TypeInState) -NS_IMPL_CYCLE_COLLECTING_RELEASE(TypeInState) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TypeInState) - NS_INTERFACE_MAP_ENTRY(nsISelectionListener) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTION_CLASS(TypeInState) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TypeInState) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastSelectionContainer) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TypeInState) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastSelectionContainer) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(TypeInState, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(TypeInState, Release) TypeInState::TypeInState() : mRelativeFontSize(0) , mLastSelectionOffset(0) { Reset(); } @@ -68,64 +73,56 @@ TypeInState::UpdateSelState(Selection* a return NS_OK; } return EditorBase::GetStartNodeAndOffset( aSelection, getter_AddRefs(mLastSelectionContainer), &mLastSelectionOffset); } - -NS_IMETHODIMP -TypeInState::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, - nsISelection* aSelection, - int16_t aReason) +void +TypeInState::OnSelectionChange(Selection& aSelection) { // XXX: Selection currently generates bogus selection changed notifications // XXX: (bug 140303). It can notify us when the selection hasn't actually // XXX: changed, and it notifies us more than once for the same change. // XXX: // XXX: The following code attempts to work around the bogus notifications, // XXX: and should probably be removed once bug 140303 is fixed. // XXX: // XXX: This code temporarily fixes the problem where clicking the mouse in // XXX: the same location clears the type-in-state. - RefPtr selection = - aSelection ? aSelection->AsSelection() : nullptr; - if (aSelection) { - int32_t rangeCount = selection->RangeCount(); + // TODO: We can make this use nsINode instead of nsIDOMNode. + if (aSelection.IsCollapsed() && aSelection.RangeCount()) { + nsCOMPtr selNode; + int32_t selOffset = 0; - if (selection->Collapsed() && rangeCount) { - nsCOMPtr selNode; - int32_t selOffset = 0; - - nsresult rv = - EditorBase::GetStartNodeAndOffset(selection, getter_AddRefs(selNode), - &selOffset); + nsresult rv = + EditorBase::GetStartNodeAndOffset(&aSelection, getter_AddRefs(selNode), + &selOffset); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } - NS_ENSURE_SUCCESS(rv, rv); + if (selNode && + selNode == mLastSelectionContainer && + selOffset == mLastSelectionOffset) { + // We got a bogus selection changed notification! + return; + } - if (selNode && - selNode == mLastSelectionContainer && - selOffset == mLastSelectionOffset) { - // We got a bogus selection changed notification! - return NS_OK; - } - - mLastSelectionContainer = selNode; - mLastSelectionOffset = selOffset; - } else { - mLastSelectionContainer = nullptr; - mLastSelectionOffset = 0; - } + mLastSelectionContainer = selNode; + mLastSelectionOffset = selOffset; + } else { + mLastSelectionContainer = nullptr; + mLastSelectionOffset = 0; } Reset(); - return NS_OK; } void TypeInState::Reset() { for (size_t i = 0, n = mClearedArray.Length(); i < n; i++) { delete mClearedArray[i]; } diff --git a/editor/libeditor/TypeInState.h b/editor/libeditor/TypeInState.h --- a/editor/libeditor/TypeInState.h +++ b/editor/libeditor/TypeInState.h @@ -4,17 +4,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TypeInState_h #define TypeInState_h #include "mozilla/UniquePtr.h" #include "nsCOMPtr.h" #include "nsCycleCollectionParticipant.h" -#include "nsISelectionListener.h" #include "nsISupportsImpl.h" #include "nsString.h" #include "nsTArray.h" #include "nscore.h" // Workaround for windows headers #ifdef SetProp #undef SetProp @@ -36,29 +35,28 @@ struct PropItem nsIAtom* attr; nsString value; PropItem(); PropItem(nsIAtom* aTag, nsIAtom* aAttr, const nsAString& aValue); ~PropItem(); }; -class TypeInState final : public nsISelectionListener +class TypeInState final { public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_CLASS(TypeInState) + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(TypeInState) + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(TypeInState) TypeInState(); void Reset(); nsresult UpdateSelState(dom::Selection* aSelection); - // nsISelectionListener - NS_DECL_NSISELECTIONLISTENER + void OnSelectionChange(dom::Selection& aSelection); void SetProp(nsIAtom* aProp, nsIAtom* aAttr, const nsAString& aValue); void ClearAllProps(); void ClearProp(nsIAtom* aProp, nsIAtom* aAttr); /** * TakeClearProperty() hands back next property item on the clear list.