diff --git a/src/observers/link_prefetch_observer.js b/src/observers/link_prefetch_observer.js index 5f5f66d43..9ce4567da 100644 --- a/src/observers/link_prefetch_observer.js +++ b/src/observers/link_prefetch_observer.js @@ -2,7 +2,8 @@ import { getLocationForLink } from "../core/url" import { dispatch, getMetaContent, - findClosestRecursively + findClosestRecursively, + getLinkHrefString } from "../util" import { FetchMethod, FetchRequest } from "../http/fetch_request" @@ -158,7 +159,7 @@ const unfetchableLink = (link) => { } const linkToTheSamePage = (link) => { - return (link.pathname + link.search === document.location.pathname + document.location.search) || link.href.startsWith("#") + return (link.pathname + link.search === document.location.pathname + document.location.search) || getLinkHrefString(link).startsWith("#") } const linkOptsOut = (link) => { diff --git a/src/tests/fixtures/hover_to_prefetch.html b/src/tests/fixtures/hover_to_prefetch.html index bd0f6f944..1873cb665 100644 --- a/src/tests/fixtures/hover_to_prefetch.html +++ b/src/tests/fixtures/hover_to_prefetch.html @@ -42,6 +42,12 @@ Hover to prefetch me Won't prefetch when hovering me + Won't prefetch when hovering me Won't prefetch when hovering me diff --git a/src/tests/fixtures/navigation.html b/src/tests/fixtures/navigation.html index 81e98793b..15fd1e5af 100644 --- a/src/tests/fixtures/navigation.html +++ b/src/tests/fixtures/navigation.html @@ -48,6 +48,7 @@
Same-origin data-turbo-method=get link
diff --git a/src/tests/functional/link_prefetch_observer_tests.js b/src/tests/functional/link_prefetch_observer_tests.js index a40ea16dc..520a6eeba 100644 --- a/src/tests/functional/link_prefetch_observer_tests.js +++ b/src/tests/functional/link_prefetch_observer_tests.js @@ -121,6 +121,11 @@ test("it doesn't prefetch the page when link has a hash as a href", async ({ pag await assertNotPrefetchedOnHover({ page, selector: "#anchor_with_hash" }) }) +test("it doesn't prefetch the page when SVG link has a hash as a href", async ({ page }) => { + await goTo({ page, path: "/hover_to_prefetch.html" }) + await assertNotPrefetchedOnHover({ page, selector: "#anchor_with_hash_inside_svg" }) +}) + test("it doesn't prefetch the page when link has a ftp protocol", async ({ page }) => { await goTo({ page, path: "/hover_to_prefetch.html" }) await assertNotPrefetchedOnHover({ page, selector: "#anchor_with_ftp_protocol" }) diff --git a/src/tests/functional/navigation_tests.js b/src/tests/functional/navigation_tests.js index 25d11b887..5017ca970 100644 --- a/src/tests/functional/navigation_tests.js +++ b/src/tests/functional/navigation_tests.js @@ -310,6 +310,22 @@ test("following a cross-origin link inside an SVG element", async ({ page }) => expect(await visitAction(page)).toEqual("load") }) +test("following a same-origin SVG link", async ({ page }) => { + await page.click("#same-origin-link-inside-svg-element") + + await expect(page).toHaveURL(withPathname("/src/tests/fixtures/one.html")) + expect(await visitAction(page)).toEqual("load") +}) + +test("following a same-origin SVG anchored link", async ({ page }) => { + await page.click("#same-origin-anchored-svg-link") + + await expect(page).toHaveURL(withPathname("/src/tests/fixtures/navigation.html")) + await expect(page).toHaveURL(withHash("#main")) + expect(await visitAction(page)).toEqual("load") + expect(await isScrolledToSelector(page, "#main"), "scrolled to #main").toEqual(true) +}) + test("clicking the back button", async ({ page }) => { await page.click("#same-origin-unannotated-link") await nextEventNamed(page, "turbo:load") diff --git a/src/util.js b/src/util.js index 9a50cb387..c9ce9362a 100644 --- a/src/util.js +++ b/src/util.js @@ -249,11 +249,21 @@ export function doesNotTargetIFrame(name) { } } +/** + * Returns consistently the href attribute value as a string for both HTMLAnchorElement and SVGAElement. + * HTMLAnchorElement href property returns an absolute URL if the attribute contains a valid relative URL. + * SVGAElement exposes href as SVGAnimatedString which does not implement String methods. + * getAttribute() will return the proper value of the attribute in both cases. + */ +export function getLinkHrefString(link) { + return link.getAttribute("href") ?? link.getAttribute("xlink:href") ?? "" +} + export function findLinkFromClickTarget(target) { const link = findClosestRecursively(target, "a[href], a[xlink\\:href]") if (!link) return null - if (link.href.startsWith("#")) return null + if (getLinkHrefString(link).startsWith("#")) return null if (link.hasAttribute("download")) return null const linkTarget = link.getAttribute("target")