diff --git a/assets/js/phoenix_live_view/live_socket.js b/assets/js/phoenix_live_view/live_socket.js
index 1052d60b14..f09cb3b155 100644
--- a/assets/js/phoenix_live_view/live_socket.js
+++ b/assets/js/phoenix_live_view/live_socket.js
@@ -362,7 +362,11 @@ export default class LiveSocket {
view.setHref(this.getHref())
view.joinDead()
if(!this.main){ this.main = view }
- window.requestAnimationFrame(() => view.execNewMounted())
+ window.requestAnimationFrame(() => {
+ view.execNewMounted()
+ // restore scroll position when navigating from an external / non-live page
+ this.maybeScroll(history.state?.scroll)
+ })
}
}
diff --git a/test/e2e/support/navigation.ex b/test/e2e/support/navigation.ex
index 70e33f45a4..fd8b90703a 100644
--- a/test/e2e/support/navigation.ex
+++ b/test/e2e/support/navigation.ex
@@ -43,6 +43,10 @@ defmodule Phoenix.LiveViewTest.E2E.Navigation.Layout do
<.link navigate="/stream" style="background-color: #f1f5f9; padding: 0.5rem;">
LiveView (other session)
+
+ <.link navigate="/navigation/dead" style="background-color: #f1f5f9; padding: 0.5rem;">
+ Dead View
+
@@ -165,3 +169,25 @@ defmodule Phoenix.LiveViewTest.E2E.Navigation.BLive do
"""
end
end
+
+defmodule Phoenix.LiveViewTest.E2E.Navigation.Dead do
+ use Phoenix.Controller, formats: [:html], layouts: [html: {Phoenix.LiveViewTest.E2E.Navigation.Layout, :live}]
+ import Phoenix.Component, only: [sigil_H: 2]
+
+ def index(conn, _params) do
+ assigns = %{}
+
+ conn
+ |> render(:index)
+ end
+end
+
+defmodule Phoenix.LiveViewTest.E2E.Navigation.DeadHTML do
+ use Phoenix.Component
+
+ def index(assigns) do
+ ~H"""
+
Dead view
+ """
+ end
+end
diff --git a/test/e2e/test_helper.exs b/test/e2e/test_helper.exs
index f599861699..1e4449f7b3 100644
--- a/test/e2e/test_helper.exs
+++ b/test/e2e/test_helper.exs
@@ -161,6 +161,7 @@ defmodule Phoenix.LiveViewTest.E2E.Router do
live "/a", Phoenix.LiveViewTest.E2E.Navigation.ALive
live "/b", Phoenix.LiveViewTest.E2E.Navigation.BLive, :index
live "/b/:id", Phoenix.LiveViewTest.E2E.Navigation.BLive, :show
+ get "/dead", Phoenix.LiveViewTest.E2E.Navigation.Dead, :index
end
end
diff --git a/test/e2e/tests/navigation.spec.js b/test/e2e/tests/navigation.spec.js
index b3b735915d..13a95597ad 100644
--- a/test/e2e/tests/navigation.spec.js
+++ b/test/e2e/tests/navigation.spec.js
@@ -226,6 +226,33 @@ test("scrolls hash el into view after live navigation (issue #3452)", async ({pa
expect(scrollTop).toBeLessThanOrEqual(offset + 500)
})
+test("restores scroll position when navigating from dead view", async ({page}) => {
+ await page.goto("/navigation/b")
+ await syncLV(page)
+
+ await expect(page.locator("#items")).toContainText("Item 42")
+
+ expect(await page.evaluate(() => document.documentElement.scrollTop)).toEqual(0)
+ const offset = (await page.locator("#items-item-42").evaluate((el) => el.offsetTop)) - 200
+ await page.evaluate((offset) => window.scrollTo(0, offset), offset)
+ // LiveView only updates the scroll position every 100ms
+ await page.waitForTimeout(150)
+
+ await page.getByRole("link", {name: "Dead"}).click()
+ await page.waitForURL("/navigation/dead")
+
+ await page.goBack()
+ await syncLV(page)
+
+ // scroll position is restored
+ await expect.poll(
+ async () => {
+ return await page.evaluate(() => document.documentElement.scrollTop)
+ },
+ {message: "scrollTop not restored", timeout: 5000}
+ ).toBe(offset)
+})
+
test("navigating all the way back works without remounting (only patching)", async ({page}) => {
await page.goto("/navigation/a")
await syncLV(page)