diff options
Diffstat (limited to 'mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PromptDelegateTest.kt')
-rw-r--r-- | mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PromptDelegateTest.kt | 1084 |
1 files changed, 1084 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PromptDelegateTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PromptDelegateTest.kt new file mode 100644 index 0000000000..10ebefb6dd --- /dev/null +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PromptDelegateTest.kt @@ -0,0 +1,1084 @@ +/* -*- 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.MediumTest +import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports +import org.junit.Assert +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.geckoview.AllowOrDeny +import org.mozilla.geckoview.Autocomplete +import org.mozilla.geckoview.GeckoResult +import org.mozilla.geckoview.GeckoSession +import org.mozilla.geckoview.GeckoSession.NavigationDelegate +import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest +import org.mozilla.geckoview.GeckoSession.ProgressDelegate +import org.mozilla.geckoview.GeckoSession.PromptDelegate +import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthPrompt +import org.mozilla.geckoview.GeckoSession.PromptDelegate.PromptResponse +import org.mozilla.geckoview.test.rule.GeckoSessionTestRule +import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled +import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay + +@RunWith(AndroidJUnit4::class) +@MediumTest +class PromptDelegateTest : BaseSessionTest() { + @Test fun popupTestAllow() { + // Ensure popup blocking is enabled for this test. + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to true)) + + sessionRule.delegateDuringNextWait(object : PromptDelegate, NavigationDelegate { + @AssertCalled(count = 1) + override fun onPopupPrompt(session: GeckoSession, prompt: PromptDelegate.PopupPrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("Session should not be null", session, notNullValue()) + assertThat("URL should not be null", prompt.targetUri, notNullValue()) + assertThat("URL should match", prompt.targetUri, endsWith(HELLO_HTML_PATH)) + return GeckoResult.fromValue(prompt.confirm(AllowOrDeny.ALLOW)) + } + + @AssertCalled(count = 2) + override fun onLoadRequest( + session: GeckoSession, + request: LoadRequest, + ): GeckoResult<AllowOrDeny>? { + assertThat("Session should not be null", session, notNullValue()) + assertThat("URL should not be null", request.uri, notNullValue()) + assertThat("URL should match", request.uri, endsWith(forEachCall(POPUP_HTML_PATH, HELLO_HTML_PATH))) + return null + } + + @AssertCalled(count = 1) + override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? { + assertThat("URL should not be null", uri, notNullValue()) + assertThat("URL should match", uri, endsWith(HELLO_HTML_PATH)) + return null + } + }) + + mainSession.loadTestPath(POPUP_HTML_PATH) + sessionRule.waitUntilCalled(NavigationDelegate::class, "onNewSession") + } + + @Test fun popupTestBlock() { + // Ensure popup blocking is enabled for this test. + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to true)) + + sessionRule.delegateUntilTestEnd(object : PromptDelegate, NavigationDelegate { + @AssertCalled(count = 1) + override fun onPopupPrompt(session: GeckoSession, prompt: PromptDelegate.PopupPrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("Session should not be null", session, notNullValue()) + assertThat("URL should not be null", prompt.targetUri, notNullValue()) + assertThat("URL should match", prompt.targetUri, endsWith(HELLO_HTML_PATH)) + return GeckoResult.fromValue(prompt.confirm(AllowOrDeny.DENY)) + } + + @AssertCalled(count = 1) + override fun onLoadRequest( + session: GeckoSession, + request: LoadRequest, + ): GeckoResult<AllowOrDeny>? { + assertThat("Session should not be null", session, notNullValue()) + assertThat("URL should not be null", request.uri, notNullValue()) + assertThat("URL should match", request.uri, endsWith(POPUP_HTML_PATH)) + return null + } + + @AssertCalled(count = 0) + override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? { + return null + } + }) + + mainSession.loadTestPath(POPUP_HTML_PATH) + sessionRule.waitForPageStop() + mainSession.waitForRoundTrip() + } + + @Ignore // TODO: Reenable when 1501574 is fixed. + @Test + fun alertTest() { + mainSession.evaluateJS("alert('Alert!');") + + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onAlertPrompt(session: GeckoSession, prompt: PromptDelegate.AlertPrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("Message should match", "Alert!", equalTo(prompt.message)) + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + } + + // This test checks that saved logins are returned to the app when calling onAuthPrompt + @Test fun loginStorageHttpAuthWithPassword() { + mainSession.loadTestPath("/basic-auth/foo/bar") + sessionRule.delegateDuringNextWait(object : Autocomplete.StorageDelegate { + @AssertCalled + override fun onLoginFetch(domain: String): GeckoResult<Array<Autocomplete.LoginEntry>>? { + return GeckoResult.fromValue( + arrayOf( + Autocomplete.LoginEntry.Builder() + .origin(GeckoSessionTestRule.TEST_ENDPOINT) + .formActionOrigin(GeckoSessionTestRule.TEST_ENDPOINT) + .httpRealm("Fake Realm") + .username("test-username") + .password("test-password") + .formActionOrigin(null) + .guid("test-guid") + .build(), + ), + ) + } + }) + sessionRule.waitUntilCalled(object : PromptDelegate, Autocomplete.StorageDelegate { + @AssertCalled + override fun onAuthPrompt(session: GeckoSession, prompt: AuthPrompt): GeckoResult<PromptResponse>? { + assertThat( + "Saved login should appear here", + prompt.authOptions.username, + equalTo("test-username"), + ) + assertThat( + "Saved login should appear here", + prompt.authOptions.password, + equalTo("test-password"), + ) + return null + } + }) + } + + // This test checks that we store login information submitted through HTTP basic auth + // This also tests that the login save prompt gets automatically dismissed if + // the login information is incorrect. + @Test fun loginStorageHttpAuth() { + sessionRule.setPrefsUntilTestEnd( + mapOf( + "signon.rememberSignons" to true, + ), + ) + val result = GeckoResult<PromptDelegate.BasePrompt>() + val promptInstanceDelegate = object : PromptDelegate.PromptInstanceDelegate { + var prompt: PromptDelegate.BasePrompt? = null + override fun onPromptDismiss(prompt: PromptDelegate.BasePrompt) { + result.complete(prompt) + } + } + + sessionRule.delegateUntilTestEnd(object : PromptDelegate, Autocomplete.StorageDelegate { + @AssertCalled + override fun onAuthPrompt(session: GeckoSession, prompt: AuthPrompt): GeckoResult<PromptResponse>? { + return GeckoResult.fromValue(prompt.confirm("foo", "bar")) + } + + @AssertCalled + override fun onLoginFetch(domain: String): GeckoResult<Array<Autocomplete.LoginEntry>>? { + return GeckoResult.fromValue(arrayOf()) + } + + @AssertCalled + override fun onLoginSave( + session: GeckoSession, + request: PromptDelegate.AutocompleteRequest<Autocomplete.LoginSaveOption>, + ): GeckoResult<PromptResponse>? { + val authInfo = request.options[0].value + assertThat("auth matches", authInfo.formActionOrigin, isEmptyOrNullString()) + assertThat("auth matches", authInfo.httpRealm, equalTo("Fake Realm")) + assertThat("auth matches", authInfo.origin, equalTo(GeckoSessionTestRule.TEST_ENDPOINT)) + assertThat("auth matches", authInfo.username, equalTo("foo")) + assertThat("auth matches", authInfo.password, equalTo("bar")) + promptInstanceDelegate.prompt = request + request.setDelegate(promptInstanceDelegate) + return GeckoResult() + } + }) + + mainSession.loadTestPath("/basic-auth/foo/bar") + + // The server we try to hit will always reject the login so we should + // get a request to reauth which should dismiss the prompt + val actualPrompt = sessionRule.waitForResult(result) + + assertThat("Prompt object should match", actualPrompt, equalTo(promptInstanceDelegate.prompt)) + } + + @Test fun dismissAuthTest() { + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 2) + override fun onAuthPrompt(session: GeckoSession, prompt: PromptDelegate.AuthPrompt): GeckoResult<PromptDelegate.PromptResponse>? { + // TODO: Figure out some better testing here. + return null + } + }) + + mainSession.loadTestPath("/basic-auth/foo/bar") + mainSession.waitForPageStop() + + mainSession.reload() + mainSession.waitForPageStop() + } + + @Test fun buttonTest() { + mainSession.loadTestPath(HELLO_HTML_PATH) + sessionRule.waitForPageStop() + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onButtonPrompt(session: GeckoSession, prompt: PromptDelegate.ButtonPrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("Message should match", "Confirm?", equalTo(prompt.message)) + return GeckoResult.fromValue(prompt.confirm(PromptDelegate.ButtonPrompt.Type.POSITIVE)) + } + }) + + assertThat( + "Result should match", + mainSession.waitForJS("confirm('Confirm?')") as Boolean, + equalTo(true), + ) + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onButtonPrompt(session: GeckoSession, prompt: PromptDelegate.ButtonPrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("Message should match", "Confirm?", equalTo(prompt.message)) + return GeckoResult.fromValue(prompt.confirm(PromptDelegate.ButtonPrompt.Type.NEGATIVE)) + } + }) + + assertThat( + "Result should match", + mainSession.waitForJS("confirm('Confirm?')") as Boolean, + equalTo(false), + ) + } + + @Test + fun onFormResubmissionPrompt() { + mainSession.loadTestPath(RESUBMIT_CONFIRM) + sessionRule.waitForPageStop() + + mainSession.evaluateJS( + "document.querySelector('#text').value = 'Some text';" + + "document.querySelector('#submit').click();", + ) + + // Submitting the form causes a navigation + sessionRule.waitForPageStop() + + val result = GeckoResult<Void>() + sessionRule.delegateUntilTestEnd(object : ProgressDelegate { + override fun onPageStart(session: GeckoSession, url: String) { + assertThat("Only HELLO_HTML_PATH should load", url, endsWith(HELLO_HTML_PATH)) + result.complete(null) + } + }) + + val promptResult = GeckoResult<PromptDelegate.PromptResponse>() + val promptResult2 = GeckoResult<PromptDelegate.PromptResponse>() + + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 2) + override fun onRepostConfirmPrompt(session: GeckoSession, prompt: PromptDelegate.RepostConfirmPrompt): GeckoResult<PromptDelegate.PromptResponse>? { + // We have to return something here because otherwise the delegate will be invoked + // before we have a chance to override it in the waitUntilCalled call below + return forEachCall(promptResult, promptResult2) + } + }) + + // This should trigger a confirm resubmit prompt + mainSession.reload() + + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onRepostConfirmPrompt(session: GeckoSession, prompt: PromptDelegate.RepostConfirmPrompt): GeckoResult<PromptDelegate.PromptResponse>? { + promptResult.complete(prompt.confirm(AllowOrDeny.DENY)) + return promptResult + } + }) + + sessionRule.waitForResult(promptResult) + + // Trigger it again, this time the load should go through + mainSession.reload() + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onRepostConfirmPrompt(session: GeckoSession, prompt: PromptDelegate.RepostConfirmPrompt): GeckoResult<PromptDelegate.PromptResponse>? { + promptResult2.complete(prompt.confirm(AllowOrDeny.ALLOW)) + return promptResult2 + } + }) + + sessionRule.waitForResult(promptResult2) + sessionRule.waitForResult(result) + } + + @Test + @WithDisplay(width = 100, height = 100) + fun selectTestSimple() { + mainSession.loadTestPath(SELECT_HTML_PATH) + sessionRule.waitForPageStop() + + val result = GeckoResult<PromptDelegate.PromptResponse>() + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onChoicePrompt(session: GeckoSession, prompt: PromptDelegate.ChoicePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("Should not be multiple", prompt.type, equalTo(PromptDelegate.ChoicePrompt.Type.SINGLE)) + assertThat("There should be two choices", prompt.choices.size, equalTo(2)) + assertThat("First choice is correct", prompt.choices[0].label, equalTo("ABC")) + assertThat("Second choice is correct", prompt.choices[1].label, equalTo("DEF")) + result.complete(prompt.confirm(prompt.choices[1])) + return result + } + }) + + val promise = mainSession.evaluatePromiseJS( + """new Promise(function(resolve) { + let events = []; + // Record the events for testing purposes. + for (const t of ["change", "input"]) { + document.querySelector("select").addEventListener(t, function(e) { + events.push(e.type + "(composed=" + e.composed + ")"); + if (events.length == 2) { + resolve(events.join(" ")); + } + }); + } + })""", + ) + + mainSession.synthesizeTap(10, 10) + sessionRule.waitForResult(result) + assertThat( + "Events should be as expected", + promise.value as String, + equalTo("input(composed=true) change(composed=false)"), + ) + } + + @Test + @WithDisplay(width = 100, height = 100) + fun selectTestSize() { + mainSession.loadTestPath(SELECT_LISTBOX_HTML_PATH) + sessionRule.waitForPageStop() + + val result = GeckoResult<Void>() + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onChoicePrompt(session: GeckoSession, prompt: PromptDelegate.ChoicePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("Should not be multiple", prompt.type, equalTo(PromptDelegate.ChoicePrompt.Type.SINGLE)) + assertThat("There should be three choices", prompt.choices.size, equalTo(3)) + assertThat("First choice is correct", prompt.choices[0].label, equalTo("ABC")) + assertThat("Second choice is correct", prompt.choices[1].label, equalTo("DEF")) + assertThat("Third choice is correct", prompt.choices[2].label, equalTo("GHI")) + result.complete(null) + return null + } + }) + + mainSession.synthesizeTap(10, 10) + sessionRule.waitForResult(result) + } + + @Test + @WithDisplay(width = 100, height = 100) + fun selectTestMultiple() { + mainSession.loadTestPath(SELECT_MULTIPLE_HTML_PATH) + sessionRule.waitForPageStop() + + val result = GeckoResult<Void>() + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onChoicePrompt(session: GeckoSession, prompt: PromptDelegate.ChoicePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("Should be multiple", prompt.type, equalTo(PromptDelegate.ChoicePrompt.Type.MULTIPLE)) + assertThat("There should be three choices", prompt.choices.size, equalTo(3)) + assertThat("First choice is correct", prompt.choices[0].label, equalTo("ABC")) + assertThat("Second choice is correct", prompt.choices[1].label, equalTo("DEF")) + assertThat("Third choice is correct", prompt.choices[2].label, equalTo("GHI")) + result.complete(null) + return null + } + }) + + mainSession.synthesizeTap(10, 10) + sessionRule.waitForResult(result) + } + + @Test + @WithDisplay(width = 100, height = 100) + fun selectTestUpdate() { + mainSession.loadTestPath(SELECT_HTML_PATH) + sessionRule.waitForPageStop() + + val result = GeckoResult<PromptDelegate.PromptResponse>() + val promptInstanceDelegate = object : PromptDelegate.PromptInstanceDelegate { + override fun onPromptUpdate(prompt: PromptDelegate.BasePrompt) { + val newPrompt: PromptDelegate.ChoicePrompt = prompt as PromptDelegate.ChoicePrompt + assertThat("First choice is correct", newPrompt.choices[0].label, equalTo("foo")) + assertThat("Second choice is correct", newPrompt.choices[1].label, equalTo("bar")) + assertThat("Third choice is correct", newPrompt.choices[2].label, equalTo("baz")) + result.complete(prompt.confirm(newPrompt.choices[2])) + } + } + + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onChoicePrompt(session: GeckoSession, prompt: PromptDelegate.ChoicePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("There should be two choices", prompt.choices.size, equalTo(2)) + prompt.setDelegate(promptInstanceDelegate) + return result + } + }) + + mainSession.evaluateJS( + """ + document.querySelector("select").addEventListener("focus", () => { + window.setTimeout(() => { + document.querySelector("select").innerHTML = + "<option>foo</option><option>bar</option><option>baz</option>"; + }, 100); + }, { once: true }) + """.trimIndent(), + ) + + val promise = mainSession.evaluatePromiseJS( + """ + new Promise(resolve => { + document.querySelector("select").addEventListener("change", e => { + resolve(e.target.value); + }); + }) + """.trimIndent(), + ) + + mainSession.synthesizeTap(10, 10) + sessionRule.waitForResult(result) + assertThat( + "Selected item should be as expected", + promise.value as String, + equalTo("baz"), + ) + } + + @Test + @WithDisplay(width = 100, height = 100) + fun selectTestDismiss() { + mainSession.loadTestPath(SELECT_HTML_PATH) + sessionRule.waitForPageStop() + + val result = GeckoResult<PromptDelegate.PromptResponse>() + val promptInstanceDelegate = object : PromptDelegate.PromptInstanceDelegate { + override fun onPromptDismiss(prompt: PromptDelegate.BasePrompt) { + result.complete(prompt.dismiss()) + } + } + + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onChoicePrompt(session: GeckoSession, prompt: PromptDelegate.ChoicePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("There should be two choices", prompt.choices.size, equalTo(2)) + prompt.setDelegate(promptInstanceDelegate) + mainSession.evaluateJS("document.querySelector('select').blur()") + return result + } + }) + + mainSession.synthesizeTap(10, 10) + sessionRule.waitForResult(result) + } + + @Test + fun onBeforeUnloadTest() { + sessionRule.setPrefsUntilTestEnd( + mapOf( + "dom.require_user_interaction_for_beforeunload" to false, + ), + ) + mainSession.loadTestPath(BEFORE_UNLOAD) + sessionRule.waitForPageStop() + + val result = GeckoResult<Void>() + sessionRule.delegateUntilTestEnd(object : ProgressDelegate { + override fun onPageStart(session: GeckoSession, url: String) { + assertThat("Only HELLO2_HTML_PATH should load", url, endsWith(HELLO2_HTML_PATH)) + result.complete(null) + } + }) + + val promptResult = GeckoResult<PromptDelegate.PromptResponse>() + val promptResult2 = GeckoResult<PromptDelegate.PromptResponse>() + + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 2) + override fun onBeforeUnloadPrompt(session: GeckoSession, prompt: PromptDelegate.BeforeUnloadPrompt): GeckoResult<PromptDelegate.PromptResponse>? { + // We have to return something here because otherwise the delegate will be invoked + // before we have a chance to override it in the waitUntilCalled call below + return forEachCall(promptResult, promptResult2) + } + }) + + // This will try to load "hello.html" but will be denied, if the request + // goes through anyway the onLoadRequest delegate above will throw an exception + mainSession.evaluateJS("document.querySelector('#navigateAway').click()") + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onBeforeUnloadPrompt(session: GeckoSession, prompt: PromptDelegate.BeforeUnloadPrompt): GeckoResult<PromptDelegate.PromptResponse>? { + promptResult.complete(prompt.confirm(AllowOrDeny.DENY)) + return promptResult + } + }) + + sessionRule.waitForResult(promptResult) + + // Although onBeforeUnloadPrompt is done, nsDocumentViewer might not clear + // mInPermitUnloadPrompt flag at this time yet. We need a wait to finish + // "nsDocumentViewer::PermitUnload" loop. + mainSession.waitForJS("new Promise(resolve => window.setTimeout(resolve, 100))") + + // This request will go through and end the test. Doing the negative case first will + // ensure that if either of this tests fail the test will fail. + mainSession.evaluateJS("document.querySelector('#navigateAway2').click()") + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onBeforeUnloadPrompt(session: GeckoSession, prompt: PromptDelegate.BeforeUnloadPrompt): GeckoResult<PromptDelegate.PromptResponse>? { + promptResult2.complete(prompt.confirm(AllowOrDeny.ALLOW)) + return promptResult2 + } + }) + + sessionRule.waitForResult(promptResult2) + sessionRule.waitForResult(result) + } + + @Test fun textTest() { + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onTextPrompt(session: GeckoSession, prompt: PromptDelegate.TextPrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("Message should match", "Prompt:", equalTo(prompt.message)) + assertThat("Default should match", "default", equalTo(prompt.defaultValue)) + return GeckoResult.fromValue(prompt.confirm("foo")) + } + }) + + assertThat( + "Result should match", + mainSession.waitForJS("prompt('Prompt:', 'default')") as String, + equalTo("foo"), + ) + } + + @Test fun colorTest() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false)) + + mainSession.loadTestPath(PROMPT_HTML_PATH) + mainSession.waitForPageStop() + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onColorPrompt(session: GeckoSession, prompt: PromptDelegate.ColorPrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("Value should match", "#ffffff", equalTo(prompt.defaultValue)) + assertThat("Predefined values size", 0, equalTo(prompt.predefinedValues!!.size)) + return GeckoResult.fromValue(prompt.confirm("#123456")) + } + }) + + mainSession.evaluateJS( + """ + this.c = document.getElementById('colorexample'); + """.trimIndent(), + ) + + val promise = mainSession.evaluatePromiseJS( + """ + new Promise((resolve, reject) => { + this.c.addEventListener( + 'change', + event => resolve(event.target.value), + false + ); + }) + """.trimIndent(), + ) + + mainSession.evaluateJS("this.c.click();") + + assertThat( + "Value should match", + promise.value as String, + equalTo("#123456"), + ) + } + + @Test fun colorTestWithDatalist() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false)) + + mainSession.loadTestPath(PROMPT_HTML_PATH) + mainSession.waitForPageStop() + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onColorPrompt(session: GeckoSession, prompt: PromptDelegate.ColorPrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("Value should match", "#ffffff", equalTo(prompt.defaultValue)) + assertThat("Predefined values size", 2, equalTo(prompt.predefinedValues!!.size)) + assertThat("First predefined value", "#000000", equalTo(prompt.predefinedValues?.get(0))) + assertThat("Second predefined value", "#808080", equalTo(prompt.predefinedValues?.get(1))) + return GeckoResult.fromValue(prompt.confirm("#123456")) + } + }) + + mainSession.evaluateJS( + """ + this.c = document.getElementById('colorexample'); + this.c.setAttribute('list', 'colorlist'); + """.trimIndent(), + ) + + val promise = mainSession.evaluatePromiseJS( + """ + new Promise((resolve, reject) => { + this.c.addEventListener( + 'change', + event => resolve(event.target.value), + ); + }) + """.trimIndent(), + ) + mainSession.evaluateJS("this.c.click();") + + assertThat( + "Value should match", + promise.value as String, + equalTo("#123456"), + ) + } + + @WithDisplay(width = 100, height = 100) + @Test + fun dateTest() { + mainSession.loadTestPath(PROMPT_HTML_PATH) + mainSession.waitForPageStop() + + mainSession.evaluateJS( + """ + document.body.addEventListener("click", () => { + document.getElementById('dateexample').showPicker(); + }); + """.trimIndent(), + ) + + mainSession.synthesizeTap(1, 1) // Provides user activation. + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onDateTimePrompt(session: GeckoSession, prompt: PromptDelegate.DateTimePrompt): GeckoResult<PromptDelegate.PromptResponse> { + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + } + + @WithDisplay(width = 100, height = 100) + @Test + fun dateTestByTap() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false)) + + mainSession.loadTestPath(PROMPT_HTML_PATH) + mainSession.waitForPageStop() + + // By removing first element in PROMPT_HTML_PATH, dateexample becomes first element. + // + // TODO: What better calculation of element bounds for synthesizeTap? + mainSession.evaluateJS( + """ + document.getElementById('selectexample').remove(); + document.getElementById('dateexample').getBoundingClientRect(); + """.trimIndent(), + ) + mainSession.synthesizeTap(10, 10) + + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onDateTimePrompt(session: GeckoSession, prompt: PromptDelegate.DateTimePrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("<input type=date> is tapped", PromptDelegate.DateTimePrompt.Type.DATE, equalTo(prompt.type)) + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + } + + @WithDisplay(width = 100, height = 100) + @Test + fun monthTestByTap() { + // Gecko doesn't have the widget for <input type=month>. But GeckoView can show the picker. + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false)) + + mainSession.loadTestPath(PROMPT_HTML_PATH) + mainSession.waitForPageStop() + + // TODO: What better calculation of element bounds for synthesizeTap? + mainSession.evaluateJS( + """ + document.getElementById('selectexample').remove(); + document.getElementById('dateexample').remove(); + document.getElementById('weekexample').getBoundingClientRect(); + """.trimIndent(), + ) + mainSession.synthesizeTap(10, 10) + + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onDateTimePrompt(session: GeckoSession, prompt: PromptDelegate.DateTimePrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("<input type=month> is tapped", PromptDelegate.DateTimePrompt.Type.MONTH, equalTo(prompt.type)) + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + } + + @WithDisplay(width = 100, height = 100) + @Test + fun dateTestParameters() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false)) + + mainSession.loadTestPath(PROMPT_HTML_PATH) + mainSession.waitForPageStop() + + mainSession.evaluateJS( + """ + document.getElementById('selectexample').remove(); + document.getElementById('dateexample').min = "2022-01-01"; + document.getElementById('dateexample').max = "2022-12-31"; + document.getElementById('dateexample').step = "10"; + document.getElementById('dateexample').getBoundingClientRect(); + """.trimIndent(), + ) + mainSession.synthesizeTap(10, 10) + + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onDateTimePrompt(session: GeckoSession, prompt: PromptDelegate.DateTimePrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("<input type=date> is tapped", prompt.type, equalTo(PromptDelegate.DateTimePrompt.Type.DATE)) + assertThat("min value is exported", prompt.minValue, equalTo("2022-01-01")) + assertThat("max value is exported", prompt.maxValue, equalTo("2022-12-31")) + assertThat("step value is exported", prompt.stepValue, equalTo("10")) + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + } + + @WithDisplay(width = 100, height = 100) + @Test + fun dateTestDismiss() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false)) + + mainSession.loadTestPath(PROMPT_HTML_PATH) + mainSession.waitForPageStop() + + val result = GeckoResult<PromptDelegate.PromptResponse>() + val promptInstanceDelegate = object : PromptDelegate.PromptInstanceDelegate { + override fun onPromptDismiss(prompt: PromptDelegate.BasePrompt) { + result.complete(prompt.dismiss()) + } + } + + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onDateTimePrompt(session: GeckoSession, prompt: PromptDelegate.DateTimePrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("<input type=date> is tapped", prompt.type, equalTo(PromptDelegate.DateTimePrompt.Type.DATE)) + prompt.setDelegate(promptInstanceDelegate) + mainSession.evaluateJS("document.getElementById('dateexample').blur()") + return result + } + }) + + mainSession.evaluateJS("document.getElementById('selectexample').remove()") + mainSession.synthesizeTap(10, 10) + sessionRule.waitForResult(result) + } + + @WithDisplay(width = 100, height = 100) + @Test + fun monthTestDismiss() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false)) + + mainSession.loadTestPath(PROMPT_HTML_PATH) + mainSession.waitForPageStop() + + val result = GeckoResult<PromptDelegate.PromptResponse>() + val promptInstanceDelegate = object : PromptDelegate.PromptInstanceDelegate { + override fun onPromptDismiss(prompt: PromptDelegate.BasePrompt) { + result.complete(prompt.dismiss()) + } + } + + sessionRule.delegateUntilTestEnd(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onDateTimePrompt(session: GeckoSession, prompt: PromptDelegate.DateTimePrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("<input type=month> is tapped", prompt.type, equalTo(PromptDelegate.DateTimePrompt.Type.MONTH)) + prompt.setDelegate(promptInstanceDelegate) + mainSession.evaluateJS("document.getElementById('monthexample').blur()") + return result + } + }) + + mainSession.evaluateJS( + """ + document.getElementById('selectexample').remove(); + document.getElementById('dateexample').remove(); + """.trimIndent(), + ) + mainSession.synthesizeTap(10, 10) + sessionRule.waitForResult(result) + } + + @Test fun fileTest() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false)) + + mainSession.loadTestPath(PROMPT_HTML_PATH) + mainSession.waitForPageStop() + + mainSession.evaluateJS("document.getElementById('fileexample').click();") + + sessionRule.waitUntilCalled(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onFilePrompt(session: GeckoSession, prompt: PromptDelegate.FilePrompt): GeckoResult<PromptDelegate.PromptResponse> { + assertThat("Length of mimeTypes should match", 2, equalTo(prompt.mimeTypes!!.size)) + assertThat("First accept attribute should match", "image/*", equalTo(prompt.mimeTypes?.get(0))) + assertThat("Second accept attribute should match", ".pdf", equalTo(prompt.mimeTypes?.get(1))) + assertThat("Capture attribute should match", PromptDelegate.FilePrompt.Capture.USER, equalTo(prompt.capture)) + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + } + + @Test fun shareTextSucceeds() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + val shareText = "Example share text" + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onSharePrompt(session: GeckoSession, prompt: PromptDelegate.SharePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("Text field is not null", prompt.text, notNullValue()) + assertThat("Title field is null", prompt.title, nullValue()) + assertThat("Url field is null", prompt.uri, nullValue()) + assertThat("Text field contains correct value", prompt.text, equalTo(shareText)) + return GeckoResult.fromValue(prompt.confirm(PromptDelegate.SharePrompt.Result.SUCCESS)) + } + }) + + try { + mainSession.waitForJS("""window.navigator.share({text: "$shareText"})""") + } catch (e: GeckoSessionTestRule.RejectedPromiseException) { + Assert.fail("Share must succeed." + e.reason as String) + } + } + + @Test fun shareUrlSucceeds() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + val shareUrl = "https://example.com/" + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onSharePrompt(session: GeckoSession, prompt: PromptDelegate.SharePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("Text field is null", prompt.text, nullValue()) + assertThat("Title field is null", prompt.title, nullValue()) + assertThat("Url field is not null", prompt.uri, notNullValue()) + assertThat("Text field contains correct value", prompt.uri, equalTo(shareUrl)) + return GeckoResult.fromValue(prompt.confirm(PromptDelegate.SharePrompt.Result.SUCCESS)) + } + }) + + try { + mainSession.waitForJS("""window.navigator.share({url: "$shareUrl"})""") + } catch (e: GeckoSessionTestRule.RejectedPromiseException) { + Assert.fail("Share must succeed." + e.reason as String) + } + } + + @Test fun shareTitleSucceeds() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + val shareTitle = "Title!" + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onSharePrompt(session: GeckoSession, prompt: PromptDelegate.SharePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + assertThat("Text field is null", prompt.text, nullValue()) + assertThat("Title field is not null", prompt.title, notNullValue()) + assertThat("Url field is null", prompt.uri, nullValue()) + assertThat("Text field contains correct value", prompt.title, equalTo(shareTitle)) + return GeckoResult.fromValue(prompt.confirm(PromptDelegate.SharePrompt.Result.SUCCESS)) + } + }) + + try { + mainSession.waitForJS("""window.navigator.share({title: "$shareTitle"})""") + } catch (e: GeckoSessionTestRule.RejectedPromiseException) { + Assert.fail("Share must succeed." + e.reason as String) + } + } + + @Test fun failedShareReturnsDataError() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + val shareUrl = "https://www.example.com" + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onSharePrompt(session: GeckoSession, prompt: PromptDelegate.SharePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + return GeckoResult.fromValue(prompt.confirm(PromptDelegate.SharePrompt.Result.FAILURE)) + } + }) + + try { + mainSession.waitForJS("""window.navigator.share({url: "$shareUrl"})""") + Assert.fail("Request should have failed") + } catch (e: GeckoSessionTestRule.RejectedPromiseException) { + assertThat( + "Error should be correct", + e.reason as String, + containsString("DataError"), + ) + } + } + + @Test fun abortedShareReturnsAbortError() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + val shareUrl = "https://www.example.com" + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onSharePrompt(session: GeckoSession, prompt: PromptDelegate.SharePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + return GeckoResult.fromValue(prompt.confirm(PromptDelegate.SharePrompt.Result.ABORT)) + } + }) + + try { + mainSession.waitForJS("""window.navigator.share({url: "$shareUrl"})""") + Assert.fail("Request should have failed") + } catch (e: GeckoSessionTestRule.RejectedPromiseException) { + assertThat( + "Error should be correct", + e.reason as String, + containsString("AbortError"), + ) + } + } + + @Test fun dismissedShareReturnsAbortError() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + val shareUrl = "https://www.example.com" + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 1) + override fun onSharePrompt(session: GeckoSession, prompt: PromptDelegate.SharePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + + try { + mainSession.waitForJS("""window.navigator.share({url: "$shareUrl"})""") + Assert.fail("Request should have failed") + } catch (e: GeckoSessionTestRule.RejectedPromiseException) { + assertThat( + "Error should be correct", + e.reason as String, + containsString("AbortError"), + ) + } + } + + @Test fun emptyShareReturnsTypeError() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 0) + override fun onSharePrompt(session: GeckoSession, prompt: PromptDelegate.SharePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + + try { + mainSession.waitForJS("""window.navigator.share({})""") + Assert.fail("Request should have failed") + } catch (e: GeckoSessionTestRule.RejectedPromiseException) { + assertThat( + "Error should be correct", + e.reason as String, + containsString("TypeError"), + ) + } + } + + @Test fun invalidShareUrlReturnsTypeError() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to false)) + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + // Invalid port should cause URL parser to fail. + val shareUrl = "http://www.example.com:123456" + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 0) + override fun onSharePrompt(session: GeckoSession, prompt: PromptDelegate.SharePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + + try { + mainSession.waitForJS("""window.navigator.share({url: "$shareUrl"})""") + Assert.fail("Request should have failed") + } catch (e: GeckoSessionTestRule.RejectedPromiseException) { + assertThat( + "Error should be correct", + e.reason as String, + containsString("TypeError"), + ) + } + } + + @Test fun shareRequiresUserInteraction() { + sessionRule.setPrefsUntilTestEnd(mapOf("dom.webshare.requireinteraction" to true)) + mainSession.loadTestPath(HELLO_HTML_PATH) + mainSession.waitForPageStop() + + val shareUrl = "https://www.example.com" + + sessionRule.delegateDuringNextWait(object : PromptDelegate { + @AssertCalled(count = 0) + override fun onSharePrompt(session: GeckoSession, prompt: PromptDelegate.SharePrompt): GeckoResult<PromptDelegate.PromptResponse>? { + return GeckoResult.fromValue(prompt.dismiss()) + } + }) + + try { + mainSession.waitForJS("""window.navigator.share({url: "$shareUrl"})""") + Assert.fail("Request should have failed") + } catch (e: GeckoSessionTestRule.RejectedPromiseException) { + assertThat( + "Error should be correct", + e.reason as String, + containsString("NotAllowedError"), + ) + } + } +} |