From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../java/org/mozilla/gecko/GeckoEditableChild.java | 456 +++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java') diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java new file mode 100644 index 0000000000..8a76548c1d --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java @@ -0,0 +1,456 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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/. */ + +package org.mozilla.gecko; + +import android.graphics.RectF; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.KeyEvent; +import androidx.annotation.Nullable; +import org.mozilla.gecko.annotation.WrapForJNI; +import org.mozilla.gecko.mozglue.JNIObject; +import org.mozilla.gecko.util.ThreadUtils; + +/** + * GeckoEditableChild implements the Gecko-facing side of IME operation. Each nsWindow in the main + * process and each PuppetWidget in each child content process has an instance of + * GeckoEditableChild, which communicates with the GeckoEditableParent instance in the main process. + */ +public final class GeckoEditableChild extends JNIObject implements IGeckoEditableChild { + + private static final boolean DEBUG = false; + private static final String LOGTAG = "GeckoEditableChild"; + + private static final int NOTIFY_IME_TO_CANCEL_COMPOSITION = 9; + + private final class RemoteChild extends IGeckoEditableChild.Stub { + @Override // IGeckoEditableChild + public void transferParent(final IGeckoEditableParent editableParent) { + GeckoEditableChild.this.transferParent(editableParent); + } + + @Override // IGeckoEditableChild + public void onKeyEvent( + final int action, + final int keyCode, + final int scanCode, + final int metaState, + final int keyPressMetaState, + final long time, + final int domPrintableKeyValue, + final int repeatCount, + final int flags, + final boolean isSynthesizedImeKey, + final KeyEvent event) { + GeckoEditableChild.this.onKeyEvent( + action, + keyCode, + scanCode, + metaState, + keyPressMetaState, + time, + domPrintableKeyValue, + repeatCount, + flags, + isSynthesizedImeKey, + event); + } + + @Override // IGeckoEditableChild + public void onImeSynchronize() { + GeckoEditableChild.this.onImeSynchronize(); + } + + @Override // IGeckoEditableChild + public void onImeReplaceText(final int start, final int end, final String text) { + GeckoEditableChild.this.onImeReplaceText(start, end, text); + } + + @Override // IGeckoEditableChild + public void onImeInsertImage(final byte[] data, final String mimeType) { + GeckoEditableChild.this.onImeInsertImage(data, mimeType); + } + + @Override // IGeckoEditableChild + public void onImeAddCompositionRange( + final int start, + final int end, + final int rangeType, + final int rangeStyles, + final int rangeLineStyle, + final boolean rangeBoldLine, + final int rangeForeColor, + final int rangeBackColor, + final int rangeLineColor) { + GeckoEditableChild.this.onImeAddCompositionRange( + start, + end, + rangeType, + rangeStyles, + rangeLineStyle, + rangeBoldLine, + rangeForeColor, + rangeBackColor, + rangeLineColor); + } + + @Override // IGeckoEditableChild + public void onImeUpdateComposition(final int start, final int end, final int flags) { + GeckoEditableChild.this.onImeUpdateComposition(start, end, flags); + } + + @Override // IGeckoEditableChild + public void onImeRequestCursorUpdates(final int requestMode) { + GeckoEditableChild.this.onImeRequestCursorUpdates(requestMode); + } + + @Override // IGeckoEditableChild + public void onImeRequestCommit() { + GeckoEditableChild.this.onImeRequestCommit(); + } + } + + private final IGeckoEditableChild mEditableChild; + private final boolean mIsDefault; + + private IGeckoEditableParent mEditableParent; + private int mCurrentTextLength; // Used by Gecko thread + + @WrapForJNI(calledFrom = "gecko") + private GeckoEditableChild( + @Nullable final IGeckoEditableParent editableParent, final boolean isDefault) { + mIsDefault = isDefault; + + if (editableParent != null + && editableParent.asBinder().queryLocalInterface(IGeckoEditableParent.class.getName()) + != null) { + // IGeckoEditableParent is local; i.e. we're in the main process. + mEditableChild = this; + } else { + // IGeckoEditableParent is remote; i.e. we're in a content process. + mEditableChild = new RemoteChild(); + } + + if (editableParent != null) { + setParent(editableParent); + } + } + + @WrapForJNI(calledFrom = "gecko") + private void setParent(final IGeckoEditableParent editableParent) { + mEditableParent = editableParent; + + if (mIsDefault) { + // Tell the parent we're the default child. + try { + editableParent.setDefaultChild(mEditableChild); + } catch (final RemoteException e) { + Log.e(LOGTAG, "Failed to set default child", e); + } + } + } + + @WrapForJNI(dispatchTo = "proxy") + @Override // IGeckoEditableChild + public native void transferParent(IGeckoEditableParent editableParent); + + @WrapForJNI(dispatchTo = "proxy") + @Override // IGeckoEditableChild + public native void onKeyEvent( + int action, + int keyCode, + int scanCode, + int metaState, + int keyPressMetaState, + long time, + int domPrintableKeyValue, + int repeatCount, + int flags, + boolean isSynthesizedImeKey, + KeyEvent event); + + @WrapForJNI(dispatchTo = "proxy") + @Override // IGeckoEditableChild + public native void onImeSynchronize(); + + @WrapForJNI(dispatchTo = "proxy") + @Override // IGeckoEditableChild + public native void onImeReplaceText(int start, int end, String text); + + @WrapForJNI(dispatchTo = "proxy") + @Override // IGeckoEditableChild + public native void onImeAddCompositionRange( + int start, + int end, + int rangeType, + int rangeStyles, + int rangeLineStyle, + boolean rangeBoldLine, + int rangeForeColor, + int rangeBackColor, + int rangeLineColor); + + // Don't update to the new composition if it's different than the current composition. + @WrapForJNI public static final int FLAG_KEEP_CURRENT_COMPOSITION = 1; + + @WrapForJNI(dispatchTo = "proxy") + @Override // IGeckoEditableChild + public native void onImeUpdateComposition(int start, int end, int flags); + + @WrapForJNI(dispatchTo = "proxy") + @Override // IGeckoEditableChild + public native void onImeRequestCursorUpdates(int requestMode); + + @WrapForJNI(dispatchTo = "proxy") + @Override // IGeckoEditableChild + public native void onImeRequestCommit(); + + @WrapForJNI(dispatchTo = "proxy") + @Override // IGeckoEditableChild + public native void onImeInsertImage(byte[] data, String mimeType); + + @Override // JNIObject + protected void disposeNative() { + // Disposal happens in native code. + throw new UnsupportedOperationException(); + } + + @WrapForJNI(calledFrom = "gecko") + private boolean hasEditableParent() { + if (mEditableParent != null) { + return true; + } + Log.w(LOGTAG, "No editable parent"); + return false; + } + + @Override // IInterface + public IBinder asBinder() { + // Return the GeckoEditableParent's binder as fallback for comparison purposes. + return mEditableChild != this + ? mEditableChild.asBinder() + : hasEditableParent() ? mEditableParent.asBinder() : null; + } + + @WrapForJNI(calledFrom = "gecko") + private void notifyIME(final int type) { + if (DEBUG) { + ThreadUtils.assertOnGeckoThread(); + Log.d(LOGTAG, "notifyIME(" + type + ")"); + } + if (!hasEditableParent()) { + return; + } + if (type == NOTIFY_IME_TO_CANCEL_COMPOSITION) { + // Composition should have been canceled on the parent side through text + // update notifications. We cannot verify that here because we don't + // keep track of spans on the child side, but it's simple to add the + // check to the parent side if ever needed. + return; + } + + try { + mEditableParent.notifyIME(mEditableChild, type); + } catch (final RemoteException e) { + Log.e(LOGTAG, "Remote call failed", e); + return; + } + } + + @WrapForJNI(calledFrom = "gecko") + private void notifyIMEContext( + final int state, + final String typeHint, + final String modeHint, + final String actionHint, + final String autocapitalize, + final int flags) { + if (DEBUG) { + ThreadUtils.assertOnGeckoThread(); + final StringBuilder sb = new StringBuilder("notifyIMEContext("); + sb.append(state) + .append(", \"") + .append(typeHint) + .append("\", \"") + .append(modeHint) + .append("\", \"") + .append(actionHint) + .append("\", \"") + .append(autocapitalize) + .append("\", 0x") + .append(Integer.toHexString(flags)) + .append(")"); + Log.d(LOGTAG, sb.toString()); + } + if (!hasEditableParent()) { + return; + } + + try { + mEditableParent.notifyIMEContext( + mEditableChild.asBinder(), state, typeHint, modeHint, actionHint, autocapitalize, flags); + } catch (final RemoteException e) { + Log.e(LOGTAG, "Remote call failed", e); + } + } + + @WrapForJNI(calledFrom = "gecko", exceptionMode = "ignore") + private void onSelectionChange( + final int start, final int end, final boolean causedOnlyByComposition) + throws RemoteException { + if (DEBUG) { + ThreadUtils.assertOnGeckoThread(); + final StringBuilder sb = new StringBuilder("onSelectionChange("); + sb.append(start) + .append(", ") + .append(end) + .append(", ") + .append(causedOnlyByComposition) + .append(")"); + Log.d(LOGTAG, sb.toString()); + } + if (!hasEditableParent()) { + return; + } + + final int currentLength = mCurrentTextLength; + if (start < 0 || start > currentLength || end < 0 || end > currentLength) { + Log.e( + LOGTAG, + "invalid selection notification range: " + + start + + " to " + + end + + ", length: " + + currentLength); + throw new IllegalArgumentException("invalid selection notification range"); + } + + mEditableParent.onSelectionChange( + mEditableChild.asBinder(), start, end, causedOnlyByComposition); + } + + @WrapForJNI(calledFrom = "gecko", exceptionMode = "ignore") + private void onTextChange( + final CharSequence text, + final int start, + final int unboundedOldEnd, + final int unboundedNewEnd, + final boolean causedOnlyByComposition) + throws RemoteException { + if (DEBUG) { + ThreadUtils.assertOnGeckoThread(); + final StringBuilder sb = new StringBuilder("onTextChange("); + sb.append(text) + .append(", ") + .append(start) + .append(", ") + .append(unboundedOldEnd) + .append(", ") + .append(unboundedNewEnd) + .append(", ") + .append(causedOnlyByComposition) + .append(")"); + Log.d(LOGTAG, sb.toString()); + } + if (!hasEditableParent()) { + return; + } + + if (start < 0 || start > unboundedOldEnd) { + Log.e(LOGTAG, "invalid text notification range: " + start + " to " + unboundedOldEnd); + throw new IllegalArgumentException("invalid text notification range"); + } + + /* For the "end" parameters, Gecko can pass in a large + number to denote "end of the text". Fix that here */ + final int currentLength = mCurrentTextLength; + final int oldEnd = unboundedOldEnd > currentLength ? currentLength : unboundedOldEnd; + // new end should always match text + if (unboundedOldEnd <= currentLength && unboundedNewEnd != (start + text.length())) { + Log.e( + LOGTAG, + "newEnd does not match text: " + unboundedNewEnd + " vs " + (start + text.length())); + throw new IllegalArgumentException("newEnd does not match text"); + } + + mCurrentTextLength += start + text.length() - oldEnd; + // Need unboundedOldEnd so GeckoEditable can distinguish changed text vs cleared text. + if (text.length() == 0) { + // Remove text in range. + mEditableParent.onTextChange( + mEditableChild.asBinder(), text, start, unboundedOldEnd, causedOnlyByComposition); + return; + } + // Using large text causes TransactionTooLargeException, so split text data. + int offset = 0; + int newUnboundedOldEnd = unboundedOldEnd; + while (offset < text.length()) { + final int end = Math.min(offset + 1024 * 64 /* 64KB */, text.length()); + mEditableParent.onTextChange( + mEditableChild.asBinder(), + text.subSequence(offset, end), + start + offset, + newUnboundedOldEnd, + causedOnlyByComposition); + offset = end; + newUnboundedOldEnd = start + offset; + } + } + + @WrapForJNI(calledFrom = "gecko") + private void onDefaultKeyEvent(final KeyEvent event) { + if (DEBUG) { + // GeckoEditableListener methods should all be called from the Gecko thread + ThreadUtils.assertOnGeckoThread(); + final StringBuilder sb = new StringBuilder("onDefaultKeyEvent("); + sb.append("action=") + .append(event.getAction()) + .append(", ") + .append("keyCode=") + .append(event.getKeyCode()) + .append(", ") + .append("metaState=") + .append(event.getMetaState()) + .append(", ") + .append("time=") + .append(event.getEventTime()) + .append(", ") + .append("repeatCount=") + .append(event.getRepeatCount()) + .append(")"); + Log.d(LOGTAG, sb.toString()); + } + if (!hasEditableParent()) { + return; + } + + try { + mEditableParent.onDefaultKeyEvent(mEditableChild.asBinder(), event); + } catch (final RemoteException e) { + Log.e(LOGTAG, "Remote call failed", e); + } + } + + @WrapForJNI(calledFrom = "gecko") + private void updateCompositionRects(final RectF[] rects, final RectF caretRect) { + if (DEBUG) { + // GeckoEditableListener methods should all be called from the Gecko thread + ThreadUtils.assertOnGeckoThread(); + Log.d(LOGTAG, "updateCompositionRects(rects.length = " + rects.length + ")"); + } + if (!hasEditableParent()) { + return; + } + + try { + mEditableParent.updateCompositionRects(mEditableChild.asBinder(), rects, caretRect); + } catch (final RemoteException e) { + Log.e(LOGTAG, "Remote call failed", e); + } + } +} -- cgit v1.2.3