1 /* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #ifndef mozilla_widget_GeckoEditableSupport_h
7 #define mozilla_widget_GeckoEditableSupport_h
9 #include "nsAppShell.h"
10 #include "nsIWidget.h"
13 #include "mozilla/java/GeckoEditableChildNatives.h"
14 #include "mozilla/java/SessionTextInputWrappers.h"
15 #include "mozilla/TextEventDispatcher.h"
16 #include "mozilla/TextEventDispatcherListener.h"
17 #include "mozilla/UniquePtr.h"
23 class TextComposition
;
31 class GeckoEditableSupport final
32 : public TextEventDispatcherListener
,
33 public java::GeckoEditableChild::Natives
<GeckoEditableSupport
> {
35 Rules for managing IME between Gecko and Java:
37 * Gecko controls the text content, and Java shadows the Gecko text
39 * Gecko and Java maintain separate selections, and synchronize when
40 needed through selection updates and set-selection events
41 * Java controls the composition, and Gecko shadows the Java
42 composition through update composition events
45 using EditableBase
= java::GeckoEditableChild::Natives
<GeckoEditableSupport
>;
46 using EditableClient
= java::SessionTextInput::EditableClient
;
47 using EditableListener
= java::SessionTextInput::EditableListener
;
49 struct IMETextChange final
{
50 int32_t mStart
, mOldEnd
, mNewEnd
;
52 IMETextChange() : mStart(-1), mOldEnd(-1), mNewEnd(-1) {}
54 explicit IMETextChange(const IMENotification
& aIMENotification
)
55 : mStart(aIMENotification
.mTextChangeData
.mStartOffset
),
56 mOldEnd(aIMENotification
.mTextChangeData
.mRemovedEndOffset
),
57 mNewEnd(aIMENotification
.mTextChangeData
.mAddedEndOffset
) {
58 MOZ_ASSERT(aIMENotification
.mMessage
== NOTIFY_IME_OF_TEXT_CHANGE
,
59 "IMETextChange initialized with wrong notification");
60 MOZ_ASSERT(aIMENotification
.mTextChangeData
.IsValid(),
61 "The text change notification isn't initialized");
62 MOZ_ASSERT(aIMENotification
.mTextChangeData
.IsInInt32Range(),
63 "The text change notification is out of range");
66 bool IsEmpty() const { return mStart
< 0; }
69 enum FlushChangesFlag
{
72 // Retrying due to IME text changes during flush.
74 // Retrying due to IME sync exceptions during flush.
78 enum RemoveCompositionFlag
{ CANCEL_IME_COMPOSITION
, COMMIT_IME_COMPOSITION
};
81 jni::NativeWeakPtr
<GeckoViewSupport
> mWindow
; // Parent only
82 RefPtr
<TextEventDispatcher
> mDispatcher
;
83 java::GeckoEditableChild::GlobalRef mEditable
;
84 bool mEditableAttached
;
85 InputContext mInputContext
;
86 AutoTArray
<UniquePtr
<mozilla::WidgetEvent
>, 4> mIMEKeyEvents
;
87 AutoTArray
<IMETextChange
, 4> mIMETextChanges
;
88 RefPtr
<TextRangeArray
> mIMERanges
;
89 RefPtr
<Runnable
> mDisposeRunnable
;
90 int32_t mIMEMaskEventsCount
; // Mask events when > 0.
91 int32_t mIMEFocusCount
; // We are focused when > 0.
92 bool mIMEDelaySynchronizeReply
; // We reply asynchronously when true.
93 int32_t mIMEActiveSynchronizeCount
; // The number of replies being delayed.
94 int32_t mIMEActiveCompositionCount
; // The number of compositions expected.
95 uint32_t mDisposeBlockCount
;
96 bool mIMESelectionChanged
;
97 bool mIMETextChangedDuringFlush
;
98 bool mIMEMonitorCursor
;
100 nsIWidget
* GetWidget() const;
101 nsWindow
* GetNsWindow() const;
103 nsresult
BeginInputTransaction(TextEventDispatcher
* aDispatcher
) {
105 return aDispatcher
->BeginInputTransaction(this);
107 return aDispatcher
->BeginNativeInputTransaction();
111 virtual ~GeckoEditableSupport() {}
113 RefPtr
<TextComposition
> GetComposition() const;
114 bool RemoveComposition(RemoveCompositionFlag aFlag
= COMMIT_IME_COMPOSITION
);
115 void SendIMEDummyKeyEvent(nsIWidget
* aWidget
, EventMessage msg
);
116 void AddIMETextChange(const IMETextChange
& aChange
);
117 void PostFlushIMEChanges();
118 void FlushIMEChanges(FlushChangesFlag aFlags
= FLUSH_FLAG_NONE
);
119 void FlushIMEText(FlushChangesFlag aFlags
= FLUSH_FLAG_NONE
);
120 void AsyncNotifyIME(int32_t aNotification
);
121 void UpdateCompositionRects();
122 bool DoReplaceText(int32_t aStart
, int32_t aEnd
, jni::String::Param aText
);
123 bool DoUpdateComposition(int32_t aStart
, int32_t aEnd
, int32_t aFlags
);
124 void OnNotifyIMEOfCompositionEventHandled();
125 void NotifyIMEContext(const InputContext
& aContext
,
126 const InputContextAction
& aAction
);
129 template <typename Functor
>
130 static void OnNativeCall(Functor
&& aCall
) {
131 struct IMEEvent
: nsAppShell::LambdaEvent
<Functor
> {
132 explicit IMEEvent(Functor
&& l
)
133 : nsAppShell::LambdaEvent
<Functor
>(std::move(l
)) {}
135 bool IsUIEvent() const override
{
136 using GES
= GeckoEditableSupport
;
137 if (this->lambda
.IsTarget(&GES::OnKeyEvent
) ||
138 this->lambda
.IsTarget(&GES::OnImeReplaceText
) ||
139 this->lambda
.IsTarget(&GES::OnImeUpdateComposition
)) {
145 void Run() override
{
146 if (NS_WARN_IF(!this->lambda
.GetNativeObject())) {
147 // Ignore stale calls after disposal.
148 jni::GetGeckoThreadEnv()->ExceptionClear();
151 nsAppShell::LambdaEvent
<Functor
>::Run();
154 nsAppShell::PostEvent(mozilla::MakeUnique
<IMEEvent
>(std::move(aCall
)));
157 static void SetOnBrowserChild(dom::BrowserChild
* aBrowserChild
);
159 // Constructor for main process GeckoEditableChild.
160 GeckoEditableSupport(jni::NativeWeakPtr
<GeckoViewSupport
> aWindow
,
161 java::GeckoEditableChild::Param aEditableChild
)
162 : mIsRemote(!aWindow
.IsAttached()),
164 mEditable(aEditableChild
),
165 mEditableAttached(!mIsRemote
),
166 mIMERanges(new TextRangeArray()),
167 mIMEMaskEventsCount(1), // Mask IME events since there's no focus yet
169 mIMEDelaySynchronizeReply(false),
170 mIMEActiveSynchronizeCount(0),
171 mDisposeBlockCount(0),
172 mIMESelectionChanged(false),
173 mIMETextChangedDuringFlush(false),
174 mIMEMonitorCursor(false) {}
176 // Constructor for content process GeckoEditableChild.
177 explicit GeckoEditableSupport(java::GeckoEditableChild::Param aEditableChild
)
178 : GeckoEditableSupport(nullptr, aEditableChild
) {}
182 // TextEventDispatcherListener methods
183 NS_IMETHOD
NotifyIME(TextEventDispatcher
* aTextEventDispatcher
,
184 const IMENotification
& aNotification
) override
;
186 NS_IMETHOD_(IMENotificationRequests
) GetIMENotificationRequests() override
;
189 OnRemovedFrom(TextEventDispatcher
* aTextEventDispatcher
) override
;
192 WillDispatchKeyboardEvent(TextEventDispatcher
* aTextEventDispatcher
,
193 WidgetKeyboardEvent
& aKeyboardEvent
,
194 uint32_t aIndexOfKeypress
, void* aData
) override
;
196 void SetInputContext(const InputContext
& aContext
,
197 const InputContextAction
& aAction
);
199 InputContext
GetInputContext();
201 bool HasIMEFocus() const { return mIMEFocusCount
!= 0; }
203 void AddBlocker() { mDisposeBlockCount
++; }
205 void ReleaseBlocker() {
206 mDisposeBlockCount
--;
208 if (!mDisposeBlockCount
&& mDisposeRunnable
) {
210 // If we have IME focus, GeckoEditableChild is already attached again.
211 // So disposer is unnecessary.
212 mDisposeRunnable
= nullptr;
216 RefPtr
<GeckoEditableSupport
> self(this);
217 RefPtr
<Runnable
> disposer
= std::move(mDisposeRunnable
);
219 nsAppShell::PostEvent(
220 [self
= std::move(self
), disposer
= std::move(disposer
)] {
221 self
->mEditableAttached
= false;
227 bool IsGeckoEditableUsed() const { return mDisposeBlockCount
!= 0; }
229 // GeckoEditableChild methods
230 using EditableBase::AttachNative
;
231 using EditableBase::DisposeNative
;
233 const java::GeckoEditableChild::Ref
& GetJavaEditable() { return mEditable
; }
235 void OnWeakNonIntrusiveDetach(already_AddRefed
<Runnable
> aDisposer
) {
236 RefPtr
<GeckoEditableSupport
> self(this);
237 nsAppShell::PostEvent(
238 [self
= std::move(self
), disposer
= RefPtr
<Runnable
>(aDisposer
)] {
239 if (self
->IsGeckoEditableUsed()) {
240 // Current calling stack uses GeckoEditableChild, so we should
241 // not dispose it now.
242 self
->mDisposeRunnable
= disposer
;
245 self
->mEditableAttached
= false;
250 // Transfer to a new parent.
251 void TransferParent(jni::Object::Param aEditableParent
);
253 // Handle an Android KeyEvent.
254 void OnKeyEvent(int32_t aAction
, int32_t aKeyCode
, int32_t aScanCode
,
255 int32_t aMetaState
, int32_t aKeyPressMetaState
, int64_t aTime
,
256 int32_t aDomPrintableKeyValue
, int32_t aRepeatCount
,
257 int32_t aFlags
, bool aIsSynthesizedImeKey
,
258 jni::Object::Param originalEvent
);
260 // Synchronize Gecko thread with the InputConnection thread.
261 void OnImeSynchronize();
263 // Replace a range of text with new text.
264 void OnImeReplaceText(int32_t aStart
, int32_t aEnd
, jni::String::Param aText
);
266 // Add styling for a range within the active composition.
267 void OnImeAddCompositionRange(int32_t aStart
, int32_t aEnd
,
268 int32_t aRangeType
, int32_t aRangeStyle
,
269 int32_t aRangeLineStyle
, bool aRangeBoldLine
,
270 int32_t aRangeForeColor
,
271 int32_t aRangeBackColor
,
272 int32_t aRangeLineColor
);
274 // Update styling for the active composition using previous-added ranges.
275 void OnImeUpdateComposition(int32_t aStart
, int32_t aEnd
, int32_t aFlags
);
277 // Set cursor mode whether IME requests
278 void OnImeRequestCursorUpdates(int aRequestMode
);
280 // Commit current composition to sync Gecko text state with Java.
281 void OnImeRequestCommit();
284 } // namespace widget
285 } // namespace mozilla
287 #endif // mozilla_widget_GeckoEditableSupport_h