summaryrefslogtreecommitdiffstats
path: root/widget/android/GeckoEditableSupport.h
diff options
context:
space:
mode:
Diffstat (limited to 'widget/android/GeckoEditableSupport.h')
-rw-r--r--widget/android/GeckoEditableSupport.h286
1 files changed, 286 insertions, 0 deletions
diff --git a/widget/android/GeckoEditableSupport.h b/widget/android/GeckoEditableSupport.h
new file mode 100644
index 0000000000..90cb2710d3
--- /dev/null
+++ b/widget/android/GeckoEditableSupport.h
@@ -0,0 +1,286 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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_widget_GeckoEditableSupport_h
+#define mozilla_widget_GeckoEditableSupport_h
+
+#include "nsAppShell.h"
+#include "nsIWidget.h"
+#include "nsTArray.h"
+
+#include "mozilla/java/GeckoEditableChildNatives.h"
+#include "mozilla/java/SessionTextInputWrappers.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/UniquePtr.h"
+
+class nsWindow;
+
+namespace mozilla {
+
+class TextComposition;
+
+namespace dom {
+class BrowserChild;
+}
+
+namespace widget {
+
+class GeckoEditableSupport final
+ : public TextEventDispatcherListener,
+ public java::GeckoEditableChild::Natives<GeckoEditableSupport> {
+ /*
+ Rules for managing IME between Gecko and Java:
+
+ * Gecko controls the text content, and Java shadows the Gecko text
+ through text updates
+ * Gecko and Java maintain separate selections, and synchronize when
+ needed through selection updates and set-selection events
+ * Java controls the composition, and Gecko shadows the Java
+ composition through update composition events
+ */
+
+ using EditableBase = java::GeckoEditableChild::Natives<GeckoEditableSupport>;
+ using EditableClient = java::SessionTextInput::EditableClient;
+ using EditableListener = java::SessionTextInput::EditableListener;
+
+ enum FlushChangesFlag {
+ // Not retrying.
+ FLUSH_FLAG_NONE,
+ // Retrying due to IME text changes during flush.
+ FLUSH_FLAG_RETRY,
+ // Retrying due to IME sync exceptions during flush.
+ FLUSH_FLAG_RECOVER
+ };
+
+ enum RemoveCompositionFlag { CANCEL_IME_COMPOSITION, COMMIT_IME_COMPOSITION };
+
+ const bool mIsRemote;
+ jni::NativeWeakPtr<GeckoViewSupport> mWindow; // Parent only
+ RefPtr<TextEventDispatcher> mDispatcher;
+ java::GeckoEditableChild::GlobalRef mEditable;
+ bool mEditableAttached;
+ InputContext mInputContext;
+ AutoTArray<UniquePtr<mozilla::WidgetEvent>, 4> mIMEKeyEvents;
+ IMENotification::TextChangeData mIMEPendingTextChange;
+ RefPtr<TextRangeArray> mIMERanges;
+ RefPtr<Runnable> mDisposeRunnable;
+ int32_t mIMEMaskEventsCount; // Mask events when > 0.
+ int32_t mIMEFocusCount; // We are focused when > 0.
+ bool mIMEDelaySynchronizeReply; // We reply asynchronously when true.
+ int32_t mIMEActiveSynchronizeCount; // The number of replies being delayed.
+ int32_t mIMEActiveCompositionCount; // The number of compositions expected.
+ uint32_t mDisposeBlockCount;
+ bool mIMESelectionChanged;
+ bool mIMETextChangedDuringFlush;
+ bool mIMEMonitorCursor;
+
+ // The cached selection data
+ struct Selection {
+ Selection() : mStartOffset(-1), mEndOffset(-1) {}
+
+ void Reset() {
+ mStartOffset = -1;
+ mEndOffset = -1;
+ }
+
+ bool IsValid() const { return mStartOffset >= 0 && mEndOffset >= 0; }
+
+ int32_t mStartOffset;
+ int32_t mEndOffset;
+ } mCachedSelection;
+
+ nsIWidget* GetWidget() const;
+ nsWindow* GetNsWindow() const;
+
+ nsresult BeginInputTransaction(TextEventDispatcher* aDispatcher) {
+ if (mIsRemote) {
+ return aDispatcher->BeginInputTransaction(this);
+ } else {
+ return aDispatcher->BeginNativeInputTransaction();
+ }
+ }
+
+ virtual ~GeckoEditableSupport() {}
+
+ RefPtr<TextComposition> GetComposition() const;
+ bool RemoveComposition(RemoveCompositionFlag aFlag = COMMIT_IME_COMPOSITION);
+ void SendIMEDummyKeyEvent(nsIWidget* aWidget, EventMessage msg);
+ void AddIMETextChange(const IMENotification::TextChangeDataBase& aChange);
+ void PostFlushIMEChanges();
+ void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
+ void FlushIMEText(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
+ void AsyncNotifyIME(int32_t aNotification);
+ void UpdateCompositionRects();
+ bool DoReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText);
+ bool DoUpdateComposition(int32_t aStart, int32_t aEnd, int32_t aFlags);
+ void OnNotifyIMEOfCompositionEventHandled();
+ void NotifyIMEContext(const InputContext& aContext,
+ const InputContextAction& aAction);
+
+ public:
+ template <typename Functor>
+ static void OnNativeCall(Functor&& aCall) {
+ struct IMEEvent : nsAppShell::LambdaEvent<Functor> {
+ explicit IMEEvent(Functor&& l)
+ : nsAppShell::LambdaEvent<Functor>(std::move(l)) {}
+
+ bool IsUIEvent() const override {
+ using GES = GeckoEditableSupport;
+ if (this->lambda.IsTarget(&GES::OnKeyEvent) ||
+ this->lambda.IsTarget(&GES::OnImeReplaceText) ||
+ this->lambda.IsTarget(&GES::OnImeUpdateComposition)) {
+ return true;
+ }
+ return false;
+ }
+
+ void Run() override {
+ if (NS_WARN_IF(!this->lambda.GetNativeObject())) {
+ // Ignore stale calls after disposal.
+ jni::GetGeckoThreadEnv()->ExceptionClear();
+ return;
+ }
+ nsAppShell::LambdaEvent<Functor>::Run();
+ }
+ };
+ nsAppShell::PostEvent(mozilla::MakeUnique<IMEEvent>(std::move(aCall)));
+ }
+
+ static void SetOnBrowserChild(dom::BrowserChild* aBrowserChild);
+
+ // Constructor for main process GeckoEditableChild.
+ GeckoEditableSupport(jni::NativeWeakPtr<GeckoViewSupport> aWindow,
+ java::GeckoEditableChild::Param aEditableChild)
+ : mIsRemote(!aWindow.IsAttached()),
+ mWindow(aWindow),
+ mEditable(aEditableChild),
+ mEditableAttached(!mIsRemote),
+ mIMERanges(new TextRangeArray()),
+ mIMEMaskEventsCount(1), // Mask IME events since there's no focus yet
+ mIMEFocusCount(0),
+ mIMEDelaySynchronizeReply(false),
+ mIMEActiveSynchronizeCount(0),
+ mDisposeBlockCount(0),
+ mIMESelectionChanged(false),
+ mIMETextChangedDuringFlush(false),
+ mIMEMonitorCursor(false) {}
+
+ // Constructor for content process GeckoEditableChild.
+ explicit GeckoEditableSupport(java::GeckoEditableChild::Param aEditableChild)
+ : GeckoEditableSupport(nullptr, aEditableChild) {}
+
+ NS_DECL_ISUPPORTS
+
+ // TextEventDispatcherListener methods
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
+
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) override;
+
+ void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction);
+
+ InputContext GetInputContext();
+
+ bool HasIMEFocus() const { return mIMEFocusCount != 0; }
+
+ void AddBlocker() { mDisposeBlockCount++; }
+
+ void ReleaseBlocker() {
+ mDisposeBlockCount--;
+
+ if (!mDisposeBlockCount && mDisposeRunnable) {
+ if (HasIMEFocus()) {
+ // If we have IME focus, GeckoEditableChild is already attached again.
+ // So disposer is unnecessary.
+ mDisposeRunnable = nullptr;
+ return;
+ }
+
+ RefPtr<GeckoEditableSupport> self(this);
+ RefPtr<Runnable> disposer = std::move(mDisposeRunnable);
+
+ nsAppShell::PostEvent(
+ [self = std::move(self), disposer = std::move(disposer)] {
+ self->mEditableAttached = false;
+ disposer->Run();
+ });
+ }
+ }
+
+ bool IsGeckoEditableUsed() const { return mDisposeBlockCount != 0; }
+
+ // GeckoEditableChild methods
+ using EditableBase::AttachNative;
+ using EditableBase::DisposeNative;
+
+ const java::GeckoEditableChild::Ref& GetJavaEditable() { return mEditable; }
+
+ void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
+ RefPtr<GeckoEditableSupport> self(this);
+ nsAppShell::PostEvent(
+ [self = std::move(self), disposer = RefPtr<Runnable>(aDisposer)] {
+ if (self->IsGeckoEditableUsed()) {
+ // Current calling stack uses GeckoEditableChild, so we should
+ // not dispose it now.
+ self->mDisposeRunnable = disposer;
+ return;
+ }
+ self->mEditableAttached = false;
+ disposer->Run();
+ });
+ }
+
+ // Transfer to a new parent.
+ void TransferParent(jni::Object::Param aEditableParent);
+
+ // Handle an Android KeyEvent.
+ void OnKeyEvent(int32_t aAction, int32_t aKeyCode, int32_t aScanCode,
+ int32_t aMetaState, int32_t aKeyPressMetaState, int64_t aTime,
+ int32_t aDomPrintableKeyValue, int32_t aRepeatCount,
+ int32_t aFlags, bool aIsSynthesizedImeKey,
+ jni::Object::Param originalEvent);
+
+ // Synchronize Gecko thread with the InputConnection thread.
+ void OnImeSynchronize();
+
+ // Replace a range of text with new text.
+ void OnImeReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText);
+
+ // Add styling for a range within the active composition.
+ void OnImeAddCompositionRange(int32_t aStart, int32_t aEnd,
+ int32_t aRangeType, int32_t aRangeStyle,
+ int32_t aRangeLineStyle, bool aRangeBoldLine,
+ int32_t aRangeForeColor,
+ int32_t aRangeBackColor,
+ int32_t aRangeLineColor);
+
+ // Update styling for the active composition using previous-added ranges.
+ void OnImeUpdateComposition(int32_t aStart, int32_t aEnd, int32_t aFlags);
+
+ // Set cursor mode whether IME requests
+ void OnImeRequestCursorUpdates(int aRequestMode);
+
+ // Commit current composition to sync Gecko text state with Java.
+ void OnImeRequestCommit();
+
+ // Insert image from software keyboard.
+ void OnImeInsertImage(jni::ByteArray::Param aData,
+ jni::String::Param aMimeType);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_GeckoEditableSupport_h