Bug 917322 part.15 Create TextEventDispatcherListener abstract class for listening notifications to IME r=smaug, sr=smaug

This commit is contained in:
Masayuki Nakano 2015-01-28 15:27:32 +09:00
Родитель b1225d076e
Коммит 66d42c3189
7 изменённых файлов: 232 добавлений и 71 удалений

Просмотреть файл

@ -3,6 +3,7 @@
* 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"
@ -10,14 +11,17 @@
#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)
{
}
@ -50,8 +54,6 @@ TextInputProcessor::InitInternal(nsIDOMWindow* aWindow,
bool& aSucceeded)
{
aSucceeded = false;
bool wasInitialized = mIsInitialized;
mIsInitialized = false;
if (NS_WARN_IF(!aWindow)) {
return NS_ERROR_INVALID_ARG;
}
@ -82,51 +84,63 @@ TextInputProcessor::InitInternal(nsIDOMWindow* aWindow,
// 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;
mForTests = aForTests;
mDispatcher = dispatcher;
mForTests = aForTests;
aSucceeded = true;
return NS_OK;
}
nsresult
TextInputProcessor::IsValidStateForComposition() const
void
TextInputProcessor::UnlinkFromTextEventDispatcher()
{
if (NS_WARN_IF(!mIsInitialized)) {
mDispatcher = nullptr;
mForTests = false;
}
nsresult
TextInputProcessor::IsValidStateForComposition()
{
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
@ -223,19 +237,31 @@ TextInputProcessor::CommitComposition(const nsAString& aCommitString,
{
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;
}
@ -243,6 +269,12 @@ 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))) {
@ -252,4 +284,44 @@ TextInputProcessor::CancelComposition()
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

Просмотреть файл

@ -6,6 +6,7 @@
#ifndef mozilla_dom_textinputprocessor_h_
#define mozilla_dom_textinputprocessor_h_
#include "mozilla/TextEventDispatcherListener.h"
#include "nsITextInputProcessor.h"
namespace mozilla {
@ -15,7 +16,9 @@ class TextEventDispatcher;
} // namespace widget
class TextInputProcessor MOZ_FINAL : public nsITextInputProcessor
, public widget::TextEventDispatcherListener
{
typedef mozilla::widget::IMENotification IMENotification;
typedef mozilla::widget::TextEventDispatcher TextEventDispatcher;
public:
@ -24,16 +27,25 @@ public:
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;
};

Просмотреть файл

@ -881,9 +881,9 @@ function _getTIP(aWindow)
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;
}

Просмотреть файл

@ -21,7 +21,6 @@ namespace widget {
TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
: mWidget(aWidget)
, mInitialized(false)
, mForTests(false)
, mIsComposing(false)
{
@ -29,26 +28,39 @@ TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
}
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()
TextEventDispatcher::InitForTests(TextEventDispatcherListener* aListener)
{
if (mInitialized) {
return NS_ERROR_ALREADY_INITIALIZED;
return InitInternal(aListener, true);
}
nsresult
TextEventDispatcher::InitInternal(TextEventDispatcherListener* aListener,
bool aForTests)
{
if (NS_WARN_IF(!aListener)) {
return NS_ERROR_INVALID_ARG;
}
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);
}
MOZ_ASSERT(!mIsComposing, "There should not be active composition");
mInitialized = true;
mForTests = true;
return NS_OK;
}
@ -57,12 +69,18 @@ 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()) {
@ -170,7 +188,6 @@ TextEventDispatcher::CommitComposition(nsEventStatus& aStatus,
// 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;
@ -190,22 +207,18 @@ TextEventDispatcher::CommitComposition(nsEventStatus& aStatus,
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;
}
/******************************************************************************

Просмотреть файл

@ -10,6 +10,7 @@
#include "nsString.h"
#include "mozilla/Attributes.h"
#include "mozilla/EventForwards.h"
#include "mozilla/TextEventDispatcherListener.h"
#include "mozilla/TextRange.h"
class nsIWidget;
@ -42,12 +43,20 @@ public:
/**
* 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.
@ -154,6 +163,12 @@ private:
// 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
@ -178,11 +193,12 @@ private:
};
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.

Просмотреть файл

@ -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_

Просмотреть файл

@ -121,6 +121,7 @@ EXPORTS.mozilla += [
'MiscEvents.h',
'MouseEvents.h',
'TextEventDispatcher.h',
'TextEventDispatcherListener.h',
'TextEvents.h',
'TextRange.h',
'TouchEvents.h',