From 22dfc8b6a4a8cd078fa0c92bb32a26b2e447018f Mon Sep 17 00:00:00 2001 From: Sankalp Thakur Date: Wed, 22 Jul 2026 14:22:09 +0530 Subject: [PATCH 1/3] Skip prefetching anchors with the download attribute Links marked for download often point at large binary payloads; excluding them by default avoids wasted bandwidth while keeping ignores for custom cases. --- README.md | 2 ++ src/chunks.mjs | 5 +++-- src/index.mjs | 8 ++++++-- test/fixtures/test-ignore-download.html | 22 ++++++++++++++++++++++ test/quicklink.spec.js | 18 ++++++++++++++++++ 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/test-ignore-download.html diff --git a/README.md b/README.md index 7705f8a4..7a707fba 100644 --- a/README.md +++ b/README.md @@ -409,6 +409,8 @@ quicklink.listen({ ### Custom Ignore Patterns +By default, quicklink skips anchors that have a [`download`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#download) attribute, since those often point at large binary payloads. + These filters run _after_ the `origins` matching has run. Ignores can be useful for avoiding large file downloads or for responding to DOM attributes dynamically. ```js diff --git a/src/chunks.mjs b/src/chunks.mjs index 91c7663c..41c2454e 100644 --- a/src/chunks.mjs +++ b/src/chunks.mjs @@ -106,8 +106,9 @@ export function listen(options = {}) { }); timeoutFn(() => { - // Find all links & Connect them to IO if allowed - const links = (options.el || document).querySelectorAll('a[href]'); + // Find all links & Connect them to IO if allowed. + // Skip anchors with the `download` attribute (large payloads). + const links = (options.el || document).querySelectorAll('a[href]:not([download])'); for (const link of links) { // If the anchor matches a permitted origin // ~> A `[]` or `true` means everything is allowed diff --git a/src/index.mjs b/src/index.mjs index cd294467..70b3ace2 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -206,11 +206,15 @@ export function listen(options = {}) { }); timeoutFn(() => { - // Find all links & Connect them to IO if allowed + // Find all links & Connect them to IO if allowed. + // Skip anchors with the `download` attribute — those often point at large + // binary payloads that are wasteful to prefetch. const isAnchorElement = options.el && options.el.length > 0 && options.el[0].nodeName === 'A'; - const elementsToListen = isAnchorElement ? options.el : (options.el || document).querySelectorAll('a'); + const elementsToListen = isAnchorElement ? options.el : (options.el || document).querySelectorAll('a:not([download])'); for (const link of elementsToListen) { + // NodeList of anchors may still include download links + if (link.hasAttribute('download')) continue; // If the anchor matches a permitted origin // ~> A `[]` or `true` means everything is allowed if (!allowed.length || allowed.includes(link.hostname)) { diff --git a/test/fixtures/test-ignore-download.html b/test/fixtures/test-ignore-download.html new file mode 100644 index 00000000..f5154099 --- /dev/null +++ b/test/fixtures/test-ignore-download.html @@ -0,0 +1,22 @@ + + + + + + Prefetch: Ignore download attribute + + + + + + Link 1 + Download Link 2 + Download Link 3 + Link 4 + + + + + diff --git a/test/quicklink.spec.js b/test/quicklink.spec.js index cfda9a10..72b0f5d9 100644 --- a/test/quicklink.spec.js +++ b/test/quicklink.spec.js @@ -207,6 +207,24 @@ mainSuite('should only prefetch links after ignore patterns allowed it', async c assert.not.ok(responseURLs.includes('https://github.githubassets.com/images/spinners/octocat-spinner-32.gif')); }); +mainSuite('should not prefetch links with the download attribute', async context => { + const responseURLs = []; + context.page.on('response', resp => { + responseURLs.push(resp.url()); + }); + await context.page.goto(`${server}/test-ignore-download.html`); + await sleep(); + assert.instance(responseURLs, Array); + + assert.ok(responseURLs.includes(`${server}/1.html`)); + // boolean download attribute + assert.not.ok(responseURLs.includes(`${server}/2.html`)); + // download="filename" attribute + assert.not.ok(responseURLs.includes(`${server}/3.html`)); + // out of viewport + assert.not.ok(responseURLs.includes(`${server}/4.html`)); +}); + mainSuite('should only prefetch links after ignore patterns allowed it (multiple)', async context => { const responseURLs = []; context.page.on('response', resp => { From e6d2e6fe6e907b3fbf971355f6fdd90b26bf13c4 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sun, 26 Jul 2026 14:54:58 +0300 Subject: [PATCH 2/3] Update chunks.mjs --- src/chunks.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chunks.mjs b/src/chunks.mjs index 41c2454e..9f6805bc 100644 --- a/src/chunks.mjs +++ b/src/chunks.mjs @@ -106,7 +106,7 @@ export function listen(options = {}) { }); timeoutFn(() => { - // Find all links & Connect them to IO if allowed. + // Find all links & connect them to IO if allowed. // Skip anchors with the `download` attribute (large payloads). const links = (options.el || document).querySelectorAll('a[href]:not([download])'); for (const link of links) { From 9cd17fdb5ef9f6b9d48ab58f0e61d1e26b7e88a4 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sun, 26 Jul 2026 14:55:33 +0300 Subject: [PATCH 3/3] Update index.mjs --- src/index.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.mjs b/src/index.mjs index 70b3ace2..d8cbe639 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -206,8 +206,8 @@ export function listen(options = {}) { }); timeoutFn(() => { - // Find all links & Connect them to IO if allowed. - // Skip anchors with the `download` attribute — those often point at large + // Find all links & connect them to IO if allowed. + // Skip anchors with the `download` attribute - those often point at large // binary payloads that are wasteful to prefetch. const isAnchorElement = options.el && options.el.length > 0 && options.el[0].nodeName === 'A'; const elementsToListen = isAnchorElement ? options.el : (options.el || document).querySelectorAll('a:not([download])');