Bug 1292323 - Update annotationProcessor to reflect WrapForJNI changes; r=snorp
authorJim Chen <nchen@mozilla.com>
Fri, 12 Aug 2016 23:15:52 -0400
changeset 309357 573afd555aa1c18e9cac73f01cb1a3245ca9fa49
parent 309356 39207912bcd0b946262bb5975f2e02c988d1dcee
child 309358 95f4b1a92cdbdb0b97e6f55af91e3adf02da9887
push id30561
push userkwierso@gmail.com
push dateMon, 15 Aug 2016 21:20:49 +0000
treeherdermozilla-central@91a319101587 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1292323
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1292323 - Update annotationProcessor to reflect WrapForJNI changes; r=snorp Update the code generator and related classes in annotation processor to use the new WrapForJNI flags. Also add some more sanity checking to make sure the flags are used correctly.
build/annotationProcessors/AnnotationInfo.java
build/annotationProcessors/CodeGenerator.java
build/annotationProcessors/utils/GeneratableElementIterator.java
widget/android/jni/Refs.h
--- a/build/annotationProcessors/AnnotationInfo.java
+++ b/build/annotationProcessors/AnnotationInfo.java
@@ -3,28 +3,52 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.annotationProcessors;
 
 /**
  * Object holding annotation data. Used by GeneratableElementIterator.
  */
 public class AnnotationInfo {
-    public final String wrapperName;
-    public final boolean isMultithreaded;
-    public final boolean noThrow;
-    public final boolean narrowChars;
-    public final boolean catchException;
+    public enum ExceptionMode {
+        ABORT,
+        NSRESULT,
+        IGNORE;
 
-    public AnnotationInfo(String aWrapperName, boolean aIsMultithreaded,
-                          boolean aNoThrow, boolean aNarrowChars, boolean aCatchException) {
-        wrapperName = aWrapperName;
-        isMultithreaded = aIsMultithreaded;
-        noThrow = aNoThrow;
-        narrowChars = aNarrowChars;
-        catchException = aCatchException;
+        String nativeValue() {
+            return "mozilla::jni::ExceptionMode::" + name();
+        }
+    }
 
-        if (noThrow && catchException) {
-            // It doesn't make sense to have these together
-            throw new IllegalArgumentException("noThrow and catchException are not allowed together");
+    public enum CallingThread {
+        GECKO,
+        UI,
+        ANY;
+
+        String nativeValue() {
+            return "mozilla::jni::CallingThread::" + name();
         }
     }
+
+    public enum DispatchTarget {
+        GECKO,
+        PROXY,
+        CURRENT;
+
+        String nativeValue() {
+            return "mozilla::jni::DispatchTarget::" + name();
+        }
+    }
+
+    public final String wrapperName;
+    public final ExceptionMode exceptionMode;
+    public final CallingThread callingThread;
+    public final DispatchTarget dispatchTarget;
+
+    public AnnotationInfo(String wrapperName, ExceptionMode exceptionMode,
+                          CallingThread callingThread, DispatchTarget dispatchTarget) {
+
+        this.wrapperName = wrapperName;
+        this.exceptionMode = exceptionMode;
+        this.callingThread = callingThread;
+        this.dispatchTarget = dispatchTarget;
+    }
 }
--- a/build/annotationProcessors/CodeGenerator.java
+++ b/build/annotationProcessors/CodeGenerator.java
@@ -22,35 +22,35 @@ public class CodeGenerator {
     // Buffers holding the strings to ultimately be written to the output files.
     private final StringBuilder cpp = new StringBuilder();
     private final StringBuilder header = new StringBuilder();
     private final StringBuilder natives = new StringBuilder();
     private final StringBuilder nativesInits = new StringBuilder();
 
     private final Class<?> cls;
     private final String clsName;
-    private boolean isMultithreaded;
+    private AnnotationInfo.CallingThread callingThread = null;
     private int numNativesInits;
 
     private final HashSet<String> takenMethodNames = new HashSet<String>();
 
     public CodeGenerator(ClassWithOptions annotatedClass) {
         this.cls = annotatedClass.wrappedClass;
         this.clsName = annotatedClass.generatedName;
 
         final String unqualifiedName = Utils.getUnqualifiedName(clsName);
         header.append(
                 "class " + clsName + " : public mozilla::jni::ObjectBase<" +
-                        unqualifiedName + ", jobject>\n" +
+                        unqualifiedName + ">\n" +
                 "{\n" +
                 "public:\n" +
                 "    static const char name[];\n" +
                 "\n" +
                 "    explicit " + unqualifiedName + "(const Context& ctx) : ObjectBase<" +
-                        unqualifiedName + ", jobject>(ctx) {}\n" +
+                        unqualifiedName + ">(ctx) {}\n" +
                 "\n");
 
         cpp.append(
                 "const char " + clsName + "::name[] =\n" +
                 "        \"" + cls.getName().replace('.', '/') + "\";\n" +
                 "\n");
 
         natives.append(
@@ -118,31 +118,37 @@ public class CodeGenerator {
                 "        typedef " + getNativeParameterType(type, info) + " SetterType;\n" +
                 "        typedef mozilla::jni::Args<" + args + "> Args;\n" +
                 "        static constexpr char name[] = \"" +
                         Utils.getMemberName(member) + "\";\n" +
                 "        static constexpr char signature[] =\n" +
                 "                \"" + Utils.getSignature(member) + "\";\n" +
                 "        static const bool isStatic = " + Utils.isStatic(member) + ";\n" +
                 "        static const mozilla::jni::ExceptionMode exceptionMode =\n" +
-                "                " + (
-                        info.catchException ? "mozilla::jni::ExceptionMode::NSRESULT" :
-                        info.noThrow ?        "mozilla::jni::ExceptionMode::IGNORE" :
-                                              "mozilla::jni::ExceptionMode::ABORT") + ";\n" +
+                "                " + info.exceptionMode.nativeValue() + ";\n" +
+                "        static const mozilla::jni::CallingThread callingThread =\n" +
+                "                " + info.callingThread.nativeValue() + ";\n" +
+                "        static const mozilla::jni::DispatchTarget dispatchTarget =\n" +
+                "                " + info.dispatchTarget.nativeValue() + ";\n" +
                 "    };\n" +
                 "\n");
 
         cpp.append(
                 "constexpr char " + getTraitsName(uniqueName, /* includeScope */ true) +
                         "::name[];\n" +
                 "constexpr char " + getTraitsName(uniqueName, /* includeScope */ true) +
                         "::signature[];\n" +
                 "\n");
 
-        this.isMultithreaded |= info.isMultithreaded;
+        if (this.callingThread == null) {
+            this.callingThread = info.callingThread;
+        } else if (this.callingThread != info.callingThread) {
+            // We have a mix of calling threads, so specify "any" for the whole class.
+            this.callingThread = AnnotationInfo.CallingThread.ANY;
+        }
     }
 
     private String getUniqueMethodName(String basename) {
         String newName = basename;
         int index = 1;
 
         while (takenMethodNames.contains(newName)) {
             newName = basename + (++index);
@@ -175,17 +181,18 @@ public class CodeGenerator {
         for (Class<?> argType : argTypes) {
             proto.append(getNativeParameterType(argType, info));
             if (includeArgName) {
                 proto.append(" a").append(argIndex++);
             }
             proto.append(", ");
         }
 
-        if (info.catchException && !returnType.equals(void.class)) {
+        if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT &&
+                !returnType.equals(void.class)) {
             proto.append(getNativeReturnType(returnType, info)).append('*');
             if (includeArgName) {
                 proto.append(" a").append(argIndex++);
             }
             proto.append(", ");
         }
 
         if (proto.substring(proto.length() - 2).equals(", ")) {
@@ -193,17 +200,17 @@ public class CodeGenerator {
         }
 
         proto.append(')');
 
         if (isConst) {
             proto.append(" const");
         }
 
-        if (info.catchException) {
+        if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
             proto.append(" -> nsresult");
         } else {
             proto.append(" -> ").append(getNativeReturnType(returnType, info));
         }
         return proto.toString();
     }
 
     /**
@@ -233,22 +240,23 @@ public class CodeGenerator {
                                   /* isConst */ !isStatic));
         def.append("\n{\n");
 
 
         // Generate code to handle the return value, if needed.
         // We initialize rv to NS_OK instead of NS_ERROR_* because loading NS_OK (0) uses
         // fewer instructions. We are guaranteed to set rv to the correct value later.
 
-        if (info.catchException && returnType.equals(void.class)) {
+        if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT &&
+                returnType.equals(void.class)) {
             def.append(
                     "    nsresult rv = NS_OK;\n" +
                     "    ");
 
-        } else if (info.catchException) {
+        } else if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
             // Non-void return type
             final String resultArg = "a" + argTypes.length;
             def.append(
                     "    MOZ_ASSERT(" + resultArg + ");\n" +
                     "    nsresult rv = NS_OK;\n" +
                     "    *" + resultArg + " = ");
 
         } else {
@@ -258,31 +266,31 @@ public class CodeGenerator {
 
 
         // Generate a call, e.g., Method<Traits>::Call(a0, a1, a2);
 
         def.append(accessorName).append("(")
            .append(Utils.getUnqualifiedName(clsName) +
                    (isStatic ? "::Context()" : "::mCtx"));
 
-        if (info.catchException) {
+        if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
             def.append(", &rv");
         } else {
             def.append(", nullptr");
         }
 
         // Generate the call argument list.
         for (int argIndex = 0; argIndex < argTypes.length; argIndex++) {
             def.append(", a").append(argIndex);
         }
 
         def.append(");\n");
 
 
-        if (info.catchException) {
+        if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
             def.append("    return rv;\n");
         }
 
         return def.append("}").toString();
     }
 
     /**
      * Append the appropriate generated code to the buffers for the method provided.
@@ -296,16 +304,23 @@ public class CodeGenerator {
         final String uniqueName = getUniqueMethodName(info.wrapperName);
         final Class<?>[] argTypes = method.getParameterTypes();
         final Class<?> returnType = method.getReturnType();
 
         if (method.isSynthetic()) {
             return;
         }
 
+        // Sanity check
+        if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT) {
+            throw new IllegalStateException("Invalid dispatch target \"" +
+                    info.dispatchTarget.name().toLowerCase() +
+                    "\" for non-native method " + clsName + "::" + uniqueName);
+        }
+
         generateMember(info, method, uniqueName, returnType, argTypes);
 
         final boolean isStatic = Utils.isStatic(method);
 
         header.append(
                 "    " + generateDeclaration(info.wrapperName, argTypes,
                                              returnType, info, isStatic) + "\n" +
                 "\n");
@@ -326,16 +341,30 @@ public class CodeGenerator {
     public void generateNative(AnnotatableEntity annotatedMethod) {
         // Unpack the tuple and extract some useful fields from the Method..
         final Method method = annotatedMethod.getMethod();
         final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
         final String uniqueName = getUniqueMethodName(info.wrapperName);
         final Class<?>[] argTypes = method.getParameterTypes();
         final Class<?> returnType = method.getReturnType();
 
+        // Sanity check
+        if (info.exceptionMode != AnnotationInfo.ExceptionMode.ABORT &&
+                info.exceptionMode != AnnotationInfo.ExceptionMode.IGNORE) {
+            throw new IllegalStateException("Invalid exception mode \"" +
+                    info.exceptionMode.name().toLowerCase() +
+                    "\" for native method " + clsName + "::" + uniqueName);
+        }
+        if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT &&
+                returnType != void.class) {
+            throw new IllegalStateException(
+                    "Must return void when not dispatching to current thread for native method " +
+                     clsName + "::" + uniqueName);
+        }
+
         generateMember(info, method, uniqueName, returnType, argTypes);
 
         final String traits = getTraitsName(uniqueName, /* includeScope */ true);
 
         if (nativesInits.length() > 0) {
             nativesInits.append(',');
         }
 
@@ -355,17 +384,17 @@ public class CodeGenerator {
             final char c = (char) val;
             if (c >= 0x20 && c < 0x7F) {
                 return "'" + c + '\'';
             }
             return "u'\\u" + Integer.toHexString(0x10000 | (int) c).substring(1) + '\'';
 
         } else if (type.equals(CharSequence.class) || type.equals(String.class)) {
             final CharSequence str = (CharSequence) val;
-            final StringBuilder out = new StringBuilder(info.narrowChars ? "u8\"" : "u\"");
+            final StringBuilder out = new StringBuilder("u\"");
             for (int i = 0; i < str.length(); i++) {
                 final char c = str.charAt(i);
                 if (c >= 0x20 && c < 0x7F) {
                     out.append(c);
                 } else {
                     out.append("\\u").append(Integer.toHexString(0x10000 | (int) c).substring(1));
                 }
             }
@@ -382,16 +411,23 @@ public class CodeGenerator {
         final Class<?> type = field.getType();
 
         // Handles a peculiar case when dealing with enum types. We don't care about this field.
         // It just gets in the way and stops our code from compiling.
         if (field.isSynthetic() || field.getName().equals("$VALUES")) {
             return;
         }
 
+        // Sanity check
+        if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT) {
+            throw new IllegalStateException("Invalid dispatch target \"" +
+                    info.dispatchTarget.name().toLowerCase() +
+                    "\" for field " + clsName + "::" + uniqueName);
+        }
+
         final boolean isStatic = Utils.isStatic(field);
         final boolean isFinal = Utils.isFinal(field);
 
         if (isStatic && isFinal && (type.isPrimitive() || type.equals(String.class))) {
             Object val = null;
             try {
                 field.setAccessible(true);
                 val = field.get(null);
@@ -402,17 +438,17 @@ public class CodeGenerator {
                 // For static final primitive fields, we can use a "static const" declaration.
                 header.append(
                     "    static const " + Utils.getNativeReturnType(type, info) +
                             ' ' + info.wrapperName + " = " + getLiteral(val, info) + ";\n" +
                     "\n");
                 return;
 
             } else if (val != null && type.equals(String.class)) {
-                final String nativeType = info.narrowChars ? "char" : "char16_t";
+                final String nativeType = "char16_t";
 
                 header.append(
                     "    static const " + nativeType + ' ' + info.wrapperName + "[];\n" +
                     "\n");
 
                 cpp.append(
                     "const " + nativeType + ' ' + clsName + "::" + info.wrapperName +
                             "[] = " + getLiteral(val, info) + ";\n" +
@@ -466,16 +502,23 @@ public class CodeGenerator {
         final String uniqueName = getUniqueMethodName(wrapperName);
         final Class<?>[] argTypes = method.getParameterTypes();
         final Class<?> returnType = cls;
 
         if (method.isSynthetic()) {
             return;
         }
 
+        // Sanity check
+        if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT) {
+            throw new IllegalStateException("Invalid dispatch target \"" +
+                    info.dispatchTarget.name().toLowerCase() +
+                    "\" for constructor " + clsName + "::" + uniqueName);
+        }
+
         generateMember(info, method, uniqueName, returnType, argTypes);
 
         header.append(
                 "    " + generateDeclaration(wrapperName, argTypes,
                                              returnType, info, /* isStatic */ true) + "\n" +
                 "\n");
 
         cpp.append(
@@ -490,19 +533,21 @@ public class CodeGenerator {
         for (Member m : members) {
             if (!Modifier.isPublic(m.getModifiers())) {
                 continue;
             }
 
             String name = Utils.getMemberName(m);
             name = name.substring(0, 1).toUpperCase() + name.substring(1);
 
+            // Default for SDK bindings.
             final AnnotationInfo info = new AnnotationInfo(name,
-                    /* multithread */ true, /* nothrow */ false,
-                    /* narrow */ false, /* catchException */ true);
+                    AnnotationInfo.ExceptionMode.NSRESULT,
+                    AnnotationInfo.CallingThread.ANY,
+                    AnnotationInfo.DispatchTarget.CURRENT);
             final AnnotatableEntity entity = new AnnotatableEntity(m, info);
 
             if (m instanceof Constructor) {
                 generateConstructor(entity);
             } else if (m instanceof Method) {
                 generateMethod(entity);
             } else if (m instanceof Field) {
                 generateField(entity);
@@ -536,18 +581,23 @@ public class CodeGenerator {
     }
 
     /**
      * Get the finalised bytes to go into the generated header file.
      *
      * @return The bytes to be written to the header file.
      */
     public String getHeaderFileContents() {
+        if (this.callingThread == null) {
+            this.callingThread = AnnotationInfo.CallingThread.ANY;
+        }
+
         header.append(
-                "    static const bool isMultithreaded = " + this.isMultithreaded + ";\n" +
+                "    static const mozilla::jni::CallingThread callingThread =\n" +
+                "            " + this.callingThread.nativeValue() + ";\n" +
                 "\n");
 
         if (nativesInits.length() > 0) {
             header.append(
                     "    template<class Impl> class Natives;\n");
         }
         header.append(
                 "};\n" +
--- a/build/annotationProcessors/utils/GeneratableElementIterator.java
+++ b/build/annotationProcessors/utils/GeneratableElementIterator.java
@@ -25,16 +25,17 @@ import java.util.Iterator;
 public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
     private final ClassWithOptions mClass;
     private final Member[] mObjects;
     private AnnotatableEntity mNextReturnValue;
     private int mElementIndex;
     private AnnotationInfo mClassInfo;
 
     private boolean mIterateEveryEntry;
+    private boolean mSkipCurrentEntry;
 
     public GeneratableElementIterator(ClassWithOptions annotatedClass) {
         mClass = annotatedClass;
 
         final Class<?> aClass = annotatedClass.wrappedClass;
         // Get all the elements of this class as AccessibleObjects.
         Member[] aMethods = aClass.getDeclaredMethods();
         Member[] aFields = aClass.getDeclaredFields();
@@ -58,16 +59,20 @@ public class GeneratableElementIterator 
         for (Annotation annotation : aClass.getDeclaredAnnotations()) {
             mClassInfo = buildAnnotationInfo(aClass, annotation);
             if (mClassInfo != null) {
                 mIterateEveryEntry = true;
                 break;
             }
         }
 
+        if (mSkipCurrentEntry) {
+            throw new IllegalArgumentException("Cannot skip entire class");
+        }
+
         findNextValue();
     }
 
     private Class<?>[] getFilteredInnerClasses() {
         // Go through all inner classes and see which ones we want to generate.
         final Class<?>[] candidates = mClass.wrappedClass.getDeclaredClasses();
         int count = 0;
 
@@ -111,59 +116,84 @@ public class GeneratableElementIterator 
         Arrays.sort(ret, new Comparator<ClassWithOptions>() {
             @Override public int compare(ClassWithOptions lhs, ClassWithOptions rhs) {
                 return lhs.generatedName.compareTo(rhs.generatedName);
             }
         });
         return ret;
     }
 
+    private static <T extends Enum<T>> T getEnumValue(Class<T> type, String name)
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+        try {
+            return Enum.valueOf(type, name.toUpperCase());
+
+        } catch (IllegalArgumentException e) {
+            Object[] values = (Object[]) type.getDeclaredMethod("values").invoke(null);
+            StringBuilder names = new StringBuilder();
+
+            for (int i = 0; i < values.length; i++) {
+                if (i != 0) {
+                    names.append(", ");
+                }
+                names.append(values[i].toString().toLowerCase());
+            }
+
+            System.err.println("***");
+            System.err.println("*** Invalid value \"" + name + "\" for " + type.getSimpleName());
+            System.err.println("*** Specify one of " + names.toString());
+            System.err.println("***");
+            e.printStackTrace(System.err);
+            System.exit(6);
+            return null;
+        }
+    }
+
     private AnnotationInfo buildAnnotationInfo(AnnotatedElement element, Annotation annotation) {
         Class<? extends Annotation> annotationType = annotation.annotationType();
         final String annotationTypeName = annotationType.getName();
         if (!annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) {
             return null;
         }
 
         String stubName = null;
-        boolean isMultithreadedStub = false;
-        boolean noThrow = false;
-        boolean narrowChars = false;
-        boolean catchException = false;
+        AnnotationInfo.ExceptionMode exceptionMode = null;
+        AnnotationInfo.CallingThread callingThread = null;
+        AnnotationInfo.DispatchTarget dispatchTarget = null;
+
         try {
+            final Method skipMethod = annotationType.getDeclaredMethod("skip");
+            skipMethod.setAccessible(true);
+            if ((Boolean) skipMethod.invoke(annotation)) {
+                mSkipCurrentEntry = true;
+                return null;
+            }
+
             // Determine the explicitly-given name of the stub to generate, if any.
             final Method stubNameMethod = annotationType.getDeclaredMethod("stubName");
             stubNameMethod.setAccessible(true);
             stubName = (String) stubNameMethod.invoke(annotation);
 
-            if (element instanceof Class<?>) {
-                // Make @WrapForJNI always allow multithread by default, individual methods can then
-                // override with their own annotation
-                isMultithreadedStub = true;
-            } else {
-                // Determine if the generated stub is to allow calls from multiple threads.
-                final Method multithreadedStubMethod = annotationType.getDeclaredMethod("allowMultithread");
-                multithreadedStubMethod.setAccessible(true);
-                isMultithreadedStub = (Boolean) multithreadedStubMethod.invoke(annotation);
-            }
+            final Method exceptionModeMethod = annotationType.getDeclaredMethod("exceptionMode");
+            exceptionModeMethod.setAccessible(true);
+            exceptionMode = getEnumValue(
+                    AnnotationInfo.ExceptionMode.class,
+                    (String) exceptionModeMethod.invoke(annotation));
 
-            // Determine if ignoring exceptions
-            final Method noThrowMethod = annotationType.getDeclaredMethod("noThrow");
-            noThrowMethod.setAccessible(true);
-            noThrow = (Boolean) noThrowMethod.invoke(annotation);
+            final Method calledFromMethod = annotationType.getDeclaredMethod("calledFrom");
+            calledFromMethod.setAccessible(true);
+            callingThread = getEnumValue(
+                    AnnotationInfo.CallingThread.class,
+                    (String) calledFromMethod.invoke(annotation));
 
-            // Determine if strings should be wide or narrow
-            final Method narrowCharsMethod = annotationType.getDeclaredMethod("narrowChars");
-            narrowCharsMethod.setAccessible(true);
-            narrowChars = (Boolean) narrowCharsMethod.invoke(annotation);
-
-            // Determine if we should catch exceptions
-            final Method catchExceptionMethod = annotationType.getDeclaredMethod("catchException");
-            catchExceptionMethod.setAccessible(true);
-            catchException = (Boolean) catchExceptionMethod.invoke(annotation);
+            final Method dispatchToMethod = annotationType.getDeclaredMethod("dispatchTo");
+            dispatchToMethod.setAccessible(true);
+            dispatchTarget = getEnumValue(
+                    AnnotationInfo.DispatchTarget.class,
+                    (String) dispatchToMethod.invoke(annotation));
 
         } catch (NoSuchMethodException e) {
             System.err.println("Unable to find expected field on WrapForJNI annotation. Did the signature change?");
             e.printStackTrace(System.err);
             System.exit(3);
         } catch (IllegalAccessException e) {
             System.err.println("IllegalAccessException reading fields on WrapForJNI annotation. Seems the semantics of Reflection have changed...");
             e.printStackTrace(System.err);
@@ -174,18 +204,17 @@ public class GeneratableElementIterator 
             System.exit(5);
         }
 
         // If the method name was not explicitly given in the annotation generate one...
         if (stubName.isEmpty()) {
             stubName = Utils.getNativeName(element);
         }
 
-        return new AnnotationInfo(
-            stubName, isMultithreadedStub, noThrow, narrowChars, catchException);
+        return new AnnotationInfo(stubName, exceptionMode, callingThread, dispatchTarget);
     }
 
     /**
      * Find and cache the next appropriately annotated method, plus the annotation parameter, if
      * one exists. Otherwise cache null, so hasNext returns false.
      */
     private void findNextValue() {
         while (mElementIndex < mObjects.length) {
@@ -194,25 +223,29 @@ public class GeneratableElementIterator 
             for (Annotation annotation : ((AnnotatedElement) candidateElement).getDeclaredAnnotations()) {
                 AnnotationInfo info = buildAnnotationInfo((AnnotatedElement)candidateElement, annotation);
                 if (info != null) {
                     mNextReturnValue = new AnnotatableEntity(candidateElement, info);
                     return;
                 }
             }
 
+            if (mSkipCurrentEntry) {
+                mSkipCurrentEntry = false;
+                continue;
+            }
+
             // If no annotation found, we might be expected to generate anyway
             // using default arguments, thanks to the "Generate everything" annotation.
             if (mIterateEveryEntry) {
                 AnnotationInfo annotationInfo = new AnnotationInfo(
                     Utils.getNativeName(candidateElement),
-                    mClassInfo.isMultithreaded,
-                    mClassInfo.noThrow,
-                    mClassInfo.narrowChars,
-                    mClassInfo.catchException);
+                    mClassInfo.exceptionMode,
+                    mClassInfo.callingThread,
+                    mClassInfo.dispatchTarget);
                 mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
                 return;
             }
         }
         mNextReturnValue = null;
     }
 
     @Override
--- a/widget/android/jni/Refs.h
+++ b/widget/android/jni/Refs.h
@@ -31,16 +31,41 @@ enum class ExceptionMode
     // Abort on unhandled excepion (default).
     ABORT,
     // Ignore the exception and return to caller.
     IGNORE,
     // Catch any exception and return a nsresult.
     NSRESULT,
 };
 
+// Thread that a particular JNI call is allowed on.
+enum class CallingThread
+{
+    // Can be called from any thread (default).
+    ANY,
+    // Can be called from the Gecko thread.
+    GECKO,
+    // Can be called from the Java UI thread.
+    UI,
+};
+
+// If and where a JNI call will be dispatched.
+enum class DispatchTarget
+{
+    // Call happens synchronously on the calling thread (default).
+    CURRENT,
+    // Call happens synchronously on the calling thread, but the call is
+    // wrapped in a function object and is passed thru UsesNativeCallProxy.
+    // Method must return void.
+    PROXY,
+    // Call is dispatched asynchronously on the Gecko thread. Method must
+    // return void.
+    GECKO,
+};
+
 
 // Class to hold the native types of a method's arguments.
 // For example, if a method has signature (ILjava/lang/String;)V,
 // its arguments class would be jni::Args<int32_t, jni::String::Param>
 template<typename...>
 struct Args {};
 
 
@@ -72,17 +97,18 @@ class Ref
     // Private copy constructor so that there's no danger of assigning a
     // temporary LocalRef/GlobalRef to a Ref, and potentially use the Ref
     // after the source had been freed.
     Ref(const Ref&) = default;
 
 protected:
     static JNIEnv* FindEnv()
     {
-        return Cls::isMultithreaded ? GetEnvForThread() : GetGeckoThreadEnv();
+        return Cls::callingThread == CallingThread::GECKO ?
+                GetGeckoThreadEnv() : GetEnvForThread();
     }
 
     Type mInstance;
 
     // Protected jobject constructor because outside code should be using
     // Ref::From. Using Ref::From makes it very easy to see which code is using
     // raw JNI types for future refactoring.
     explicit Ref(Type instance) : mInstance(instance) {}
@@ -226,34 +252,34 @@ public:
     Cls operator->() const
     {
         MOZ_ASSERT(Ref::mInstance, "Null jobject");
         return Cls(*this);
     }
 };
 
 
-template<class Cls, typename Type>
+template<class Cls, typename Type = jobject>
 class ObjectBase
 {
 protected:
     const jni::Context<Cls, Type>& mCtx;
 
     jclass ClassRef() const { return mCtx.ClassRef(); }
     JNIEnv* Env() const { return mCtx.Env(); }
     Type Instance() const { return mCtx.Get(); }
 
 public:
     using Ref = jni::Ref<Cls, Type>;
     using Context = jni::Context<Cls, Type>;
     using LocalRef = jni::LocalRef<Cls>;
     using GlobalRef = jni::GlobalRef<Cls>;
     using Param = const Ref&;
 
-    static const bool isMultithreaded = true;
+    static const CallingThread callingThread = CallingThread::ANY;
     static const char name[];
 
     explicit ObjectBase(const Context& ctx) : mCtx(ctx) {}
 
     Cls* operator->()
     {
         return static_cast<Cls*>(this);
     }