From 19ee448a6ace5740057be89aeb6cbcbe96e967e0 Mon Sep 17 00:00:00 2001 From: Micah Geisel Date: Sun, 22 Sep 2024 14:47:05 +0200 Subject: [PATCH 1/3] morph frame external navigation and form submission when url doesn't change. --- src/core/frames/frame_controller.js | 11 ++++++--- src/tests/functional/frame_tests.js | 36 +++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/core/frames/frame_controller.js b/src/core/frames/frame_controller.js index 963e3190d..113b5cf9c 100644 --- a/src/core/frames/frame_controller.js +++ b/src/core/frames/frame_controller.js @@ -91,10 +91,9 @@ export class FrameController { } sourceURLReloaded() { - const { refresh, src } = this.element - - this.#shouldMorphFrame = src && refresh === "morph" + const { src } = this.element + this.#shouldMorphFrame = this.element.shouldReloadWithMorph this.element.removeAttribute("complete") this.element.src = null this.element.src = src @@ -234,6 +233,9 @@ export class FrameController { const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter) frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame)) + if (frame.src === response.response.url && frame.shouldReloadWithMorph) { + frame.delegate.#shouldMorphFrame = true + } frame.delegate.loadResponse(response) if (!formSubmission.isSafe) { @@ -343,6 +345,9 @@ export class FrameController { frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame)) this.#withCurrentNavigationElement(element, () => { + if (frame.src === url && frame.shouldReloadWithMorph) { + frame.delegate.#shouldMorphFrame = true + } frame.src = url }) } diff --git a/src/tests/functional/frame_tests.js b/src/tests/functional/frame_tests.js index 264024720..2a8d4beea 100644 --- a/src/tests/functional/frame_tests.js +++ b/src/tests/functional/frame_tests.js @@ -267,7 +267,7 @@ test("following a link to a page with a matching frame does not dispatch a turbo ) }) -test("following a link within a frame which has a target set navigates the target frame without morphing even when frame[refresh=morph]", async ({ page }) => { +test("following a link within a frame which has a target set navigates the target frame without morphing even when frame[refresh=morph] if the url changes", async ({ page }) => { await page.click("#add-refresh-morph-to-frame") await page.click("#hello a") await nextBeat() @@ -277,7 +277,7 @@ test("following a link within a frame which has a target set navigates the targe await expect(page.locator("#frame h2")).toHaveText("Frame: Loaded") }) -test("navigating from within replaces the contents even with turbo-frame[refresh=morph]", async ({ page }) => { +test("navigating from within replaces the contents even with turbo-frame[refresh=morph] if the url changes", async ({ page }) => { await page.click("#add-refresh-morph-to-frame") await page.click("#link-frame") await nextBeat() @@ -287,6 +287,38 @@ test("navigating from within replaces the contents even with turbo-frame[refresh await expect(page.locator("#frame h2")).toHaveText("Frame: Loaded") }) +test("following a link that targets a frame with refresh morph morphs the page if the url doesn't change", async ({ page }) => { + await page.click("#add-refresh-morph-to-frame") + await page.click("#outside-frame-form") + await nextBeat() + + expect(await nextEventOnTarget(page, "frame", "turbo:before-frame-render")).toBeTruthy() + expect(await noNextEventOnTarget(page, "frame", "turbo:before-frame-morph")).toBeTruthy() + + await page.click("#outside-frame-form") + await nextBeat() + + expect(await nextEventOnTarget(page, "frame", "turbo:before-frame-morph")).toBeTruthy() + expect(await noNextEventOnTarget(page, "frame", "turbo:before-frame-render")).toBeTruthy() +}) + +test("submitting a form that targets a frame with refresh morph morphs the page if the url doesn't change", async ({ page }) => { + test.setTimeout(3000) + expect(await noNextEventOnTarget(page, "frame", "turbo:before-frame-render")).toBeTruthy() + await page.click("#add-refresh-morph-to-frame") + await page.click("#button-frame-action-advance") + await nextBeat() + + expect(await nextEventOnTarget(page, "frame", "turbo:before-frame-render")).toBeTruthy() + expect(await noNextEventOnTarget(page, "frame", "turbo:before-frame-morph")).toBeTruthy() + + await page.click("#button-frame-action-advance") + await nextBeat() + + expect(await nextEventOnTarget(page, "frame", "turbo:before-frame-morph")).toBeTruthy() + expect(await noNextEventOnTarget(page, "frame", "turbo:before-frame-render")).toBeTruthy() +}) + test("calling reload on a frame replaces the contents", async ({ page }) => { await page.click("#add-src-to-frame") From 966dddaf399ea92a11685e90a455efd7a0d82b97 Mon Sep 17 00:00:00 2001 From: Micah Geisel Date: Thu, 26 Sep 2024 12:05:09 +0200 Subject: [PATCH 2/3] compare pathnames rather than full urls, to be consistent with how pages detect a refresh vs a visit. --- src/core/frames/frame_controller.js | 6 +++--- src/core/url.js | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/frames/frame_controller.js b/src/core/frames/frame_controller.js index 113b5cf9c..441017dda 100644 --- a/src/core/frames/frame_controller.js +++ b/src/core/frames/frame_controller.js @@ -14,7 +14,7 @@ import { } from "../../util" import { FormSubmission } from "../drive/form_submission" import { Snapshot } from "../snapshot" -import { getAction, expandURL, urlsAreEqual, locationIsVisitable } from "../url" +import { getAction, expandURL, urlsAreEqual, pathnamesAreEqual, locationIsVisitable } from "../url" import { FormSubmitObserver } from "../../observers/form_submit_observer" import { FrameView } from "./frame_view" import { LinkInterceptor } from "./link_interceptor" @@ -233,7 +233,7 @@ export class FrameController { const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter) frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame)) - if (frame.src === response.response.url && frame.shouldReloadWithMorph) { + if (frame.src && pathnamesAreEqual(frame.src, response.response.url) && frame.shouldReloadWithMorph) { frame.delegate.#shouldMorphFrame = true } frame.delegate.loadResponse(response) @@ -345,7 +345,7 @@ export class FrameController { frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame)) this.#withCurrentNavigationElement(element, () => { - if (frame.src === url && frame.shouldReloadWithMorph) { + if (frame.src && pathnamesAreEqual(frame.src, url) && frame.shouldReloadWithMorph) { frame.delegate.#shouldMorphFrame = true } frame.src = url diff --git a/src/core/url.js b/src/core/url.js index ec7955fad..c0e027a21 100644 --- a/src/core/url.js +++ b/src/core/url.js @@ -46,6 +46,10 @@ export function urlsAreEqual(left, right) { return expandURL(left).href == expandURL(right).href } +export function pathnamesAreEqual(left, right) { + return expandURL(left).pathname == expandURL(right).pathname +} + function getPathComponents(url) { return url.pathname.split("/").slice(1) } From 064357cc7b14ed44b73d7c1920f54a27704b0409 Mon Sep 17 00:00:00 2001 From: Micah Geisel Date: Thu, 26 Sep 2024 12:33:43 +0200 Subject: [PATCH 3/3] extract duplication. --- src/core/frames/frame_controller.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/frames/frame_controller.js b/src/core/frames/frame_controller.js index 441017dda..33dedd2ac 100644 --- a/src/core/frames/frame_controller.js +++ b/src/core/frames/frame_controller.js @@ -233,9 +233,7 @@ export class FrameController { const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter) frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame)) - if (frame.src && pathnamesAreEqual(frame.src, response.response.url) && frame.shouldReloadWithMorph) { - frame.delegate.#shouldMorphFrame = true - } + frame.delegate.decideOnMorphingTo(response.response.url) frame.delegate.loadResponse(response) if (!formSubmission.isSafe) { @@ -345,13 +343,17 @@ export class FrameController { frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame)) this.#withCurrentNavigationElement(element, () => { - if (frame.src && pathnamesAreEqual(frame.src, url) && frame.shouldReloadWithMorph) { - frame.delegate.#shouldMorphFrame = true - } + frame.delegate.decideOnMorphingTo(url) frame.src = url }) } + decideOnMorphingTo(url) { + if (this.element.src && pathnamesAreEqual(this.element.src, url) && this.element.shouldReloadWithMorph) { + this.#shouldMorphFrame = true + } + } + proposeVisitIfNavigatedWithAction(frame, action = null) { this.action = action