Bug 1439410 - 4. Add JUnit4 rule for testing GeckoSession; r=snorp

Add a rule for setting up a GeckoSession for a JUnit4 test and letting
the test wait for listener invocations and to verify listener behavior.

MozReview-Commit-ID: 20ij409yY1Z

--HG--
extra : rebase_source : 50f8a01aad41938910710421b97d5dcc97594a06
This commit is contained in:
Jim Chen 2018-02-22 18:39:12 -05:00
parent 448584018f
commit e59ef3e43d
2 changed files with 931 additions and 0 deletions

View File

@ -0,0 +1,810 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.geckoview.test.rule;
import org.mozilla.gecko.GeckoSession;
import org.mozilla.gecko.GeckoSessionSettings;
import org.mozilla.geckoview.test.util.Callbacks;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import android.app.Instrumentation;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.UiThreadTestRule;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Pattern;
/**
* TestRule that, for each test, sets up a GeckoSession, runs the test on the UI thread,
* and tears down the GeckoSession at the end of the test. The rule also provides methods
* for waiting on particular callbacks to be called, and methods for asserting that
* callbacks are called in the proper order.
*/
public class GeckoSessionTestRule extends UiThreadTestRule {
private static final long DEFAULT_TIMEOUT_MILLIS = 10000;
public static final String APK_URI_PREFIX = "resource://android";
/**
* Specify the timeout for any of the wait methods, in milliseconds. Can be used
* on classes or methods.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeoutMillis {
long value();
}
/**
* Specify a list of GeckoSession settings to be applied to the GeckoSession object
* under test. Can be used on classes or methods. Note that the settings values must
* be string literals regardless of the type of the settings.
* <p>
* Disable e10s for a particular test:
* <pre>
* &#64;Setting.List(&#64;Setting(key = Setting.Key.USE_MULTIPROCESS,
* value = "false"))
* &#64;Test public void test() { ... }
* </pre>
* <p>
* Use multiple settings:
* <pre>
* &#64;Setting.List({&#64;Setting(key = Setting.Key.USE_MULTIPROCESS,
* value = "false"),
* &#64;Setting(key = Setting.Key.USE_TRACKING_PROTECTION,
* value = "true")})
* </pre>
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Setting {
enum Key {
CHROME_URI,
DISPLAY_MODE,
SCREEN_ID,
USE_MULTIPROCESS,
USE_PRIVATE_MODE,
USE_REMOTE_DEBUGGER,
USE_TRACKING_PROTECTION;
private final GeckoSessionSettings.Key<?> mKey;
private final Class<?> mType;
Key() {
final Field field;
try {
field = GeckoSessionSettings.class.getField(name());
mKey = (GeckoSessionSettings.Key<?>) field.get(null);
} catch (final NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
final ParameterizedType genericType = (ParameterizedType) field.getGenericType();
mType = (Class<?>) genericType.getActualTypeArguments()[0];
}
@SuppressWarnings("unchecked")
public void set(final GeckoSessionSettings settings, final String value) {
if (boolean.class.equals(mType) || Boolean.class.equals(mType)) {
settings.setBoolean((GeckoSessionSettings.Key<Boolean>) mKey,
Boolean.valueOf(value));
} else if (int.class.equals(mType) || Integer.class.equals(mType)) {
try {
settings.setInt((GeckoSessionSettings.Key<Integer>) mKey,
(Integer) GeckoSessionSettings.class.getField(value)
.get(null));
return;
} catch (final NoSuchFieldException | IllegalAccessException |
ClassCastException e) {
}
settings.setInt((GeckoSessionSettings.Key<Integer>) mKey,
Integer.valueOf(value));
} else if (String.class.equals(mType)) {
settings.setString((GeckoSessionSettings.Key<String>) mKey, value);
} else {
throw new IllegalArgumentException("Unsupported type: " +
mType.getSimpleName());
}
}
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface List {
Setting[] value();
}
Key key();
String value();
}
/**
* Assert that a method is called or not called, and if called, the order and number
* of times it is called. The order number is a monotonically increasing integer; if
* an called method's order number is less than the current order number, an exception
* is raised for out-of-order call.
* <p>
* {@code @AssertCalled} asserts the method must be called at least once.
* <p>
* {@code @AssertCalled(false)} asserts the method must not be called.
* <p>
* {@code @AssertCalled(order = 2)} asserts the method must be called once and
* after any other method with order number less than 2.
* <p>
* {@code @AssertCalled(order = {2, 4})} asserts order number 2 for first
* call and order number 4 for any subsequent calls.
* <p>
* {@code @AssertCalled(count = 2)} asserts two calls total in any order
* with respect to other calls.
* <p>
* {@code @AssertCalled(count = 2, order = 2)} asserts two calls, both with
* order number 2.
* <p>
* {@code @AssertCalled(count = 2, order = {2, 4, 6})} asserts two calls
* total: the first with order number 2 and the second with order number 4.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AssertCalled {
/**
* @return True if the method must be called,
* or false if the method must not be called.
*/
boolean value() default true;
/**
* @return If called, the number of calls called, or 0 to allow any number > 0.
*/
int count() default 0;
/**
* @return If called, the order number for each call, or 0 to allow arbitrary
* order. If order's length is more than count, extra elements are not used;
* if order's length is less than count, the last element is repeated.
*/
int[] order() default 0;
}
public static class CallRequirement {
public final boolean allowed;
public final int count;
public final int[] order;
public CallRequirement(final boolean allowed, final int count, final int[] order) {
this.allowed = allowed;
this.count = count;
this.order = order;
}
}
public static class CallInfo {
public final int counter;
public final int order;
/* package */ CallInfo(final int counter, final int order) {
this.counter = counter;
this.order = order;
}
}
public static class MethodCall {
public final Method method;
public final CallRequirement requirement;
private int currentCount;
/* package */ MethodCall(final Method method) {
this(method, (CallRequirement) null);
}
/* package */ MethodCall(final Method method,
final AssertCalled requirement) {
this(method, requirement != null ? new CallRequirement(
requirement.value(), requirement.count(), requirement.order()) : null);
}
public MethodCall(final Method method,
final CallRequirement requirement) {
this.method = method;
this.requirement = requirement;
currentCount = 0;
}
@Override
public boolean equals(final Object other) {
if (this == other) {
return true;
} else if (other instanceof MethodCall) {
return methodsEqual(method, ((MethodCall) other).method);
} else if (other instanceof Method) {
return methodsEqual(method, (Method) other);
}
return false;
}
/* package */ int getOrder() {
if (requirement == null || currentCount == 0) {
return 0;
}
final int[] order = requirement.order;
if (order == null || order.length == 0) {
return 0;
}
return order[Math.min(currentCount - 1, order.length - 1)];
}
/* package */ int getCount() {
return (requirement == null) ? 0 :
!requirement.allowed ? -1 : requirement.count;
}
/* package */ void incrementCounter() {
currentCount++;
}
/* package */ boolean allowCalls() {
return getCount() >= 0;
}
/* package */ boolean allowUnlimitedCalls() {
return getCount() == 0;
}
/* package */ boolean allowMoreCalls() {
final int count = getCount();
return count == 0 || count > currentCount;
}
/* package */ void assertAllowMoreCalls() {
final int count = getCount();
assertTrue(method.getName() + " should not be called", allowCalls());
assertTrue(method.getName() + " should be limited to " + count +
" call" + (count > 1 ? "s" : ""), allowMoreCalls());
}
/* package */ void assertOrder(final int order) {
final int newOrder = getOrder();
if (newOrder != 0) {
assertTrue(method.getName() + " order number " + newOrder +
" does not match expected order number " + order,
newOrder >= order);
}
}
/* package */ void assertMatchesCount() {
if (requirement == null) {
return;
}
final int count = getCount();
if (count < 0) {
assertEquals(method.getName() + " should not be called",
0, currentCount);
} else if (count == 0) {
assertThat(method.getName() + " should be called", currentCount,
is(greaterThan(0)));
} else {
assertEquals(method.getName() + " should be called " + count + " time" +
(count == 1 ? "" : "s"), count, currentCount);
}
}
/* package */ CallInfo getInfo() {
return new CallInfo(currentCount, getOrder());
}
// Similar to Method.equals, but treat the same method from an interface and an
// overriding class as the same (e.g. CharSequence.length == String.length).
private static boolean methodsEqual(final @NonNull Method m1, final @NonNull Method m2) {
return (m1.getDeclaringClass().isAssignableFrom(m2.getDeclaringClass()) ||
m2.getDeclaringClass().isAssignableFrom(m1.getDeclaringClass())) &&
m1.getName().equals(m2.getName()) &&
m1.getReturnType().equals(m2.getReturnType()) &&
Arrays.equals(m1.getParameterTypes(), m2.getParameterTypes());
}
}
protected static class CallRecord {
public final Method method;
public final MethodCall methodCall;
public final Object[] args;
public CallRecord(final Method method, final Object[] args) {
this.method = method;
this.methodCall = new MethodCall(method);
this.args = args;
}
}
/* package */ static AssertCalled getAssertCalled(final Method method, final Object callback) {
final AssertCalled annotation = method.getAnnotation(AssertCalled.class);
if (annotation != null) {
return annotation;
}
// Some Kotlin lambdas have an invoke method that carries the annotation,
// instead of the interface method carrying the annotation.
try {
return callback.getClass().getDeclaredMethod(
"invoke", method.getParameterTypes()).getAnnotation(AssertCalled.class);
} catch (final NoSuchMethodException e) {
return null;
}
}
private static void addCallbackClasses(final List<Class<?>> list, final Class<?> ifce) {
if (!Callbacks.class.equals(ifce.getDeclaringClass())) {
list.add(ifce);
return;
}
final Class<?>[] superIfces = ifce.getInterfaces();
for (final Class<?> superIfce : superIfces) {
addCallbackClasses(list, superIfce);
}
}
private static Class<?>[] getCallbackClasses() {
final Class<?>[] ifces = Callbacks.class.getDeclaredClasses();
final List<Class<?>> list = new ArrayList<>(ifces.length);
for (final Class<?> ifce : ifces) {
addCallbackClasses(list, ifce);
}
final HashSet<Class<?>> set = new HashSet<>(list);
return set.toArray(new Class<?>[set.size()]);
}
private static final List<Class<?>> CALLBACK_CLASSES = Arrays.asList(getCallbackClasses());
protected final Instrumentation mInstrumentation =
InstrumentationRegistry.getInstrumentation();
protected final GeckoSessionSettings mDefaultSettings;
protected GeckoSession mSession;
protected Object mCallbackProxy;
protected List<CallRecord> mCallRecords;
protected int mLastWaitStart;
protected int mLastWaitEnd;
protected MethodCall mCurrentMethodCall;
protected long mTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
public GeckoSessionTestRule() {
mDefaultSettings = new GeckoSessionSettings();
}
/**
* Get the session set up for the current test.
*
* @return GeckoSession object.
*/
public @NonNull GeckoSession getSession() {
return mSession;
}
protected static Method getCallbackSetter(final @NonNull Class<?> cls)
throws NoSuchMethodException {
return GeckoSession.class.getMethod("set" + cls.getSimpleName(), cls);
}
protected static Method getCallbackGetter(final @NonNull Class<?> cls)
throws NoSuchMethodException {
return GeckoSession.class.getMethod("get" + cls.getSimpleName());
}
protected void applyAnnotations(final Collection<Annotation> annotations,
final GeckoSessionSettings settings) {
for (final Annotation annotation : annotations) {
if (TimeoutMillis.class.equals(annotation.annotationType())) {
mTimeoutMillis = Math.max(((TimeoutMillis) annotation).value(), 100);
} else if (Setting.List.class.equals(annotation.annotationType())) {
for (final Setting setting : ((Setting.List) annotation).value()) {
setting.key().set(settings, setting.value());
}
}
}
}
protected void prepareSession(final Description description) throws Throwable {
final GeckoSessionSettings settings = new GeckoSessionSettings(mDefaultSettings);
applyAnnotations(Arrays.asList(description.getTestClass().getAnnotations()), settings);
applyAnnotations(description.getAnnotations(), settings);
final List<CallRecord> records = new ArrayList<>();
mCallRecords = records;
mLastWaitStart = 0;
mLastWaitEnd = 0;
final InvocationHandler recorder = new InvocationHandler() {
@Override
public Object invoke(final Object proxy, final Method method,
final Object[] args) {
assertEquals("Callbacks must be on UI thread",
Looper.getMainLooper(), Looper.myLooper());
records.add(new CallRecord(method, args));
try {
return method.invoke(Callbacks.Default.INSTANCE, args);
} catch (final IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e.getCause() != null ? e.getCause() : e);
}
}
};
final Class<?>[] classes = CALLBACK_CLASSES.toArray(new Class<?>[CALLBACK_CLASSES.size()]);
mCallbackProxy = Proxy.newProxyInstance(GeckoSession.class.getClassLoader(),
classes, recorder);
mSession = new GeckoSession(settings);
for (final Class<?> cls : CALLBACK_CLASSES) {
if (cls != null) {
getCallbackSetter(cls).invoke(mSession, mCallbackProxy);
}
}
mSession.openWindow(mInstrumentation.getTargetContext());
if (settings.getBoolean(GeckoSessionSettings.USE_MULTIPROCESS)) {
// Under e10s, we receive an initial about:blank load; don't expose that to the test.
waitForPageStop();
}
}
protected void cleanupSession() throws Throwable {
if (mSession.isOpen()) {
mSession.closeWindow();
}
mSession = null;
mCallbackProxy = null;
mCallRecords = null;
mLastWaitStart = 0;
mLastWaitEnd = 0;
mTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
}
@Override
public Statement apply(final Statement base, final Description description) {
return super.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
try {
prepareSession(description);
base.evaluate();
} finally {
cleanupSession();
}
}
}, description);
}
@Override
protected boolean shouldRunOnUiThread(final Description description) {
return true;
}
/**
* Loop the current thread until the message queue is idle. If loop is already idle and
* timeout is not specified, return immediately. If loop is already idle and timeout is
* specified, wait for a message to arrive first; an exception is thrown if timeout
* expires during the wait.
*
* @param timeout Wait timeout in milliseconds or 0 to not wait.
*/
protected static void loopUntilIdle(final long timeout) {
// Adapted from GeckoThread.pumpMessageLoop.
final Looper looper = Looper.myLooper();
assertNotNull("Looper must exist", looper);
final MessageQueue queue = looper.getQueue();
final Handler handler = new Handler(looper);
final MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
final Message msg = Message.obtain(handler);
msg.obj = handler;
handler.sendMessageAtFrontOfQueue(msg);
return false; // Remove this idle handler.
}
};
final Runnable timeoutRunnable = new Runnable() {
@Override
public void run() {
fail("Timed out after " + timeout + "ms");
}
};
if (timeout > 0) {
handler.postDelayed(timeoutRunnable, timeout);
} else {
queue.addIdleHandler(idleHandler);
}
final Method getNextMessage;
try {
getNextMessage = queue.getClass().getDeclaredMethod("next");
} catch (final NoSuchMethodException e) {
throw new RuntimeException(e);
}
getNextMessage.setAccessible(true);
while (true) {
final Message msg;
try {
msg = (Message) getNextMessage.invoke(queue);
} catch (final IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e.getCause() != null ? e.getCause() : e);
}
if (msg.getTarget() == handler && msg.obj == handler) {
// Our idle signal.
break;
} else if (msg.getTarget() == null) {
looper.quit();
break;
}
msg.getTarget().dispatchMessage(msg);
if (timeout > 0) {
handler.removeCallbacks(timeoutRunnable);
queue.addIdleHandler(idleHandler);
}
}
}
/**
* Wait until a page load has finished. The session must have started a page load since
* the last wait, or this method will wait indefinitely.
*/
public void waitForPageStop() {
waitForPageStops(/* count */ 1);
}
/**
* Wait until a page load has finished. The session must have started a page load since
* the last wait, or this method will wait indefinitely.
*
* @param count Number of page loads to wait for.
*/
public void waitForPageStops(final int count) {
final Method onPageStop;
try {
onPageStop = GeckoSession.ProgressListener.class.getMethod(
"onPageStop", GeckoSession.class, boolean.class);
} catch (final NoSuchMethodException e) {
throw new RuntimeException(e);
}
final List<MethodCall> methodCalls = new ArrayList<>(1);
methodCalls.add(new MethodCall(onPageStop,
new CallRequirement(/* allowed */ true, count, null)));
waitUntilCalled(GeckoSession.ProgressListener.class, methodCalls);
}
/**
* Wait until the specified methods have been called on the specified callback
* interface. If no methods are specified, wait until any method has been called.
*
* @param callback Target callback interface; must be an interface under GeckoSession.
* @param methods List of methods to wait on; use empty or null or wait on any method.
*/
public void waitUntilCalled(final @NonNull Class<?> callback,
final @Nullable String... methods) {
assertTrue("Class should be a GeckoSession interface",
CALLBACK_CLASSES.contains(callback));
final int length = (methods != null) ? methods.length : 0;
final Pattern[] patterns = new Pattern[length];
for (int i = 0; i < length; i++) {
patterns[i] = Pattern.compile(methods[i]);
}
final List<MethodCall> waitMethods = new ArrayList<>();
for (final Method method : callback.getDeclaredMethods()) {
for (final Pattern pattern : patterns) {
if (pattern.matcher(method.getName()).matches()) {
waitMethods.add(new MethodCall(method));
}
}
}
waitUntilCalled(callback, waitMethods);
}
/**
* Wait until the specified methods have been called on the specified object,
* as specified by any {@link AssertCalled @AssertCalled} annotations. If no
* {@link AssertCalled @AssertCalled} annotations are found, wait until any method
* has been called. Only methods belonging to a GeckoSession callback are supported.
*
* @param callback Target callback object; must implement an interface under GeckoSession.
*/
public void waitUntilCalled(final @NonNull Object callback) {
if (callback instanceof Class<?>) {
waitUntilCalled((Class<?>) callback, (String[]) null);
return;
}
final List<MethodCall> methodCalls = new ArrayList<>();
for (final Class<?> ifce : CALLBACK_CLASSES) {
if (!ifce.isInstance(callback)) {
continue;
}
for (final Method method : ifce.getMethods()) {
final Method callbackMethod;
try {
callbackMethod = callback.getClass().getMethod(method.getName(),
method.getParameterTypes());
} catch (final NoSuchMethodException e) {
throw new RuntimeException(e);
}
final AssertCalled ac = getAssertCalled(callbackMethod, callback);
if (ac != null && ac.value()) {
methodCalls.add(new MethodCall(callbackMethod, ac));
}
}
}
waitUntilCalled(callback.getClass(), methodCalls);
forCallbacksDuringWait(callback);
}
protected void waitUntilCalled(final @NonNull Class<?> listener,
final @NonNull List<MethodCall> methodCalls) {
// Make sure all handlers are set though #delegateUntilTestEnd or #delegateDuringNextWait,
// instead of through GeckoSession directly, so that we can still record calls even with
// custom handlers set.
for (final Class<?> ifce : CALLBACK_CLASSES) {
try {
assertSame("Callbacks should be set through" +
" GeckoSessionTestRule delegate methods",
mCallbackProxy, getCallbackGetter(ifce).invoke(mSession));
} catch (final NoSuchMethodException | IllegalAccessException |
InvocationTargetException e) {
throw new RuntimeException(e.getCause() != null ? e.getCause() : e);
}
}
boolean calledAny = false;
int index = mLastWaitStart = mLastWaitEnd;
while (!calledAny || !methodCalls.isEmpty()) {
while (index >= mCallRecords.size()) {
loopUntilIdle(mTimeoutMillis);
}
final MethodCall recorded = mCallRecords.get(index).methodCall;
calledAny |= recorded.method.getDeclaringClass().isAssignableFrom(listener);
index++;
final int i = methodCalls.indexOf(recorded);
if (i < 0) {
continue;
}
final MethodCall methodCall = methodCalls.get(i);
methodCall.incrementCounter();
if (methodCall.allowUnlimitedCalls() || !methodCall.allowMoreCalls()) {
methodCalls.remove(i);
}
}
mLastWaitEnd = index;
}
/**
* Playback callbacks that were made during the previous wait. For any methods
* annotated with {@link AssertCalled @AssertCalled}, assert that the callbacks
* satisfy the specified requirements. If no {@link AssertCalled @AssertCalled}
* annotations are found, assert any method has been called. Only methods belonging
* to a GeckoSession callback are supported.
*
* @param callback Target callback object; must implement one or more interfaces
* under GeckoSession.
*/
public void forCallbacksDuringWait(final @NonNull Object callback) {
final Method[] declaredMethods = callback.getClass().getDeclaredMethods();
final List<MethodCall> methodCalls = new ArrayList<>(declaredMethods.length);
for (final Class<?> ifce : CALLBACK_CLASSES) {
if (!ifce.isInstance(callback)) {
continue;
}
for (final Method method : ifce.getMethods()) {
final Method callbackMethod;
try {
callbackMethod = callback.getClass().getMethod(method.getName(),
method.getParameterTypes());
} catch (final NoSuchMethodException e) {
throw new RuntimeException(e);
}
methodCalls.add(new MethodCall(
callbackMethod, getAssertCalled(callbackMethod, callback)));
}
}
int order = 0;
boolean calledAny = false;
for (int index = mLastWaitStart; index < mLastWaitEnd; index++) {
final CallRecord record = mCallRecords.get(index);
if (!record.method.getDeclaringClass().isInstance(callback)) {
continue;
}
final int i = methodCalls.indexOf(record.methodCall);
assertTrue(record.method.getName() + " should be found", i >= 0);
final MethodCall methodCall = methodCalls.get(i);
methodCall.assertAllowMoreCalls();
methodCall.incrementCounter();
methodCall.assertOrder(order);
order = Math.max(methodCall.getOrder(), order);
try {
mCurrentMethodCall = methodCall;
record.method.invoke(callback, record.args);
} catch (final IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e.getCause() != null ? e.getCause() : e);
} finally {
mCurrentMethodCall = null;
}
calledAny = true;
}
for (final MethodCall methodCall : methodCalls) {
methodCall.assertMatchesCount();
if (methodCall.requirement != null) {
calledAny = true;
}
}
assertTrue("Should have called one of " +
Arrays.toString(callback.getClass().getInterfaces()), calledAny);
}
/**
* Get information about the current call. Only valid during a {@link #forCallbacksDuringWait}
* callback.
*
* @return Call information
*/
public @NonNull CallInfo getCurrentCall() {
assertNotNull("Should be in a method call", mCurrentMethodCall);
return mCurrentMethodCall.getInfo();
}
}

View File

@ -0,0 +1,121 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.geckoview.test.util
import org.mozilla.gecko.GeckoSession
import org.mozilla.gecko.util.GeckoBundle
class Callbacks private constructor() {
object Default : All {
}
interface All : ContentListener, NavigationListener, PermissionDelegate, ProgressListener,
PromptDelegate, ScrollListener, TrackingProtectionDelegate {
}
interface ContentListener : GeckoSession.ContentListener {
override fun onTitleChange(session: GeckoSession, title: String) {
}
override fun onFocusRequest(session: GeckoSession) {
}
override fun onCloseRequest(session: GeckoSession) {
}
override fun onFullScreen(session: GeckoSession, fullScreen: Boolean) {
}
override fun onContextMenu(session: GeckoSession, screenX: Int, screenY: Int, uri: String, elementSrc: String) {
}
}
interface NavigationListener : GeckoSession.NavigationListener {
override fun onLocationChange(session: GeckoSession, url: String) {
}
override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) {
}
override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
}
override fun onLoadUri(session: GeckoSession, uri: String, where: GeckoSession.NavigationListener.TargetWindow): Boolean {
return false;
}
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoSession.Response<GeckoSession>) {
response.respond(null)
}
}
interface PermissionDelegate : GeckoSession.PermissionDelegate {
override fun requestAndroidPermissions(session: GeckoSession, permissions: Array<out String>, callback: GeckoSession.PermissionDelegate.Callback) {
callback.reject()
}
override fun requestContentPermission(session: GeckoSession, uri: String, type: String, access: String, callback: GeckoSession.PermissionDelegate.Callback) {
callback.reject()
}
override fun requestMediaPermission(session: GeckoSession, uri: String, video: Array<out GeckoSession.PermissionDelegate.MediaSource>, audio: Array<out GeckoSession.PermissionDelegate.MediaSource>, callback: GeckoSession.PermissionDelegate.MediaCallback) {
callback.reject()
}
}
interface ProgressListener : GeckoSession.ProgressListener {
override fun onPageStart(session: GeckoSession, url: String) {
}
override fun onPageStop(session: GeckoSession, success: Boolean) {
}
override fun onSecurityChange(session: GeckoSession, securityInfo: GeckoSession.ProgressListener.SecurityInformation) {
}
}
interface PromptDelegate : GeckoSession.PromptDelegate {
override fun alert(session: GeckoSession, title: String, msg: String, callback: GeckoSession.PromptDelegate.AlertCallback) {
callback.dismiss()
}
override fun promptForButton(session: GeckoSession, title: String, msg: String, btnMsg: Array<out String>, callback: GeckoSession.PromptDelegate.ButtonCallback) {
callback.dismiss()
}
override fun promptForText(session: GeckoSession, title: String, msg: String, value: String, callback: GeckoSession.PromptDelegate.TextCallback) {
callback.dismiss()
}
override fun promptForAuth(session: GeckoSession, title: String, msg: String, options: GeckoSession.PromptDelegate.AuthenticationOptions, callback: GeckoSession.PromptDelegate.AuthCallback) {
callback.dismiss()
}
override fun promptForChoice(session: GeckoSession, title: String, msg: String, type: Int, choices: Array<out GeckoSession.PromptDelegate.Choice>, callback: GeckoSession.PromptDelegate.ChoiceCallback) {
callback.dismiss()
}
override fun promptForColor(session: GeckoSession, title: String, value: String, callback: GeckoSession.PromptDelegate.TextCallback) {
callback.dismiss()
}
override fun promptForDateTime(session: GeckoSession, title: String, type: Int, value: String, min: String, max: String, callback: GeckoSession.PromptDelegate.TextCallback) {
callback.dismiss()
}
override fun promptForFile(session: GeckoSession, title: String, type: Int, mimeTypes: Array<out String>, callback: GeckoSession.PromptDelegate.FileCallback) {
callback.dismiss()
}
}
interface ScrollListener : GeckoSession.ScrollListener {
override fun onScrollChanged(session: GeckoSession, scrollX: Int, scrollY: Int) {
}
}
interface TrackingProtectionDelegate : GeckoSession.TrackingProtectionDelegate {
override fun onTrackerBlocked(session: GeckoSession, uri: String, categories: Int) {
}
}
}