Bug 1692971 [wpt PR 27638] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / widget / android / GeckoEditableSupport.h
blob5e8b445803a791b0ba747139dfcd31f329e201e8
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"
11 #include "nsTArray.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"
19 class nsWindow;
21 namespace mozilla {
23 class TextComposition;
25 namespace dom {
26 class BrowserChild;
29 namespace widget {
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
38 through text updates
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 {
70 // Not retrying.
71 FLUSH_FLAG_NONE,
72 // Retrying due to IME text changes during flush.
73 FLUSH_FLAG_RETRY,
74 // Retrying due to IME sync exceptions during flush.
75 FLUSH_FLAG_RECOVER
78 enum RemoveCompositionFlag { CANCEL_IME_COMPOSITION, COMMIT_IME_COMPOSITION };
80 const bool mIsRemote;
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) {
104 if (mIsRemote) {
105 return aDispatcher->BeginInputTransaction(this);
106 } else {
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);
128 public:
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)) {
140 return true;
142 return false;
145 void Run() override {
146 if (NS_WARN_IF(!this->lambda.GetNativeObject())) {
147 // Ignore stale calls after disposal.
148 jni::GetGeckoThreadEnv()->ExceptionClear();
149 return;
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()),
163 mWindow(aWindow),
164 mEditable(aEditableChild),
165 mEditableAttached(!mIsRemote),
166 mIMERanges(new TextRangeArray()),
167 mIMEMaskEventsCount(1), // Mask IME events since there's no focus yet
168 mIMEFocusCount(0),
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) {}
180 NS_DECL_ISUPPORTS
182 // TextEventDispatcherListener methods
183 NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
184 const IMENotification& aNotification) override;
186 NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
188 NS_IMETHOD_(void)
189 OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
191 NS_IMETHOD_(void)
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) {
209 if (HasIMEFocus()) {
210 // If we have IME focus, GeckoEditableChild is already attached again.
211 // So disposer is unnecessary.
212 mDisposeRunnable = nullptr;
213 return;
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;
222 disposer->Run();
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;
243 return;
245 self->mEditableAttached = false;
246 disposer->Run();
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