Backed out 13 changesets (bug 1546297, bug 1553515) for increasing gv-junit intermittent failures on a CLOSED TREE

Backed out changeset e291257145ad (bug 1546297)
Backed out changeset 080d770c7b35 (bug 1553515)
Backed out changeset bbc184402f36 (bug 1553515)
Backed out changeset 8fe9f5d7fef9 (bug 1553515)
Backed out changeset faa333bb64e2 (bug 1553515)
Backed out changeset 98cf5b0cadf1 (bug 1553515)
Backed out changeset dbbb8fe1ecfb (bug 1553515)
Backed out changeset 84414ac99629 (bug 1553515)
Backed out changeset d3734e6fe22a (bug 1553515)
Backed out changeset 9e9dc0e7afb3 (bug 1553515)
Backed out changeset 3d1a0d412dfd (bug 1553515)
Backed out changeset 2b1f78e255ce (bug 1553515)
Backed out changeset 0c29929cc67c (bug 1553515)
This commit is contained in:
Andreea Pavel 2019-06-28 00:11:50 +03:00
Родитель e9ed963838
Коммит 9f0c287f44
29 изменённых файлов: 689 добавлений и 788 удалений

Просмотреть файл

@ -239,8 +239,6 @@ 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>);
@ -256,10 +254,6 @@ 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);
}

Просмотреть файл

@ -2,13 +2,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mozilla.geckoview.test">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application
android:allowBackup="true"

Просмотреть файл

@ -5,34 +5,31 @@
package org.mozilla.geckoview.test
import android.app.assist.AssistStructure
import android.graphics.Matrix
import android.graphics.SurfaceTexture
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.LocaleList
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
import org.mozilla.geckoview.test.util.Callbacks
import org.mozilla.geckoview.test.util.UiThreadUtils
import android.os.Looper
import android.support.test.InstrumentationRegistry
import android.support.test.filters.MediumTest
import android.support.test.filters.SdkSuppress
import android.support.test.runner.AndroidJUnit4
import android.text.InputType
import android.util.Pair
import android.util.SparseArray
import android.view.Surface
import android.view.View
import android.view.ViewStructure
import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue
import android.widget.EditText
import org.hamcrest.Matchers.*
import org.json.JSONObject
@ -43,6 +40,8 @@ import org.mozilla.geckoview.test.util.HttpBin
import java.net.URI
import kotlin.concurrent.thread
@RunWith(AndroidJUnit4::class)
@MediumTest
class ContentDelegateTest : BaseSessionTest() {
@ -92,6 +91,7 @@ class ContentDelegateTest : BaseSessionTest() {
}
@IgnoreCrash
@ReuseSession(false)
@Test fun crashContent() {
// This test doesn't make sense without multiprocess
assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
@ -121,6 +121,7 @@ class ContentDelegateTest : BaseSessionTest() {
}
@IgnoreCrash
@ReuseSession(false)
@WithDisplay(width = 10, height = 10)
@Test fun crashContent_tapAfterCrash() {
// This test doesn't make sense without multiprocess
@ -147,6 +148,7 @@ class ContentDelegateTest : BaseSessionTest() {
}
@IgnoreCrash
@ReuseSession(false)
@Test fun crashContentMultipleSessions() {
// This test doesn't make sense without multiprocess
assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
@ -159,9 +161,6 @@ class ContentDelegateTest : BaseSessionTest() {
// If we add multiple content processes, this test will need to be fixed to ensure the
// test sessions go into the same one.
val newSession = sessionRule.createOpenSession()
newSession.loadTestPath(HELLO_HTML_PATH)
newSession.waitForPageStop()
mainSession.loadUri(CONTENT_CRASH_URL)
// We can inadvertently catch the `onCrash` call for the cached session if we don't specify
@ -178,6 +177,18 @@ class ContentDelegateTest : BaseSessionTest() {
}
}
val ViewNode by lazy {
AssistStructure.ViewNode::class.java.getDeclaredConstructor().apply { isAccessible = true }
}
val ViewNodeBuilder by lazy {
Class.forName("android.app.assist.AssistStructure\$ViewNodeBuilder")
.getDeclaredConstructor(AssistStructure::class.java,
AssistStructure.ViewNode::class.java,
Boolean::class.javaPrimitiveType)
.apply { isAccessible = true }
}
// TextInputDelegateTest is parameterized, so we put this test under ContentDelegateTest.
@SdkSuppress(minSdkVersion = 23)
@WithDevToolsAPI
@ -214,29 +225,34 @@ class ContentDelegateTest : BaseSessionTest() {
event instanceof InputEvent ? "InputEvent" :
event instanceof UIEvent ? "UIEvent" :
event instanceof Event ? "Event" : "Unknown";
resolve(['${entry.key}', event.target.value, '${entry.value}', eventInterface]);
resolve([event.target.value, '${entry.value}', eventInterface]);
}, { once: true }))""").asJSPromise()
}
}
val rootNode = ViewNode.newInstance()
val rootStructure = ViewNodeBuilder.newInstance(AssistStructure(), rootNode,
/* async */ false) as ViewStructure
val autoFillValues = SparseArray<CharSequence>()
// Perform auto-fill and return number of auto-fills performed.
fun checkAutoFillChild(child: MockViewNode) {
fun checkAutoFillChild(child: AssistStructure.ViewNode) {
// Seal the node info instance so we can perform actions on it.
if (child.childCount > 0) {
for (c in child.children) {
checkAutoFillChild(c!!)
for (i in 0 until child.childCount) {
checkAutoFillChild(child.getChildAt(i))
}
}
if (child.id == View.NO_ID) {
if (child === rootNode) {
return
}
assertThat("ID should be valid", child.id, not(equalTo(View.NO_ID)))
if (Build.VERSION.SDK_INT >= 26) {
assertThat("Should have HTML tag",
child.htmlInfo!!.tag, not(isEmptyOrNullString()))
child.htmlInfo.tag, not(isEmptyOrNullString()))
assertThat("Web domain should match",
child.webDomain, equalTo("android"))
}
@ -254,7 +270,7 @@ class ContentDelegateTest : BaseSessionTest() {
}
val htmlInfo = child.htmlInfo
assertThat("Should have HTML tag", htmlInfo!!.tag, equalTo("input"))
assertThat("Should have HTML tag", htmlInfo.tag, equalTo("input"))
assertThat("Should have ID attribute",
htmlInfo.attributes.map { it.first }, hasItem("id"))
@ -286,23 +302,14 @@ class ContentDelegateTest : BaseSessionTest() {
}
}
val rootStructure = MockViewNode()
mainSession.textInput.onProvideAutofillVirtualStructure(rootStructure, 0)
checkAutoFillChild(rootStructure)
checkAutoFillChild(rootNode)
mainSession.textInput.autofill(autoFillValues)
// Wait on the promises and check for correct values.
for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
for ((actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
assertThat("Auto-filled value must match", actual, equalTo(expected))
// <input type=number> elements don't get InputEvent events.
if (key == "#number1") {
assertThat("input type=number event should be dispatched with Event interface", eventInterface, equalTo("Event"))
} else {
assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
}
assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
}
}
@ -311,16 +318,19 @@ class ContentDelegateTest : BaseSessionTest() {
@WithDevToolsAPI
@WithDisplay(width = 100, height = 100)
@Test fun autoFill_navigation() {
fun countAutoFillNodes(cond: (MockViewNode) -> Boolean =
{ it.className == "android.widget.EditText" },
root: MockViewNode? = null): Int {
val node = if (root !== null) root else MockViewNode().also {
mainSession.textInput.onProvideAutofillVirtualStructure(it, 0)
}
fun countAutoFillNodes(cond: (AssistStructure.ViewNode) -> Boolean =
{ it.className == "android.widget.EditText" },
root: AssistStructure.ViewNode? = null): Int {
val node = if (root !== null) root else ViewNode.newInstance().also {
// Fill the nodes first.
val structure = ViewNodeBuilder.newInstance(
AssistStructure(), it, /* async */ false) as ViewStructure
mainSession.textInput.onProvideAutofillVirtualStructure(structure, 0)
}
return (if (cond(node)) 1 else 0) +
(if (node.childCount > 0) node.children.sumBy {
countAutoFillNodes(cond, it) } else 0)
(if (node.childCount > 0) (0 until node.childCount).sumBy {
countAutoFillNodes(cond, node.getChildAt(it)) } else 0)
}
// Wait for the accessibility nodes to populate.
@ -409,35 +419,39 @@ class ContentDelegateTest : BaseSessionTest() {
mainSession.loadTestPath(FORMS2_HTML_PATH)
// Wait for the auto-fill nodes to populate.
sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
@AssertCalled(count = 1)
@AssertCalled(count = 2)
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
}
})
val rootNode = ViewNode.newInstance()
val rootStructure = ViewNodeBuilder.newInstance(AssistStructure(), rootNode,
/* async */ false) as ViewStructure
// Perform auto-fill and return number of auto-fills performed.
fun checkAutoFillChild(child: MockViewNode): Int {
fun checkAutoFillChild(child: AssistStructure.ViewNode): Int {
var sum = 0
// Seal the node info instance so we can perform actions on it.
if (child.children.size > 0) {
for (c in child.children) {
sum += checkAutoFillChild(c!!)
if (child.childCount > 0) {
for (i in 0 until child.childCount) {
sum += checkAutoFillChild(child.getChildAt(i))
}
}
if (child.id == View.NO_ID) {
if (child === rootNode) {
return sum
}
assertThat("ID should be valid", child.id, not(equalTo(View.NO_ID)))
if (EditText::class.java.name == child.className) {
val htmlInfo = child.htmlInfo!!
val htmlInfo = child.htmlInfo
assertThat("Should have HTML tag", htmlInfo.tag, equalTo("input"))
if (child.autofillHints == null) {
return sum
}
child.autofillHints!!.forEach {
child.autofillHints.forEach {
when (it) {
View.AUTOFILL_HINT_USERNAME, View.AUTOFILL_HINT_PASSWORD -> {
sum++
@ -448,274 +462,10 @@ class ContentDelegateTest : BaseSessionTest() {
return sum
}
val rootStructure = MockViewNode()
mainSession.textInput.onProvideAutofillVirtualStructure(rootStructure, 0)
// form and iframe have each 2 hints.
assertThat("autofill hint count",
checkAutoFillChild(rootStructure), equalTo(4))
}
class MockViewNode : ViewStructure() {
private var mClassName: String? = null
private var mEnabled = false
private var mVisibility = -1
private var mPackageName: String? = null
private var mTypeName: String? = null
private var mEntryName: String? = null
private var mAutofillType = -1
private var mAutofillHints: Array<String>? = null
private var mInputType = -1
private var mHtmlInfo: HtmlInfo? = null
private var mWebDomain: String? = null
private var mFocused = false
private var mFocusable = false
var children = ArrayList<MockViewNode?>()
var id = View.NO_ID
var height = 0
var width = 0
val className get() = mClassName
val htmlInfo get() = mHtmlInfo
val autofillHints get() = mAutofillHints
val autofillType get() = mAutofillType
val webDomain get() = mWebDomain
val isEnabled get() = mEnabled
val isFocused get() = mFocused
val isFocusable get() = mFocusable
val visibility get() = mVisibility
val inputType get() = mInputType
override fun setId(id: Int, packageName: String?, typeName: String?, entryName: String?) {
this.id = id
mPackageName = packageName
mTypeName = typeName
mEntryName = entryName
}
override fun setHint(hint: CharSequence?) {
TODO("not implemented")
}
override fun setElevation(elevation: Float) {
TODO("not implemented")
}
override fun getText(): CharSequence {
TODO("not implemented")
}
override fun setText(text: CharSequence?) {
TODO("not implemented")
}
override fun setText(text: CharSequence?, selectionStart: Int, selectionEnd: Int) {
TODO("not implemented")
}
override fun asyncCommit() {
TODO("not implemented")
}
override fun getChildCount(): Int = children.size
override fun setEnabled(state: Boolean) {
mEnabled = state
}
override fun setLocaleList(localeList: LocaleList?) {
TODO("not implemented")
}
override fun setDimens(left: Int, top: Int, scrollX: Int, scrollY: Int, width: Int, height: Int) {
this.width = width
this.height = height
}
override fun setChecked(state: Boolean) {
TODO("not implemented")
}
override fun setContextClickable(state: Boolean) {
TODO("not implemented")
}
override fun setAccessibilityFocused(state: Boolean) {
TODO("not implemented")
}
override fun setAlpha(alpha: Float) {
TODO("not implemented")
}
override fun setTransformation(matrix: Matrix?) {
TODO("not implemented")
}
override fun setClassName(className: String?) {
mClassName = className
}
override fun setLongClickable(state: Boolean) {
TODO("not implemented")
}
override fun newChild(index: Int): ViewStructure {
val child = MockViewNode()
children[index] = child
return child
}
override fun getHint(): CharSequence {
TODO("not implemented")
}
override fun setInputType(inputType: Int) {
mInputType = inputType
}
override fun setWebDomain(domain: String?) {
mWebDomain = domain
}
override fun setAutofillOptions(options: Array<out CharSequence>?) {
TODO("not implemented")
}
override fun setTextStyle(size: Float, fgColor: Int, bgColor: Int, style: Int) {
TODO("not implemented")
}
override fun setVisibility(visibility: Int) {
mVisibility = visibility
}
override fun getAutofillId(): AutofillId? {
TODO("not implemented")
}
override fun setHtmlInfo(htmlInfo: HtmlInfo) {
mHtmlInfo = htmlInfo
}
override fun setTextLines(charOffsets: IntArray?, baselines: IntArray?) {
TODO("not implemented")
}
override fun getExtras(): Bundle {
TODO("not implemented")
}
override fun setClickable(state: Boolean) {
TODO("not implemented")
}
override fun newHtmlInfoBuilder(tagName: String): HtmlInfo.Builder {
return MockHtmlInfoBuilder(tagName)
}
override fun getTextSelectionEnd(): Int {
TODO("not implemented")
}
override fun setAutofillId(id: AutofillId) {
TODO("not implemented")
}
override fun setAutofillId(parentId: AutofillId, virtualId: Int) {
TODO("not implemented")
}
override fun hasExtras(): Boolean {
TODO("not implemented")
}
override fun addChildCount(num: Int): Int {
TODO("not implemented")
}
override fun setAutofillType(type: Int) {
mAutofillType = type
}
override fun setActivated(state: Boolean) {
TODO("not implemented")
}
override fun setFocused(state: Boolean) {
mFocused = state
}
override fun getTextSelectionStart(): Int {
TODO("not implemented")
}
override fun setChildCount(num: Int) {
children = ArrayList()
for (i in 0 until num) {
children.add(null)
}
}
override fun setAutofillValue(value: AutofillValue?) {
TODO("not implemented")
}
override fun setAutofillHints(hint: Array<String>?) {
mAutofillHints = hint
}
override fun setContentDescription(contentDescription: CharSequence?) {
TODO("not implemented")
}
override fun setFocusable(state: Boolean) {
mFocusable = state
}
override fun setCheckable(state: Boolean) {
TODO("not implemented")
}
override fun asyncNewChild(index: Int): ViewStructure {
TODO("not implemented")
}
override fun setSelected(state: Boolean) {
TODO("not implemented")
}
override fun setDataIsSensitive(sensitive: Boolean) {
TODO("not implemented")
}
override fun setOpaque(opaque: Boolean) {
TODO("not implemented")
}
}
class MockHtmlInfoBuilder(tagName: String) : ViewStructure.HtmlInfo.Builder() {
val mTagName = tagName
val mAttributes: MutableList<Pair<String, String>> = mutableListOf()
override fun addAttribute(name: String, value: String): ViewStructure.HtmlInfo.Builder {
mAttributes.add(Pair(name, value))
return this
}
override fun build(): ViewStructure.HtmlInfo {
return MockHtmlInfo(mTagName, mAttributes)
}
}
class MockHtmlInfo(tagName: String, attributes: MutableList<Pair<String, String>>)
: ViewStructure.HtmlInfo() {
private val mTagName = tagName
private val mAttributes = attributes
override fun getTag() = mTagName
override fun getAttributes() = mAttributes
checkAutoFillChild(rootNode), equalTo(4))
}
private fun goFullscreen() {

Просмотреть файл

@ -5,6 +5,7 @@
package org.mozilla.geckoview.test
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import android.support.test.filters.MediumTest
@ -17,6 +18,7 @@ import org.junit.runner.RunWith
@MediumTest
class FinderTest : BaseSessionTest() {
@ReuseSession(false) // "wrapped" in the first result depends on a fresh session.
@Test fun find() {
mainSession.loadTestPath(LOREM_IPSUM_HTML_PATH)
mainSession.waitForPageStop()

Просмотреть файл

@ -1,8 +1,8 @@
package org.mozilla.geckoview.test;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.test.util.Environment;
import org.mozilla.geckoview.GeckoResult.OnExceptionListener;
import org.mozilla.geckoview.GeckoResult.OnValueListener;
import org.mozilla.geckoview.test.util.UiThreadUtils;
import android.os.Handler;
@ -24,20 +24,28 @@ import static org.junit.Assert.assertThat;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class GeckoResultTest {
private static final long DEFAULT_TIMEOUT = 5000;
private static class MockException extends RuntimeException {
}
private boolean mDone;
private final Environment mEnv = new Environment();
private void waitUntilDone() {
assertThat("We should not be done", mDone, equalTo(false));
UiThreadUtils.waitForCondition(() -> mDone, mEnv.getDefaultTimeoutMillis());
while (!mDone) {
UiThreadUtils.loopUntilIdle(DEFAULT_TIMEOUT);
}
}
private void done() {
UiThreadUtils.HANDLER.post(() -> mDone = true);
UiThreadUtils.HANDLER.post(new Runnable() {
@Override
public void run() {
mDone = true;
}
});
}
@Before
@ -48,9 +56,13 @@ public class GeckoResultTest {
@Test
@UiThreadTest
public void thenWithResult() {
GeckoResult.fromValue(42).accept(value -> {
assertThat("Value should match", value, equalTo(42));
done();
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;
}
});
waitUntilDone();
@ -60,9 +72,13 @@ public class GeckoResultTest {
@UiThreadTest
public void thenWithException() {
final Throwable boom = new Exception("boom");
GeckoResult.fromException(boom).accept(null, error -> {
assertThat("Exception should match", error, equalTo(boom));
done();
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;
}
});
waitUntilDone();
@ -78,9 +94,13 @@ public class GeckoResultTest {
@UiThreadTest
public void testCopy() {
final GeckoResult<Integer> result = new GeckoResult<>(GeckoResult.fromValue(42));
result.accept(value -> {
assertThat("Value should match", value, equalTo(42));
done();
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;
}
});
waitUntilDone();
@ -89,7 +109,7 @@ public class GeckoResultTest {
@Test(expected = IllegalStateException.class)
@UiThreadTest
public void completeMultiple() {
final GeckoResult<Integer> deferred = new GeckoResult<>();
final GeckoResult<Integer> deferred = new GeckoResult<Integer>();
deferred.complete(42);
deferred.complete(43);
}
@ -97,7 +117,7 @@ public class GeckoResultTest {
@Test(expected = IllegalStateException.class)
@UiThreadTest
public void completeMultipleExceptions() {
final GeckoResult<Integer> deferred = new GeckoResult<>();
final GeckoResult<Integer> deferred = new GeckoResult<Integer>();
deferred.completeExceptionally(new Exception("boom"));
deferred.completeExceptionally(new Exception("boom again"));
}
@ -105,7 +125,7 @@ public class GeckoResultTest {
@Test(expected = IllegalStateException.class)
@UiThreadTest
public void completeMixed() {
final GeckoResult<Integer> deferred = new GeckoResult<>();
final GeckoResult<Integer> deferred = new GeckoResult<Integer>();
deferred.complete(42);
deferred.completeExceptionally(new Exception("boom again"));
}
@ -120,12 +140,22 @@ public class GeckoResultTest {
@UiThreadTest
public void completeThreaded() {
final GeckoResult<Integer> deferred = new GeckoResult<>();
final Thread thread = new Thread(() -> deferred.complete(42));
final Thread thread = new Thread() {
@Override
public void run() {
deferred.complete(42);
}
};
deferred.accept(value -> {
assertThat("Value should match", value, equalTo(42));
ThreadUtils.assertOnUiThread();
done();
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;
}
});
thread.start();
@ -135,17 +165,24 @@ public class GeckoResultTest {
@Test
@UiThreadTest
public void dispatchOnInitialThread() throws InterruptedException {
final Thread thread = new Thread(() -> {
Looper.prepare();
final Thread dispatchThread = Thread.currentThread();
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
final Thread dispatchThread = Thread.currentThread();
GeckoResult.fromValue(42).accept(value -> {
assertThat("Thread should match", Thread.currentThread(),
equalTo(dispatchThread));
Looper.myLooper().quit();
});
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;
}
});
Looper.loop();
Looper.loop();
}
});
thread.start();
@ -157,13 +194,22 @@ public class GeckoResultTest {
public void completeExceptionallyThreaded() {
final GeckoResult<Integer> deferred = new GeckoResult<>();
final Throwable boom = new Exception("boom");
final Thread thread = new Thread(() -> deferred.completeExceptionally(boom));
final Thread thread = new Thread() {
@Override
public void run() {
deferred.completeExceptionally(boom);
}
};
deferred.exceptionally(error -> {
assertThat("Exception should match", error, equalTo(boom));
ThreadUtils.assertOnUiThread();
done();
return null;
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;
}
});
thread.start();
@ -175,21 +221,37 @@ public class GeckoResultTest {
public void resultChaining() {
assertThat("We're on the UI thread", Thread.currentThread(), equalTo(Looper.getMainLooper().getThread()));
GeckoResult.fromValue(42).then(value -> {
assertThat("Value should match", value, equalTo(42));
return GeckoResult.fromValue("hello");
}).then(value -> {
assertThat("Value should match", value, equalTo("hello"));
return GeckoResult.fromValue(42.0f);
}).then(value -> {
assertThat("Value should match", value, equalTo(42.0f));
return GeckoResult.fromException(new Exception("boom"));
}).exceptionally(error -> {
assertThat("Error message should match", error.getMessage(), equalTo("boom"));
throw new MockException();
}).accept(null, exception -> {
assertThat("Exception should be MockException", exception, instanceOf(MockException.class));
done();
GeckoResult.fromValue(42).then(new OnValueListener<Integer, String>() {
@Override
public GeckoResult<String> onValue(Integer value) {
assertThat("Value should match", value, equalTo(42));
return GeckoResult.fromValue("hello");
}
}).then(new OnValueListener<String, Float>() {
@Override
public GeckoResult<Float> onValue(String value) {
assertThat("Value should match", value, equalTo("hello"));
return GeckoResult.fromValue(42.0f);
}
}).then(new OnValueListener<Float, Float>() {
@Override
public GeckoResult<Float> onValue(Float value) {
assertThat("Value should match", value, equalTo(42.0f));
return GeckoResult.fromException(new Exception("boom"));
}
}).exceptionally(new OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable error) {
assertThat("Error message should match", error.getMessage(), equalTo("boom"));
throw new MockException();
}
}).exceptionally(new OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable exception) {
assertThat("Exception should be MockException", exception, instanceOf(MockException.class));
done();
return null;
}
});
waitUntilDone();
@ -200,10 +262,18 @@ 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(error -> null)
.accept(value -> {
assertThat("Propagated value is null", value, nullValue());
done();
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;
}
});
waitUntilDone();
@ -212,8 +282,11 @@ public class GeckoResultTest {
@UiThreadTest
@Test(expected = GeckoResult.UncaughtException.class)
public void then_uncaughtException() {
GeckoResult.fromValue(42).then(value -> {
throw new MockException();
GeckoResult.fromValue(42).then(new OnValueListener<Integer, String>() {
@Override
public GeckoResult<String> onValue(Integer value) {
throw new MockException();
}
});
waitUntilDone();
@ -222,9 +295,17 @@ public class GeckoResultTest {
@UiThreadTest
@Test(expected = GeckoResult.UncaughtException.class)
public void then_propagatedUncaughtException() {
GeckoResult.fromValue(42).then(value -> {
throw new MockException();
}).accept(value -> {});
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;
}
});
waitUntilDone();
}
@ -232,14 +313,25 @@ public class GeckoResultTest {
@UiThreadTest
@Test
public void then_caughtException() {
GeckoResult.fromValue(42).then(value -> { throw new MockException(); })
.accept(value -> {})
.exceptionally(exception -> {
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 {
assertThat("Exception should be expected",
exception, instanceOf(MockException.class));
done();
return null;
});
}
});
waitUntilDone();
}
@ -279,10 +371,11 @@ public class GeckoResultTest {
assertThat("We shouldn't have a Looper", result.getLooper(), nullValue());
try {
result.withHandler(queue.take()).accept(value -> {
result.withHandler(queue.take()).then(value -> {
assertThat("Thread should match", Thread.currentThread(), equalTo(thread));
assertThat("Value should match", value, equalTo(42));
Looper.myLooper().quit();
return null;
});
thread.join();
@ -302,6 +395,25 @@ public class GeckoResultTest {
GeckoResult.fromException(new MockException()).poll(0);
}
@Test
public void pollIncompleteWithValue() throws Throwable {
final GeckoResult<Integer> result = new GeckoResult<>();
final Thread thread = new Thread(() -> result.complete(42));
thread.start();
assertThat("Value should match", result.poll(), equalTo(42));
}
@Test(expected = MockException.class)
public void pollIncompleteWithError() throws Throwable {
final GeckoResult<Void> result = new GeckoResult<>();
final Thread thread = new Thread(() -> result.completeExceptionally(new MockException()));
thread.start();
result.poll();
}
@Test(expected = TimeoutException.class)
public void pollTimeout() throws Throwable {

Просмотреть файл

@ -1,37 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.geckoview.test
import org.junit.Test
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.test.util.Environment
import org.hamcrest.Matchers.*
import org.junit.Assert.assertThat
val env = Environment()
fun <T> GeckoResult<T>.pollDefault(): T? =
this.poll(env.defaultTimeoutMillis)
class GeckoResultTestKotlin {
class MockException : RuntimeException()
@Test fun pollIncompleteWithValue() {
val result = GeckoResult<Int>()
val thread = Thread { result.complete(42) }
thread.start()
assertThat("Value should match", result.pollDefault(), equalTo(42))
}
@Test(expected = MockException::class) fun pollIncompleteWithError() {
val result = GeckoResult<Void>()
val thread = Thread { result.completeExceptionally(MockException()) }
thread.start()
result.pollDefault()
}
}

Просмотреть файл

@ -1707,6 +1707,7 @@ class GeckoSessionTestRuleTest : BaseSessionTest(noErrorCollector = true) {
}
@IgnoreCrash
@ReuseSession(false)
@Test fun contentCrashIgnored() {
assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
// Cannot test x86 debug builds due to Gecko's "ah_crap_handler"
@ -1722,6 +1723,7 @@ class GeckoSessionTestRuleTest : BaseSessionTest(noErrorCollector = true) {
}
@Test(expected = ChildCrashedException::class)
@ReuseSession(false)
fun contentCrashFails() {
assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
assumeThat(sessionRule.env.shouldShutdownOnCrash(), equalTo(false))

Просмотреть файл

@ -7,6 +7,7 @@ package org.mozilla.geckoview.test
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
@ -133,6 +134,7 @@ class HistoryDelegateTest : BaseSessionTest() {
)
}
@ReuseSession(false)
@Ignore //disable test on debug for frequent failures Bug 1544169
@Test fun onHistoryStateChange() {
sessionRule.session.loadTestPath(HELLO_HTML_PATH)

Просмотреть файл

@ -5,6 +5,7 @@
package org.mozilla.geckoview.test
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import android.support.test.filters.MediumTest

Просмотреть файл

@ -17,6 +17,7 @@ import org.mozilla.geckoview.WebRequestError
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
@ -30,11 +31,11 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.test.util.HttpBin
import org.mozilla.geckoview.test.util.UiThreadUtils
import java.net.URI
@RunWith(AndroidJUnit4::class)
@MediumTest
@ReuseSession(false)
class NavigationDelegateTest : BaseSessionTest() {
companion object {
val TEST_ENDPOINT: String = "http://localhost:4242"
@ -1194,7 +1195,6 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.session.waitUntilCalled(GeckoSession.NavigationDelegate::class,
"onNewSession")
UiThreadUtils.loopUntilIdle(sessionRule.env.defaultTimeoutMillis)
}
@Test

Просмотреть файл

@ -139,8 +139,7 @@ class PermissionDelegateTest : BaseSessionTest() {
hasPermission(Manifest.permission.ACCESS_FINE_LOCATION),
equalTo(true))
val url = "https://example.com/"
mainSession.loadUri(url)
mainSession.loadTestPath(HELLO_HTML_PATH)
mainSession.waitForPageStop()
mainSession.delegateDuringNextWait(object : Callbacks.PermissionDelegate {
@ -149,7 +148,7 @@ class PermissionDelegateTest : BaseSessionTest() {
override fun onContentPermissionRequest(
session: GeckoSession, uri: String?, type: Int,
callback: GeckoSession.PermissionDelegate.Callback) {
assertThat("URI should match", uri, endsWith(url))
assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
assertThat("Type should match", type,
equalTo(GeckoSession.PermissionDelegate.PERMISSION_GEOLOCATION))
callback.grant()
@ -165,18 +164,13 @@ class PermissionDelegateTest : BaseSessionTest() {
}
})
try {
val position = mainSession.waitForJS("""new Promise((resolve, reject) =>
window.navigator.geolocation.getCurrentPosition(resolve, reject))""")
val position = mainSession.waitForJS("""new Promise((resolve, reject) =>
window.navigator.geolocation.getCurrentPosition(resolve, reject))""")
assertThat("Request should succeed",
position.asJSMap(),
hasEntry(equalTo("coords"),
both(hasKey("longitude")).and(hasKey("latitude"))))
} catch (ex: RejectedPromiseException) {
assertThat("Error should not because the permission was denied.",
ex.reason.asJSMap(), hasEntry(equalTo("code"), not(1.0)))
}
assertThat("Request should succeed",
position.asJSMap(),
hasEntry(equalTo("coords"),
both(hasKey("longitude")).and(hasKey("latitude"))))
}
@WithDevToolsAPI

Просмотреть файл

@ -4,6 +4,7 @@
package org.mozilla.geckoview.test
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
@ -284,30 +285,6 @@ class ProgressDelegateTest : BaseSessionTest() {
})
}
val errorEpsilon = 0.1
private fun waitForScroll(offset: Double, timeout: Double, param: String) {
sessionRule.evaluateJS(mainSession, """
new Promise((resolve, reject) => {
const start = Date.now();
function step() {
if (window.visualViewport.$param >= ($offset - $errorEpsilon)) {
resolve();
} else if ($timeout < (Date.now() - start)) {
reject();
} else {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
});
""".trimIndent()).asJSPromise().value
}
private fun waitForVerticalScroll(offset: Double, timeout: Double) {
waitForScroll(offset, timeout, "pageTop")
}
@WithDevToolsAPI
@WithDisplay(width = 400, height = 400)
@Test fun saveAndRestoreState() {
@ -317,11 +294,9 @@ class ProgressDelegateTest : BaseSessionTest() {
mainSession.loadUri(startUri)
sessionRule.waitForPageStop()
mainSession.evaluateJS("$('#name').value = 'the name';")
mainSession.evaluateJS("$('#name').value = 'the name'; window.setTimeout(() => window.scrollBy(0, 100),0);")
mainSession.evaluateJS("$('#name').dispatchEvent(new Event('input'));")
mainSession.evaluateJS("window.scrollBy(0, 100);")
waitForVerticalScroll(100.0, sessionRule.env.defaultTimeoutMillis.toDouble())
sessionRule.waitUntilCalled(Callbacks.ScrollDelegate::class, "onScrollChanged")
var savedState : GeckoSession.SessionState? = null
sessionRule.waitUntilCalled(object : Callbacks.ProgressDelegate {

Просмотреть файл

@ -5,6 +5,7 @@ import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import org.mozilla.geckoview.test.util.Callbacks
@ -17,6 +18,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
@ReuseSession(false)
@WithDevToolsAPI
class PromptDelegateTest : BaseSessionTest() {
@Ignore("disable test for frequently failing Bug 1535423")

Просмотреть файл

@ -12,11 +12,14 @@ import org.hamcrest.Matchers.*
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import kotlin.math.roundToInt
import org.junit.Assume.assumeThat
@RunWith(AndroidJUnit4::class)
@MediumTest
@ReuseSession(false)
class RuntimeSettingsTest : BaseSessionTest() {
@Ignore("disable test for frequently failing Bug 1538430")
@ -73,8 +76,9 @@ class RuntimeSettingsTest : BaseSessionTest() {
}
@WithDevToolsAPI
@Ignore // Bug 1546297 disabled test on pgo for frequent failures
// disable test on pgo for frequently failing #Bug 1546297
@Test fun fontSize() {
assumeThat(sessionRule.env.isDebugBuild, equalTo(true))
val settings = sessionRule.runtime.settings
settings.fontSizeFactor = 1.0f
sessionRule.session.loadTestPath(HELLO_HTML_PATH)

Просмотреть файл

@ -16,6 +16,7 @@ import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.runner.RunWith
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
import java.nio.ByteBuffer
@ -24,6 +25,7 @@ private const val SCREEN_WIDTH = 100
@RunWith(AndroidJUnit4::class)
@MediumTest
@ReuseSession(false)
class ScreenshotTest : BaseSessionTest() {
@get:Rule

Просмотреть файл

@ -11,6 +11,7 @@ import org.mozilla.geckoview.GeckoView
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ClosedSessionAtStart
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import org.mozilla.geckoview.test.util.Callbacks
import org.mozilla.geckoview.test.util.UiThreadUtils
@ -37,6 +38,7 @@ import java.lang.ref.WeakReference
@RunWith(AndroidJUnit4::class)
@MediumTest
@ReuseSession(false)
class SessionLifecycleTest : BaseSessionTest() {
companion object {
val LOGTAG = "SessionLifecycleTest"
@ -481,10 +483,20 @@ class SessionLifecycleTest : BaseSessionTest() {
}
private fun waitUntilCollected(ref: QueuedWeakReference<*>) {
UiThreadUtils.waitForCondition({
val start = SystemClock.uptimeMillis()
while (ref.queue.poll() == null) {
val elapsed = SystemClock.uptimeMillis() - start
if (elapsed > sessionRule.timeoutMillis) {
dumpHprof()
throw UiThreadUtils.TimeoutException("Timed out after " + elapsed + "ms")
}
try {
UiThreadUtils.loopUntilIdle(100)
} catch (e: UiThreadUtils.TimeoutException) {
}
Runtime.getRuntime().gc()
ref.queue.poll() != null
}, sessionRule.timeoutMillis)
}
}
class QueuedWeakReference<T> @JvmOverloads constructor(obj: T, var queue: ReferenceQueue<T> =

Просмотреть файл

@ -8,6 +8,7 @@ import android.os.SystemClock
import android.support.test.InstrumentationRegistry
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
import org.mozilla.geckoview.test.util.Callbacks
@ -305,6 +306,7 @@ class TextInputDelegateTest : BaseSessionTest() {
checkGecko: Boolean = true) =
assertTextAndSelection(message, ic, expected, value, value, checkGecko)
@ReuseSession(false) // Test is only reliable on automation when not reusing session.
@WithDisplay(width = 512, height = 512) // Child process updates require having a display.
@Test fun inputConnection() {
// too slow on debug

Просмотреть файл

@ -34,7 +34,6 @@ import org.junit.*
import org.junit.rules.ExpectedException
import org.junit.runner.RunWith
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoWebExecutor
import org.mozilla.geckoview.WebRequest
@ -56,6 +55,7 @@ class WebExecutorTest {
lateinit var executor: GeckoWebExecutor
lateinit var server: HttpBin
val env = Environment()
@get:Rule val thrown = ExpectedException.none()
@ -86,7 +86,7 @@ class WebExecutorTest {
}
private fun fetch(request: WebRequest, flags: Int): WebResponse {
return executor.fetch(request, flags).pollDefault()!!
return executor.fetch(request, flags).poll(env.defaultTimeoutMillis)!!
}
fun String.toDirectByteBuffer(): ByteBuffer {
@ -254,7 +254,7 @@ class WebExecutorTest {
@Test
fun testResolveV4() {
val addresses = executor.resolve("localhost").pollDefault()!!
val addresses = executor.resolve("localhost").poll()!!
assertThat("Addresses should not be null",
addresses, notNullValue())
assertThat("First address should be loopback",
@ -266,7 +266,7 @@ class WebExecutorTest {
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
fun testResolveV6() {
val addresses = executor.resolve("ip6-localhost").pollDefault()!!
val addresses = executor.resolve("ip6-localhost").poll()!!
assertThat("Addresses should not be null",
addresses, notNullValue())
assertThat("First address should be loopback",
@ -283,13 +283,13 @@ class WebExecutorTest {
@Test(expected = UnknownHostException::class)
fun testResolveError() {
executor.resolve("this.should.not.resolve").pollDefault()
executor.resolve("this.should.not.resolve").poll()
}
@Test
fun testFetchStream() {
val expectedCount = 1 * 1024 * 1024 // 1MB
val response = executor.fetch(WebRequest("$TEST_ENDPOINT/bytes/$expectedCount")).pollDefault()!!
val response = executor.fetch(WebRequest("$TEST_ENDPOINT/bytes/$expectedCount")).poll(env.defaultTimeoutMillis)!!
assertThat("Status code should match", response.statusCode, equalTo(200))
assertThat("Content-Length should match", response.headers["Content-Length"]!!.toInt(), equalTo(expectedCount))
@ -308,7 +308,7 @@ class WebExecutorTest {
@Test
fun testFetchStreamCancel() {
val expectedCount = 1 * 1024 * 1024 // 1MB
val response = executor.fetch(WebRequest("$TEST_ENDPOINT/bytes/$expectedCount")).pollDefault()!!
val response = executor.fetch(WebRequest("$TEST_ENDPOINT/bytes/$expectedCount")).poll(env.defaultTimeoutMillis)!!
assertThat("Status code should match", response.statusCode, equalTo(200))
assertThat("Content-Length should match", response.headers["Content-Length"]!!.toInt(), equalTo(expectedCount))

Просмотреть файл

@ -5,6 +5,7 @@
package org.mozilla.geckoview.test
import android.support.test.InstrumentationRegistry
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import android.support.test.filters.MediumTest
import android.support.test.runner.AndroidJUnit4
@ -27,6 +28,7 @@ import java.util.UUID
@RunWith(AndroidJUnit4::class)
@MediumTest
@ReuseSession(false)
class WebExtensionTest : BaseSessionTest() {
companion object {
val TEST_ENDPOINT: String = "http://localhost:4243"

Просмотреть файл

@ -10,30 +10,50 @@ import android.support.test.runner.AndroidJUnit4
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.notNullValue
import org.junit.Assert.assertThat
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.Timeout
import org.junit.runner.RunWith
import org.mozilla.geckoview.BuildConfig
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.test.util.Environment
import java.io.File
import java.util.concurrent.TimeUnit
@RunWith(AndroidJUnit4::class)
@MediumTest
class CrashTest {
companion object {
val DEFAULT_X86_EMULATOR_TIMEOUT_MILLIS = 90000L
val DEFAULT_X86_DEVICE_TIMEOUT_MILLIS = 60000L
val DEFAULT_ARM_EMULATOR_TIMEOUT_MILLIS = 180000L
val DEFAULT_ARM_DEVICE_TIMEOUT_MILLIS = 60000L
}
lateinit var messenger: Messenger
val env = Environment()
@get:Rule val rule = ServiceTestRule()
@get:Rule val timeoutRule = Timeout.millis(getTimeoutMillis())
fun getTimeoutMillis(): Long {
val env = Environment()
if (env.isX86) {
return if (env.isEmulator)
CrashTest.DEFAULT_X86_EMULATOR_TIMEOUT_MILLIS
else
CrashTest.DEFAULT_X86_DEVICE_TIMEOUT_MILLIS
}
return if (env.isEmulator)
CrashTest.DEFAULT_ARM_EMULATOR_TIMEOUT_MILLIS
else
CrashTest.DEFAULT_ARM_DEVICE_TIMEOUT_MILLIS
}
@Before
fun setup() {
CrashTestHandler.queue.clear()
val context = InstrumentationRegistry.getTargetContext()
val context = InstrumentationRegistry.getTargetContext();
val binder = rule.bindService(Intent(context, RemoteGeckoService::class.java))
messenger = Messenger(binder)
assertThat("messenger should not be null", binder, notNullValue())
@ -64,11 +84,7 @@ class CrashTest {
@Test
fun crashContent() {
// We need the crash reporter for this test
Assume.assumeTrue(BuildConfig.MOZ_CRASHREPORTER)
messenger.send(Message.obtain(null, RemoteGeckoService.CMD_CRASH_CONTENT_NATIVE))
assertCrashIntent(CrashTestHandler.queue.poll(
env.defaultTimeoutMillis, TimeUnit.MILLISECONDS), false)
assertCrashIntent(CrashTestHandler.queue.take(), false)
}
}

Просмотреть файл

@ -5,11 +5,10 @@
package org.mozilla.geckoview.test.rule;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.geckoview.ContentBlocking;
import org.mozilla.geckoview.GeckoDisplay;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoResult.OnValueListener;
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
@ -37,14 +36,15 @@ import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import android.app.Instrumentation;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.net.LocalSocketAddress;
import android.os.Looper;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.Surface;
@ -69,7 +69,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
@ -269,6 +268,17 @@ public class GeckoSessionTestRule implements TestRule {
String value();
}
/**
* If a test requests a default open session, reuse a cached session instead of creating an
* open session every time. A new session is still created if the test requests a non-default
* session such as a closed session or a session with custom settings.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReuseSession {
boolean value() default true;
}
/**
* 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
@ -397,8 +407,9 @@ public class GeckoSessionTestRule implements TestRule {
* @return Fulfilled value of the promise.
*/
public Object getValue() {
UiThreadUtils.waitForCondition(() -> !mPromise.isPending(), mTimeoutMillis);
while (mPromise.isPending()) {
UiThreadUtils.loopUntilIdle(mTimeoutMillis);
}
if (mPromise.isRejected()) {
throw new RejectedPromiseException(mPromise.getReason());
}
@ -815,6 +826,8 @@ public class GeckoSessionTestRule implements TestRule {
private static final Set<Class<?>> DEFAULT_DELEGATES = getDefaultDelegates();
private static RDPConnection sRDPConnection;
protected static GeckoSession sCachedSession;
protected static Tab sCachedRDPTab;
public final Environment env = new Environment();
@ -844,6 +857,7 @@ public class GeckoSessionTestRule implements TestRule {
protected boolean mWithDevTools;
protected Map<GeckoSession, Tab> mRDPTabs;
protected Tab mRDPChromeProcess;
protected boolean mReuseSession;
protected boolean mIgnoreCrash;
public GeckoSessionTestRule() {
@ -1038,6 +1052,8 @@ public class GeckoSessionTestRule implements TestRule {
mClosedSession = ((ClosedSessionAtStart) annotation).value();
} else if (WithDevToolsAPI.class.equals(annotation.annotationType())) {
mWithDevTools = ((WithDevToolsAPI) annotation).value();
} else if (ReuseSession.class.equals(annotation.annotationType())) {
mReuseSession = ((ReuseSession) annotation).value();
} else if (IgnoreCrash.class.equals(annotation.annotationType())) {
mIgnoreCrash = ((IgnoreCrash) annotation).value();
}
@ -1055,12 +1071,13 @@ public class GeckoSessionTestRule implements TestRule {
return new RuntimeException(cause != null ? cause : e);
}
protected void prepareStatement(final Description description) {
protected void prepareStatement(final Description description) throws Throwable {
final GeckoSessionSettings settings = new GeckoSessionSettings(mDefaultSettings);
mTimeoutMillis = env.getDefaultTimeoutMillis();
mNullDelegates = new HashSet<>();
mClosedSession = false;
mWithDevTools = false;
mReuseSession = true;
mIgnoreCrash = false;
applyAnnotations(Arrays.asList(description.getTestClass().getAnnotations()), settings);
@ -1098,9 +1115,8 @@ public class GeckoSessionTestRule implements TestRule {
!DEFAULT_DELEGATES.contains(method.getDeclaringClass());
if (!ignore) {
if (!isExternalDelegate) {
ThreadUtils.assertOnUiThread();
}
assertThat("Callbacks must be on UI thread",
Looper.myLooper(), equalTo(Looper.getMainLooper()));
final GeckoSession session;
if (isExternalDelegate) {
@ -1156,17 +1172,25 @@ public class GeckoSessionTestRule implements TestRule {
@SuppressWarnings("unchecked")
final GeckoResult<GeckoSession> result = (GeckoResult<GeckoSession>)returnValue;
final GeckoResult<GeckoSession> tmpResult = new GeckoResult<>();
result.accept(session -> {
tmpResult.complete(session);
result.then(new OnValueListener<GeckoSession, Void>() {
@Override
public GeckoResult<Void> onValue(final GeckoSession newSession) throws Throwable {
tmpResult.complete(newSession);
// 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);
}
});
// 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;
}
});
return tmpResult;
@ -1179,7 +1203,16 @@ public class GeckoSessionTestRule implements TestRule {
classes, recorder);
mAllDelegates = new HashSet<>(DEFAULT_DELEGATES);
mMainSession = new GeckoSession(settings);
if (sCachedSession != null && !sCachedSession.isOpen()) {
sCachedSession = null;
}
final boolean useDefaultSession = !mClosedSession && mDefaultSettings.equals(settings);
if (useDefaultSession && mReuseSession && sCachedSession != null) {
mMainSession = sCachedSession;
} else {
mMainSession = new GeckoSession(settings);
}
prepareSession(mMainSession);
if (mDisplaySize != null) {
@ -1190,18 +1223,28 @@ public class GeckoSessionTestRule implements TestRule {
mDisplay.surfaceChanged(mDisplaySurface, mDisplaySize.x, mDisplaySize.y);
}
if (!mClosedSession) {
if (useDefaultSession && mReuseSession) {
if (sCachedSession == null) {
// We are creating a cached session.
final boolean withDevTools = mWithDevTools;
mWithDevTools = true; // Always get an RDP tab for cached session.
openSession(mMainSession);
sCachedSession = mMainSession;
sCachedRDPTab = mRDPTabs.get(mMainSession);
mWithDevTools = withDevTools;
} else {
// We are reusing a cached session.
mMainSession.loadUri("about:blank");
waitForOpenSession(mMainSession);
}
} else if (!mClosedSession) {
openSession(mMainSession);
}
}
protected void prepareSession(final GeckoSession session) {
protected void prepareSession(final GeckoSession session) throws Throwable {
for (final Class<?> cls : DEFAULT_DELEGATES) {
try {
setDelegate(cls, session, mNullDelegates.contains(cls) ? null : mCallbackProxy);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
setDelegate(cls, session, mNullDelegates.contains(cls) ? null : mCallbackProxy);
}
}
@ -1212,92 +1255,88 @@ public class GeckoSessionTestRule implements TestRule {
* @param session Session to open.
*/
public void openSession(final GeckoSession session) {
ThreadUtils.assertOnUiThread();
session.open(getRuntime());
waitForOpenSession(session);
}
/* package */ void waitForOpenSession(final GeckoSession session) {
waitForInitialLoad(session);
if (mWithDevTools) {
if (sRDPConnection == null) {
final String packageName = InstrumentationRegistry.getTargetContext()
.getPackageName();
final LocalSocketAddress address = new LocalSocketAddress(
packageName + "/firefox-debugger-socket",
LocalSocketAddress.Namespace.ABSTRACT);
sRDPConnection = new RDPConnection(address);
sRDPConnection.setTimeout(mTimeoutMillis);
}
if (mRDPTabs == null) {
mRDPTabs = new HashMap<>();
}
final Tab tab = session.equals(sCachedSession) ? sCachedRDPTab
: sRDPConnection.getMostRecentTab();
mRDPTabs.put(session, tab);
}
}
private void waitForInitialLoad(final GeckoSession session) {
// We receive an initial about:blank load; don't expose that to the test. The initial
// load ends with the first onPageStop call, so ignore everything from the session
// until the first onPageStop call.
try {
// We cannot detect initial page load without progress delegate.
assertThat("ProgressDelegate cannot be null-delegate when opening session",
GeckoSession.ProgressDelegate.class, not(isIn(mNullDelegates)));
mCallRecordHandler = (method, args) -> {
Log.e(LOGTAG, "method: " + method);
final boolean matching = DEFAULT_DELEGATES.contains(
method.getDeclaringClass()) && session.equals(args[0]);
if (matching && sOnPageStop.equals(method)) {
mCallRecordHandler = null;
}
return matching;
};
session.open(getRuntime());
UiThreadUtils.waitForCondition(() -> mCallRecordHandler == null,
env.getDefaultTimeoutMillis());
} finally {
mCallRecordHandler = null;
}
attachDevTools(session);
}
private void attachDevTools(final GeckoSession session) {
if (!mWithDevTools) {
return;
}
if (sRDPConnection == null) {
final String packageName = InstrumentationRegistry.getTargetContext()
.getPackageName();
final LocalSocketAddress address = new LocalSocketAddress(
packageName + "/firefox-debugger-socket",
LocalSocketAddress.Namespace.ABSTRACT);
sRDPConnection = new RDPConnection(address);
sRDPConnection.setTimeout(mTimeoutMillis);
}
if (mRDPTabs == null) {
mRDPTabs = new HashMap<>();
}
final Tab tab = sRDPConnection.getMostRecentTab();
mRDPTabs.put(session, tab);
}
private void waitForOpenSession(final GeckoSession session) {
ThreadUtils.assertOnUiThread();
// We receive an initial about:blank load; don't expose that to the test. The initial
// load ends with the first onPageStop call, so ignore everything from the session
// until the first onPageStop call.
// For the cached session, we may get multiple initial loads. We should specifically look
// for an about:blank load, and wait until that has stopped.
final boolean lookForAboutBlank = session.equals(sCachedSession);
try {
// We cannot detect initial page load without progress delegate.
assertThat("ProgressDelegate cannot be null-delegate when opening session",
GeckoSession.ProgressDelegate.class, not(isIn(mNullDelegates)));
mCallRecordHandler = (method, args) -> {
Log.e(LOGTAG, "method: " + method);
final boolean matching = DEFAULT_DELEGATES.contains(
method.getDeclaringClass()) && session.equals(args[0]);
if (matching && sOnPageStop.equals(method)) {
mCallRecordHandler = null;
mCallRecordHandler = new CallRecordHandler() {
private boolean mIsAboutBlank = !lookForAboutBlank;
@Override
public boolean handleCall(final Method method, final Object[] args) {
final boolean matching = DEFAULT_DELEGATES.contains(
method.getDeclaringClass()) && session.equals(args[0]);
if (matching && sOnPageStart.equals(method)) {
mIsAboutBlank = "about:blank".equals(args[1]);
} else if (matching && mIsAboutBlank && sOnPageStop.equals(method)) {
mCallRecordHandler = null;
}
return matching;
}
return matching;
};
UiThreadUtils.waitForCondition(() -> mCallRecordHandler == null,
env.getDefaultTimeoutMillis());
do {
UiThreadUtils.loopUntilIdle(env.getDefaultTimeoutMillis());
} while (mCallRecordHandler != null);
} finally {
mCallRecordHandler = null;
}
attachDevTools(session);
}
/**
* Internal method to perform callback checks at the end of a test.
*/
public void performTestEndCheck() {
if (sCachedSession != null && mIgnoreCrash) {
// Make sure the cached session has been closed by crashes.
while (sCachedSession.isOpen()) {
UiThreadUtils.loopUntilIdle(mTimeoutMillis);
}
}
mWaitScopeDelegates.clearAndAssert();
mTestScopeDelegates.clearAndAssert();
if (sCachedSession != null && mReuseSession) {
assertThat("Cached session should be open",
sCachedSession.isOpen(), equalTo(true));
}
}
protected void cleanupSession(final GeckoSession session) {
@ -1326,7 +1365,7 @@ public class GeckoSessionTestRule implements TestRule {
}
}
protected void cleanupStatement() {
protected void cleanupStatement() throws Throwable {
mWaitScopeDelegates.clear();
mTestScopeDelegates.clear();
@ -1334,7 +1373,12 @@ public class GeckoSessionTestRule implements TestRule {
cleanupSession(session);
}
cleanupSession(mMainSession);
if (mMainSession.isOpen() && mMainSession.equals(sCachedSession)) {
// We have to detach the Promises object, but keep the Tab itself.
sCachedRDPTab.getPromises().detach();
} else {
cleanupSession(mMainSession);
}
if (mIgnoreCrash) {
deleteCrashDumps();
@ -1370,41 +1414,21 @@ public class GeckoSessionTestRule implements TestRule {
@Override
public void evaluate() throws Throwable {
final AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
mInstrumentation.runOnMainSync(() -> {
try {
getRuntime();
long timeout = env.getDefaultTimeoutMillis() + System.currentTimeMillis();
while (!GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
if (System.currentTimeMillis() > timeout) {
throw new TimeoutException("Could not startup runtime after "
+ env.getDefaultTimeoutMillis() + ".ms");
}
Log.e(LOGTAG, "GeckoThread not ready, sleeping 1000ms.");
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
}
Log.e(LOGTAG, "====");
Log.e(LOGTAG, "before prepareStatement " + description);
prepareStatement(description);
Log.e(LOGTAG, "after prepareStatement");
base.evaluate();
Log.e(LOGTAG, "after evaluate");
performTestEndCheck();
Log.e(LOGTAG, "after performTestEndCheck");
Log.e(LOGTAG, "====");
} catch (Throwable t) {
Log.e(LOGTAG, "====", t);
exceptionRef.set(t);
} finally {
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
try {
cleanupStatement();
prepareStatement(description);
base.evaluate();
performTestEndCheck();
} catch (Throwable t) {
exceptionRef.compareAndSet(null, t);
exceptionRef.set(t);
} finally {
try {
cleanupStatement();
} catch (Throwable t) {
exceptionRef.set(t);
}
}
}
});
@ -1604,11 +1628,9 @@ public class GeckoSessionTestRule implements TestRule {
forCallbacksDuringWait(session, callback);
}
private void waitUntilCalled(final @Nullable GeckoSession session,
final @NonNull Class<?> delegate,
final @NonNull List<MethodCall> methodCalls) {
ThreadUtils.assertOnUiThread();
protected void waitUntilCalled(final @Nullable GeckoSession session,
final @NonNull Class<?> delegate,
final @NonNull List<MethodCall> methodCalls) {
if (session != null && !session.equals(mMainSession)) {
assertThat("Session should be wrapped through wrapSession",
session, isIn(mSubSessions));
@ -1655,8 +1677,15 @@ public class GeckoSessionTestRule implements TestRule {
beforeWait();
while (!calledAny || !methodCalls.isEmpty()) {
final int checkIndex = index;
UiThreadUtils.waitForCondition(() -> (checkIndex < mCallRecords.size()), mTimeoutMillis);
while (index >= mCallRecords.size()) {
UiThreadUtils.loopUntilIdle(mTimeoutMillis);
// We could loop forever here if the UI thread keeps receiving
// messages that don't result in any methods being called.
// Check whether we've exceeded our allotted time and bail out.
if (SystemClock.uptimeMillis() - startTime > mTimeoutMillis) {
break;
}
}
if (SystemClock.uptimeMillis() - startTime > mTimeoutMillis) {
throw new UiThreadUtils.TimeoutException("Timed out after " + mTimeoutMillis + "ms");
@ -2020,7 +2049,9 @@ public class GeckoSessionTestRule implements TestRule {
private Object evaluateJS(final @NonNull Tab tab, final @NonNull String js) {
final Actor.Reply<Object> reply = tab.getConsole().evaluateJS(js);
UiThreadUtils.waitForCondition(reply::hasResult, mTimeoutMillis);
while (!reply.hasResult()) {
UiThreadUtils.loopUntilIdle(mTimeoutMillis);
}
final Object result = reply.get();
if (result instanceof Promise) {

Просмотреть файл

@ -5,13 +5,11 @@ import org.mozilla.geckoview.GeckoRuntimeSettings;
import org.mozilla.geckoview.test.TestCrashHandler;
import android.os.Process;
import android.support.annotation.UiThread;
import android.support.test.InstrumentationRegistry;
public class RuntimeCreator {
private static GeckoRuntime sRuntime;
@UiThread
public static GeckoRuntime getRuntime() {
if (sRuntime != null) {
return sRuntime;
@ -32,7 +30,12 @@ public class RuntimeCreator {
InstrumentationRegistry.getTargetContext(),
runtimeSettingsBuilder.build());
sRuntime.setDelegate(() -> Process.killProcess(Process.myPid()));
sRuntime.setDelegate(new GeckoRuntime.Delegate() {
@Override
public void onShutdown() {
Process.killProcess(Process.myPid());
}
});
return sRuntime;
}

Просмотреть файл

@ -5,7 +5,6 @@
package org.mozilla.geckoview.test.util;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.geckoview.GeckoResult;
import android.os.Handler;
@ -14,16 +13,15 @@ import android.os.Message;
import android.os.MessageQueue;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.internal.runner.InstrumentationConnection;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
public class UiThreadUtils {
private static final String LOGTAG = "UiThreadUtils";
private static long sLongestWait;
private static Method sGetNextMessage = null;
static {
try {
@ -61,6 +59,16 @@ public class UiThreadUtils {
public static final Handler HANDLER = new Handler(Looper.getMainLooper());
private static final TimeoutRunnable TIMEOUT_RUNNABLE = new TimeoutRunnable();
private static final MessageQueue.IdleHandler IDLE_HANDLER = 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.
}
};
private static RuntimeException unwrapRuntimeException(final Throwable e) {
final Throwable cause = e.getCause();
if (cause != null && cause instanceof RuntimeException) {
@ -83,7 +91,9 @@ public class UiThreadUtils {
public static <T> T waitForResult(@NonNull GeckoResult<T> result, long timeout) throws Throwable {
final ResultHolder<T> holder = new ResultHolder<>(result);
waitForCondition(() -> holder.isComplete, timeout);
while (!holder.isComplete) {
loopUntilIdle(timeout);
}
if (holder.error != null) {
throw holder.error;
@ -98,63 +108,66 @@ public class UiThreadUtils {
public boolean isComplete;
public ResultHolder(GeckoResult<T> result) {
result.accept(value -> {
ResultHolder.this.value = value;
isComplete = true;
}, error -> {
ResultHolder.this.error = error;
isComplete = true;
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;
}
});
}
}
public interface Condition {
boolean test();
}
public static void loopUntilIdle(final long timeout) {
AtomicBoolean idle = new AtomicBoolean(false);
MessageQueue.IdleHandler handler = null;
try {
handler = () -> {
idle.set(true);
// Remove handler
return false;
};
HANDLER.getLooper().getQueue().addIdleHandler(handler);
waitForCondition(() -> idle.get(), timeout);
} finally {
if (handler != null) {
HANDLER.getLooper().getQueue().removeIdleHandler(handler);
}
}
}
public static void waitForCondition(Condition condition, final long timeout) {
// Adapted from GeckoThread.pumpMessageLoop.
// Adapted from GeckoThread.pumpMessageLoop.
final MessageQueue queue = HANDLER.getLooper().getQueue();
if (timeout > 0) {
TIMEOUT_RUNNABLE.set(timeout);
} else {
queue.addIdleHandler(IDLE_HANDLER);
}
TIMEOUT_RUNNABLE.set(timeout);
final long startTime = SystemClock.uptimeMillis();
try {
while (!condition.test()) {
while (true) {
final Message msg;
try {
msg = (Message) sGetNextMessage.invoke(queue);
} catch (final IllegalAccessException | InvocationTargetException e) {
throw unwrapRuntimeException(e);
}
if (msg.getTarget() == null) {
if (msg.getTarget() == HANDLER && msg.obj == HANDLER) {
// Our idle signal.
break;
} else if (msg.getTarget() == null) {
HANDLER.getLooper().quit();
return;
}
msg.getTarget().dispatchMessage(msg);
if (timeout > 0) {
TIMEOUT_RUNNABLE.cancel();
queue.addIdleHandler(IDLE_HANDLER);
}
}
final long waitDuration = SystemClock.uptimeMillis() - startTime;
if (waitDuration > sLongestWait) {
sLongestWait = waitDuration;
Log.i(LOGTAG, "New longest wait: " + waitDuration + "ms");
}
} finally {
TIMEOUT_RUNNABLE.cancel();
if (timeout > 0) {
TIMEOUT_RUNNABLE.cancel();
}
}
}
}

Просмотреть файл

@ -12,7 +12,6 @@ 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,
@ -295,64 +294,6 @@ 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

Просмотреть файл

@ -296,9 +296,20 @@ public class GeckoSession implements Parcelable {
return;
}
result.accept(
visited -> callback.sendSuccess(visited.booleanValue()),
exception -> callback.sendSuccess(false));
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;
}
});
} else if ("GeckoView:GetVisited".equals(event)) {
final String[] urls = message.getStringArray("urls");
@ -310,9 +321,20 @@ public class GeckoSession implements Parcelable {
return;
}
result.accept(
visited -> callback.sendSuccess(visited),
exception -> callback.sendError("Failed to fetch visited statuses for URIs"));
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;
}
});
}
}
};
@ -526,16 +548,27 @@ public class GeckoSession implements Parcelable {
return;
}
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");
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;
}
}, exception -> callback.sendError(exception.getMessage()));
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final Throwable exception)
throws Throwable {
callback.sendError(exception.getMessage());
return null;
}
});
} else if ("GeckoView:OnLoadError".equals(event)) {
final String uri = message.getString("uri");
final long errorCode = message.getLong("error");
@ -554,17 +587,28 @@ public class GeckoSession implements Parcelable {
return;
}
result.accept(url -> {
if (url == null) {
if (GeckoAppShell.isFennec()) {
callback.sendSuccess(null);
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");
}
} else {
callback.sendError("abort");
callback.sendSuccess(url);
}
} else {
callback.sendSuccess(url);
return null;
}
}, exception -> callback.sendError(exception.getMessage()));
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final @NonNull Throwable exception) throws Throwable {
callback.sendError(exception.getMessage());
return null;
}
});
} else if ("GeckoView:OnNewSession".equals(event)) {
final String uri = message.getString("uri");
final GeckoResult<GeckoSession> result = delegate.onNewSession(GeckoSession.this, uri);
@ -573,24 +617,37 @@ public class GeckoSession implements Parcelable {
return;
}
result.accept(session -> {
ThreadUtils.assertOnUiThread();
if (session == null) {
callback.sendSuccess(null);
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 (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());
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;
}
}, exception -> callback.sendError(exception.getMessage()));
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final Throwable exception)
throws Throwable {
callback.sendError(exception.getMessage());
return null;
}
});
}
}
};
@ -2699,16 +2756,26 @@ public class GeckoSession implements Parcelable {
return;
}
res.accept(value -> {
if (value == AllowOrDeny.ALLOW) {
callback.sendSuccess(true);
} else if (value == AllowOrDeny.DENY) {
callback.sendSuccess(false);
} else {
callback.sendError("Invalid response");
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;
}
}, exception ->
callback.sendError("Failed to get popup-blocking decision"));
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(final Throwable exception)
throws Throwable {
callback.sendError("Failed to get popup-blocking decision");
return null;
}
});
break;
}
default: {

Просмотреть файл

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

Просмотреть файл

@ -8,9 +8,6 @@ exclude: true
<h1> GeckoView API Changelog. </h1>
## v69
- Added [`GeckoResult.accept`] for consuming a result without transforming it.
## v68
- Added [`GeckoRuntime#configurationChanged`][68.1] to notify the device
configuration has changed.
@ -323,4 +320,4 @@ exclude: true
[65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: ../GeckoResult.html
[api-version]: 783f253fda7287f55497c15f867dc14cd1622666
[api-version]: 29ff764a2ca4aaa16dbe79a269d6c1c8a166775e

Просмотреть файл

@ -449,10 +449,19 @@ public class GeckoViewActivity extends AppCompatActivity {
private void downloadFile(GeckoSession.WebResponseInfo response) {
mTabSessionManager.getCurrentSession()
.getUserAgent()
.accept(userAgent -> downloadFile(response, userAgent),
exception -> {
throw new IllegalStateException("Could not get UserAgent string.");
});
.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.");
}
});
}
private void downloadFile(GeckoSession.WebResponseInfo response, String userAgent) {

Просмотреть файл

@ -17,9 +17,9 @@ geckoview-junit:
default: default
chunks:
by-test-platform:
android-em-4.3-arm7-api-16-ccov/debug: 12
android-em-4.3-arm7-api-16.*\/debug: 12
android-em-4.*\/(?:opt|pgo)?: 7
android-em-4.3-arm7-api-16-ccov/debug: 8
android-em-4.3-arm7-api-16.*\/debug: 8
android-em-4.*\/(?:opt|pgo)?: 3
default: 1
run-on-projects:
by-test-platform: