Bug 917322 part.15 Create TextEventDispatcherListener abstract class for listening notifications to IME r=smaug, sr=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 28 Jan 2015 15:27:32 +0900
changeset 253334 3ea72759272e33b68ccec076b037ebbb4c922a4a
parent 253333 72718ad7b573dbb66267aca63c151f5b36de3120
child 253335 19e2530a986d19036595af59a24f27639258d636
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, smaug
bugs917322
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 917322 part.15 Create TextEventDispatcherListener abstract class for listening notifications to IME r=smaug, sr=smaug
dom/base/TextInputProcessor.cpp
dom/base/TextInputProcessor.h
testing/mochitest/tests/SimpleTest/EventUtils.js
widget/TextEventDispatcher.cpp
widget/TextEventDispatcher.h
widget/TextEventDispatcherListener.h
widget/moz.build
--- a/dom/base/TextInputProcessor.cpp
+++ b/dom/base/TextInputProcessor.cpp
@@ -1,28 +1,32 @@
 /* -*- 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/EventForwards.h"
 #include "mozilla/TextEventDispatcher.h"
 #include "mozilla/TextInputProcessor.h"
 #include "nsIDocShell.h"
 #include "nsIWidget.h"
 #include "nsPIDOMWindow.h"
 #include "nsPresContext.h"
 
+using namespace mozilla::widget;
+
 namespace mozilla {
 
 NS_IMPL_ISUPPORTS(TextInputProcessor,
-                  nsITextInputProcessor)
+                  nsITextInputProcessor,
+                  TextEventDispatcherListener,
+                  nsISupportsWeakReference)
 
 TextInputProcessor::TextInputProcessor()
   : mDispatcher(nullptr)
-  , mIsInitialized(false)
   , mForTests(false)
 {
 }
 
 TextInputProcessor::~TextInputProcessor()
 {
 }
 
@@ -45,18 +49,16 @@ TextInputProcessor::InitForTests(nsIDOMW
 }
 
 nsresult
 TextInputProcessor::InitInternal(nsIDOMWindow* aWindow,
                                  bool aForTests,
                                  bool& aSucceeded)
 {
   aSucceeded = false;
-  bool wasInitialized = mIsInitialized;
-  mIsInitialized = false;
   if (NS_WARN_IF(!aWindow)) {
     return NS_ERROR_INVALID_ARG;
   }
   nsCOMPtr<nsPIDOMWindow> pWindow(do_QueryInterface(aWindow));
   if (NS_WARN_IF(!pWindow)) {
     return NS_ERROR_INVALID_ARG;
   }
   nsCOMPtr<nsIDocShell> docShell(pWindow->GetDocShell());
@@ -77,61 +79,73 @@ TextInputProcessor::InitInternal(nsIDOMW
   }
 
   nsRefPtr<TextEventDispatcher> dispatcher = widget->GetTextEventDispatcher();
   MOZ_RELEASE_ASSERT(dispatcher, "TextEventDispatcher must not be null");
 
   // If the instance was initialized and is being initialized for same
   // dispatcher and same purpose, we don't need to initialize the dispatcher
   // again.
-  if (wasInitialized && dispatcher == mDispatcher && aForTests == mForTests) {
-    mIsInitialized = true;
+  if (mDispatcher && dispatcher == mDispatcher && aForTests == mForTests) {
     aSucceeded = true;
     return NS_OK;
   }
 
-  if (aForTests) {
-    rv = dispatcher->InitForTests();
-  } else {
-    rv = dispatcher->Init();
+  // If this instance is composing, don't allow to initialize again.
+  if (mDispatcher && mDispatcher->IsComposing()) {
+    return NS_ERROR_ALREADY_INITIALIZED;
   }
 
-  // Another IME framework is using it now.
-  if (rv == NS_ERROR_ALREADY_INITIALIZED) {
-    return NS_OK;  // Don't throw exception.
+  // And also if another instance is composing with the new dispatcher, it'll
+  // fail to steal its ownership.  Then, we should not throw an exception,
+  // just return false.
+  if (dispatcher->IsComposing()) {
+    return NS_OK;
+  }
+
+  // This instance has finished preparing to link to the dispatcher.  Therefore,
+  // let's forget the old dispatcher and purpose.
+  UnlinkFromTextEventDispatcher();
+
+  if (aForTests) {
+    rv = dispatcher->InitForTests(this);
+  } else {
+    rv = dispatcher->Init(this);
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  mIsInitialized = true;
+  mDispatcher = dispatcher;
   mForTests = aForTests;
-  mDispatcher = dispatcher;
   aSucceeded = true;
   return NS_OK;
 }
 
+void
+TextInputProcessor::UnlinkFromTextEventDispatcher()
+{
+  mDispatcher = nullptr;
+  mForTests = false;
+}
+
 nsresult
-TextInputProcessor::IsValidStateForComposition() const
+TextInputProcessor::IsValidStateForComposition()
 {
-  if (NS_WARN_IF(!mIsInitialized)) {
+  if (NS_WARN_IF(!mDispatcher)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  if (!mDispatcher) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
   nsresult rv = mDispatcher->GetState();
-  if (rv != NS_ERROR_NOT_INITIALIZED) {
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  return mForTests ? mDispatcher->InitForTests() : mDispatcher->Init();
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 TextInputProcessor::StartComposition(bool* aSucceeded)
 {
   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
   *aSucceeded = false;
@@ -218,38 +232,96 @@ TextInputProcessor::FlushPendingComposit
 
 NS_IMETHODIMP
 TextInputProcessor::CommitComposition(const nsAString& aCommitString,
                                       uint8_t aOptionalArgc,
                                       bool* aSucceeded)
 {
   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  const nsAString* commitString =
+    aOptionalArgc >= 1 ? &aCommitString : nullptr;
+  return CommitCompositionInternal(commitString, aSucceeded);
+}
+
+nsresult
+TextInputProcessor::CommitCompositionInternal(const nsAString* aCommitString,
+                                              bool* aSucceeded)
+{
+  if (aSucceeded) {
+    *aSucceeded = false;
+  }
   nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
   nsresult rv = IsValidStateForComposition();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  const nsAString* commitString =
-    aOptionalArgc >= 1 ? &aCommitString : nullptr;
   nsEventStatus status = nsEventStatus_eIgnore;
-  rv = mDispatcher->CommitComposition(status, commitString);
+  rv = mDispatcher->CommitComposition(status, aCommitString);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
+  if (aSucceeded) {
+    *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
+  }
   return rv;
 }
 
 NS_IMETHODIMP
 TextInputProcessor::CancelComposition()
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  return CancelCompositionInternal();
+}
+
+nsresult
+TextInputProcessor::CancelCompositionInternal()
+{
   nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
   nsresult rv = IsValidStateForComposition();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   nsEventStatus status = nsEventStatus_eIgnore;
   return mDispatcher->CommitComposition(status, &EmptyString());
 }
 
+NS_IMETHODIMP
+TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+                              const IMENotification& aNotification)
+{
+  // If This is called while this is being initialized, ignore the call.
+  if (!mDispatcher) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  MOZ_ASSERT(aTextEventDispatcher == mDispatcher,
+             "Wrong TextEventDispatcher notifies this");
+  switch (aNotification.mMessage) {
+    case REQUEST_TO_COMMIT_COMPOSITION: {
+      NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+                   "Why is this requested without composition?");
+      CommitCompositionInternal();
+      return NS_OK;
+    }
+    case REQUEST_TO_CANCEL_COMPOSITION: {
+      NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+                   "Why is this requested without composition?");
+      CancelCompositionInternal();
+      return NS_OK;
+    }
+    default:
+      return NS_ERROR_NOT_IMPLEMENTED;
+  }
+}
+
+NS_IMETHODIMP_(void)
+TextInputProcessor::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
+{
+  // If This is called while this is being initialized, ignore the call.
+  if (!mDispatcher) {
+    return;
+  }
+  MOZ_ASSERT(aTextEventDispatcher == mDispatcher,
+             "Wrong TextEventDispatcher notifies this");
+  UnlinkFromTextEventDispatcher();
+}
+
 } // namespace mozilla
--- a/dom/base/TextInputProcessor.h
+++ b/dom/base/TextInputProcessor.h
@@ -1,42 +1,54 @@
 /* -*- 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 mozilla_dom_textinputprocessor_h_
 #define mozilla_dom_textinputprocessor_h_
 
+#include "mozilla/TextEventDispatcherListener.h"
 #include "nsITextInputProcessor.h"
 
 namespace mozilla {
 
 namespace widget{
 class TextEventDispatcher;
 } // namespace widget
 
 class TextInputProcessor MOZ_FINAL : public nsITextInputProcessor
+                                   , public widget::TextEventDispatcherListener
 {
+  typedef mozilla::widget::IMENotification IMENotification;
   typedef mozilla::widget::TextEventDispatcher TextEventDispatcher;
 
 public:
   TextInputProcessor();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSITEXTINPUTPROCESSOR
 
+  // TextEventDispatcherListener
+  NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+                       const IMENotification& aNotification) MOZ_OVERRIDE;
+  NS_IMETHOD_(void)
+    OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) MOZ_OVERRIDE;
+
 private:
   ~TextInputProcessor();
 
   nsresult InitInternal(nsIDOMWindow* aWindow,
                         bool aForTests,
                         bool& aSucceeded);
-  nsresult IsValidStateForComposition() const;
+  nsresult CommitCompositionInternal(const nsAString* aCommitString = nullptr,
+                                     bool* aSucceeded = nullptr);
+  nsresult CancelCompositionInternal();
+  nsresult IsValidStateForComposition();
+  void UnlinkFromTextEventDispatcher();
 
   TextEventDispatcher* mDispatcher; // [Weak]
-  bool mIsInitialized;
   bool mForTests;
 };
 
 } // namespace mozilla
 
 #endif // #ifndef mozilla_dom_textinputprocessor_h_
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -876,19 +876,19 @@ function _getTIP(aWindow)
 {
   if (!aWindow) {
     aWindow = window;
   }
   if (!aWindow._EU_TIP) {
     aWindow._EU_TIP =
       _EU_Cc["@mozilla.org/text-input-processor;1"].
         createInstance(_EU_Ci.nsITextInputProcessor);
-    if (!aWindow._EU_TIP.initForTests(aWindow)) {
-      aWindow._EU_TIP = null;
-    }
+  }
+  if (!aWindow._EU_TIP.initForTests(aWindow)) {
+    aWindow._EU_TIP = null;
   }
   return aWindow._EU_TIP;
 }
 
 /**
  * Synthesize a composition event.
  *
  * @param aEvent               The composition event information.  This must
--- a/widget/TextEventDispatcher.cpp
+++ b/widget/TextEventDispatcher.cpp
@@ -16,58 +16,76 @@ namespace mozilla {
 namespace widget {
 
 /******************************************************************************
  * TextEventDispatcher
  *****************************************************************************/
 
 TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
   : mWidget(aWidget)
-  , mInitialized(false)
   , mForTests(false)
   , mIsComposing(false)
 {
   MOZ_RELEASE_ASSERT(mWidget, "aWidget must not be nullptr");
 }
 
 nsresult
-TextEventDispatcher::Init()
+TextEventDispatcher::Init(TextEventDispatcherListener* aListener)
 {
-  if (mInitialized) {
-    return NS_ERROR_ALREADY_INITIALIZED;
-  }
-  MOZ_ASSERT(!mIsComposing, "There should not be active composition");
-  mInitialized = true;
-  mForTests = false;
-  return NS_OK;
+  return InitInternal(aListener, false);
+}
+
+nsresult
+TextEventDispatcher::InitForTests(TextEventDispatcherListener* aListener)
+{
+  return InitInternal(aListener, true);
 }
 
 nsresult
-TextEventDispatcher::InitForTests()
+TextEventDispatcher::InitInternal(TextEventDispatcherListener* aListener,
+                                  bool aForTests)
 {
-  if (mInitialized) {
-    return NS_ERROR_ALREADY_INITIALIZED;
+  if (NS_WARN_IF(!aListener)) {
+    return NS_ERROR_INVALID_ARG;
   }
-  MOZ_ASSERT(!mIsComposing, "There should not be active composition");
-  mInitialized = true;
-  mForTests = true;
+  nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+  if (listener) {
+    if (listener == aListener && mForTests == aForTests) {
+      return NS_OK;
+    }
+    // If this has composition, any other listener can steal ownership.
+    if (IsComposing()) {
+      return NS_ERROR_ALREADY_INITIALIZED;
+    }
+  }
+  mListener = do_GetWeakReference(aListener);
+  mForTests = aForTests;
+  if (listener && listener != aListener) {
+    listener->OnRemovedFrom(this);
+  }
   return NS_OK;
 }
 
 void
 TextEventDispatcher::OnDestroyWidget()
 {
   mWidget = nullptr;
   mPendingComposition.Clear();
+  nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+  mListener = nullptr;
+  if (listener) {
+    listener->OnRemovedFrom(this);
+  }
 }
 
 nsresult
 TextEventDispatcher::GetState() const
 {
-  if (!mInitialized) {
+  nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+  if (!listener) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   if (!mWidget || mWidget->Destroyed()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   return NS_OK;
 }
 
@@ -165,17 +183,16 @@ TextEventDispatcher::CommitComposition(n
     return rv;
   }
   if (aStatus == nsEventStatus_eConsumeNoDefault) {
     return NS_OK;
   }
 
   // End current composition and make this free for other IMEs.
   mIsComposing = false;
-  mInitialized = false;
 
   uint32_t message = aCommitString ? NS_COMPOSITION_COMMIT :
                                      NS_COMPOSITION_COMMIT_AS_IS;
   WidgetCompositionEvent compositionCommitEvent(true, message, widget);
   InitEvent(compositionCommitEvent);
   if (message == NS_COMPOSITION_COMMIT) {
     compositionCommitEvent.mData = *aCommitString;
   }
@@ -185,32 +202,28 @@ TextEventDispatcher::CommitComposition(n
   }
 
   return NS_OK;
 }
 
 nsresult
 TextEventDispatcher::NotifyIME(const IMENotification& aIMENotification)
 {
-  switch (aIMENotification.mMessage) {
-    case REQUEST_TO_COMMIT_COMPOSITION: {
-      NS_ASSERTION(mIsComposing, "Why is this requested without composition?");
-      nsEventStatus status = nsEventStatus_eIgnore;
-      CommitComposition(status);
-      return NS_OK;
-    }
-    case REQUEST_TO_CANCEL_COMPOSITION: {
-      NS_ASSERTION(mIsComposing, "Why is this requested without composition?");
-      nsEventStatus status = nsEventStatus_eIgnore;
-      CommitComposition(status, &EmptyString());
-      return NS_OK;
-    }
-    default:
-      return NS_ERROR_NOT_IMPLEMENTED;
+  nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+  if (!listener) {
+    return NS_ERROR_NOT_IMPLEMENTED;
   }
+  nsresult rv = listener->NotifyIME(this, aIMENotification);
+  // If the listener isn't available, it means that it cannot handle the
+  // notification or request for now.  In this case, we should return
+  // NS_ERROR_NOT_IMPLEMENTED because it's not implemented at such moment.
+  if (rv == NS_ERROR_NOT_AVAILABLE) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  return rv;
 }
 
 /******************************************************************************
  * TextEventDispatcher::PendingComposition
  *****************************************************************************/
 
 TextEventDispatcher::PendingComposition::PendingComposition()
 {
--- a/widget/TextEventDispatcher.h
+++ b/widget/TextEventDispatcher.h
@@ -5,16 +5,17 @@
 
 #ifndef mozilla_textcompositionsynthesizer_h_
 #define mozilla_textcompositionsynthesizer_h_
 
 #include "nsAutoPtr.h"
 #include "nsString.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
 #include "mozilla/TextRange.h"
 
 class nsIWidget;
 
 namespace mozilla {
 namespace widget {
 
 struct IMENotification;
@@ -37,22 +38,30 @@ class TextEventDispatcher MOZ_FINAL
 
   NS_INLINE_DECL_REFCOUNTING(TextEventDispatcher)
 
 public:
   explicit TextEventDispatcher(nsIWidget* aWidget);
 
   /**
    * Initializes the instance for IME or automated test.  Either IME or tests
-   * need to call one of them before starting composition every time.  If they
-   * return NS_ERROR_ALREADY_INITIALIZED, it means that another IME composes
-   * with the instance.  Then, the caller shouldn't start composition.
+   * need to call one of them before starting composition.  If they return
+   * NS_ERROR_ALREADY_INITIALIZED, it means that the listener already listens
+   * notifications from TextEventDispatcher for same purpose (for IME or tests).
+   * If this returns another error, the caller shouldn't keep starting
+   * composition.
+   *
+   * @param aListener       Specify the listener to listen notifications and
+   *                        requests.  This must not be null.
+   *                        NOTE: aListener is stored as weak reference in
+   *                              TextEventDispatcher.  See mListener
+   *                              definition below.
    */
-  nsresult Init();
-  nsresult InitForTests();
+  nsresult Init(TextEventDispatcherListener* aListener);
+  nsresult InitForTests(TextEventDispatcherListener* aListener);
 
   /**
    * OnDestroyWidget() is called when mWidget is being destroyed.
    */
   void OnDestroyWidget();
 
   /**
    * GetState() returns current state of this class.
@@ -149,16 +158,22 @@ public:
   nsresult NotifyIME(const IMENotification& aIMENotification);
 
 private:
   // mWidget is owner of the instance.  When this is created, this is set.
   // And when mWidget is released, this is cleared by OnDestroyWidget().
   // Note that mWidget may be destroyed already (i.e., mWidget->Destroyed() may
   // return true).
   nsIWidget* mWidget;
+  // mListener is a weak reference to TextEventDispatcherListener.  That might
+  // be referred by JS.  Therefore, the listener might be difficult to release
+  // itself if this is a strong reference.  Additionally, it's difficult to
+  // check if a method to uninstall the listener is called by valid instance.
+  // So, using weak reference is the best way in this case.
+  nsWeakPtr mListener;
 
   // mPendingComposition stores new composition string temporarily.
   // These values will be used for dispatching NS_COMPOSITION_CHANGE event
   // in Flush().  When Flush() is called, the members will be cleared
   // automatically.
   class PendingComposition
   {
   public:
@@ -173,21 +188,22 @@ private:
     nsAutoString mString;
     nsRefPtr<TextRangeArray> mClauses;
     TextRange mCaret;
 
     void EnsureClauseArray();
   };
   PendingComposition mPendingComposition;
 
-  bool mInitialized;
   bool mForTests;
   // See IsComposing().
   bool mIsComposing;
 
+  nsresult InitInternal(TextEventDispatcherListener* aListener, bool aForTests);
+
   /**
    * InitEvent() initializes aEvent.  This must be called before dispatching
    * the event.
    */
   void InitEvent(WidgetCompositionEvent& aEvent) const;
 
   /**
    * StartCompositionAutomaticallyIfNecessary() starts composition if it hasn't
new file mode 100644
--- /dev/null
+++ b/widget/TextEventDispatcherListener.h
@@ -0,0 +1,47 @@
+/* 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 mozilla_textinputdispatcherlistener_h_
+#define mozilla_textinputdispatcherlistener_h_
+
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class TextEventDispatcher;
+struct IMENotification;
+
+#define NS_TEXT_INPUT_PROXY_LISTENER_IID \
+{ 0xf2226f55, 0x6ddb, 0x40d5, \
+  { 0x8a, 0x24, 0xce, 0x4d, 0x5b, 0x38, 0x15, 0xf0 } };
+
+class TextEventDispatcherListener : public nsSupportsWeakReference
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEXT_INPUT_PROXY_LISTENER_IID)
+
+  /**
+   * NotifyIME() is called by TextEventDispatcher::NotifyIME().  This is a
+   * notification or request to IME.  See document of nsIWidget::NotifyIME()
+   * for the detail.
+   */
+  NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+                       const IMENotification& aNotification) = 0;
+
+  /**
+   * OnRemovedFrom() is called when the TextEventDispatcher stops working and
+   * is releasing the listener.
+   */
+  NS_IMETHOD_(void) OnRemovedFrom(
+                      TextEventDispatcher* aTextEventDispatcher) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(TextEventDispatcherListener,
+                              NS_TEXT_INPUT_PROXY_LISTENER_IID)
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef mozilla_textinputdispatcherlistener_h_
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -116,16 +116,17 @@ EXPORTS.mozilla += [
     'CommandList.h',
     'ContentEvents.h',
     'EventClassList.h',
     'EventForwards.h',
     'LookAndFeel.h',
     'MiscEvents.h',
     'MouseEvents.h',
     'TextEventDispatcher.h',
+    'TextEventDispatcherListener.h',
     'TextEvents.h',
     'TextRange.h',
     'TouchEvents.h',
     'VsyncDispatcher.h',
     'WidgetUtils.h',
 ]
 
 UNIFIED_SOURCES += [