summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
diff options
context:
space:
mode:
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.kt240
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)
+}