diff --git a/README.md b/README.md index 39e9f4e3..182ab96a 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Quickstart: ``` @@ -53,7 +53,7 @@ For example, you can initialize after the `load` event fires: ```html ``` @@ -61,29 +61,121 @@ window.addEventListener('load', () =>{ ES Module import: ```js -import quicklink from "quicklink/dist/quicklink.mjs"; -quicklink(); +import { listen, prefetch } from "quicklink"; ``` The above options are best for multi-page sites. Single-page apps have a few options available for using quicklink with a router: -* Call `quicklink()` once a navigation to a new route has completed -* Call `quicklink()` against a specific DOM element / component -* Call `quicklink({urls:[...]})` with a custom set of URLs to prefetch +* Call `quicklink.listen()` once a navigation to a new route has completed +* Call `quicklink.listen()` against a specific DOM element / component +* Call `quicklink.prefetch()` with a custom set of URLs to prefetch ## API -`quicklink` accepts an optional options object with the following parameters: +### quicklink.listen(options) +Returns: `Function` -* `el`: DOM element to observe for in-viewport links to prefetch -* `urls`: Static array of URLs to prefetch (instead of observing `document` or a DOM element links in the viewport) -* `timeout`: Integer for the `requestIdleCallback` timeout. A time in milliseconds by which the browser must execute prefetching. Defaults to 2 seconds. -* `timeoutFn`: Function for specifying a timeout. Defaults to `requestIdleCallback`. Can also be swapped out for a custom function like [networkIdleCallback](https://github.com/pastelsky/network-idle-callback) (see demos) -* `priority`: Boolean specifying preferred priority for fetches. Defaults to `false`. `true` will attempt to use the `fetch()` API where supported (rather than rel=prefetch) -* `origins`: Static array of URL hostname strings that are allowed to be prefetched. Defaults to the same domain origin, which prevents _any_ cross-origin requests. -* `ignores`: A RegExp, Function, or Array that further determines if a URL should be prefetched. These execute _after_ origin matching. +A "reset" function is returned, which will empty the active `IntersectionObserver` and the cache of URLs that have already been prefetched. This can be used between page navigations and/or when significant DOM changes have occurred. + +#### options.el +Type: `HTMLElement`
+Default: `document.body` + +The DOM element to observe for in-viewport links to prefetch. + +#### options.limit +Type: `Number`
+Default: `Infinity` + +The _total_ requests that can be prefetched while observing the `options.el` container. + +#### options.throttle +Type: `Number`
+Default: `Infinity` + +The _concurrency limit_ for simultaneous requests while observing the `options.el` container. + +#### options.timeout +Type: `Number`
+Default: `2000` + +The `requestIdleCallback` timeout, in milliseconds. + +> **Note:** The browser must be idle for the configured duration before prefetching. + +#### options.timeoutFn +Type: `Function`
+Default: `requestIdleCallback` + +A function used for specifying a `timeout` delay.
+This can be swapped out for a custom function like [networkIdleCallback](https://github.com/pastelsky/network-idle-callback) (see demos). + +By default, this uses [`requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback) or the embedded polyfill. + +#### options.priority +Type: `Boolean`
+Default: `false` + +Whether or not the URLs within the `options.el` container should be treated as high priority. + +When `true`, quicklink will attempt to use the `fetch()` API if supported (rather than `link[rel=prefetch]`). + +#### options.origins +Type: `Array`
+Default: `[location.hostname]` + +A static array of URL hostnames that are allowed to be prefetched.
+Defaults to the same domain origin, which prevents _any_ cross-origin requests. + +**Important:** An empty array (`[]`) allows ***all origins*** to be prefetched. + +#### options.ignores +Type: `RegExp` or `Function` or `Array`
+Default: `[]` + +Determine if a URL should be prefetched. + +When a `RegExp` tests positive, a `Function` returns `true`, or an `Array` contains the string, then the URL is _not_ prefetched. + +> **Note:** An `Array` may contain `String`, `RegExp`, or `Function` values. + +> **Important:** This logic is executed _after_ origin matching! + +#### options.onError +Type: `Function`
+Default: None + +An optional error handler that will receive any errors from prefetched requests.
+By default, these errors are silently ignored. + + +### quicklink.prefetch(urls, isPriority) +Returns: `Promise` + +The `urls` provided are always passed through `Promise.all`, which means the result will always resolve to an Array. + +> **Important:** You much `catch` you own request error(s). + +#### urls +Type: `String` or `Array`
+Required: `true` + +One or many URLs to be prefetched. + +> **Note:** Each `url` value is resolved from the current location. + +#### isPriority +Type: `Boolean`
+Default: `false` + +Whether or not the URL(s) should be treated as "high priority" targets.
+By default, calls to `prefetch()` are low priority. + +> **Note:** This behaves identically to `listen()`'s `priority` option. + + +## TODO -TODO: * Explore detecting file-extension of resources and using [rel=preload](https://w3c.github.io/preload/) for high priority fetches * Explore using [Priority Hints](https://github.com/WICG/priority-hints) for importance hinting @@ -107,7 +199,7 @@ Alternatively, see the [Intersection Observer polyfill](https://github.com/w3c/I Defaults to 2 seconds (via `requestIdleCallback`). Here we override it to 4 seconds: ```js -quicklink({ +quicklink.listen({ timeout: 4000 }); ``` @@ -117,28 +209,35 @@ quicklink({ Defaults to `document` otherwise. ```js -const elem = document.getElementById('carousel'); -quicklink({ - el: elem +quicklink.listen({ + el: document.getElementById('carousel') }); ``` -### Set a custom array of URLs to be prefetched +### Programmatically `prefetch()` URLs If you would prefer to provide a static list of URLs to be prefetched, instead of detecting those in-viewport, customizing URLs is supported. ```js -quicklink({ - urls: ['2.html','3.html', '4.js'] -}); +// Single URL +quicklink.prefetch('2.html'); + +// Multiple URLs +quicklink.prefetch(['2.html', '3.html', '4.js']); + +// Multiple URLs, with high priority +// Note: Can also be use with single URL! +quicklink.prefetch(['2.html', '3.html', '4.js'], true); ``` -### Set the request priority for prefetches +### Set the request priority for prefetches while scrolling Defaults to low-priority (`rel=prefetch` or XHR). For high-priority (`priority: true`), attempts to use `fetch()` or falls back to XHR. +> **Note:** This runs `prefetch(..., true)` with URLs found within the `options.el` container. + ```js -quicklink({ priority: true }); +quicklink.listen({ priority: true }); ``` ### Specify a custom list of allowed origins @@ -148,7 +247,7 @@ Provide a list of hostnames that should be prefetch-able. Only the same origin i > **Important:** You must also include your own hostname! ```js -quicklink({ +quicklink.listen({ origins: [ // add mine 'my-website.com', @@ -168,7 +267,7 @@ Enables all cross-origin requests to be made. > **Note:** You may run into [CORB](https://chromium.googlesource.com/chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md) and [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) issues! ```js -quicklink({ +quicklink.listen({ origins: true, // or origins: [] @@ -187,7 +286,7 @@ These filters run _after_ the `origins` matching has run. Ignores can be useful // - all ".zip" extensions // - all tags with "noprefetch" attribute // -quicklink({ +quicklink.listen({ ignores: [ /\/api\/?/, uri => uri.includes('.zip'), @@ -201,16 +300,16 @@ You may also wish to ignore prefetches to URLs which contain a URL fragment (e.g Using `ignores` this can be achieved as follows: ```js -quicklink({ - ignores: [ - uri => uri.includes('#') - // or RegExp: /#(.+)/ - // or element matching: (uri, elem) => !!elem.hash - ] +quicklink.listen({ + ignores: [ + uri => uri.includes('#') + // or RegExp: /#(.+)/ + // or element matching: (uri, elem) => !!elem.hash + ] }); ``` -## Browser support +## Browser Support The prefetching provided by `quicklink` can be viewed as a [progressive enhancement](https://www.smashingmagazine.com/2009/04/progressive-enhancement-what-it-is-and-how-to-use-it/). Cross-browser support is as follows: @@ -225,15 +324,17 @@ Certain features have layered support: ## Using the prefetcher directly -`quicklink` includes a prefetcher that can be individually imported for use in other projects. After installing `quicklink` as a dependency, you can use it as follows: +A `prefetch` method can be individually imported for use in other projects.
+This method includes the logic to respect Data Saver and 2G connections. It also issues requests thru `fetch()`, XHRs, or `link[rel=prefetch]` depending on (a) the `isPriority` value and (b) the current browser's support. + +After installing `quicklink` as a dependency, you can use it as follows: ```html ``` @@ -258,7 +359,7 @@ Please note: this is by no means an exhaustive benchmark of the pros and cons of ### Session Stitching -Cross-origin prefetching (e.g a.com/foo.html prefetches b.com/bar.html) has a number of limitations. One such limitation is with session-stitching. b.com may expect a.com's navigation requests to include session information (e.g a temporary ID - e.g b.com/bar.html?hash=<>×tamp=<>), where this information is used to customize the experience or log information to analytics. If session-stitching requires a timestamp in the URL, what is prefetched and stored in the HTTP cache may not be the same as the one the user ultimately navigates to. This introduces a challenge as it can result in double prefetches. +Cross-origin prefetching (e.g a.com/foo.html prefetches b.com/bar.html) has a number of limitations. One such limitation is with session-stitching. b.com may expect a.com's navigation requests to include session information (e.g a temporary ID - e.g b.com/bar.html?hash=<>×tamp=<>), where this information is used to customize the experience or log information to analytics. If session-stitching requires a timestamp in the URL, what is prefetched and stored in the HTTP cache may not be the same as the one the user ultimately navigates to. This introduces a challenge as it can result in double prefetches. To workaround this problem, you can consider passing along session information via the [ping attribute](https://caniuse.com/#feat=ping) (separately) so the origin can stitch a session together asynchronously. diff --git a/demos/basic.html b/demos/basic.html index 0e9deab5..b04c66f5 100644 --- a/demos/basic.html +++ b/demos/basic.html @@ -34,8 +34,8 @@

Basic demo

- \ No newline at end of file + diff --git a/demos/network-idle.html b/demos/network-idle.html index aee38c7d..e88d1169 100644 --- a/demos/network-idle.html +++ b/demos/network-idle.html @@ -34,8 +34,8 @@ - \ No newline at end of file + diff --git a/package.json b/package.json index 849f0174..faab5ffc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "quicklink", "version": "1.0.1", "description": "Faster subsequent page-loads by prefetching in-viewport links during idle time", - "main": "dist/quicklink.js", "repository": "https://github.com/GoogleChromeLabs/quicklink.git", "homepage": "https://github.com/GoogleChromeLabs/quicklink", "bugs": { @@ -10,6 +9,7 @@ }, "author": "addyosmani ", "license": "Apache-2.0", + "main": "dist/quicklink.js", "module": "dist/quicklink.mjs", "jsnext:main": "dist/quicklink.mjs", "umd:main": "dist/quicklink.umd.js", @@ -19,7 +19,7 @@ "lint-fix": "eslint src/*.mjs test/*.js --fix demos/*.js", "start": "http-server .", "test": "yarn run build && mocha test/bootstrap.js --recursive test", - "build": "microbundle src/index.mjs --no-sourcemap", + "build": "microbundle src/index.mjs --no-sourcemap --external none", "prepare": "yarn run -s build", "bundlesize": "bundlesize", "changelog": "yarn conventional-changelog -i CHANGELOG.md -s -r 0", @@ -33,6 +33,9 @@ "background", "speed" ], + "dependencies": { + "throttles": "^1.0.0" + }, "devDependencies": { "babel-preset-env": "^1.7.0", "bundlesize": "^0.17.0", @@ -53,4 +56,4 @@ "maxSize": "2 kB" } ] -} \ No newline at end of file +} diff --git a/src/index.mjs b/src/index.mjs index 82939b28..ccdb78af 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -13,34 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ - -import prefetch from './prefetch.mjs'; +import throttle from 'throttles'; +import { priority, supported } from './prefetch.mjs'; import requestIdleCallback from './request-idle-callback.mjs'; +// Cache of URLs we've prefetched +// Its `size` is compared against `opts.limit` value. const toPrefetch = new Set(); -const observer = window.IntersectionObserver && new IntersectionObserver(entries => { - entries.forEach(entry => { - if (entry.isIntersecting) { - const link = entry.target; - if (toPrefetch.has(link.href)) { - observer.unobserve(link); - prefetcher(link.href); - } - } - }); -}); - -/** - * Prefetch a supplied URL. This will also remove - * the URL from the toPrefetch Set. - * @param {String} url - URL to prefetch - */ -function prefetcher(url) { - toPrefetch.delete(url); - prefetch(new URL(url, location.href).toString(), observer.priority); -} - /** * Determine if the anchor tag should be prefetched. * A filter can be a RegExp, Function, or Array of both. @@ -63,40 +43,92 @@ function isIgnored(node, filter) { * links for `document`. Can also work off a supplied * DOM element or static array of URLs. * @param {Object} options - Configuration options for quicklink - * @param {Array} options.urls - Array of URLs to prefetch (override) - * @param {Object} options.el - DOM element to prefetch in-viewport links of - * @param {Boolean} options.priority - Attempt higher priority fetch (low or high) - * @param {Array} options.origins - Allowed origins to prefetch (empty allows all) - * @param {Array|RegExp|Function} options.ignores - Custom filter(s) that run after origin checks - * @param {Number} options.timeout - Timeout after which prefetching will occur - * @param {Function} options.timeoutFn - Custom timeout function + * @param {Object} [options.el] - DOM element to prefetch in-viewport links of + * @param {Boolean} [options.priority] - Attempt higher priority fetch (low or high) + * @param {Array} [options.origins] - Allowed origins to prefetch (empty allows all) + * @param {Array|RegExp|Function} [options.ignores] - Custom filter(s) that run after origin checks + * @param {Number} [options.timeout] - Timeout after which prefetching will occur + * @param {Number} [options.throttle] - The concurrency limit for prefetching + * @param {Number} [options.limit] - The total number of prefetches to allow + * @param {Function} [options.timeoutFn] - Custom timeout function + * @param {Function} [options.onError] - Error handler for failed `prefetch` requests */ -export default function (options) { +export function listen(options) { if (!options) options = {}; + if (!window.IntersectionObserver) return; - observer && (observer.priority = options.priority || false); + const [toAdd, isDone] = throttle(options.throttle || 1/0); + const limit = options.limit || 1/0; const allowed = options.origins || [location.hostname]; const ignores = options.ignores || []; - const timeout = options.timeout || 2e3; const timeoutFn = options.timeoutFn || requestIdleCallback; - timeoutFn(() => { - // If URLs are given, prefetch them. - if (options.urls) { - options.urls.forEach(prefetcher); - } else if (observer) { - // If not, find all links and use IntersectionObserver. - Array.from((options.el || document).querySelectorAll('a'), link => { - observer.observe(link); - // If the anchor matches a permitted origin - // ~> A `[]` or `true` means everything is allowed - if (!allowed.length || allowed.includes(link.hostname)) { - // If there are any filters, the link must not match any of them - isIgnored(link, ignores) || toPrefetch.add(link.href); + const observer = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (entry.isIntersecting) { + observer.unobserve(entry = entry.target); + // Do not prefetch if will match/exceed limit + if (toPrefetch.size < limit) { + toAdd(() => { + prefetch(entry.href, options.priority).then(isDone).catch(err => { + isDone(); if (options.onError) options.onError(err); + }); + }); } - }); - } - }, {timeout}); + } + }); + }); + + timeoutFn(() => { + // Find all links & Connect them to IO if allowed + (options.el || document).querySelectorAll('a').forEach(link => { + // If the anchor matches a permitted origin + // ~> A `[]` or `true` means everything is allowed + if (!allowed.length || allowed.includes(link.hostname)) { + // If there are any filters, the link must not match any of them + isIgnored(link, ignores) || observer.observe(link); + } + }); + }, { + timeout: options.timeout || 2000 + }); + + return function () { + // wipe url list + toPrefetch.clear(); + // detach IO entries + observer.disconnect(); + }; +} + + +/** +* Prefetch a given URL with an optional preferred fetch priority +* @param {String} url - the URL to fetch +* @param {Boolean} [isPriority] - if is "high" priority +* @param {Object} [conn] - navigator.connection (internal) +* @return {Object} a Promise +*/ +export function prefetch(url, isPriority, conn) { + if (conn = navigator.connection) { + // Don't prefetch if using 2G or if Save-Data is enabled. + if (conn.saveData || /2g/.test(conn.effectiveType)) return; + } + + // Dev must supply own catch() + return Promise.all( + [].concat(url).map(str => { + if (!toPrefetch.has(str)) { + // Add it now, regardless of its success + // ~> so that we don't repeat broken links + toPrefetch.add(str); + + return (isPriority ? priority : supported)( + new URL(str, location.href).toString() + ); + } + }) + ); } diff --git a/src/prefetch.mjs b/src/prefetch.mjs index 18b43c17..05e7f16b 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -16,17 +16,15 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -const preFetched = {}; /** * Checks if a feature on `link` is natively supported. * Examples of features include `prefetch` and `preload`. - * @param {string} feature - name of the feature to test * @return {Boolean} whether the feature is supported */ -function support(feature) { - const link = document.createElement('link'); - return link.relList && link.relList.supports && link.relList.supports(feature); +function hasPrefetch(link) { + link = document.createElement('link'); + return link.relList && link.relList.supports && link.relList.supports('prefetch'); } /** @@ -34,14 +32,14 @@ function support(feature) { * @param {string} url - the URL to fetch * @return {Object} a Promise */ -function linkPrefetchStrategy(url) { - return new Promise((resolve, reject) => { - const link = document.createElement(`link`); +function viaDOM(url) { + return new Promise((res, rej, link) => { + link = document.createElement(`link`); link.rel = `prefetch`; link.href = url; - link.onload = resolve; - link.onerror = reject; + link.onload = res; + link.onerror = rej; document.head.appendChild(link); }); @@ -52,14 +50,14 @@ function linkPrefetchStrategy(url) { * @param {string} url - the URL to fetch * @return {Object} a Promise */ -function xhrPrefetchStrategy(url) { - return new Promise((resolve, reject) => { - const req = new XMLHttpRequest(); +function viaXHR(url) { + return new Promise((res, rej, req) => { + req = new XMLHttpRequest(); req.open(`GET`, url, req.withCredentials=true); req.onload = () => { - (req.status === 200) ? resolve() : reject(); + (req.status === 200) ? res() : rej(); }; req.send(); @@ -72,7 +70,7 @@ function xhrPrefetchStrategy(url) { * @param {string} url - the URL to fetch * @return {Object} a Promise */ -function highPriFetchStrategy(url) { +export function priority(url) { // TODO: Investigate using preload for high-priority // fetches. May have to sniff file-extension to provide // valid 'as' values. In the future, we may be able to @@ -80,36 +78,7 @@ function highPriFetchStrategy(url) { // // As of 2018, fetch() is high-priority in Chrome // and medium-priority in Safari. - return self.fetch == null - ? xhrPrefetchStrategy(url) - : fetch(url, {credentials: `include`}); + return window.fetch ? fetch(url, {credentials: `include`}) : viaXHR(url); } -const supportedPrefetchStrategy = support('prefetch') - ? linkPrefetchStrategy - : xhrPrefetchStrategy; - -/** - * Prefetch a given URL with an optional preferred fetch priority - * @param {String} url - the URL to fetch - * @param {Boolean} isPriority - if is "high" priority - * @param {Object} conn - navigator.connection (internal) - * @return {Object} a Promise - */ -function prefetcher(url, isPriority, conn) { - if (preFetched[url]) { - return; - } - - if (conn = navigator.connection) { - // Don't prefetch if the user is on 2G or if Save-Data is enabled. - if ((conn.effectiveType || '').includes('2g') || conn.saveData) return; - } - - // Wanna do something on catch()? - return (isPriority ? highPriFetchStrategy : supportedPrefetchStrategy)(url).then(() => { - preFetched[url] = true; - }); -}; - -export default prefetcher; +export const supported = hasPrefetch() ? viaDOM : viaXHR; diff --git a/test/index.html b/test/index.html index c055dc0a..5fc86bfe 100644 --- a/test/index.html +++ b/test/index.html @@ -20,20 +20,17 @@
Link 4 - \ No newline at end of file + diff --git a/test/quicklink.spec.js b/test/quicklink.spec.js index 3243034b..1fe8ad44 100644 --- a/test/quicklink.spec.js +++ b/test/quicklink.spec.js @@ -57,18 +57,6 @@ describe('quicklink tests', function () { expect(responseURLs).to.include(`${server}/4.html`); }); - it('should prefetch a static list of URLs correctly', async function () { - const responseURLs = []; - page.on('response', resp => { - responseURLs.push(resp.url()); - }); - await page.goto(`${server}/test-static-url-list.html`); - await page.waitFor(1000); - expect(responseURLs).to.be.an('array'); - expect(responseURLs).to.include(`${server}/2.html`); - expect(responseURLs).to.include(`${server}/4.html`); - }); - it('should prefetch in-viewport links from a custom DOM source', async function () { const responseURLs = []; page.on('response', resp => { @@ -166,4 +154,105 @@ describe('quicklink tests', function () { // (uri, elem) => elem.textContent.includes('Spinner') expect(responseURLs).to.not.include('https://github.githubassets.com/images/spinners/octocat-spinner-32.gif'); }); + + it('should accept a single URL to prefetch()', async function () { + const responseURLs = []; + page.on('response', resp => { + responseURLs.push(resp.url()); + }); + await page.goto(`${server}/test-prefetch-single.html`); + await page.waitFor(1000); + expect(responseURLs).to.be.an('array'); + expect(responseURLs).to.include(`${server}/2.html`); + }); + + it('should accept multiple URLs to prefetch()', async function () { + const responseURLs = []; + page.on('response', resp => { + responseURLs.push(resp.url()); + }); + await page.goto(`${server}/test-prefetch-multiple.html`); + await page.waitFor(1000); + + // don't care about first 4 URLs (markup) + const ours = responseURLs.slice(4); + + expect(ours.length).to.equal(3); + expect(ours).to.include(`${server}/2.html`); + expect(ours).to.include(`${server}/3.html`); + expect(ours).to.include(`${server}/4.html`); + }); + + it('should not prefetch() the same URL repeatedly', async function () { + const responseURLs = []; + page.on('response', resp => { + responseURLs.push(resp.url()); + }); + await page.goto(`${server}/test-prefetch-duplicate.html`); + await page.waitFor(1000); + + // don't care about first 4 URLs (markup) + const ours = responseURLs.slice(4); + + expect(ours.length).to.equal(1); + expect(ours).to.include(`${server}/2.html`); + }); + + it('should not call the same URL repeatedly (shared)', async function () { + const responseURLs = []; + page.on('response', resp => { + responseURLs.push(resp.url()); + }); + await page.goto(`${server}/test-prefetch-duplicate-shared.html`); + await page.waitFor(1000); + + // count occurences of our link + const target = responseURLs.filter(x => x === `${server}/2.html`); + expect(target.length).to.equal(1); + }); + + it('should not exceed the `limit` total', async function () { + const responseURLs = []; + page.on('response', resp => { + responseURLs.push(resp.url()); + }); + await page.goto(`${server}/test-limit.html`); + await page.waitFor(1000); + + // don't care about first 4 URLs (markup) + const ours = responseURLs.slice(4); + + expect(ours.length).to.equal(1); + expect(ours).to.include(`${server}/1.html`); + }); + + it('should respect the `throttle` concurrency', async function () { + const sleep = ms => new Promise(r => setTimeout(r, ms)); + const URLs = []; // Note: Page makes 4 requests + + // Make HTML requests take a long time + // ~> so that we can ensure throttling occurs + await page.setRequestInterception(true); + + page.on('request', async req => { + if (/test\/\d+\.html$/i.test(req.url())) { + await sleep(100); + URLs.push(req.url()); + return req.respond({ status: 200 }); + } + req.continue(); + }); + + await page.goto(`${server}/test-throttle.html`); + + // Only 2 should be done by now + // Note: Parallel requests, w/ 50ms buffer + await page.waitFor(150); + expect(URLs.length).to.equal(2); + + // All should be done by now + // Note: Parallel requests, w/ 50ms buffer + await page.waitFor(250); + expect(URLs.length).to.equal(4); + }); }); diff --git a/test/test-allow-origin-all.html b/test/test-allow-origin-all.html index dfc87971..b1d6faee 100644 --- a/test/test-allow-origin-all.html +++ b/test/test-allow-origin-all.html @@ -21,10 +21,10 @@ Link 4 - \ No newline at end of file + diff --git a/test/test-allow-origin.html b/test/test-allow-origin.html index c1057c78..c341d82e 100644 --- a/test/test-allow-origin.html +++ b/test/test-allow-origin.html @@ -20,10 +20,10 @@ Link 4 - \ No newline at end of file + diff --git a/test/test-basic-usage.html b/test/test-basic-usage.html index 37461f55..fff87c0a 100644 --- a/test/test-basic-usage.html +++ b/test/test-basic-usage.html @@ -20,8 +20,8 @@ Link 4 - \ No newline at end of file + diff --git a/test/test-custom-dom-source.html b/test/test-custom-dom-source.html index e3a5c198..7ce08403 100644 --- a/test/test-custom-dom-source.html +++ b/test/test-custom-dom-source.html @@ -20,11 +20,10 @@ Link 4 - \ No newline at end of file + diff --git a/test/test-es-modules.html b/test/test-es-modules.html index b39b531c..70498565 100644 --- a/test/test-es-modules.html +++ b/test/test-es-modules.html @@ -19,9 +19,9 @@ Link 4 - \ No newline at end of file + diff --git a/test/test-ignore-basic.html b/test/test-ignore-basic.html index 67612a16..da080708 100644 --- a/test/test-ignore-basic.html +++ b/test/test-ignore-basic.html @@ -21,10 +21,10 @@ Link 4 - \ No newline at end of file + diff --git a/test/test-ignore-multiple.html b/test/test-ignore-multiple.html index 2978fd11..574ab221 100644 --- a/test/test-ignore-multiple.html +++ b/test/test-ignore-multiple.html @@ -21,7 +21,7 @@ Link 4 - \ No newline at end of file + diff --git a/test/test-limit.html b/test/test-limit.html new file mode 100644 index 00000000..51d879a2 --- /dev/null +++ b/test/test-limit.html @@ -0,0 +1,26 @@ + + + + + + + Prefetch: Basic Usage + + + + + + + Link 1 + Link 2 + Link 3 + Link 4 + + + + + diff --git a/test/test-prefetch-duplicate-shared.html b/test/test-prefetch-duplicate-shared.html new file mode 100644 index 00000000..f5650591 --- /dev/null +++ b/test/test-prefetch-duplicate-shared.html @@ -0,0 +1,27 @@ + + + + + + Prefetch: Static URL list + + + + + + + Link 2 +
+ CSS +
+ Link 4 + + + + diff --git a/test/test-static-url-list.html b/test/test-prefetch-duplicate.html similarity index 91% rename from test/test-static-url-list.html rename to test/test-prefetch-duplicate.html index f979c230..a5d9f865 100644 --- a/test/test-static-url-list.html +++ b/test/test-prefetch-duplicate.html @@ -1,6 +1,5 @@ - @@ -20,10 +19,7 @@ Link 4 - - \ No newline at end of file + diff --git a/test/test-prefetch-multiple.html b/test/test-prefetch-multiple.html new file mode 100644 index 00000000..80d882ec --- /dev/null +++ b/test/test-prefetch-multiple.html @@ -0,0 +1,25 @@ + + + + + + Prefetch: Static URL list + + + + + + + Link 1 + Link 2 + Link 3 +
+ CSS +
+ Link 4 + + + + diff --git a/test/test-prefetch-single.html b/test/test-prefetch-single.html new file mode 100644 index 00000000..f948160b --- /dev/null +++ b/test/test-prefetch-single.html @@ -0,0 +1,25 @@ + + + + + + Prefetch: Static URL list + + + + + + + Link 1 + Link 2 + Link 3 +
+ CSS +
+ Link 4 + + + + diff --git a/test/test-same-origin.html b/test/test-same-origin.html index 8d81e66f..7a784e2a 100644 --- a/test/test-same-origin.html +++ b/test/test-same-origin.html @@ -20,8 +20,8 @@ Link 4 - \ No newline at end of file + diff --git a/test/test-throttle.html b/test/test-throttle.html new file mode 100644 index 00000000..3a2a77ba --- /dev/null +++ b/test/test-throttle.html @@ -0,0 +1,26 @@ + + + + + + + Prefetch: Basic Usage + + + + + + + Link 1 + Link 2 + Link 3 + Link 4 + + + + +