From 7234d3ed007e9b79392b1c28fa3ad268c4900cb2 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Thu, 14 Dec 2023 10:55:42 -0500 Subject: [PATCH] Dispatch `turbo:before-fetch-{request,response}` during preloading (#1034) Closes [#963][] Replace the raw call to `fetch` with a new `FetchRequest` instance that treats the `Preloaded` instances as its delegate. During that request's lifecycle, dispatch the `turbo:before-fetch-request` and `turbo:before-fetch-response` events with the `` element as its target. Prepare the request with the [Sec-Purpose][] header in the `prepareRequest` delegate callback. Write to the snapshot cache from within the `requestSucceededWithResponse` delegate callback. [#963]: https://github.com/hotwired/turbo/issues/963 [Sec-Purpose]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Purpose#prefetch --- src/core/drive/preloader.js | 30 ++++++++++++++++++++----- src/tests/fixtures/hot_preloading.html | 1 + src/tests/fixtures/preloaded.html | 1 + src/tests/fixtures/preloading.html | 1 + src/tests/functional/preloader_tests.js | 14 ++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/core/drive/preloader.js b/src/core/drive/preloader.js index b93d9b7ee..7deca1857 100644 --- a/src/core/drive/preloader.js +++ b/src/core/drive/preloader.js @@ -1,5 +1,5 @@ import { PageSnapshot } from "./page_snapshot" -import { fetch } from "../../http/fetch" +import { FetchMethod, FetchRequest } from "../../http/fetch_request" export class Preloader { selector = "a[data-turbo-preload]" @@ -36,17 +36,37 @@ export class Preloader { return } + const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams(), link) + await fetchRequest.perform() + } + + // Fetch request delegate + + prepareRequest(fetchRequest) { + fetchRequest.headers["Sec-Purpose"] = "prefetch" + } + + async requestSucceededWithResponse(fetchRequest, fetchResponse) { try { - const response = await fetch(location.toString(), { headers: { "Sec-Purpose": "prefetch", Accept: "text/html" } }) - const responseText = await response.text() - const snapshot = PageSnapshot.fromHTMLString(responseText) + const responseHTML = await fetchResponse.responseHTML + const snapshot = PageSnapshot.fromHTMLString(responseHTML) - this.snapshotCache.put(location, snapshot) + this.snapshotCache.put(fetchRequest.url, snapshot) } catch (_) { // If we cannot preload that is ok! } } + requestStarted(fetchRequest) {} + + requestErrored(fetchRequest) {} + + requestFinished(fetchRequest) {} + + requestPreventedHandlingResponse(fetchRequest, fetchResponse) {} + + requestFailedWithResponse(fetchRequest, fetchResponse) {} + #preloadAll = () => { this.preloadOnLoadLinksForView(document.body) } diff --git a/src/tests/fixtures/hot_preloading.html b/src/tests/fixtures/hot_preloading.html index 0a9d511c5..029fdda8e 100644 --- a/src/tests/fixtures/hot_preloading.html +++ b/src/tests/fixtures/hot_preloading.html @@ -5,6 +5,7 @@ Page That Links to Preloading Page + diff --git a/src/tests/fixtures/preloaded.html b/src/tests/fixtures/preloaded.html index 9b34768fb..99de99152 100644 --- a/src/tests/fixtures/preloaded.html +++ b/src/tests/fixtures/preloaded.html @@ -5,6 +5,7 @@ Preloaded Page + diff --git a/src/tests/fixtures/preloading.html b/src/tests/fixtures/preloading.html index 9bdacc1aa..db1a57534 100644 --- a/src/tests/fixtures/preloading.html +++ b/src/tests/fixtures/preloading.html @@ -5,6 +5,7 @@ Preloading Page + diff --git a/src/tests/functional/preloader_tests.js b/src/tests/functional/preloader_tests.js index d4c750b93..853e00b5c 100644 --- a/src/tests/functional/preloader_tests.js +++ b/src/tests/functional/preloader_tests.js @@ -12,6 +12,20 @@ test("preloads snapshot on initial load", async ({ page }) => { assert.ok(await urlInSnapshotCache(page, href)) }) +test("preloading dispatch turbo:before-fetch-{request,response} events", async ({ page }) => { + await page.goto("/src/tests/fixtures/preloading.html") + + const link = await page.locator("#preload_anchor") + const href = await link.evaluate((link) => link.href) + + const { url, fetchOptions } = await nextEventOnTarget(page, "preload_anchor", "turbo:before-fetch-request") + const { fetchResponse } = await nextEventOnTarget(page, "preload_anchor", "turbo:before-fetch-response") + + assert.equal(href, url, "dispatches request during preloading") + assert.equal(fetchOptions.headers.Accept, "text/html, application/xhtml+xml") + assert.equal(fetchResponse.response.url, href) +}) + test("preloads snapshot on page visit", async ({ page }) => { // contains `a[rel="preload"][href="http://localhost:9000/src/tests/fixtures/preloading.html"]` await page.goto("/src/tests/fixtures/hot_preloading.html")