From 0ca4e35859012241752a32eeeb405685c7355429 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Thu, 12 Oct 2023 13:12:07 -0400 Subject: [PATCH] Dispatch `turbo:before-fetch-{request,response}` during preloading 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 | 29 +++++++++++++++++++++---- 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 | 16 +++++++++++++- 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/core/drive/preloader.js b/src/core/drive/preloader.js index b971cea19..dce4da78a 100644 --- a/src/core/drive/preloader.js +++ b/src/core/drive/preloader.js @@ -1,4 +1,5 @@ import { PageSnapshot } from "./page_snapshot" +import { FetchMethod, FetchRequest } from "../../http/fetch_request" export class Preloader { selector = "a[data-turbo-preload]" @@ -32,14 +33,34 @@ export class Preloader { if (await this.snapshotCache.has(location)) 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) {} } 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 ad3a6d0b2..f54d9ba97 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 ecd7ca619..b5423b5c3 100644 --- a/src/tests/functional/preloader_tests.js +++ b/src/tests/functional/preloader_tests.js @@ -1,6 +1,6 @@ import { test } from "@playwright/test" import { assert } from "chai" -import { nextBeat } from "../helpers/page" +import { nextBeat, nextEventNamed } from "../helpers/page" test("test preloads snapshot on initial load", async ({ page }) => { // contains `a[rel="preload"][href="http://localhost:9000/src/tests/fixtures/preloaded.html"]` @@ -17,6 +17,20 @@ test("test preloads snapshot on initial load", async ({ page }) => { ) }) +test("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 nextEventNamed(page, "turbo:before-fetch-request") + const { fetchResponse } = await nextEventNamed(page, "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("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")