diff options
Diffstat (limited to 'mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt')
-rw-r--r-- | mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt new file mode 100644 index 0000000000..50f64301fd --- /dev/null +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt @@ -0,0 +1,240 @@ +/* -*- 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 android.os.Bundle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.geckoview.GeckoRuntimeSettings +import org.mozilla.geckoview.GeckoSession +import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ClosedSessionAtStart +import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay +import org.mozilla.geckoview.test.util.UiThreadUtils +import java.lang.ref.ReferenceQueue +import java.lang.ref.WeakReference + +@RunWith(AndroidJUnit4::class) +@MediumTest +class SessionLifecycleTest : BaseSessionTest() { + companion object { + val LOGTAG = "SessionLifecycleTest" + } + + @Test fun open_interleaved() { + val session1 = sessionRule.createOpenSession() + val session2 = sessionRule.createOpenSession() + session1.close() + val session3 = sessionRule.createOpenSession() + session2.close() + session3.close() + + mainSession.reload() + mainSession.waitForPageStop() + } + + @Test fun open_repeated() { + for (i in 1..5) { + mainSession.close() + mainSession.open() + } + mainSession.reload() + mainSession.waitForPageStop() + } + + @Test fun open_allowCallsWhileClosed() { + mainSession.close() + + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.reload() + + mainSession.open() + mainSession.waitForPageStops(2) + } + + @Test(expected = IllegalStateException::class) + fun open_throwOnAlreadyOpen() { + // Throw exception if retrying to open again; otherwise we would leak the old open window. + mainSession.open() + } + + @ClosedSessionAtStart + @Test + fun restoreRuntimeSettings_noSession() { + val extrasSetting = Bundle(2) + extrasSetting.putInt("test1", 10) + extrasSetting.putBoolean("test2", true) + + val settings = GeckoRuntimeSettings.Builder() + .javaScriptEnabled(false) + .extras(extrasSetting) + .build() + + settings.toParcel { parcel -> + val newSettings = GeckoRuntimeSettings.Builder().build() + newSettings.readFromParcel(parcel) + + assertThat( + "Parceled settings must match", + newSettings.javaScriptEnabled, + equalTo(settings.javaScriptEnabled), + ) + assertThat( + "Parceled settings must match", + newSettings.extras.getInt("test1"), + equalTo(settings.extras.getInt("test1")), + ) + assertThat( + "Parceled settings must match", + newSettings.extras.getBoolean("test2"), + equalTo(settings.extras.getBoolean("test2")), + ) + } + } + + @Test fun collectClosed() { + // We can't use a normal scoped function like `run` because + // those are inlined, which leaves a local reference. + fun createSession(): QueuedWeakReference<GeckoSession> { + return QueuedWeakReference<GeckoSession>(GeckoSession()) + } + + waitUntilCollected(createSession()) + } + + @Test fun collectAfterClose() { + fun createSession(): QueuedWeakReference<GeckoSession> { + val s = GeckoSession() + s.open(sessionRule.runtime) + s.close() + return QueuedWeakReference<GeckoSession>(s) + } + + waitUntilCollected(createSession()) + } + + @Test fun collectOpen() { + fun createSession(): QueuedWeakReference<GeckoSession> { + val s = GeckoSession() + s.open(sessionRule.runtime) + return QueuedWeakReference<GeckoSession>(s) + } + + waitUntilCollected(createSession()) + } + + // Waits for 4 requestAnimationFrame calls and computes rate + private fun computeRequestAnimationFrameRate(session: GeckoSession): Double { + return session.evaluateJS( + """ + new Promise(resolve => { + let start = 0; + let frames = 0; + const ITERATIONS = 4; + function raf() { + if (frames === 0) { + start = window.performance.now(); + } + if (frames === ITERATIONS) { + resolve((window.performance.now() - start) / ITERATIONS); + } + frames++; + window.requestAnimationFrame(raf); + } + window.requestAnimationFrame(raf); + }); + """, + ) as Double + } + + @WithDisplay(width = 100, height = 100) + @Test + fun asyncScriptsSuspendedWhileInactive() { + sessionRule.setPrefsUntilTestEnd( + mapOf( + "privacy.reduceTimerPrecision" to false, + // This makes the throttled frame rate 4 times faster than normal, + // so this test doesn't time out. Should still be significantly slower tha + // the active frame rate so we can measure the effects + "layout.throttled_frame_rate" to 4, + ), + ) + + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + assertThat("docShell should start active", mainSession.active, equalTo(true)) + + // Deactivate the GeckoSession and confirm that rAF/setTimeout/etc callbacks do not run + mainSession.setActive(false) + assertThat( + "docShell shouldn't be active after calling setActive(false)", + mainSession.active, + equalTo(false), + ) + + mainSession.evaluateJS( + """ + function fail() { + document.documentElement.style.backgroundColor = 'green'; + } + setTimeout(fail, 1); + fetch("missing.html").catch(fail); + """, + ) + + var rafRate = computeRequestAnimationFrameRate(mainSession) + assertThat( + "requestAnimationFrame should be called about once a second", + rafRate, + greaterThan(450.0), + ) + assertThat( + "requestAnimationFrame should be called about once a second", + rafRate, + lessThan(10000.0), + ) + + val isNotGreen = mainSession.evaluateJS( + "document.documentElement.style.backgroundColor !== 'green'", + ) as Boolean + assertThat("timeouts have not run yet", isNotGreen, equalTo(true)) + + // Reactivate the GeckoSession and confirm that rAF/setTimeout/etc callbacks now run + mainSession.setActive(true) + assertThat( + "docShell should be active after calling setActive(true)", + mainSession.active, + equalTo(true), + ) + + // At 60fps, once a frame is about 16.6 ms + rafRate = computeRequestAnimationFrameRate(mainSession) + assertThat( + "requestAnimationFrame should be called about once a frame", + rafRate, + lessThan(60.0), + ) + assertThat( + "requestAnimationFrame should be called about once a frame", + rafRate, + greaterThan(5.0), + ) + } + + private fun waitUntilCollected(ref: QueuedWeakReference<*>) { + UiThreadUtils.waitForCondition({ + Runtime.getRuntime().gc() + ref.queue.poll() != null + }, sessionRule.timeoutMillis) + } + + class QueuedWeakReference<T> @JvmOverloads constructor( + obj: T, + var queue: ReferenceQueue<T> = ReferenceQueue(), + ) : WeakReference<T>(obj, queue) +} |