Bug 1553515 - Add GeckoResult.accept. r=snorp

Differential Revision: https://phabricator.services.mozilla.com/D32582

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Agi Sferro 2019-07-08 20:48:35 +00:00
parent f3c69e1f87
commit c54dc66a26
9 changed files with 189 additions and 287 deletions

View File

@ -239,6 +239,8 @@ package org.mozilla.geckoview {
ctor public GeckoResult();
ctor public GeckoResult(Handler);
ctor public GeckoResult(GeckoResult<T>);
method @NonNull public GeckoResult<Void> accept(@Nullable GeckoResult.Consumer<T>);
method @NonNull public GeckoResult<Void> accept(@Nullable GeckoResult.Consumer<T>, @Nullable GeckoResult.Consumer<Throwable>);
method public synchronized void complete(@Nullable T);
method public synchronized void completeExceptionally(@NonNull Throwable);
method @NonNull public <U> GeckoResult<U> exceptionally(@NonNull GeckoResult.OnExceptionListener<U>);
@ -254,6 +256,10 @@ package org.mozilla.geckoview {
field public static final GeckoResult<AllowOrDeny> DENY;
}
public static interface GeckoResult.Consumer<T> {
method @AnyThread public void accept(@Nullable T);
}
public static interface GeckoResult.OnExceptionListener<V> {
method @AnyThread @Nullable public GeckoResult<V> onException(@NonNull Throwable);
}

View File

@ -57,13 +57,9 @@ public class GeckoResultTest {
@Test
@UiThreadTest
public void thenWithResult() {
GeckoResult.fromValue(42).then(new OnValueListener<Integer, Void>() {
@Override
public GeckoResult<Void> onValue(Integer value) {
assertThat("Value should match", value, equalTo(42));
done();
return null;
}
GeckoResult.fromValue(42).accept(value -> {
assertThat("Value should match", value, equalTo(42));
done();
});
waitUntilDone();
@ -73,13 +69,9 @@ public class GeckoResultTest {
@UiThreadTest
public void thenWithException() {
final Throwable boom = new Exception("boom");
GeckoResult.fromException(boom).then(null, new OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable error) {
assertThat("Exception should match", error, equalTo(boom));
done();
return null;
}
GeckoResult.fromException(boom).accept(null, error -> {
assertThat("Exception should match", error, equalTo(boom));
done();
});
waitUntilDone();
@ -95,13 +87,9 @@ public class GeckoResultTest {
@UiThreadTest
public void testCopy() {
final GeckoResult<Integer> result = new GeckoResult<>(GeckoResult.fromValue(42));
result.then(new OnValueListener<Integer, Void>() {
@Override
public GeckoResult<Void> onValue(Integer value) throws Throwable {
assertThat("Value should match", value, equalTo(42));
done();
return null;
}
result.accept(value -> {
assertThat("Value should match", value, equalTo(42));
done();
});
waitUntilDone();
@ -110,7 +98,7 @@ public class GeckoResultTest {
@Test(expected = IllegalStateException.class)
@UiThreadTest
public void completeMultiple() {
final GeckoResult<Integer> deferred = new GeckoResult<Integer>();
final GeckoResult<Integer> deferred = new GeckoResult<>();
deferred.complete(42);
deferred.complete(43);
}
@ -118,7 +106,7 @@ public class GeckoResultTest {
@Test(expected = IllegalStateException.class)
@UiThreadTest
public void completeMultipleExceptions() {
final GeckoResult<Integer> deferred = new GeckoResult<Integer>();
final GeckoResult<Integer> deferred = new GeckoResult<>();
deferred.completeExceptionally(new Exception("boom"));
deferred.completeExceptionally(new Exception("boom again"));
}
@ -126,7 +114,7 @@ public class GeckoResultTest {
@Test(expected = IllegalStateException.class)
@UiThreadTest
public void completeMixed() {
final GeckoResult<Integer> deferred = new GeckoResult<Integer>();
final GeckoResult<Integer> deferred = new GeckoResult<>();
deferred.complete(42);
deferred.completeExceptionally(new Exception("boom again"));
}
@ -141,22 +129,12 @@ public class GeckoResultTest {
@UiThreadTest
public void completeThreaded() {
final GeckoResult<Integer> deferred = new GeckoResult<>();
final Thread thread = new Thread() {
@Override
public void run() {
deferred.complete(42);
}
};
final Thread thread = new Thread(() -> deferred.complete(42));
deferred.then(new OnValueListener<Integer, Void>() {
@Override
public GeckoResult<Void> onValue(Integer value) {
assertThat("Value should match", value, equalTo(42));
assertThat("Thread should match", Thread.currentThread(),
equalTo(Looper.getMainLooper().getThread()));
done();
return null;
}
deferred.accept(value -> {
assertThat("Value should match", value, equalTo(42));
ThreadUtils.assertOnUiThread();
done();
});
thread.start();
@ -166,24 +144,17 @@ public class GeckoResultTest {
@Test
@UiThreadTest
public void dispatchOnInitialThread() throws InterruptedException {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
final Thread dispatchThread = Thread.currentThread();
final Thread thread = new Thread(() -> {
Looper.prepare();
final Thread dispatchThread = Thread.currentThread();
GeckoResult.fromValue(42).then(new OnValueListener<Integer, Void>() {
@Override
public GeckoResult<Void> onValue(Integer value) throws Throwable {
assertThat("Thread should match", Thread.currentThread(),
equalTo(dispatchThread));
Looper.myLooper().quit();
return null;
}
});
GeckoResult.fromValue(42).accept(value -> {
assertThat("Thread should match", Thread.currentThread(),
equalTo(dispatchThread));
Looper.myLooper().quit();
});
Looper.loop();
}
Looper.loop();
});
thread.start();
@ -195,22 +166,13 @@ public class GeckoResultTest {
public void completeExceptionallyThreaded() {
final GeckoResult<Integer> deferred = new GeckoResult<>();
final Throwable boom = new Exception("boom");
final Thread thread = new Thread() {
@Override
public void run() {
deferred.completeExceptionally(boom);
}
};
final Thread thread = new Thread(() -> deferred.completeExceptionally(boom));
deferred.exceptionally(new OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable error) {
assertThat("Exception should match", error, equalTo(boom));
assertThat("Thread should match", Thread.currentThread(),
equalTo(Looper.getMainLooper().getThread()));
done();
return null;
}
deferred.exceptionally(error -> {
assertThat("Exception should match", error, equalTo(boom));
ThreadUtils.assertOnUiThread();
done();
return null;
});
thread.start();
@ -234,10 +196,9 @@ public class GeckoResultTest {
}).exceptionally(error -> {
assertThat("Error message should match", error.getMessage(), equalTo("boom"));
throw new MockException();
}).exceptionally(exception -> {
}).accept(null, exception -> {
assertThat("Exception should be MockException", exception, instanceOf(MockException.class));
done();
return null;
});
waitUntilDone();
@ -248,18 +209,10 @@ public class GeckoResultTest {
public void then_propagatedValue() {
// The first GeckoResult only has an exception listener, so when the value 42 is
// propagated to subsequent GeckoResult instances, the propagated value is coerced to null.
GeckoResult.fromValue(42).exceptionally(new OnExceptionListener<String>() {
@Override
public GeckoResult<String> onException(Throwable exception) throws Throwable {
return null;
}
}).then(new OnValueListener<String, Void>() {
@Override
public GeckoResult<Void> onValue(String value) throws Throwable {
assertThat("Propagated value is null", value, nullValue());
done();
return null;
}
GeckoResult.fromValue(42).exceptionally(error -> null)
.accept(value -> {
assertThat("Propagated value is null", value, nullValue());
done();
});
waitUntilDone();
@ -268,11 +221,8 @@ public class GeckoResultTest {
@UiThreadTest
@Test(expected = GeckoResult.UncaughtException.class)
public void then_uncaughtException() {
GeckoResult.fromValue(42).then(new OnValueListener<Integer, String>() {
@Override
public GeckoResult<String> onValue(Integer value) {
throw new MockException();
}
GeckoResult.fromValue(42).then(value -> {
throw new MockException();
});
waitUntilDone();
@ -281,17 +231,9 @@ public class GeckoResultTest {
@UiThreadTest
@Test(expected = GeckoResult.UncaughtException.class)
public void then_propagatedUncaughtException() {
GeckoResult.fromValue(42).then(new OnValueListener<Integer, String>() {
@Override
public GeckoResult<String> onValue(Integer value) {
throw new MockException();
}
}).then(new OnValueListener<String, Void>() {
@Override
public GeckoResult<Void> onValue(String value) throws Throwable {
return null;
}
});
GeckoResult.fromValue(42).then(value -> {
throw new MockException();
}).accept(value -> {});
waitUntilDone();
}
@ -299,25 +241,14 @@ public class GeckoResultTest {
@UiThreadTest
@Test
public void then_caughtException() {
GeckoResult.fromValue(42).then(new OnValueListener<Integer, String>() {
@Override
public GeckoResult<String> onValue(Integer value) throws Exception {
throw new MockException();
}
}).then(new OnValueListener<String, Void>() {
@Override
public GeckoResult<Void> onValue(String value) throws Throwable {
return null;
}
}).exceptionally(new OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable exception) throws Throwable {
GeckoResult.fromValue(42).then(value -> { throw new MockException(); })
.accept(value -> {})
.exceptionally(exception -> {
assertThat("Exception should be expected",
exception, instanceOf(MockException.class));
done();
return null;
}
});
});
waitUntilDone();
}
@ -357,11 +288,10 @@ public class GeckoResultTest {
assertThat("We shouldn't have a Looper", result.getLooper(), nullValue());
try {
result.withHandler(queue.take()).then(value -> {
result.withHandler(queue.take()).accept(value -> {
assertThat("Thread should match", Thread.currentThread(), equalTo(thread));
assertThat("Value should match", value, equalTo(42));
Looper.myLooper().quit();
return null;
});
thread.join();

View File

@ -1156,25 +1156,17 @@ public class GeckoSessionTestRule implements TestRule {
@SuppressWarnings("unchecked")
final GeckoResult<GeckoSession> result = (GeckoResult<GeckoSession>)returnValue;
final GeckoResult<GeckoSession> tmpResult = new GeckoResult<>();
result.then(new OnValueListener<GeckoSession, Void>() {
@Override
public GeckoResult<Void> onValue(final GeckoSession newSession) throws Throwable {
tmpResult.complete(newSession);
result.accept(session -> {
tmpResult.complete(session);
// GeckoSession has already hooked up its then() listener earlier,
// so ours will run after. We can wait for the session to
// open here.
tmpResult.then(new OnValueListener<GeckoSession, Void>() {
@Override
public GeckoResult<Void> onValue(GeckoSession newSession) throws Throwable {
if (oldSession.isOpen() && newSession != null) {
GeckoSessionTestRule.this.waitForOpenSession(newSession);
}
return null;
}
});
return null;
}
// GeckoSession has already hooked up its then() listener earlier,
// so ours will run after. We can wait for the session to
// open here.
tmpResult.accept(newSession -> {
if (oldSession.isOpen() && newSession != null) {
GeckoSessionTestRule.this.waitForOpenSession(newSession);
}
});
});
return tmpResult;

View File

@ -108,20 +108,12 @@ public class UiThreadUtils {
public boolean isComplete;
public ResultHolder(GeckoResult<T> result) {
result.then(new GeckoResult.OnValueListener<T, Void>() {
@Override
public GeckoResult<Void> onValue(T value) {
ResultHolder.this.value = value;
isComplete = true;
return null;
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable error) {
ResultHolder.this.error = error;
isComplete = true;
return null;
}
result.accept(value -> {
ResultHolder.this.value = value;
isComplete = true;
}, error -> {
ResultHolder.this.error = error;
isComplete = true;
});
}
}

View File

@ -12,6 +12,7 @@ import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
/**
* GeckoResult is a class that represents an asynchronous result. The result is initially pending,
@ -294,6 +295,64 @@ public class GeckoResult<T> {
return then(null, exceptionListener);
}
/**
* Replacement for {@link java.util.function.Consumer} for devices with minApi &lt; 24.
*
* @param <T> the type of the input for this consumer.
*/
// TODO: Remove this when we move to min API 24
public interface Consumer<T> {
/**
* Run this consumer for the given input.
*
* @param t the input value.
*/
@AnyThread
void accept(@Nullable T t);
}
/**
* Convenience method for {@link #accept(Consumer, Consumer)}.
*
* @param valueListener An instance of {@link Consumer}, called when the
* {@link GeckoResult} is completed with a value.
* @return A new {@link GeckoResult} that the listeners will complete.
*/
public @NonNull GeckoResult<Void> accept(@Nullable final Consumer<T> valueListener) {
return accept(valueListener, null);
}
/**
* Adds listeners to be called when the {@link GeckoResult} is completed either with
* a value or {@link Throwable}. Listeners will be invoked on the {@link Looper} returned from
* {@link #getLooper()}. If null, this method will throw {@link IllegalThreadStateException}.
*
* If the result is already complete when this method is called, listeners will be invoked in
* a future {@link Looper} iteration.
*
* @param valueConsumer An instance of {@link Consumer}, called when the
* {@link GeckoResult} is completed with a value.
* @param exceptionConsumer An instance of {@link Consumer}, called when the
* {@link GeckoResult} is completed with an {@link Throwable}.
* @return A new {@link GeckoResult} that the listeners will complete.
*/
public @NonNull GeckoResult<Void> accept(@Nullable final Consumer<T> valueConsumer,
@Nullable final Consumer<Throwable> exceptionConsumer) {
final OnValueListener<T, Void> valueListener = valueConsumer == null ? null :
value -> {
valueConsumer.accept(value);
return null;
};
final OnExceptionListener<Void> exceptionListener = exceptionConsumer == null ? null :
value -> {
exceptionConsumer.accept(value);
return null;
};
return then(valueListener, exceptionListener);
}
/**
* Adds listeners to be called when the {@link GeckoResult} is completed either with
* a value or {@link Throwable}. Listeners will be invoked on the {@link Looper} returned from

View File

@ -296,20 +296,9 @@ public class GeckoSession implements Parcelable {
return;
}
result.then(new GeckoResult.OnValueListener<Boolean, Void>() {
@Override
public GeckoResult<Void> onValue(final Boolean visited) throws Throwable {
callback.sendSuccess(visited.booleanValue());
return null;
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final Throwable exception)
throws Throwable {
callback.sendSuccess(false);
return null;
}
});
result.accept(
visited -> callback.sendSuccess(visited.booleanValue()),
exception -> callback.sendSuccess(false));
} else if ("GeckoView:GetVisited".equals(event)) {
final String[] urls = message.getStringArray("urls");
@ -321,20 +310,9 @@ public class GeckoSession implements Parcelable {
return;
}
result.then(new GeckoResult.OnValueListener<boolean[], Void>() {
@Override
public GeckoResult<Void> onValue(final boolean[] visited) throws Throwable {
callback.sendSuccess(visited);
return null;
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final Throwable exception)
throws Throwable {
callback.sendError("Failed to fetch visited statuses for URIs");
return null;
}
});
result.accept(
visited -> callback.sendSuccess(visited),
exception -> callback.sendError("Failed to fetch visited statuses for URIs"));
}
}
};
@ -548,27 +526,16 @@ public class GeckoSession implements Parcelable {
return;
}
result.then(new GeckoResult.OnValueListener<AllowOrDeny, Void>() {
@Override
public GeckoResult<Void> onValue(final AllowOrDeny value) throws Throwable {
ThreadUtils.assertOnUiThread();
if (value == AllowOrDeny.ALLOW) {
callback.sendSuccess(false);
} else if (value == AllowOrDeny.DENY) {
callback.sendSuccess(true);
} else {
callback.sendError("Invalid response");
}
return null;
result.accept(value -> {
ThreadUtils.assertOnUiThread();
if (value == AllowOrDeny.ALLOW) {
callback.sendSuccess(false);
} else if (value == AllowOrDeny.DENY) {
callback.sendSuccess(true);
} else {
callback.sendError("Invalid response");
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final Throwable exception)
throws Throwable {
callback.sendError(exception.getMessage());
return null;
}
});
}, exception -> callback.sendError(exception.getMessage()));
} else if ("GeckoView:OnLoadError".equals(event)) {
final String uri = message.getString("uri");
final long errorCode = message.getLong("error");
@ -587,28 +554,17 @@ public class GeckoSession implements Parcelable {
return;
}
result.then(new GeckoResult.OnValueListener<String, Void>() {
@Override
public GeckoResult<Void> onValue(final @Nullable String url)
throws Throwable {
if (url == null) {
if (GeckoAppShell.isFennec()) {
callback.sendSuccess(null);
} else {
callback.sendError("abort");
}
result.accept(url -> {
if (url == null) {
if (GeckoAppShell.isFennec()) {
callback.sendSuccess(null);
} else {
callback.sendSuccess(url);
callback.sendError("abort");
}
return null;
} else {
callback.sendSuccess(url);
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final @NonNull Throwable exception) throws Throwable {
callback.sendError(exception.getMessage());
return null;
}
});
}, exception -> callback.sendError(exception.getMessage()));
} else if ("GeckoView:OnNewSession".equals(event)) {
final String uri = message.getString("uri");
final GeckoResult<GeckoSession> result = delegate.onNewSession(GeckoSession.this, uri);
@ -617,37 +573,24 @@ public class GeckoSession implements Parcelable {
return;
}
result.then(new GeckoResult.OnValueListener<GeckoSession, Void>() {
@Override
public GeckoResult<Void> onValue(final GeckoSession session)
throws Throwable {
ThreadUtils.assertOnUiThread();
if (session == null) {
callback.sendSuccess(null);
return null;
}
if (session.isOpen()) {
throw new IllegalArgumentException("Must use an unopened GeckoSession instance");
}
if (GeckoSession.this.mWindow == null) {
callback.sendError("Session is not attached to a window");
} else {
session.open(GeckoSession.this.mWindow.runtime);
callback.sendSuccess(session.getId());
}
return null;
result.accept(session -> {
ThreadUtils.assertOnUiThread();
if (session == null) {
callback.sendSuccess(null);
return;
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final Throwable exception)
throws Throwable {
callback.sendError(exception.getMessage());
return null;
if (session.isOpen()) {
throw new IllegalArgumentException("Must use an unopened GeckoSession instance");
}
});
if (GeckoSession.this.mWindow == null) {
callback.sendError("Session is not attached to a window");
} else {
session.open(GeckoSession.this.mWindow.runtime);
callback.sendSuccess(session.getId());
}
}, exception -> callback.sendError(exception.getMessage()));
}
}
};
@ -2756,26 +2699,16 @@ public class GeckoSession implements Parcelable {
return;
}
res.then(new GeckoResult.OnValueListener<AllowOrDeny, Void>() {
@Override
public GeckoResult<Void> onValue(final AllowOrDeny value) throws Throwable {
if (value == AllowOrDeny.ALLOW) {
callback.sendSuccess(true);
} else if (value == AllowOrDeny.DENY) {
callback.sendSuccess(false);
} else {
callback.sendError("Invalid response");
}
return null;
res.accept(value -> {
if (value == AllowOrDeny.ALLOW) {
callback.sendSuccess(true);
} else if (value == AllowOrDeny.DENY) {
callback.sendSuccess(false);
} else {
callback.sendError("Invalid response");
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final Throwable exception)
throws Throwable {
callback.sendError("Failed to get popup-blocking decision");
return null;
}
});
}, exception ->
callback.sendError("Failed to get popup-blocking decision"));
break;
}
default: {

View File

@ -221,15 +221,9 @@ import java.util.Map;
return;
}
response.then(
value -> {
callback.sendSuccess(value);
return null;
},
exception -> {
callback.sendError(exception);
return null;
});
response.accept(
value -> callback.sendSuccess(value),
exception -> callback.sendError(exception));
}
public void handleMessage(final String event, final GeckoBundle message,

View File

@ -15,6 +15,11 @@ exclude: true
[69.1]: ./GeckoRuntimeSettings.html#setAutomaticFontSizeAdjustment-boolean-
[69.2]: ./GeckoRuntimeSettings.html#setFontInflationEnabled-boolean-
- Added [`GeckoResult.accept`][69.3] for consuming a result without
transforming it.
[69.3]: ../GeckoResult.html#accept-org.mozilla.geckoview.GeckoResult.Consumer-org.mozilla.geckoview.GeckoResult.Consumer-
## v68
- Added [`GeckoRuntime#configurationChanged`][68.1] to notify the device
configuration has changed.
@ -327,4 +332,4 @@ exclude: true
[65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: ../GeckoResult.html
[api-version]: 29ff764a2ca4aaa16dbe79a269d6c1c8a166775e
[api-version]: 783f253fda7287f55497c15f867dc14cd1622666

View File

@ -449,19 +449,10 @@ public class GeckoViewActivity extends AppCompatActivity {
private void downloadFile(GeckoSession.WebResponseInfo response) {
mTabSessionManager.getCurrentSession()
.getUserAgent()
.then(new GeckoResult.OnValueListener<String, Void>() {
@Override
public GeckoResult<Void> onValue(String userAgent) throws Throwable {
downloadFile(response, userAgent);
return null;
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable exception) throws Throwable {
// getUserAgent() cannot fail.
throw new IllegalStateException("Could not get UserAgent string.");
}
});
.accept(userAgent -> downloadFile(response, userAgent),
exception -> {
throw new IllegalStateException("Could not get UserAgent string.");
});
}
private void downloadFile(GeckoSession.WebResponseInfo response, String userAgent) {