From 273456ad345ca10577551e591798aff6cde14b5a Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Mon, 4 Dec 2023 21:42:28 -0700
Subject: [PATCH 01/42] Move doesNotTargetIFrame to util.js
---
src/observers/link_click_observer.js | 16 ++--------------
src/util.js | 12 ++++++++++++
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/observers/link_click_observer.js b/src/observers/link_click_observer.js
index e6bd2fcaf..d02b73810 100644
--- a/src/observers/link_click_observer.js
+++ b/src/observers/link_click_observer.js
@@ -1,5 +1,5 @@
import { expandURL } from "../core/url"
-import { findClosestRecursively } from "../util"
+import { findClosestRecursively, doesNotTargetIFrame } from "../util"
export class LinkClickObserver {
started = false
@@ -61,16 +61,4 @@ export class LinkClickObserver {
getLocationForLink(link) {
return expandURL(link.getAttribute("href") || "")
}
-}
-
-function doesNotTargetIFrame(anchor) {
- if (anchor.hasAttribute("target")) {
- for (const element of document.getElementsByName(anchor.target)) {
- if (element instanceof HTMLIFrameElement) return false
- }
-
- return true
- } else {
- return true
- }
-}
+}
\ No newline at end of file
diff --git a/src/util.js b/src/util.js
index 1dcf943fb..b1bfb1220 100644
--- a/src/util.js
+++ b/src/util.js
@@ -215,3 +215,15 @@ export async function around(callback, reader) {
return [before, after]
}
+
+export function doesNotTargetIFrame(anchor) {
+ if (anchor.hasAttribute("target")) {
+ for (const element of document.getElementsByName(anchor.target)) {
+ if (element instanceof HTMLIFrameElement) return false
+ }
+
+ return true
+ } else {
+ return true
+ }
+}
\ No newline at end of file
From 1df1428686722079c2326d49a6a8d9b5b8ed07f4 Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Tue, 5 Dec 2023 09:09:03 -0700
Subject: [PATCH 02/42] Move findLinkFromClickTarget to util.js
---
src/observers/link_click_observer.js | 8 ++------
src/util.js | 8 +++++++-
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/src/observers/link_click_observer.js b/src/observers/link_click_observer.js
index d02b73810..6a11ebf69 100644
--- a/src/observers/link_click_observer.js
+++ b/src/observers/link_click_observer.js
@@ -1,5 +1,5 @@
import { expandURL } from "../core/url"
-import { findClosestRecursively, doesNotTargetIFrame } from "../util"
+import { findLinkFromClickTarget, doesNotTargetIFrame } from "../util"
export class LinkClickObserver {
started = false
@@ -31,7 +31,7 @@ export class LinkClickObserver {
clickBubbled = (event) => {
if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {
const target = (event.composedPath && event.composedPath()[0]) || event.target
- const link = this.findLinkFromClickTarget(target)
+ const link = findLinkFromClickTarget(target)
if (link && doesNotTargetIFrame(link)) {
const location = this.getLocationForLink(link)
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
@@ -54,10 +54,6 @@ export class LinkClickObserver {
)
}
- findLinkFromClickTarget(target) {
- return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])")
- }
-
getLocationForLink(link) {
return expandURL(link.getAttribute("href") || "")
}
diff --git a/src/util.js b/src/util.js
index b1bfb1220..41758a5ab 100644
--- a/src/util.js
+++ b/src/util.js
@@ -1,3 +1,5 @@
+import { expandURL } from "./core/url"
+
export function activateScriptElement(element) {
if (element.getAttribute("data-turbo-eval") == "false") {
return element
@@ -226,4 +228,8 @@ export function doesNotTargetIFrame(anchor) {
} else {
return true
}
-}
\ No newline at end of file
+}
+
+export function findLinkFromClickTarget(target) {
+ return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])")
+}
From 2f8c272214a599fe560d598d404ea7f896eef200 Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Tue, 5 Dec 2023 09:09:28 -0700
Subject: [PATCH 03/42] Move getLocationForLink to util.js
---
src/observers/link_click_observer.js | 9 ++-------
src/util.js | 4 ++++
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/observers/link_click_observer.js b/src/observers/link_click_observer.js
index 6a11ebf69..29148ad3d 100644
--- a/src/observers/link_click_observer.js
+++ b/src/observers/link_click_observer.js
@@ -1,5 +1,4 @@
-import { expandURL } from "../core/url"
-import { findLinkFromClickTarget, doesNotTargetIFrame } from "../util"
+import { doesNotTargetIFrame, findLinkFromClickTarget, getLocationForLink } from "../util"
export class LinkClickObserver {
started = false
@@ -33,7 +32,7 @@ export class LinkClickObserver {
const target = (event.composedPath && event.composedPath()[0]) || event.target
const link = findLinkFromClickTarget(target)
if (link && doesNotTargetIFrame(link)) {
- const location = this.getLocationForLink(link)
+ const location = getLocationForLink(link)
if (this.delegate.willFollowLinkToLocation(link, location, event)) {
event.preventDefault()
this.delegate.followedLinkToLocation(link, location)
@@ -53,8 +52,4 @@ export class LinkClickObserver {
event.shiftKey
)
}
-
- getLocationForLink(link) {
- return expandURL(link.getAttribute("href") || "")
- }
}
\ No newline at end of file
diff --git a/src/util.js b/src/util.js
index 41758a5ab..68d828f67 100644
--- a/src/util.js
+++ b/src/util.js
@@ -233,3 +233,7 @@ export function doesNotTargetIFrame(anchor) {
export function findLinkFromClickTarget(target) {
return findClosestRecursively(target, "a[href]:not([target^=_]):not([download])")
}
+
+export function getLocationForLink(link) {
+ return expandURL(link.getAttribute("href") || "")
+}
\ No newline at end of file
From de3ffcfc4d263b5e841ed0c34caf5ae419d2ab98 Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Sun, 3 Dec 2023 15:07:17 -0600
Subject: [PATCH 04/42] Allow request to be intercepted and overriden on
turbo:before-fetch-request
---
src/http/fetch_request.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/http/fetch_request.js b/src/http/fetch_request.js
index 3d79f24ae..6f5080c68 100644
--- a/src/http/fetch_request.js
+++ b/src/http/fetch_request.js
@@ -121,10 +121,10 @@ export class FetchRequest {
async perform() {
const { fetchOptions } = this
this.delegate.prepareRequest(this)
- await this.#allowRequestToBeIntercepted(fetchOptions)
+ const event = await this.#allowRequestToBeIntercepted(fetchOptions)
try {
this.delegate.requestStarted(this)
- const response = await fetch(this.url.href, fetchOptions)
+ const response = await (event.detail.response || fetch(this.url.href, fetchOptions))
return await this.receive(response)
} catch (error) {
if (error.name !== "AbortError") {
@@ -186,6 +186,8 @@ export class FetchRequest {
})
this.url = event.detail.url
if (event.defaultPrevented) await requestInterception
+
+ return event
}
#willDelegateErrorHandling(error) {
From 94ff0e1562213d63bfa5b577e367da3904c112d9 Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Mon, 4 Dec 2023 21:42:51 -0700
Subject: [PATCH 05/42] Add instantclick behavior
---
src/core/drive/prefetch_cache.js | 3 +
src/core/session.js | 49 ++++++
src/observers/form_link_click_observer.js | 10 ++
.../link_prefetch_on_mouseover_observer.js | 111 +++++++++++++
src/tests/fixtures/hover_to_prefetch.html | 42 +++++
.../fixtures/hover_to_prefetch_disabled.html | 13 ++
.../fixtures/hover_to_prefetch_iframe.html | 13 ++
src/tests/fixtures/prefetched.html | 12 ++
...nk_prefetch_on_mouseover_observer_tests.js | 153 ++++++++++++++++++
9 files changed, 406 insertions(+)
create mode 100644 src/core/drive/prefetch_cache.js
create mode 100644 src/observers/link_prefetch_on_mouseover_observer.js
create mode 100644 src/tests/fixtures/hover_to_prefetch.html
create mode 100644 src/tests/fixtures/hover_to_prefetch_disabled.html
create mode 100644 src/tests/fixtures/hover_to_prefetch_iframe.html
create mode 100644 src/tests/fixtures/prefetched.html
create mode 100644 src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
diff --git a/src/core/drive/prefetch_cache.js b/src/core/drive/prefetch_cache.js
new file mode 100644
index 000000000..eaf33d4a9
--- /dev/null
+++ b/src/core/drive/prefetch_cache.js
@@ -0,0 +1,3 @@
+export const prefetchCache = new Map()
+
+export const cacheTtl = 10 * 1000
diff --git a/src/core/session.js b/src/core/session.js
index be28a8287..620cbad4b 100644
--- a/src/core/session.js
+++ b/src/core/session.js
@@ -3,6 +3,7 @@ import { CacheObserver } from "../observers/cache_observer"
import { FormSubmitObserver } from "../observers/form_submit_observer"
import { FrameRedirector } from "./frames/frame_redirector"
import { History } from "./drive/history"
+import { LinkPrefetchOnMouseoverObserver } from "../observers/link_prefetch_on_mouseover_observer"
import { LinkClickObserver } from "../observers/link_click_observer"
import { FormLinkClickObserver } from "../observers/form_link_click_observer"
import { getAction, expandURL, locationIsVisitable } from "./url"
@@ -17,6 +18,7 @@ import { PageView } from "./drive/page_view"
import { FrameElement } from "../elements/frame_element"
import { Preloader } from "./drive/preloader"
import { Cache } from "./cache"
+import { prefetchCache, cacheTtl } from "./drive/prefetch_cache"
export class Session {
navigator = new Navigator(this)
@@ -27,6 +29,7 @@ export class Session {
pageObserver = new PageObserver(this)
cacheObserver = new CacheObserver()
+ linkPrefetchOnMouseoverObserver = new LinkPrefetchOnMouseoverObserver(this, document)
linkClickObserver = new LinkClickObserver(this, window)
formSubmitObserver = new FormSubmitObserver(this, document)
scrollObserver = new ScrollObserver(this)
@@ -50,6 +53,7 @@ export class Session {
if (!this.started) {
this.pageObserver.start()
this.cacheObserver.start()
+ this.linkPrefetchOnMouseoverObserver.start()
this.formLinkClickObserver.start()
this.linkClickObserver.start()
this.formSubmitObserver.start()
@@ -71,6 +75,7 @@ export class Session {
if (this.started) {
this.pageObserver.stop()
this.cacheObserver.stop()
+ this.linkPrefetchOnMouseoverObserver.stop()
this.formLinkClickObserver.stop()
this.linkClickObserver.stop()
this.formSubmitObserver.stop()
@@ -166,6 +171,50 @@ export class Session {
submittedFormLinkToLocation() {}
+ // Link hover observer delegate
+
+ canPrefetchAndCacheRequestToLocation(link, location, event) {
+ const absoluteUrl = location.toString()
+ const cached = prefetchCache.get(absoluteUrl)
+
+ return (
+ this.elementIsNavigatable(link) &&
+ locationIsVisitable(location, this.snapshot.rootLocation) &&
+ (!cached || cached.ttl < new Date())
+ )
+ }
+
+ prefetchAndCacheRequestToLocation(link, location) {
+ const requestOptions = {
+ credentials: "same-origin",
+ headers: {
+ "Sec-Purpose": "prefetch",
+ Accept: "text/html"
+ },
+ redirect: "follow"
+ }
+
+ if (
+ link.dataset.turboFrame &&
+ link.dataset.turboFrame !== "_top"
+ ) {
+ requestOptions.headers["Turbo-Frame"] = link.dataset.turboFrame
+ } else if (link.dataset.turboFrame !== "_top") {
+ const turboFrame = link.closest("turbo-frame")
+
+ if (turboFrame) {
+ requestOptions.headers["Turbo-Frame"] = turboFrame.id
+ }
+ }
+
+ const absoluteUrl = location.toString()
+
+ prefetchCache.set(absoluteUrl, {
+ request: fetch(absoluteUrl, requestOptions),
+ ttl: new Date(new Date().getTime() + cacheTtl)
+ })
+ }
+
// Link click observer delegate
willFollowLinkToLocation(link, location, event) {
diff --git a/src/observers/form_link_click_observer.js b/src/observers/form_link_click_observer.js
index 55b94bb64..bf8d22e68 100644
--- a/src/observers/form_link_click_observer.js
+++ b/src/observers/form_link_click_observer.js
@@ -15,6 +15,16 @@ export class FormLinkClickObserver {
this.linkInterceptor.stop()
}
+ // Link hover observer delegate
+
+ canPrefetchAndCacheRequestToLocation(link, location, event) {
+ return false
+ }
+
+ prefetchAndCacheRequestToLocation(link, location) {
+ return
+ }
+
// Link click observer delegate
willFollowLinkToLocation(link, location, originalEvent) {
diff --git a/src/observers/link_prefetch_on_mouseover_observer.js b/src/observers/link_prefetch_on_mouseover_observer.js
new file mode 100644
index 000000000..6bae75fb5
--- /dev/null
+++ b/src/observers/link_prefetch_on_mouseover_observer.js
@@ -0,0 +1,111 @@
+import { doesNotTargetIFrame, findLinkFromClickTarget, getLocationForLink, getMetaContent, findClosestRecursively } from "../util"
+import { prefetchCache } from "../core/drive/prefetch_cache"
+
+export class LinkPrefetchOnMouseoverObserver {
+ started = false
+
+ constructor(delegate, eventTarget) {
+ this.delegate = delegate
+ this.eventTarget = eventTarget
+ }
+
+ start() {
+ if (this.started) return
+
+ if (this.eventTarget.readyState === "loading") {
+ this.eventTarget.addEventListener("DOMContentLoaded", this._enable, { once: true })
+ } else {
+ this._enable()
+ }
+ }
+
+ stop() {
+ if (!this.started) return
+
+ this.eventTarget.removeEventListener("mouseover", this.tryToPrefetchRequest, {
+ capture: true,
+ passive: true
+ })
+ this.eventTarget.removeEventListener("turbo:before-fetch-request", this.tryToUsePrefetchedRequest, true)
+ this.started = false
+ }
+
+ _enable = () => {
+ if (getMetaContent("turbo-prefetch") !== "true") return
+
+ this.eventTarget.addEventListener("mouseover", this.tryToPrefetchRequest, {
+ capture: true,
+ passive: true
+ })
+ this.eventTarget.addEventListener("turbo:before-fetch-request", this.tryToUsePrefetchedRequest, true)
+ this.started = true
+ }
+
+ tryToPrefetchRequest = (event) => {
+ const target = event.target
+ const link = findLinkFromClickTarget(target)
+
+ if (link && this.isPrefetchable(link)) {
+ const location = getLocationForLink(link)
+ if (this.delegate.canPrefetchAndCacheRequestToLocation(link, location, event)) {
+ this.delegate.prefetchAndCacheRequestToLocation(link, location)
+ }
+ }
+ }
+
+ tryToUsePrefetchedRequest = (event) => {
+ if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "get") {
+ const cached = prefetchCache.get(event.detail.url.toString())
+
+ if (cached && cached.ttl > new Date()) {
+ event.detail.response = cached.request
+ }
+ }
+
+ prefetchCache.clear()
+ }
+
+ isPrefetchable(link) {
+ const href = link.getAttribute("href")
+
+ if (!href || href === "#" || link.dataset.turbo === "false" || link.dataset.turboPrefetch === "false") {
+ return false
+ }
+
+ if (link.origin !== document.location.origin) {
+ return false
+ }
+
+ if (!["http:", "https:"].includes(link.protocol)) {
+ return false
+ }
+
+ if (link.pathname + link.search === document.location.pathname + document.location.search) {
+ return false
+ }
+
+ if (link.dataset.turboMethod && link.dataset.turboMethod !== "get") {
+ return false
+ }
+
+ if (targetsIframe(link)) {
+ return false
+ }
+
+ if (link.pathname + link.search === document.location.pathname + document.location.search) {
+ return false
+ }
+
+ const turboPrefetchParent = findClosestRecursively(link, "[data-turbo-prefetch]")
+
+ if (turboPrefetchParent && turboPrefetchParent.dataset.turboPrefetch === "false") {
+ return false
+ }
+
+ return true
+ }
+}
+
+const targetsIframe = (link) => {
+ return !doesNotTargetIFrame(link)
+}
diff --git a/src/tests/fixtures/hover_to_prefetch.html b/src/tests/fixtures/hover_to_prefetch.html
new file mode 100644
index 000000000..56f7e1009
--- /dev/null
+++ b/src/tests/fixtures/hover_to_prefetch.html
@@ -0,0 +1,42 @@
+
+
+
+
+ Hover to Prefetch
+
+
+
+
+
+ Hover to prefetch me
+
+
+ Won't prefetch when hovering me
+ Won't prefetch when hovering me
+ Won't prefetch when hovering me
+ Hover to prefetch me
+ Won't prefetch when hovering me
+ Hover to prefetch me
+ Won't prefetch when hovering 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/hover_to_prefetch_disabled.html b/src/tests/fixtures/hover_to_prefetch_disabled.html
new file mode 100644
index 000000000..3827f69e4
--- /dev/null
+++ b/src/tests/fixtures/hover_to_prefetch_disabled.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Hover to Prefetch
+
+
+
+
+
+ Hover to prefetch me
+
+
diff --git a/src/tests/fixtures/hover_to_prefetch_iframe.html b/src/tests/fixtures/hover_to_prefetch_iframe.html
new file mode 100644
index 000000000..046fefbd8
--- /dev/null
+++ b/src/tests/fixtures/hover_to_prefetch_iframe.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Hover to Prefetch
+
+
+
+
+
+ Hover to prefetch me
+
+
diff --git a/src/tests/fixtures/prefetched.html b/src/tests/fixtures/prefetched.html
new file mode 100644
index 000000000..492cd9bc8
--- /dev/null
+++ b/src/tests/fixtures/prefetched.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Prefetched Page
+
+
+
+
+ Prefetched Page Content
+
+
diff --git a/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js b/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
new file mode 100644
index 000000000..a30cdc73b
--- /dev/null
+++ b/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
@@ -0,0 +1,153 @@
+import { test } from "@playwright/test"
+import { assert } from "chai"
+import { nextBeat } from "../helpers/page"
+
+test.describe("when hovering over a link", () => {
+ test.beforeEach(async ({ page }) => {
+ await goTo({ page, path: "/hover_to_prefetch.html" })
+ })
+
+ test("it prefetches the page", async ({ page }) => {
+ await assertPrefetchedOnMouseover({ page, selector: "#prefetch_anchor" })
+ })
+
+ test("it doesn't follow the link", async ({ page }) => {
+ await hoverSelector({ page, selector: "#prefetch_anchor" })
+
+ assert.equal(await page.title(), "Hover to Prefetch")
+ })
+
+ test.describe("when link has a whole valid url as a href", () => {
+ test("it prefetches the page", async ({ page }) => {
+ await assertPrefetchedOnMouseover({ page, selector: "#anchor_with_whole_url" })
+ })
+ })
+
+ test.describe("when link has the same location but with a query string", () => {
+ test("it prefetches the page", async ({ page }) => {
+ await assertPrefetchedOnMouseover({ page, selector: "#same_location_anchor_with_query" })
+ })
+ })
+
+ test.describe("when link is inside an element with data-turbo=false", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#turbo_false_parent_anchor" })
+ })
+ })
+
+ test.describe("when link is inside an element with data-turbo-prefetch=false", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#turbo_prefetch_false_parent_anchor" })
+ })
+ })
+
+ test.describe("when link has data-turbo-prefetch=false", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#turbo_prefetch_false_anchor" })
+ })
+ })
+
+ test.describe("when link has data-turbo=false", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#turbo_false_anchor" })
+ })
+ })
+
+ test.describe("when link has the same location as the current page", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#same_location_anchor" })
+ })
+ })
+
+ test.describe("when link has a different origin", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#different_origin_anchor" })
+ })
+ })
+
+ test.describe("when link has an hash as a href", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#anchor_with_hash" })
+ })
+ })
+
+ test.describe("when link has a ftp protocol", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#anchor_with_ftp_protocol" })
+ })
+ })
+
+ test.describe("when link is valid but it's inside an iframe", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#anchor_with_iframe_target" })
+ })
+ })
+
+ test.describe("when link has a POST data-turbo-method", () => {
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#anchor_with_post_method" })
+ })
+ })
+
+ test.describe("when turbo-prefetch meta tag is set to false", () => {
+ test.beforeEach(async ({ page }) => {
+ await goTo({ page, path: "/hover_to_prefetch_disabled.html" })
+ })
+
+ test("it doesn't prefetch the page", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#prefetch_anchor" })
+ })
+ })
+})
+
+test.describe("when clicking on a link that has been prefetched", () => {
+ test.beforeEach(async ({ page }) => {
+ await goTo({ page, path: "/hover_to_prefetch.html" })
+ await hoverSelector({ page, selector: "#prefetch_anchor" })
+ })
+
+ test("it does not make a network request", async ({ page }) => {
+ await assertNotPrefetchedOnMouseover({ page, selector: "#prefetch_anchor" })
+ })
+
+ test("it follows the link using the cached response", async ({ page }) => {
+ await clickSelector({ page, selector: "#prefetch_anchor" })
+
+ assert.equal(await page.title(), "Prefetched Page")
+ })
+})
+
+const assertPrefetchedOnMouseover = async ({ page, selector }) => {
+ let requestMade = false
+
+ page.on("request", (request) => (requestMade = true))
+
+ await hoverSelector({ page, selector })
+
+ assert.equal(requestMade, true, "Network request wasn't made when it should have been.")
+}
+
+const assertNotPrefetchedOnMouseover = async ({ page, selector }) => {
+ let requestMade = false
+
+ page.on("request", (request) => (requestMade = true))
+
+ await hoverSelector({ page, selector })
+
+ assert.equal(requestMade, false, "Network request was made when it should not have been.")
+}
+
+const goTo = async ({ page, path }) => {
+ await page.goto(`/src/tests/fixtures${path}`)
+ await nextBeat()
+}
+
+const hoverSelector = async ({ page, selector }) => {
+ await page.hover(selector)
+ await nextBeat()
+}
+
+const clickSelector = async ({ page, selector }) => {
+ await page.click(selector)
+ await nextBeat()
+}
From e662690cfde68ddc8300e9e59af0e1a7edbe71fd Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Tue, 5 Dec 2023 17:28:02 -0700
Subject: [PATCH 06/42] Allow customizing the event that triggers prefetching
---
.../link_prefetch_on_mouseover_observer.js | 20 ++++++-
.../fixtures/hover_to_prefetch_mousedown.html | 14 +++++
...nk_prefetch_on_mouseover_observer_tests.js | 60 +++++++++++++------
3 files changed, 74 insertions(+), 20 deletions(-)
create mode 100644 src/tests/fixtures/hover_to_prefetch_mousedown.html
diff --git a/src/observers/link_prefetch_on_mouseover_observer.js b/src/observers/link_prefetch_on_mouseover_observer.js
index 6bae75fb5..a9c1e65de 100644
--- a/src/observers/link_prefetch_on_mouseover_observer.js
+++ b/src/observers/link_prefetch_on_mouseover_observer.js
@@ -1,7 +1,17 @@
-import { doesNotTargetIFrame, findLinkFromClickTarget, getLocationForLink, getMetaContent, findClosestRecursively } from "../util"
+import {
+ doesNotTargetIFrame,
+ findLinkFromClickTarget,
+ getLocationForLink,
+ getMetaContent,
+ findClosestRecursively
+} from "../util"
import { prefetchCache } from "../core/drive/prefetch_cache"
export class LinkPrefetchOnMouseoverObserver {
+ triggerEvents = {
+ mouseover: "mouseover",
+ mousedown: "mousedown"
+ }
started = false
constructor(delegate, eventTarget) {
@@ -22,7 +32,7 @@ export class LinkPrefetchOnMouseoverObserver {
stop() {
if (!this.started) return
- this.eventTarget.removeEventListener("mouseover", this.tryToPrefetchRequest, {
+ this.eventTarget.removeEventListener(this.triggerEvent, this.tryToPrefetchRequest, {
capture: true,
passive: true
})
@@ -30,10 +40,14 @@ export class LinkPrefetchOnMouseoverObserver {
this.started = false
}
+ get triggerEvent() {
+ return this.triggerEvents[getMetaContent("turbo-prefetch-trigger-event")] || this.triggerEvents.mouseover
+ }
+
_enable = () => {
if (getMetaContent("turbo-prefetch") !== "true") return
- this.eventTarget.addEventListener("mouseover", this.tryToPrefetchRequest, {
+ this.eventTarget.addEventListener(this.triggerEvent, this.tryToPrefetchRequest, {
capture: true,
passive: true
})
diff --git a/src/tests/fixtures/hover_to_prefetch_mousedown.html b/src/tests/fixtures/hover_to_prefetch_mousedown.html
new file mode 100644
index 000000000..0a4c3a7dd
--- /dev/null
+++ b/src/tests/fixtures/hover_to_prefetch_mousedown.html
@@ -0,0 +1,14 @@
+
+
+
+
+ Hover to Prefetch
+
+
+
+
+
+
+ Hover to prefetch me
+
+
diff --git a/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js b/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
index a30cdc73b..ac21b11db 100644
--- a/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
+++ b/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
@@ -8,7 +8,7 @@ test.describe("when hovering over a link", () => {
})
test("it prefetches the page", async ({ page }) => {
- await assertPrefetchedOnMouseover({ page, selector: "#prefetch_anchor" })
+ await assertPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
})
test("it doesn't follow the link", async ({ page }) => {
@@ -19,73 +19,73 @@ test.describe("when hovering over a link", () => {
test.describe("when link has a whole valid url as a href", () => {
test("it prefetches the page", async ({ page }) => {
- await assertPrefetchedOnMouseover({ page, selector: "#anchor_with_whole_url" })
+ await assertPrefetchedOnHover({ page, selector: "#anchor_with_whole_url" })
})
})
test.describe("when link has the same location but with a query string", () => {
test("it prefetches the page", async ({ page }) => {
- await assertPrefetchedOnMouseover({ page, selector: "#same_location_anchor_with_query" })
+ await assertPrefetchedOnHover({ page, selector: "#same_location_anchor_with_query" })
})
})
test.describe("when link is inside an element with data-turbo=false", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#turbo_false_parent_anchor" })
+ await assertNotPrefetchedOnHover({ page, selector: "#turbo_false_parent_anchor" })
})
})
test.describe("when link is inside an element with data-turbo-prefetch=false", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#turbo_prefetch_false_parent_anchor" })
+ await assertNotPrefetchedOnHover({ page, selector: "#turbo_prefetch_false_parent_anchor" })
})
})
test.describe("when link has data-turbo-prefetch=false", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#turbo_prefetch_false_anchor" })
+ await assertNotPrefetchedOnHover({ page, selector: "#turbo_prefetch_false_anchor" })
})
})
test.describe("when link has data-turbo=false", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#turbo_false_anchor" })
+ await assertNotPrefetchedOnHover({ page, selector: "#turbo_false_anchor" })
})
})
test.describe("when link has the same location as the current page", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#same_location_anchor" })
+ await assertNotPrefetchedOnHover({ page, selector: "#same_location_anchor" })
})
})
test.describe("when link has a different origin", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#different_origin_anchor" })
+ await assertNotPrefetchedOnHover({ page, selector: "#different_origin_anchor" })
})
})
test.describe("when link has an hash as a href", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#anchor_with_hash" })
+ await assertNotPrefetchedOnHover({ page, selector: "#anchor_with_hash" })
})
})
test.describe("when link has a ftp protocol", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#anchor_with_ftp_protocol" })
+ await assertNotPrefetchedOnHover({ page, selector: "#anchor_with_ftp_protocol" })
})
})
test.describe("when link is valid but it's inside an iframe", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#anchor_with_iframe_target" })
+ await assertNotPrefetchedOnHover({ page, selector: "#anchor_with_iframe_target" })
})
})
test.describe("when link has a POST data-turbo-method", () => {
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#anchor_with_post_method" })
+ await assertNotPrefetchedOnHover({ page, selector: "#anchor_with_post_method" })
})
})
@@ -95,7 +95,21 @@ test.describe("when hovering over a link", () => {
})
test("it doesn't prefetch the page", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#prefetch_anchor" })
+ await assertNotPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
+ })
+ })
+
+ test.describe("when turbo-prefetch-trigger-event is set to mousedown", () => {
+ test.beforeEach(async ({ page }) => {
+ await goTo({ page, path: "/hover_to_prefetch_mousedown.html" })
+ })
+
+ test("it prefetches the page on mousedown", async ({ page }) => {
+ await assertPrefetchedOnMouseDown({ page, selector: "#prefetch_anchor" })
+ })
+
+ test("it doesn't prefetch the page on mouseover", async ({ page }) => {
+ await assertNotPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
})
})
})
@@ -107,7 +121,7 @@ test.describe("when clicking on a link that has been prefetched", () => {
})
test("it does not make a network request", async ({ page }) => {
- await assertNotPrefetchedOnMouseover({ page, selector: "#prefetch_anchor" })
+ await assertNotPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
})
test("it follows the link using the cached response", async ({ page }) => {
@@ -117,7 +131,7 @@ test.describe("when clicking on a link that has been prefetched", () => {
})
})
-const assertPrefetchedOnMouseover = async ({ page, selector }) => {
+const assertPrefetchedOnHover = async ({ page, selector }) => {
let requestMade = false
page.on("request", (request) => (requestMade = true))
@@ -127,7 +141,7 @@ const assertPrefetchedOnMouseover = async ({ page, selector }) => {
assert.equal(requestMade, true, "Network request wasn't made when it should have been.")
}
-const assertNotPrefetchedOnMouseover = async ({ page, selector }) => {
+const assertNotPrefetchedOnHover = async ({ page, selector }) => {
let requestMade = false
page.on("request", (request) => (requestMade = true))
@@ -137,6 +151,18 @@ const assertNotPrefetchedOnMouseover = async ({ page, selector }) => {
assert.equal(requestMade, false, "Network request was made when it should not have been.")
}
+const assertPrefetchedOnMouseDown = async ({ page, selector }) => {
+ let requestMade = false
+
+ page.on("request", (request) => (requestMade = true))
+
+ await page.hover(selector)
+ await page.mouse.down()
+ await nextBeat()
+
+ assert.equal(requestMade, true, "Network request wasn't made when it should have been.")
+}
+
const goTo = async ({ page, path }) => {
await page.goto(`/src/tests/fixtures${path}`)
await nextBeat()
From 15517d4045ee26151207410b8f13244b36288e00 Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Tue, 5 Dec 2023 19:10:18 -0700
Subject: [PATCH 07/42] Allow customizing the cache time for prefetching
---
src/core/session.js | 4 ++--
.../link_prefetch_on_mouseover_observer.js | 8 +++++--
.../hover_to_prefetch_custom_cache_time.html | 14 +++++++++++++
...nk_prefetch_on_mouseover_observer_tests.js | 21 ++++++++++++++++++-
4 files changed, 42 insertions(+), 5 deletions(-)
create mode 100644 src/tests/fixtures/hover_to_prefetch_custom_cache_time.html
diff --git a/src/core/session.js b/src/core/session.js
index 620cbad4b..0b33f79be 100644
--- a/src/core/session.js
+++ b/src/core/session.js
@@ -18,7 +18,7 @@ import { PageView } from "./drive/page_view"
import { FrameElement } from "../elements/frame_element"
import { Preloader } from "./drive/preloader"
import { Cache } from "./cache"
-import { prefetchCache, cacheTtl } from "./drive/prefetch_cache"
+import { prefetchCache } from "./drive/prefetch_cache"
export class Session {
navigator = new Navigator(this)
@@ -184,7 +184,7 @@ export class Session {
)
}
- prefetchAndCacheRequestToLocation(link, location) {
+ prefetchAndCacheRequestToLocation(link, location, cacheTtl) {
const requestOptions = {
credentials: "same-origin",
headers: {
diff --git a/src/observers/link_prefetch_on_mouseover_observer.js b/src/observers/link_prefetch_on_mouseover_observer.js
index a9c1e65de..63efb298c 100644
--- a/src/observers/link_prefetch_on_mouseover_observer.js
+++ b/src/observers/link_prefetch_on_mouseover_observer.js
@@ -5,7 +5,7 @@ import {
getMetaContent,
findClosestRecursively
} from "../util"
-import { prefetchCache } from "../core/drive/prefetch_cache"
+import { prefetchCache, cacheTtl } from "../core/drive/prefetch_cache"
export class LinkPrefetchOnMouseoverObserver {
triggerEvents = {
@@ -44,6 +44,10 @@ export class LinkPrefetchOnMouseoverObserver {
return this.triggerEvents[getMetaContent("turbo-prefetch-trigger-event")] || this.triggerEvents.mouseover
}
+ get cacheTtl() {
+ return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl
+ }
+
_enable = () => {
if (getMetaContent("turbo-prefetch") !== "true") return
@@ -62,7 +66,7 @@ export class LinkPrefetchOnMouseoverObserver {
if (link && this.isPrefetchable(link)) {
const location = getLocationForLink(link)
if (this.delegate.canPrefetchAndCacheRequestToLocation(link, location, event)) {
- this.delegate.prefetchAndCacheRequestToLocation(link, location)
+ this.delegate.prefetchAndCacheRequestToLocation(link, location, this.cacheTtl)
}
}
}
diff --git a/src/tests/fixtures/hover_to_prefetch_custom_cache_time.html b/src/tests/fixtures/hover_to_prefetch_custom_cache_time.html
new file mode 100644
index 000000000..b17178eac
--- /dev/null
+++ b/src/tests/fixtures/hover_to_prefetch_custom_cache_time.html
@@ -0,0 +1,14 @@
+
+
+
+
+ Hover to Prefetch
+
+
+
+
+
+
+ Hover to prefetch me
+
+
diff --git a/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js b/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
index ac21b11db..b17b39066 100644
--- a/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
+++ b/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
@@ -1,6 +1,6 @@
import { test } from "@playwright/test"
import { assert } from "chai"
-import { nextBeat } from "../helpers/page"
+import { nextBeat, sleep } from "../helpers/page"
test.describe("when hovering over a link", () => {
test.beforeEach(async ({ page }) => {
@@ -112,6 +112,25 @@ test.describe("when hovering over a link", () => {
await assertNotPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
})
})
+
+ test.describe("when turbo-prefetch-cache-time is set to 1", () => {
+ test.beforeEach(async ({ page }) => {
+ await goTo({ page, path: "/hover_to_prefetch_custom_cache_time.html" })
+ })
+
+ test("it prefetches the page", async ({ page }) => {
+ await assertPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
+ })
+
+ test("it caches the request for 1 second", async ({ page }) => {
+ await assertPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
+
+ await sleep(1100)
+ await page.mouse.move(0, 0)
+
+ await assertPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
+ })
+ })
})
test.describe("when clicking on a link that has been prefetched", () => {
From ab0e5389aad7f5cddb49ad99205eef5aa044d02a Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Tue, 5 Dec 2023 19:12:07 -0700
Subject: [PATCH 08/42] Rename LinkPrefetchOnMouseoverObserver to
LinkPrefetchObserver
Because it is not only triggered on mouseover, but could also be on mousedown, or eventually touchstart.
---
src/core/session.js | 8 ++++----
...on_mouseover_observer.js => link_prefetch_observer.js} | 2 +-
..._observer_tests.js => link_prefetch_observer_tests.js} | 0
3 files changed, 5 insertions(+), 5 deletions(-)
rename src/observers/{link_prefetch_on_mouseover_observer.js => link_prefetch_observer.js} (98%)
rename src/tests/functional/{link_prefetch_on_mouseover_observer_tests.js => link_prefetch_observer_tests.js} (100%)
diff --git a/src/core/session.js b/src/core/session.js
index 0b33f79be..c068333d9 100644
--- a/src/core/session.js
+++ b/src/core/session.js
@@ -3,7 +3,7 @@ import { CacheObserver } from "../observers/cache_observer"
import { FormSubmitObserver } from "../observers/form_submit_observer"
import { FrameRedirector } from "./frames/frame_redirector"
import { History } from "./drive/history"
-import { LinkPrefetchOnMouseoverObserver } from "../observers/link_prefetch_on_mouseover_observer"
+import { LinkPrefetchObserver } from "../observers/link_prefetch_observer"
import { LinkClickObserver } from "../observers/link_click_observer"
import { FormLinkClickObserver } from "../observers/form_link_click_observer"
import { getAction, expandURL, locationIsVisitable } from "./url"
@@ -29,7 +29,7 @@ export class Session {
pageObserver = new PageObserver(this)
cacheObserver = new CacheObserver()
- linkPrefetchOnMouseoverObserver = new LinkPrefetchOnMouseoverObserver(this, document)
+ linkPrefetchObserver = new LinkPrefetchObserver(this, document)
linkClickObserver = new LinkClickObserver(this, window)
formSubmitObserver = new FormSubmitObserver(this, document)
scrollObserver = new ScrollObserver(this)
@@ -53,7 +53,7 @@ export class Session {
if (!this.started) {
this.pageObserver.start()
this.cacheObserver.start()
- this.linkPrefetchOnMouseoverObserver.start()
+ this.linkPrefetchObserver.start()
this.formLinkClickObserver.start()
this.linkClickObserver.start()
this.formSubmitObserver.start()
@@ -75,7 +75,7 @@ export class Session {
if (this.started) {
this.pageObserver.stop()
this.cacheObserver.stop()
- this.linkPrefetchOnMouseoverObserver.stop()
+ this.linkPrefetchObserver.stop()
this.formLinkClickObserver.stop()
this.linkClickObserver.stop()
this.formSubmitObserver.stop()
diff --git a/src/observers/link_prefetch_on_mouseover_observer.js b/src/observers/link_prefetch_observer.js
similarity index 98%
rename from src/observers/link_prefetch_on_mouseover_observer.js
rename to src/observers/link_prefetch_observer.js
index 63efb298c..d712e8e25 100644
--- a/src/observers/link_prefetch_on_mouseover_observer.js
+++ b/src/observers/link_prefetch_observer.js
@@ -7,7 +7,7 @@ import {
} from "../util"
import { prefetchCache, cacheTtl } from "../core/drive/prefetch_cache"
-export class LinkPrefetchOnMouseoverObserver {
+export class LinkPrefetchObserver {
triggerEvents = {
mouseover: "mouseover",
mousedown: "mousedown"
diff --git a/src/tests/functional/link_prefetch_on_mouseover_observer_tests.js b/src/tests/functional/link_prefetch_observer_tests.js
similarity index 100%
rename from src/tests/functional/link_prefetch_on_mouseover_observer_tests.js
rename to src/tests/functional/link_prefetch_observer_tests.js
From fca147e5e9145f429a02f9bd03e444c08d9da42b Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Tue, 5 Dec 2023 19:15:34 -0700
Subject: [PATCH 09/42] Use private methods in LinkPrefetchObserver
---
src/observers/link_prefetch_observer.js | 28 ++++++++++++-------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/observers/link_prefetch_observer.js b/src/observers/link_prefetch_observer.js
index d712e8e25..1054d0185 100644
--- a/src/observers/link_prefetch_observer.js
+++ b/src/observers/link_prefetch_observer.js
@@ -23,55 +23,55 @@ export class LinkPrefetchObserver {
if (this.started) return
if (this.eventTarget.readyState === "loading") {
- this.eventTarget.addEventListener("DOMContentLoaded", this._enable, { once: true })
+ this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, { once: true })
} else {
- this._enable()
+ this.#enable()
}
}
stop() {
if (!this.started) return
- this.eventTarget.removeEventListener(this.triggerEvent, this.tryToPrefetchRequest, {
+ this.eventTarget.removeEventListener(this.#triggerEvent, this.#tryToPrefetchRequest, {
capture: true,
passive: true
})
- this.eventTarget.removeEventListener("turbo:before-fetch-request", this.tryToUsePrefetchedRequest, true)
+ this.eventTarget.removeEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true)
this.started = false
}
- get triggerEvent() {
+ get #triggerEvent() {
return this.triggerEvents[getMetaContent("turbo-prefetch-trigger-event")] || this.triggerEvents.mouseover
}
- get cacheTtl() {
+ get #cacheTtl() {
return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl
}
- _enable = () => {
+ #enable = () => {
if (getMetaContent("turbo-prefetch") !== "true") return
- this.eventTarget.addEventListener(this.triggerEvent, this.tryToPrefetchRequest, {
+ this.eventTarget.addEventListener(this.#triggerEvent, this.#tryToPrefetchRequest, {
capture: true,
passive: true
})
- this.eventTarget.addEventListener("turbo:before-fetch-request", this.tryToUsePrefetchedRequest, true)
+ this.eventTarget.addEventListener("turbo:before-fetch-request", this.#tryToUsePrefetchedRequest, true)
this.started = true
}
- tryToPrefetchRequest = (event) => {
+ #tryToPrefetchRequest = (event) => {
const target = event.target
const link = findLinkFromClickTarget(target)
- if (link && this.isPrefetchable(link)) {
+ if (link && this.#isPrefetchable(link)) {
const location = getLocationForLink(link)
if (this.delegate.canPrefetchAndCacheRequestToLocation(link, location, event)) {
- this.delegate.prefetchAndCacheRequestToLocation(link, location, this.cacheTtl)
+ this.delegate.prefetchAndCacheRequestToLocation(link, location, this.#cacheTtl)
}
}
}
- tryToUsePrefetchedRequest = (event) => {
+ #tryToUsePrefetchedRequest = (event) => {
if (event.target.tagName !== "FORM" && event.detail.fetchOptions.method === "get") {
const cached = prefetchCache.get(event.detail.url.toString())
@@ -83,7 +83,7 @@ export class LinkPrefetchObserver {
prefetchCache.clear()
}
- isPrefetchable(link) {
+ #isPrefetchable(link) {
const href = link.getAttribute("href")
if (!href || href === "#" || link.dataset.turbo === "false" || link.dataset.turboPrefetch === "false") {
From 3baf81cbd9cc05039e6083ea055f86cc07ed70f1 Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Tue, 5 Dec 2023 19:17:12 -0700
Subject: [PATCH 10/42] Reorganize methods on LinkPrefetchObserver
---
src/observers/link_prefetch_observer.js | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/observers/link_prefetch_observer.js b/src/observers/link_prefetch_observer.js
index 1054d0185..261b36d1d 100644
--- a/src/observers/link_prefetch_observer.js
+++ b/src/observers/link_prefetch_observer.js
@@ -40,14 +40,6 @@ export class LinkPrefetchObserver {
this.started = false
}
- get #triggerEvent() {
- return this.triggerEvents[getMetaContent("turbo-prefetch-trigger-event")] || this.triggerEvents.mouseover
- }
-
- get #cacheTtl() {
- return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl
- }
-
#enable = () => {
if (getMetaContent("turbo-prefetch") !== "true") return
@@ -83,6 +75,14 @@ export class LinkPrefetchObserver {
prefetchCache.clear()
}
+ get #triggerEvent() {
+ return this.triggerEvents[getMetaContent("turbo-prefetch-trigger-event")] || this.triggerEvents.mouseover
+ }
+
+ get #cacheTtl() {
+ return Number(getMetaContent("turbo-prefetch-cache-time")) || cacheTtl
+ }
+
#isPrefetchable(link) {
const href = link.getAttribute("href")
From d2ec0211274106180996a0dd2e1ed210001a268a Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Tue, 5 Dec 2023 19:41:37 -0700
Subject: [PATCH 11/42] Require a shorter sleep time in the test
Since turbo-prefetch-cache-time is set to 1 millisecond in the html fixture
---
src/tests/functional/link_prefetch_observer_tests.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/tests/functional/link_prefetch_observer_tests.js b/src/tests/functional/link_prefetch_observer_tests.js
index b17b39066..c27221a7f 100644
--- a/src/tests/functional/link_prefetch_observer_tests.js
+++ b/src/tests/functional/link_prefetch_observer_tests.js
@@ -125,7 +125,7 @@ test.describe("when hovering over a link", () => {
test("it caches the request for 1 second", async ({ page }) => {
await assertPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
- await sleep(1100)
+ await sleep(10)
await page.mouse.move(0, 0)
await assertPrefetchedOnHover({ page, selector: "#prefetch_anchor" })
From 7c7ded9b0ef111bf789ede518c60900656055e91 Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Tue, 5 Dec 2023 22:46:18 -0700
Subject: [PATCH 12/42] Standardize anchor IDs in link_prefetch_observer_tests
anchor_ prefix is used for all anchors in the tests
---
src/tests/fixtures/hover_to_prefetch.html | 22 +++++++-----
.../hover_to_prefetch_custom_cache_time.html | 2 +-
.../fixtures/hover_to_prefetch_disabled.html | 2 +-
.../fixtures/hover_to_prefetch_mousedown.html | 2 +-
.../link_prefetch_observer_tests.js | 36 +++++++++----------
5 files changed, 35 insertions(+), 29 deletions(-)
diff --git a/src/tests/fixtures/hover_to_prefetch.html b/src/tests/fixtures/hover_to_prefetch.html
index 56f7e1009..990e96a1b 100644
--- a/src/tests/fixtures/hover_to_prefetch.html
+++ b/src/tests/fixtures/hover_to_prefetch.html
@@ -8,24 +8,30 @@
- Hover to prefetch me
+ Hover to prefetch me
- Won't prefetch when hovering me
+ Won't prefetch when hovering me
- Won't prefetch when hovering me
- Won't prefetch when hovering me
- Hover to prefetch me
- Won't prefetch when hovering me
+ Hover to prefetch me
+ Won't prefetch when hovering me
Hover to prefetch me
diff --git a/src/tests/fixtures/hover_to_prefetch_custom_cache_time.html b/src/tests/fixtures/hover_to_prefetch_custom_cache_time.html
index b17178eac..00ed35fd2 100644
--- a/src/tests/fixtures/hover_to_prefetch_custom_cache_time.html
+++ b/src/tests/fixtures/hover_to_prefetch_custom_cache_time.html
@@ -9,6 +9,6 @@
- Hover to prefetch me
+ Hover to prefetch me