diff options
Diffstat (limited to '')
-rw-r--r-- | mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt new file mode 100644 index 0000000000..2410db66c5 --- /dev/null +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt @@ -0,0 +1,582 @@ +/* -*- 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.filters.MediumTest +import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports +import org.junit.Assume.assumeThat +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.geckoview.GeckoSession +import org.mozilla.geckoview.GeckoSession.NavigationDelegate +import org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission +import org.mozilla.geckoview.GeckoSession.ProgressDelegate +import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.* // ktlint-disable no-wildcard-imports + +@RunWith(AndroidJUnit4::class) +@MediumTest +class ProgressDelegateTest : BaseSessionTest() { + + fun testProgress(path: String) { + mainSession.loadTestPath(path) + sessionRule.waitForPageStop() + + var counter = 0 + var lastProgress = -1 + + sessionRule.forCallbacksDuringWait(object : + ProgressDelegate, + NavigationDelegate { + @AssertCalled + override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) { + assertThat("LocationChange is called", url, endsWith(path)) + } + + @AssertCalled + override fun onProgressChange(session: GeckoSession, progress: Int) { + assertThat( + "Progress must be strictly increasing", + progress, + greaterThan(lastProgress), + ) + lastProgress = progress + counter++ + } + + @AssertCalled + override fun onPageStart(session: GeckoSession, url: String) { + assertThat("PageStart is called", url, endsWith(path)) + } + + @AssertCalled + override fun onPageStop(session: GeckoSession, success: Boolean) { + assertThat("PageStop is called", success, equalTo(true)) + } + }) + + assertThat( + "Callback should be called at least twice", + counter, + greaterThanOrEqualTo(2), + ) + assertThat( + "Last progress value should be 100", + lastProgress, + equalTo(100), + ) + } + + @Test fun loadProgress() { + testProgress(HELLO_HTML_PATH) + // Test that loading the same path again still + // results in the right progress events + testProgress(HELLO_HTML_PATH) + // Test that calling a different path works too + testProgress(HELLO2_HTML_PATH) + } + + @Test fun load() { + mainSession.loadTestPath(HELLO_HTML_PATH) + sessionRule.waitForPageStop() + + sessionRule.forCallbacksDuringWait(object : ProgressDelegate { + @AssertCalled(count = 1, order = [1]) + override fun onPageStart(session: GeckoSession, url: String) { + assertThat("Session should not be null", session, notNullValue()) + assertThat("URL should not be null", url, notNullValue()) + assertThat("URL should match", url, endsWith(HELLO_HTML_PATH)) + } + + @AssertCalled(count = 1, order = [2]) + override fun onSecurityChange( + session: GeckoSession, + securityInfo: GeckoSession.ProgressDelegate.SecurityInformation, + ) { + assertThat("Session should not be null", session, notNullValue()) + assertThat("Security info should not be null", securityInfo, notNullValue()) + + assertThat("Should not be secure", securityInfo.isSecure, equalTo(false)) + } + + @AssertCalled(count = 1, order = [3]) + override fun onPageStop(session: GeckoSession, success: Boolean) { + assertThat("Session should not be null", session, notNullValue()) + assertThat("Load should succeed", success, equalTo(true)) + } + }) + } + + @Ignore + @Test + fun multipleLoads() { + mainSession.loadUri(UNKNOWN_HOST_URI) + mainSession.loadTestPath(HELLO_HTML_PATH) + sessionRule.waitForPageStops(2) + + sessionRule.forCallbacksDuringWait(object : ProgressDelegate { + @AssertCalled(count = 2, order = [1, 3]) + override fun onPageStart(session: GeckoSession, url: String) { + assertThat( + "URL should match", + url, + endsWith(forEachCall(UNKNOWN_HOST_URI, HELLO_HTML_PATH)), + ) + } + + @AssertCalled(count = 2, order = [2, 4]) + override fun onPageStop(session: GeckoSession, success: Boolean) { + // The first load is certain to fail because of interruption by the second load + // or by invalid domain name, whereas the second load is certain to succeed. + assertThat( + "Success flag should match", + success, + equalTo(forEachCall(false, true)), + ) + } + }) + } + + @Test fun reload() { + mainSession.loadTestPath(HELLO_HTML_PATH) + sessionRule.waitForPageStop() + + mainSession.reload() + sessionRule.waitForPageStop() + + sessionRule.forCallbacksDuringWait(object : ProgressDelegate { + @AssertCalled(count = 1, order = [1]) + override fun onPageStart(session: GeckoSession, url: String) { + assertThat("URL should match", url, endsWith(HELLO_HTML_PATH)) + } + + @AssertCalled(count = 1, order = [2]) + override fun onSecurityChange( + session: GeckoSession, + securityInfo: GeckoSession.ProgressDelegate.SecurityInformation, + ) { + } + + @AssertCalled(count = 1, order = [3]) + override fun onPageStop(session: GeckoSession, success: Boolean) { + assertThat("Load should succeed", success, equalTo(true)) + } + }) + } + + @Test fun goBackAndForward() { + mainSession.loadTestPath(HELLO_HTML_PATH) + sessionRule.waitForPageStop() + mainSession.loadTestPath(HELLO2_HTML_PATH) + sessionRule.waitForPageStop() + + mainSession.goBack() + sessionRule.waitForPageStop() + + sessionRule.forCallbacksDuringWait(object : ProgressDelegate { + @AssertCalled(count = 1, order = [1]) + override fun onPageStart(session: GeckoSession, url: String) { + assertThat("URL should match", url, endsWith(HELLO_HTML_PATH)) + } + + @AssertCalled(count = 1, order = [2]) + override fun onSecurityChange( + session: GeckoSession, + securityInfo: GeckoSession.ProgressDelegate.SecurityInformation, + ) { + } + + @AssertCalled(count = 1, order = [3]) + override fun onPageStop(session: GeckoSession, success: Boolean) { + assertThat("Load should succeed", success, equalTo(true)) + } + }) + + mainSession.goForward() + sessionRule.waitForPageStop() + + sessionRule.forCallbacksDuringWait(object : ProgressDelegate { + @AssertCalled(count = 1, order = [1]) + override fun onPageStart(session: GeckoSession, url: String) { + assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH)) + } + + @AssertCalled(count = 1, order = [2]) + override fun onSecurityChange( + session: GeckoSession, + securityInfo: GeckoSession.ProgressDelegate.SecurityInformation, + ) { + } + + @AssertCalled(count = 1, order = [3]) + override fun onPageStop(session: GeckoSession, success: Boolean) { + assertThat("Load should succeed", success, equalTo(true)) + } + }) + } + + @Test fun correctSecurityInfoForValidTLS_automation() { + assumeThat(sessionRule.env.isAutomation, equalTo(true)) + + mainSession.loadUri("https://example.com") + sessionRule.waitForPageStop() + + sessionRule.forCallbacksDuringWait(object : ProgressDelegate { + @AssertCalled(count = 1) + override fun onSecurityChange( + session: GeckoSession, + securityInfo: GeckoSession.ProgressDelegate.SecurityInformation, + ) { + assertThat( + "Should be secure", + securityInfo.isSecure, + equalTo(true), + ) + assertThat( + "Should not be exception", + securityInfo.isException, + equalTo(false), + ) + assertThat( + "Origin should match", + securityInfo.origin, + equalTo("https://example.com"), + ) + assertThat( + "Host should match", + securityInfo.host, + equalTo("example.com"), + ) + assertThat( + "Subject should match", + securityInfo.certificate?.subjectX500Principal?.name, + equalTo("CN=example.com"), + ) + assertThat( + "Issuer should match", + securityInfo.certificate?.issuerX500Principal?.name, + equalTo("OU=Profile Guided Optimization,O=Mozilla Testing,CN=Temporary Certificate Authority"), + ) + assertThat( + "Security mode should match", + securityInfo.securityMode, + equalTo(GeckoSession.ProgressDelegate.SecurityInformation.SECURITY_MODE_IDENTIFIED), + ) + assertThat( + "Active mixed mode should match", + securityInfo.mixedModeActive, + equalTo(GeckoSession.ProgressDelegate.SecurityInformation.CONTENT_UNKNOWN), + ) + assertThat( + "Passive mixed mode should match", + securityInfo.mixedModePassive, + equalTo(GeckoSession.ProgressDelegate.SecurityInformation.CONTENT_UNKNOWN), + ) + } + }) + } + + @LargeTest + @Test + fun correctSecurityInfoForValidTLS_local() { + assumeThat(sessionRule.env.isAutomation, equalTo(false)) + + mainSession.loadUri("https://mozilla-modern.badssl.com") + sessionRule.waitForPageStop() + + sessionRule.forCallbacksDuringWait(object : ProgressDelegate { + @AssertCalled(count = 1) + override fun onSecurityChange( + session: GeckoSession, + securityInfo: GeckoSession.ProgressDelegate.SecurityInformation, + ) { + assertThat( + "Should be secure", + securityInfo.isSecure, + equalTo(true), + ) + assertThat( + "Should not be exception", + securityInfo.isException, + equalTo(false), + ) + assertThat( + "Origin should match", + securityInfo.origin, + equalTo("https://mozilla-modern.badssl.com"), + ) + assertThat( + "Host should match", + securityInfo.host, + equalTo("mozilla-modern.badssl.com"), + ) + assertThat( + "Subject should match", + securityInfo.certificate?.subjectX500Principal?.name, + equalTo("CN=*.badssl.com,O=Lucas Garron,L=Walnut Creek,ST=California,C=US"), + ) + assertThat( + "Issuer should match", + securityInfo.certificate?.issuerX500Principal?.name, + equalTo("CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US"), + ) + assertThat( + "Security mode should match", + securityInfo.securityMode, + equalTo(GeckoSession.ProgressDelegate.SecurityInformation.SECURITY_MODE_IDENTIFIED), + ) + assertThat( + "Active mixed mode should match", + securityInfo.mixedModeActive, + equalTo(GeckoSession.ProgressDelegate.SecurityInformation.CONTENT_UNKNOWN), + ) + assertThat( + "Passive mixed mode should match", + securityInfo.mixedModePassive, + equalTo(GeckoSession.ProgressDelegate.SecurityInformation.CONTENT_UNKNOWN), + ) + } + }) + } + + @LargeTest + @Test + fun noSecurityInfoForExpiredTLS() { + mainSession.loadUri( + if (sessionRule.env.isAutomation) { + "https://expired.example.com" + } else { + "https://expired.badssl.com" + }, + ) + sessionRule.waitForPageStop() + + sessionRule.forCallbacksDuringWait(object : ProgressDelegate { + @AssertCalled(count = 1) + override fun onPageStop(session: GeckoSession, success: Boolean) { + assertThat("Load should fail", success, equalTo(false)) + } + + @AssertCalled(false) + override fun onSecurityChange( + session: GeckoSession, + securityInfo: GeckoSession.ProgressDelegate.SecurityInformation, + ) { + } + }) + } + + val errorEpsilon = 0.1 + + private fun waitForScroll(offset: Double, timeout: Double, param: String) { + mainSession.evaluateJS( + """ + 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(), + ) + } + + private fun waitForVerticalScroll(offset: Double, timeout: Double) { + waitForScroll(offset, timeout, "pageTop") + } + + fun collectState(vararg uris: String): GeckoSession.SessionState { + for (uri in uris) { + mainSession.loadUri(uri) + sessionRule.waitForPageStop() + } + + mainSession.evaluateJS("document.querySelector('#name').value = 'the name';") + mainSession.evaluateJS("document.querySelector('#name').dispatchEvent(new Event('input'));") + + mainSession.evaluateJS("window.scrollBy(0, 100);") + waitForVerticalScroll(100.0, sessionRule.env.defaultTimeoutMillis.toDouble()) + + var savedState: GeckoSession.SessionState? = null + sessionRule.waitUntilCalled(object : ProgressDelegate { + @AssertCalled(count = 1) + override fun onSessionStateChange(session: GeckoSession, state: GeckoSession.SessionState) { + savedState = state + + val serialized = state.toString() + val deserialized = GeckoSession.SessionState.fromString(serialized) + assertThat("Deserialized session state should match", deserialized, equalTo(state)) + } + }) + + assertThat("State should not be null", savedState, notNullValue()) + return savedState!! + } + + @WithDisplay(width = 400, height = 400) + @Test + fun containsFormData() { + val startUri = createTestUrl(SAVE_STATE_PATH) + mainSession.loadUri(startUri) + sessionRule.waitForPageStop() + + val formData = mainSession.containsFormData() + sessionRule.waitForResult(formData).let { + assertThat("There should be no form data", it, equalTo(false)) + } + + mainSession.evaluateJS("document.querySelector('#name').value = 'the name';") + mainSession.evaluateJS("document.querySelector('#name').dispatchEvent(new Event('input'));") + + val formData2 = mainSession.containsFormData() + sessionRule.waitForResult(formData2).let { + assertThat("There should be form data", it, equalTo(true)) + } + } + + @WithDisplay(width = 400, height = 400) + @Test + fun saveAndRestoreStateNewSession() { + // TODO: Bug 1648158 + assumeThat(sessionRule.env.isFission, equalTo(false)) + val helloUri = createTestUrl(HELLO_HTML_PATH) + val startUri = createTestUrl(SAVE_STATE_PATH) + + val savedState = collectState(helloUri, startUri) + + val session = sessionRule.createOpenSession() + session.addDisplay(400, 400) + + session.restoreState(savedState) + session.waitForPageStop() + + session.forCallbacksDuringWait(object : NavigationDelegate { + @AssertCalled + override fun onLocationChange( + session: GeckoSession, + url: String?, + perms: MutableList<ContentPermission>, + ) { + assertThat("URI should match", url, equalTo(startUri)) + } + }) + + /* TODO: Reenable when we have a workaround for ContentSessionStore not + saving in response to JS-driven formdata changes. + assertThat("'name' field should match", + mainSession.evaluateJS("$('#name').value").toString(), + equalTo("the name"))*/ + + assertThat( + "Scroll position should match", + session.evaluateJS("window.visualViewport.pageTop") as Double, + closeTo(100.0, .5), + ) + + session.goBack() + + session.waitUntilCalled(object : NavigationDelegate { + override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) { + assertThat("History should be preserved", url, equalTo(helloUri)) + } + }) + } + + @WithDisplay(width = 400, height = 400) + @Test + fun saveAndRestoreState() { + // TODO: Bug 1648158 + // Bug 1662035 - disable to reduce intermittent failures + assumeThat(sessionRule.env.isX86, equalTo(false)) + assumeThat(sessionRule.env.isFission, equalTo(false)) + val startUri = createTestUrl(SAVE_STATE_PATH) + val savedState = collectState(startUri) + + mainSession.loadUri("about:blank") + sessionRule.waitForPageStop() + + mainSession.restoreState(savedState) + sessionRule.waitForPageStop() + + sessionRule.forCallbacksDuringWait(object : NavigationDelegate { + @AssertCalled + override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) { + assertThat("URI should match", url, equalTo(startUri)) + } + }) + + /* TODO: Reenable when we have a workaround for ContentSessionStore not + saving in response to JS-driven formdata changes. + assertThat("'name' field should match", + mainSession.evaluateJS("$('#name').value").toString(), + equalTo("the name"))*/ + + assertThat( + "Scroll position should match", + mainSession.evaluateJS("window.visualViewport.pageTop") as Double, + closeTo(100.0, .5), + ) + } + + @WithDisplay(width = 400, height = 400) + @Test + fun flushSessionState() { + // TODO: Bug 1648158 + assumeThat(sessionRule.env.isFission, equalTo(false)) + val startUri = createTestUrl(SAVE_STATE_PATH) + mainSession.loadUri(startUri) + sessionRule.waitForPageStop() + + var oldState: GeckoSession.SessionState? = null + + sessionRule.waitUntilCalled(object : ProgressDelegate { + @AssertCalled(count = 1) + override fun onSessionStateChange(session: GeckoSession, sessionState: GeckoSession.SessionState) { + oldState = sessionState + } + }) + + assertThat("State should not be null", oldState, notNullValue()) + + mainSession.setActive(false) + + sessionRule.waitUntilCalled(object : ProgressDelegate { + @AssertCalled(count = 1) + override fun onSessionStateChange(session: GeckoSession, sessionState: GeckoSession.SessionState) { + assertThat("Old session state and new should match", sessionState, equalTo(oldState)) + } + }) + } + + @Test fun nullState() { + val stateFromNull: GeckoSession.SessionState? = GeckoSession.SessionState.fromString(null) + val nullState: GeckoSession.SessionState? = null + assertThat("Null string should result in null state", stateFromNull, equalTo(nullState)) + } + + @NullDelegate(GeckoSession.HistoryDelegate::class) + @Test + fun noHistoryDelegateOnSessionStateChange() { + // TODO: Bug 1648158 + assumeThat(sessionRule.env.isFission, equalTo(false)) + mainSession.loadTestPath(HELLO_HTML_PATH) + sessionRule.waitForPageStop() + + sessionRule.waitUntilCalled(object : ProgressDelegate { + @AssertCalled(count = 1) + override fun onSessionStateChange(session: GeckoSession, sessionState: GeckoSession.SessionState) { + } + }) + } +} |