Bug 1053048 Implement nsIEditor.isInEditAction readonly attribute r=ehsan, sr=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 19 Aug 2014 20:54:08 +0900
changeset 221986 77bf46f52906ecd6c09a13539642be83471661a1
parent 221985 99f640f313595fc5ce635439fbeb9571b7f36629
child 221987 eb7712477525ae30ecf1d95569abaf5932522c0f
push idunknown
push userunknown
push dateunknown
reviewersehsan, smaug
bugs1053048
milestone34.0a1
Bug 1053048 Implement nsIEditor.isInEditAction readonly attribute r=ehsan, sr=smaug
content/html/content/src/nsTextEditorState.cpp
dom/events/IMEContentObserver.cpp
dom/events/IMEContentObserver.h
editor/libeditor/nsEditor.cpp
editor/libeditor/nsEditor.h
editor/nsIEditor.idl
editor/nsIHTMLEditor.idl
editor/nsIPlaintextEditor.idl
--- a/content/html/content/src/nsTextEditorState.cpp
+++ b/content/html/content/src/nsTextEditorState.cpp
@@ -659,18 +659,16 @@ public:
   /** SetEditor gives an address to the editor that will be accessed
    *  @param aEditor the editor this listener calls for editing operations
    */
   void SetFrame(nsTextControlFrame *aFrame){mFrame = aFrame;}
 
   void SettingValue(bool aValue) { mSettingValue = aValue; }
   void SetValueChanged(bool aSetValueChanged) { mSetValueChanged = aSetValueChanged; }
 
-  bool IsInEditAction() const { return mInEditAction; }
-
   NS_DECL_ISUPPORTS
 
   NS_DECL_NSISELECTIONLISTENER
 
   NS_DECL_NSIDOMEVENTLISTENER
 
   NS_DECL_NSIEDITOROBSERVER
 
@@ -705,36 +703,31 @@ 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;
-  /**
-   * mInEditAction is true while editor handling an edit action.
-   */
-  bool mInEditAction;
 };
 
 
 /*
  * nsTextInputListener implementation
  */
 
 nsTextInputListener::nsTextInputListener(nsITextControlElement* aTxtCtrlElement)
 : mFrame(nullptr)
 , mTxtCtrlElement(aTxtCtrlElement)
 , mSelectionWasCollapsed(true)
 , mHadUndoItems(false)
 , mHadRedoItems(false)
 , mSettingValue(false)
 , mSetValueChanged(true)
-, mInEditAction(false)
 {
 }
 
 nsTextInputListener::~nsTextInputListener() 
 {
 }
 
 NS_IMPL_ISUPPORTS(nsTextInputListener,
@@ -891,18 +884,16 @@ nsTextInputListener::HandleEvent(nsIDOME
   return NS_OK;
 }
 
 // BEGIN nsIEditorObserver
 
 NS_IMETHODIMP
 nsTextInputListener::EditAction()
 {
-  mInEditAction = false;
-
   nsWeakFrame weakFrame = mFrame;
 
   nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
   nsTextControlFrame* frame = static_cast<nsTextControlFrame*> (frameBase);
   NS_ASSERTION(frame, "Where is our frame?");
   //
   // Update the undo / redo menus
   //
@@ -938,24 +929,22 @@ nsTextInputListener::EditAction()
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextInputListener::BeforeEditAction()
 {
-  mInEditAction = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextInputListener::CancelEditAction()
 {
-  mInEditAction = false;
   return NS_OK;
 }
 
 // END nsIEditorObserver
 
 
 nsresult
 nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate,
@@ -1524,20 +1513,21 @@ void
 nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame)
 {
   NS_ENSURE_TRUE_VOID(mBoundFrame);
 
   // If it was, however, it should be unbounded from the same frame.
   NS_ASSERTION(!aFrame || aFrame == mBoundFrame, "Unbinding from the wrong frame");
   NS_ENSURE_TRUE_VOID(!aFrame || aFrame == mBoundFrame);
 
-  // If the editor is modified, we need to notify it here because editor may be
-  // destroyed before EditAction() is called if selection listener causes
-  // flushing layout.
-  if (mTextListener && mTextListener->IsInEditAction()) {
+  // If the editor is modified but nsIEditorObserver::EditAction() hasn't been
+  // called yet, we need to notify it here because editor may be destroyed
+  // before EditAction() is called if selection listener causes flushing layout.
+  if (mTextListener && mEditor && mEditorInitialized &&
+      mEditor->GetIsInEditAction()) {
     mTextListener->EditAction();
   }
 
   // We need to start storing the value outside of the editor if we're not
   // going to use it anymore, so retrieve it for now.
   nsAutoString value;
   GetValue(value, true);
 
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -79,17 +79,16 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver)
 
 IMEContentObserver::IMEContentObserver()
   : mESM(nullptr)
   , mPreCharacterDataChangeLength(-1)
-  , mIsEditorInTransaction(false)
   , mIsSelectionChangeEventPending(false)
   , mSelectionChangeCausedOnlyByComposition(false)
   , mIsPositionChangeEventPending(false)
   , mIsFlushingPendingNotifications(false)
 {
 #ifdef DEBUG
   TestMergingTextChangeData();
 #endif
@@ -906,36 +905,33 @@ IMEContentObserver::AttributeChanged(nsI
   TextChangeData data(start, start + mPreAttrChangeLength,
                       start + postAttrChangeLength, causedByComposition);
   MaybeNotifyIMEOfTextChange(data);
 }
 
 NS_IMETHODIMP
 IMEContentObserver::EditAction()
 {
-  mIsEditorInTransaction = false;
   mEndOfAddedTextCache.Clear();
   mStartOfRemovingTextRangeCache.Clear();
   FlushMergeableNotifications();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IMEContentObserver::BeforeEditAction()
 {
-  mIsEditorInTransaction = true;
   mEndOfAddedTextCache.Clear();
   mStartOfRemovingTextRangeCache.Clear();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IMEContentObserver::CancelEditAction()
 {
-  mIsEditorInTransaction = false;
   mEndOfAddedTextCache.Clear();
   mStartOfRemovingTextRangeCache.Clear();
   FlushMergeableNotifications();
   return NS_OK;
 }
 
 void
 IMEContentObserver::MaybeNotifyIMEOfTextChange(const TextChangeData& aData)
@@ -983,20 +979,24 @@ public:
 
 private:
   nsRefPtr<IMEContentObserver> mIMEContentObserver;
 };
 
 void
 IMEContentObserver::FlushMergeableNotifications()
 {
-  // If we're in handling an edit action, this method will be called later.
   // If this is already detached from the widget, this doesn't need to notify
   // anything.
-  if (mIsEditorInTransaction || !mWidget) {
+  if (!mWidget) {
+    return;
+  }
+
+  // If we're in handling an edit action, this method will be called later.
+  if (mEditor && mEditor->GetIsInEditAction()) {
     return;
   }
 
   // Notifying something may cause nested call of this method.  For example,
   // when somebody notified one of the notifications may dispatch query content
   // event. Then, it causes flushing layout which may cause another layout
   // change notification.
 
--- a/dom/events/IMEContentObserver.h
+++ b/dom/events/IMEContentObserver.h
@@ -212,17 +212,16 @@ private:
   TextChangeData mTextChangeData;
 
   EventStateManager* mESM;
 
   nsIMEUpdatePreference mUpdatePreference;
   uint32_t mPreAttrChangeLength;
   int64_t mPreCharacterDataChangeLength;
 
-  bool mIsEditorInTransaction;
   bool mIsSelectionChangeEventPending;
   bool mSelectionChangeCausedOnlyByComposition;
   bool mIsPositionChangeEventPending;
   bool mIsFlushingPendingNotifications;
 };
 
 } // namespace mozilla
 
--- a/editor/libeditor/nsEditor.cpp
+++ b/editor/libeditor/nsEditor.cpp
@@ -142,16 +142,17 @@ nsEditor::nsEditor()
 ,  mIMETextOffset(0)
 ,  mDirection(eNone)
 ,  mDocDirtyState(-1)
 ,  mSpellcheckCheckboxState(eTriUnset)
 ,  mShouldTxnSetSelection(true)
 ,  mDidPreDestroy(false)
 ,  mDidPostCreate(false)
 ,  mDispatchInputEvent(true)
+,  mIsInEditAction(false)
 {
 }
 
 nsEditor::~nsEditor()
 {
   NS_ASSERTION(!mDocWeak || mDidPreDestroy, "Why PreDestroy hasn't been called?");
 
   mTxnMgr = nullptr;
@@ -1827,32 +1828,35 @@ private:
   bool mIsComposing;
 };
 
 void
 nsEditor::NotifyEditorObservers(NotificationForEditorObservers aNotification)
 {
   switch (aNotification) {
     case eNotifyEditorObserversOfEnd:
+      mIsInEditAction = false;
       for (int32_t i = 0; i < mEditorObservers.Count(); i++) {
         mEditorObservers[i]->EditAction();
       }
 
       if (!mDispatchInputEvent) {
         return;
       }
 
       FireInputEvent();
       break;
     case eNotifyEditorObserversOfBefore:
+      mIsInEditAction = true;
       for (int32_t i = 0; i < mEditorObservers.Count(); i++) {
         mEditorObservers[i]->BeforeEditAction();
       }
       break;
     case eNotifyEditorObserversOfCancel:
+      mIsInEditAction = false;
       for (int32_t i = 0; i < mEditorObservers.Count(); i++) {
         mEditorObservers[i]->CancelEditAction();
       }
       break;
     default:
       MOZ_CRASH("Handle all notifications here");
       break;
   }
@@ -5268,8 +5272,16 @@ nsEditor::GetSuppressDispatchingInputEve
 }
 
 NS_IMETHODIMP
 nsEditor::SetSuppressDispatchingInputEvent(bool aSuppress)
 {
   mDispatchInputEvent = !aSuppress;
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsEditor::GetIsInEditAction(bool* aIsInEditAction)
+{
+  MOZ_ASSERT(aIsInEditAction, "aIsInEditAction must not be null");
+  *aIsInEditAction = mIsInEditAction;
+  return NS_OK;
+}
--- a/editor/libeditor/nsEditor.h
+++ b/editor/libeditor/nsEditor.h
@@ -890,16 +890,17 @@ protected:
   EDirection        mDirection;          // the current direction of editor action
   int8_t            mDocDirtyState;      // -1 = not initialized
   uint8_t           mSpellcheckCheckboxState; // a Tristate value
 
   bool mShouldTxnSetSelection;  // turn off for conservative selection adjustment by txns
   bool mDidPreDestroy;    // whether PreDestroy has been called
   bool mDidPostCreate;    // whether PostCreate has been called
   bool mDispatchInputEvent;
+  bool mIsInEditAction;   // true while the instance is handling an edit action
 
   friend bool NSCanUnload(nsISupports* serviceMgr);
   friend class nsAutoTxnsConserveSelection;
   friend class nsAutoSelectionReset;
   friend class nsAutoRules;
   friend class nsRangeUpdater;
 };
 
--- a/editor/nsIEditor.idl
+++ b/editor/nsIEditor.idl
@@ -16,17 +16,17 @@ interface nsIDocumentStateListener;
 interface nsIOutputStream;
 interface nsITransactionManager;
 interface nsITransaction;
 interface nsIEditorObserver;
 interface nsIEditActionListener;
 interface nsIInlineSpellChecker;
 interface nsITransferable;
 
-[scriptable, uuid(65523eab-db1f-44aa-893e-dfe57ad306f0)]
+[builtinclass, scriptable, uuid(c3b61bc9-ccdd-4bcd-acd8-1b8dcbe6a247)]
 
 interface nsIEditor  : nsISupports
 {
 %{C++
   typedef short EDirection;
   typedef short EStripWrappers;
 %}
   const short eNone = 0;
@@ -544,9 +544,16 @@ interface nsIEditor  : nsISupports
   /* Run unit tests. Noop in optimized builds */
   void debugUnitTests(out long outNumTests, out long  outNumTestsFailed);
 
   /* checks if a node is read-only or not */
   [notxpcom] boolean isModifiableNode(in nsIDOMNode aNode);
 
   /* Set true if you want to suppress dispatching input event. */
   attribute boolean suppressDispatchingInputEvent;
+
+  /**
+   * True if an edit action is being handled (in other words, between calls of
+   * nsIEditorObserver::BeforeEditAction() and nsIEditorObserver::EditAction()
+   * or nsIEditorObserver::CancelEditAction().  Otherwise, false.
+   */
+  [infallible, noscript] readonly attribute boolean isInEditAction;
 };
--- a/editor/nsIHTMLEditor.idl
+++ b/editor/nsIHTMLEditor.idl
@@ -17,17 +17,17 @@ namespace mozilla {
 namespace dom {
 class Element;
 }
 }
 %}
 
 [ptr] native Element (mozilla::dom::Element);
 
-[scriptable, uuid(833f30de-94c7-4630-a852-2300ef329d7b)]
+[builtinclass, scriptable, uuid(9470bee7-cad3-4382-8fb4-6bdda9f0273a)]
 
 interface nsIHTMLEditor : nsISupports
 {
 %{C++
   typedef short EAlignment;
 %}
 
   // used by GetAlignment()
--- a/editor/nsIPlaintextEditor.idl
+++ b/editor/nsIPlaintextEditor.idl
@@ -1,16 +1,16 @@
 /* -*- 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"
 
-[scriptable, uuid(07b6d070-ccea-4a00-84b4-f4b94dd9eb52)]
+[builtinclass, scriptable, uuid(da8f244b-6ffc-4be1-8b1a-667abfe1d304)]
 interface nsIPlaintextEditor : nsISupports
 {
 
   // XXX Why aren't these in nsIEditor?
   // only plain text entry is allowed via events
   const long eEditorPlaintextMask       = 0x0001;
   // enter key and CR-LF handled specially
   const long eEditorSingleLineMask      = 0x0002;