diff --git a/src/core/session.js b/src/core/session.js
index cdb978348..0c33a5513 100644
--- a/src/core/session.js
+++ b/src/core/session.js
@@ -427,6 +427,8 @@ export class Session {
submissionIsNavigatable(form, submitter) {
if (this.formMode == "off") {
return false
+ } else if (this.submitterReferencesNavigatable(form, submitter)) {
+ return true
} else {
const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true
@@ -438,6 +440,13 @@ export class Session {
}
}
+ submitterReferencesNavigatable(form, submitter) {
+ const formReference = submitter.hasAttribute("form")
+ const optedOut = submitter.getAttribute("data-turbo") == "false"
+
+ return formReference && !optedOut && this.elementIsNavigatable(form)
+ }
+
elementIsNavigatable(element) {
const container = findClosestRecursively(element, "[data-turbo]")
const withinFrame = findClosestRecursively(element, "turbo-frame")
diff --git a/src/tests/fixtures/form_turbo_optin_submitter.html b/src/tests/fixtures/form_turbo_optin_submitter.html
new file mode 100644
index 000000000..caa7e8ad3
--- /dev/null
+++ b/src/tests/fixtures/form_turbo_optin_submitter.html
@@ -0,0 +1,17 @@
+
+
+
+
+ Form
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/functional/form_turbo_option_submitter_tests.js b/src/tests/functional/form_turbo_option_submitter_tests.js
new file mode 100644
index 000000000..e2874adae
--- /dev/null
+++ b/src/tests/functional/form_turbo_option_submitter_tests.js
@@ -0,0 +1,38 @@
+import { test } from "@playwright/test"
+import { assert } from "chai"
+import { getFromLocalStorage, nextEventNamed, readEventLogs, setLocalStorageFromEvent } from "../helpers/page"
+
+test.beforeEach(async ({ page }) => {
+ await page.goto("/src/tests/fixtures/form_turbo_optin_submitter.html")
+ await setLocalStorageFromEvent(page, "turbo:submit-start", "formSubmitStarted", "true")
+ await setLocalStorageFromEvent(page, "turbo:submit-end", "formSubmitEnded", "true")
+ await readEventLogs(page)
+})
+
+test("allows submitting an associated data-turbo=true form without data-turbo=true on submitter", async ({ page }) => {
+ await page.click("#default-behavior-submit")
+
+ assert.ok(await formSubmitStarted(page), "fires turbo:submit-start")
+
+ const { fetchOptions } = await nextEventNamed(page, "turbo:before-fetch-request")
+
+ assert.ok(fetchOptions.headers["Accept"].includes("text/vnd.turbo-stream.html"))
+
+ await nextEventNamed(page, "turbo:before-fetch-response")
+
+ assert.ok(await formSubmitEnded(page), "fires turbo:submit-end")
+})
+
+test("prevents submitting an associated data-turbo=true form when explicitly opted out", async ({ page }) => {
+ await page.click("#opted-out-submit")
+
+ assert.notOk(await formSubmitStarted(page), "fires turbo:submit-start")
+})
+
+function formSubmitStarted(page) {
+ return getFromLocalStorage(page, "formSubmitStarted")
+}
+
+function formSubmitEnded(page) {
+ return getFromLocalStorage(page, "formSubmitEnded")
+}