зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
e9ed963838
Коммит
9f0c287f44
|
@ -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 < 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:
|
||||
|
|
Загрузка…
Ссылка в новой задаче