From ad761ffffb992e2e0a5402fdf5ec771bb18f322e Mon Sep 17 00:00:00 2001 From: Chris Peterson Date: Fri, 29 Jun 2012 15:49:48 -0700 Subject: [PATCH] Bug 769520 - Part 2: In debug builds, assert IME code is running on UI thread. r=blassey --- mobile/android/base/GeckoApp.java | 33 ++++++--- mobile/android/base/GeckoInputConnection.java | 70 +++++++++++++++---- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 9efdd5871ac7..90c8a6ec301d 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -6,19 +6,12 @@ package org.mozilla.gecko; import org.mozilla.gecko.db.BrowserDB; -import org.mozilla.gecko.gfx.CairoImage; -import org.mozilla.gecko.gfx.BufferedCairoImage; -import org.mozilla.gecko.gfx.FloatSize; import org.mozilla.gecko.gfx.GeckoLayerClient; -import org.mozilla.gecko.gfx.IntSize; import org.mozilla.gecko.gfx.Layer; import org.mozilla.gecko.gfx.LayerController; import org.mozilla.gecko.gfx.LayerView; import org.mozilla.gecko.gfx.PluginLayer; -import org.mozilla.gecko.gfx.RectUtils; import org.mozilla.gecko.gfx.SurfaceTextureLayer; -import org.mozilla.gecko.gfx.ViewportMetrics; -import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import java.io.*; import java.util.*; @@ -27,7 +20,6 @@ import java.util.regex.Matcher; import java.util.zip.*; import java.net.URL; import java.nio.*; -import java.nio.channels.FileChannel; import java.util.concurrent.*; import java.lang.reflect.*; import java.net.*; @@ -40,7 +32,6 @@ import android.text.*; import android.text.format.Time; import android.view.*; import android.view.inputmethod.*; -import android.view.ViewGroup.LayoutParams; import android.content.*; import android.content.res.*; import android.graphics.*; @@ -94,7 +85,7 @@ abstract public class GeckoApp public static boolean mDOMFullScreen = false; protected MenuPanel mMenuPanel; protected Menu mMenu; - private static GeckoThread sGeckoThread = null; + private static GeckoThread sGeckoThread; public Handler mMainHandler; private GeckoProfile mProfile; public static boolean sIsGeckoReady = false; @@ -3220,6 +3211,28 @@ abstract public class GeckoApp return false; } + public static void assertOnUiThread() { + Thread uiThread = mAppContext.getMainLooper().getThread(); + assertOnThread(uiThread); + } + + public static void assertOnGeckoThread() { + assertOnThread(sGeckoThread); + } + + private static void assertOnThread(Thread expectedThread) { + Thread currentThread = Thread.currentThread(); + long currentThreadId = currentThread.getId(); + long expectedThreadId = expectedThread.getId(); + + if (currentThreadId != expectedThreadId) { + throw new IllegalThreadStateException("Expected thread " + expectedThreadId + " (\"" + + expectedThread.getName() + + "\"), but running on thread " + currentThreadId + + " (\"" + currentThread.getName() + ")"); + } + } + // SDK version 15 accessibility methods retrieved through reflection. private static class AccessibilityCompat { private static boolean mInitialized = false; diff --git a/mobile/android/base/GeckoInputConnection.java b/mobile/android/base/GeckoInputConnection.java index 55b9ed0cb5e0..08a611b10f44 100644 --- a/mobile/android/base/GeckoInputConnection.java +++ b/mobile/android/base/GeckoInputConnection.java @@ -134,7 +134,8 @@ public class GeckoInputConnection @Override public boolean commitText(CharSequence text, int newCursorPosition) { if (mCommittingText) - Log.e(LOGTAG, "Please report this bug:", new IllegalStateException("commitText, but already committing text?!")); + Log.e(LOGTAG, "Please report this bug:", + new IllegalStateException("commitText, but already committing text?!")); mCommittingText = true; replaceText(text, newCursorPosition, false); @@ -321,6 +322,7 @@ public class GeckoInputConnection if (DEBUG) { Log.d(LOGTAG, String.format("IME: replaceText(\"%s\", %d, %b)", text, newCursorPosition, composing)); + GeckoApp.assertOnUiThread(); } if (text == null) @@ -699,10 +701,14 @@ public class GeckoInputConnection } private void endComposition() { - if (DEBUG) Log.d(LOGTAG, "IME: endComposition: IME_COMPOSITION_END"); + if (DEBUG) { + Log.d(LOGTAG, "IME: endComposition: IME_COMPOSITION_END"); + GeckoApp.assertOnUiThread(); + } if (!hasCompositionString()) - Log.e(LOGTAG, "Please report this bug:", new IllegalStateException("endComposition, but not composing text?!")); + Log.e(LOGTAG, "Please report this bug:", + new IllegalStateException("endComposition, but not composing text?!")); GeckoAppShell.sendEventToGecko( GeckoEvent.createIMEEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0)); @@ -711,7 +717,10 @@ public class GeckoInputConnection } private void sendTextToGecko(CharSequence text, int caretPos) { - if (DEBUG) Log.d(LOGTAG, "IME: sendTextToGecko(\"" + text + "\")"); + if (DEBUG) { + Log.d(LOGTAG, "IME: sendTextToGecko(\"" + text + "\")"); + GeckoApp.assertOnUiThread(); + } // Handle composition text styles if (text != null && text instanceof Spanned) { @@ -809,19 +818,19 @@ public class GeckoInputConnection outAttrs.inputType = InputType.TYPE_CLASS_PHONE; else if (mIMETypeHint.equalsIgnoreCase("number") || mIMETypeHint.equalsIgnoreCase("range")) - outAttrs.inputType = InputType.TYPE_CLASS_NUMBER | - InputType.TYPE_NUMBER_FLAG_SIGNED | - InputType.TYPE_NUMBER_FLAG_DECIMAL; + outAttrs.inputType = InputType.TYPE_CLASS_NUMBER + | InputType.TYPE_NUMBER_FLAG_SIGNED + | InputType.TYPE_NUMBER_FLAG_DECIMAL; else if (mIMETypeHint.equalsIgnoreCase("datetime") || mIMETypeHint.equalsIgnoreCase("datetime-local")) - outAttrs.inputType = InputType.TYPE_CLASS_DATETIME | - InputType.TYPE_DATETIME_VARIATION_NORMAL; + outAttrs.inputType = InputType.TYPE_CLASS_DATETIME + | InputType.TYPE_DATETIME_VARIATION_NORMAL; else if (mIMETypeHint.equalsIgnoreCase("date")) - outAttrs.inputType = InputType.TYPE_CLASS_DATETIME | - InputType.TYPE_DATETIME_VARIATION_DATE; + outAttrs.inputType = InputType.TYPE_CLASS_DATETIME + | InputType.TYPE_DATETIME_VARIATION_DATE; else if (mIMETypeHint.equalsIgnoreCase("time")) - outAttrs.inputType = InputType.TYPE_CLASS_DATETIME | - InputType.TYPE_DATETIME_VARIATION_TIME; + outAttrs.inputType = InputType.TYPE_CLASS_DATETIME + | InputType.TYPE_DATETIME_VARIATION_TIME; if (mIMEActionHint.equalsIgnoreCase("go")) outAttrs.imeOptions = EditorInfo.IME_ACTION_GO; @@ -875,6 +884,7 @@ public class GeckoInputConnection if (DEBUG) { Log.d(LOGTAG, "IME: processKeyDown(keyCode=" + keyCode + ", event=" + event + ", " + isPreIme + ")"); + GeckoApp.assertOnUiThread(); } if (keyCode > KeyEvent.getMaxKeyCode()) @@ -937,6 +947,7 @@ public class GeckoInputConnection if (DEBUG) { Log.d(LOGTAG, "IME: processKeyUp(keyCode=" + keyCode + ", event=" + event + ", " + isPreIme + ")"); + GeckoApp.assertOnUiThread(); } if (keyCode > KeyEvent.getMaxKeyCode()) @@ -1104,7 +1115,6 @@ public class GeckoInputConnection postToUiThread(new Runnable() { public void run() { final View v = GeckoApp.mAppContext.getLayerController().getView(); - if (DEBUG) Log.d(LOGTAG, "IME: v=" + v); final InputMethodManager imm = getInputMethodManager(); if (imm == null) @@ -1205,29 +1215,34 @@ public class GeckoInputConnection private static final class DebugGeckoInputConnection extends GeckoInputConnection { public DebugGeckoInputConnection(View targetView) { super(targetView); + GeckoApp.assertOnUiThread(); } @Override public boolean beginBatchEdit() { Log.d(LOGTAG, "IME: beginBatchEdit"); + GeckoApp.assertOnUiThread(); return super.beginBatchEdit(); } @Override public boolean endBatchEdit() { Log.d(LOGTAG, "IME: endBatchEdit"); + GeckoApp.assertOnUiThread(); return super.endBatchEdit(); } @Override public boolean commitCompletion(CompletionInfo text) { Log.d(LOGTAG, "IME: commitCompletion"); + GeckoApp.assertOnUiThread(); return super.commitCompletion(text); } @Override public boolean commitText(CharSequence text, int newCursorPosition) { Log.d(LOGTAG, String.format("IME: commitText(\"%s\", %d)", text, newCursorPosition)); + GeckoApp.assertOnUiThread(); return super.commitText(text, newCursorPosition); } @@ -1235,12 +1250,14 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio public boolean deleteSurroundingText(int leftLength, int rightLength) { Log.d(LOGTAG, "IME: deleteSurroundingText(leftLen=" + leftLength + ", rightLen=" + rightLength + ")"); + GeckoApp.assertOnUiThread(); return super.deleteSurroundingText(leftLength, rightLength); } @Override public boolean finishComposingText() { Log.d(LOGTAG, "IME: finishComposingText"); + GeckoApp.assertOnUiThread(); return super.finishComposingText(); } @@ -1248,18 +1265,21 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio public Editable getEditable() { Editable editable = super.getEditable(); Log.d(LOGTAG, "IME: getEditable -> " + editable); + GeckoApp.assertOnUiThread(); return editable; } @Override public boolean performContextMenuAction(int id) { Log.d(LOGTAG, "IME: performContextMenuAction"); + GeckoApp.assertOnUiThread(); return super.performContextMenuAction(id); } @Override public ExtractedText getExtractedText(ExtractedTextRequest req, int flags) { Log.d(LOGTAG, "IME: getExtractedText"); + GeckoApp.assertOnUiThread(); ExtractedText extract = super.getExtractedText(req, flags); if (extract != null) Log.d(LOGTAG, String.format( @@ -1271,6 +1291,7 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio @Override public CharSequence getTextAfterCursor(int length, int flags) { Log.d(LOGTAG, "IME: getTextAfterCursor(length=" + length + ", flags=" + flags + ")"); + GeckoApp.assertOnUiThread(); CharSequence s = super.getTextAfterCursor(length, flags); Log.d(LOGTAG, ". . . getTextAfterCursor returns \"" + s + "\""); return s; @@ -1279,6 +1300,7 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio @Override public CharSequence getTextBeforeCursor(int length, int flags) { Log.d(LOGTAG, "IME: getTextBeforeCursor"); + GeckoApp.assertOnUiThread(); CharSequence s = super.getTextBeforeCursor(length, flags); Log.d(LOGTAG, ". . . getTextBeforeCursor returns \"" + s + "\""); return s; @@ -1287,24 +1309,28 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { Log.d(LOGTAG, String.format("IME: setComposingText(\"%s\", %d)", text, newCursorPosition)); + GeckoApp.assertOnUiThread(); return super.setComposingText(text, newCursorPosition); } @Override public boolean setComposingRegion(int start, int end) { Log.d(LOGTAG, "IME: setComposingRegion(start=" + start + ", end=" + end + ")"); + GeckoApp.assertOnUiThread(); return super.setComposingRegion(start, end); } @Override public boolean setSelection(int start, int end) { Log.d(LOGTAG, "IME: setSelection(start=" + start + ", end=" + end + ")"); + GeckoApp.assertOnUiThread(); return super.setSelection(start, end); } @Override public String getComposingText() { Log.d(LOGTAG, "IME: getComposingText"); + GeckoApp.assertOnUiThread(); String s = super.getComposingText(); Log.d(LOGTAG, ". . . getComposingText: Composing text = \"" + s + "\""); return s; @@ -1313,12 +1339,15 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio @Override public boolean onKeyDel() { Log.d(LOGTAG, "IME: onKeyDel"); + GeckoApp.assertOnUiThread(); return super.onKeyDel(); } @Override protected void notifyTextChange(InputMethodManager imm, String text, int start, int oldEnd, int newEnd) { + // notifyTextChange() call is posted to UI thread from notifyIMEChange(). + GeckoApp.assertOnUiThread(); Log.d(LOGTAG, String.format( "IME: >notifyTextChange(\"%s\", start=%d, oldEnd=%d, newEnd=%d)", text, start, oldEnd, newEnd)); @@ -1327,6 +1356,8 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio @Override protected void notifySelectionChange(InputMethodManager imm, int start, int end) { + // notifySelectionChange() call is posted to UI thread from notifyIMEChange(). + GeckoApp.assertOnUiThread(); Log.d(LOGTAG, String.format("IME: >notifySelectionChange(start=%d, end=%d)", start, end)); super.notifySelectionChange(imm, start, end); } @@ -1334,6 +1365,7 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio @Override protected void resetCompositionState() { Log.d(LOGTAG, "IME: resetCompositionState"); + GeckoApp.assertOnUiThread(); if (hasCompositionString()) { Log.d(LOGTAG, "resetCompositionState() is abandoning an active composition string"); } @@ -1344,12 +1376,14 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio public void onTextChanged(CharSequence s, int start, int before, int count) { Log.d(LOGTAG, String.format("IME: onTextChanged(\"%s\" start=%d, before=%d, count=%d)", s, start, before, count)); + GeckoApp.assertOnUiThread(); super.onTextChanged(s, start, before, count); } @Override public void afterTextChanged(Editable s) { Log.d(LOGTAG, "IME: afterTextChanged(\"" + s + "\")"); + GeckoApp.assertOnUiThread(); super.afterTextChanged(s); } @@ -1357,30 +1391,35 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio public void beforeTextChanged(CharSequence s, int start, int count, int after) { Log.d(LOGTAG, String.format("IME: beforeTextChanged(\"%s\", start=%d, count=%d, after=%d)", s, start, count, after)); + GeckoApp.assertOnUiThread(); super.beforeTextChanged(s, start, count, after); } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { Log.d(LOGTAG, "IME: onCreateInputConnection called"); + GeckoApp.assertOnUiThread(); return super.onCreateInputConnection(outAttrs); } @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { Log.d(LOGTAG, "IME: onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")"); + GeckoApp.assertOnUiThread(); return super.onKeyPreIme(keyCode, event); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { Log.d(LOGTAG, "IME: onKeyDown(keyCode=" + keyCode + ", event=" + event + ")"); + GeckoApp.assertOnUiThread(); return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { Log.d(LOGTAG, "IME: onKeyUp(keyCode=" + keyCode + ", event=" + event + ")"); + GeckoApp.assertOnUiThread(); return super.onKeyUp(keyCode, event); } @@ -1388,18 +1427,21 @@ private static final class DebugGeckoInputConnection extends GeckoInputConnectio public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { Log.d(LOGTAG, "IME: onKeyMultiple(keyCode=" + keyCode + ", repeatCount=" + repeatCount + ", event=" + event + ")"); + GeckoApp.assertOnUiThread(); return super.onKeyMultiple(keyCode, repeatCount, event); } @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { Log.d(LOGTAG, "IME: onKeyLongPress(keyCode=" + keyCode + ", event=" + event + ")"); + GeckoApp.assertOnUiThread(); return super.onKeyLongPress(keyCode, event); } @Override public void notifyIME(int type, int state) { Log.d(LOGTAG, String.format("IME: >notifyIME(type=%d, state=%d)", type, state)); + GeckoApp.assertOnGeckoThread(); super.notifyIME(type, state); } }