vendor: 11,672 bytes, lines 1-330
1/*! instant.page v5.2.0 - (C) 2019-2023 Alexandre Dieulot - https://instant.page/license */ 2 3let _chromiumMajorVersionInUserAgent = null 4 , _allowQueryString 5 , _allowExternalLinks 6 , _useWhitelist 7 , _delayOnHover = 65 8 , _lastTouchTimestamp 9 , _mouseoverTimer 10 , _preloadedList = new Set() 11 12const DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION = 1111 13 14init() 15 16function init() { 17 const isSupported = document.createElement('link').relList.supports('prefetch') 18 // instant.page is meant to be loaded with <script type=module> 19 // (though sometimes webmasters load it as a regular script). 20 // So itâs normally executed (and must not cause JavaScript errors) in: 21 // - Chromium 61+ 22 // - Gecko in Firefox 60+ 23 // - WebKit in Safari 10.1+ (iOS 10.3+, macOS 10.10+) 24 // 25 // The check above used to check for IntersectionObserverEntry.isIntersecting 26 // but module scripts support implies this compatibility â except in Safari 27 // 10.1â12.0, but this prefetch check takes care of it. 28 29 if (!isSupported) { 30 return 31 } 32 33 const handleVaryAcceptHeader = 'instantVaryAccept' in document.body.dataset || 'Shopify' in window 34 // The `Vary: Accept` header when received in Chromium 79â109 makes prefetches 35 // unusable, as Chromium used to send a different `Accept` header. 36 // Itâs applied on all Shopify sites by default, as Shopify is very popular 37 // and is the main source of this problem. 38 // `window.Shopify` only exists on âclassicâ Shopify sites. Those using 39 // Hydrogen (Remix SPA) arenât concerned. 40 41 const chromiumUserAgentIndex = navigator.userAgent.indexOf('Chrome/') 42 if (chromiumUserAgentIndex > -1) { 43 _chromiumMajorVersionInUserAgent = parseInt(navigator.userAgent.substring(chromiumUserAgentIndex + 'Chrome/'.length)) 44 } 45 // The user agent client hints API is a theoretically more reliable way to 46 // get Chromiumâs version⦠but itâs not available in Samsung Internet 20. 47 // It also requires a secure context, which would make debugging harder, 48 // and is only available in recent Chromium versions. 49 // In practice, Chromium browsers never shy from announcing "Chrome" in 50 // their regular user agent string, as that maximizes their compatibility. 51 52 if (handleVaryAcceptHeader && _chromiumMajorVersionInUserAgent && _chromiumMajorVersionInUserAgent < 110) { 53 return 54 } 55 56 const mousedownShortcut = 'instantMousedownShortcut' in document.body.dataset 57 _allowQueryString = 'instantAllowQueryString' in document.body.dataset 58 _allowExternalLinks = 'instantAllowExternalLinks' in document.body.dataset 59 _useWhitelist = 'instantWhitelist' in document.body.dataset 60 61 const eventListenersOptions = { 62 capture: true, 63 passive: true, 64 } 65 66 let useMousedown = false 67 let useMousedownOnly = false 68 let useViewport = false 69 if ('instantIntensity' in document.body.dataset) { 70 const intensity = document.body.dataset.instantIntensity 71 72 if (intensity.startsWith('mousedown')) { 73 useMousedown = true 74 if (intensity == 'mousedown-only') { 75 useMousedownOnly = true 76 } 77 } 78 else if (intensity.startsWith('viewport')) { 79 const isNavigatorConnectionSaveDataEnabled = navigator.connection && navigator.connection.saveData 80 const isNavigatorConnectionLike2g = navigator.connection && navigator.connection.effectiveType && navigator.connection.effectiveType.includes('2g') 81 if (!isNavigatorConnectionSaveDataEnabled && !isNavigatorConnectionLike2g) { 82 if (intensity == "viewport") { 83 if (document.documentElement.clientWidth * document.documentElement.clientHeight < 450000) { 84 useViewport = true 85 // Smartphones are the most likely to have a slow connection, and 86 // their small screen size limits the number of links (and thus 87 // server load). 88 // 89 // Foldable phones (being expensive as of 2023), tablets and PCs 90 // generally have a decent connection, and a big screen displaying 91 // more links that would put more load on the server. 92 // 93 // iPhone 14 Pro Max (want): 430Ã932 = 400â¯760 94 // Samsung Galaxy S22 Ultra with display size set to 80% (want): 95 // 450Ã965 = 434â¯250 96 // Small tablet (donât want): 600Ã960 = 576â¯000 97 // Those number are virtual screen size, the viewport (used for 98 // the check above) will be smaller with the browserâs interface. 99 } 100 } 101 else if (intensity == "viewport-all") { 102 useViewport = true 103 } 104 } 105 } 106 else { 107 const milliseconds = parseInt(intensity) 108 if (!isNaN(milliseconds)) { 109 _delayOnHover = milliseconds 110 } 111 } 112 } 113 114 if (!useMousedownOnly) { 115 document.addEventListener('touchstart', touchstartListener, eventListenersOptions) 116 } 117 118 if (!useMousedown) { 119 document.addEventListener('mouseover', mouseoverListener, eventListenersOptions) 120 } 121 else if (!mousedownShortcut) { 122 document.addEventListener('mousedown', mousedownListener, eventListenersOptions) 123 } 124 125 if (mousedownShortcut) { 126 document.addEventListener('mousedown', mousedownShortcutListener, eventListenersOptions) 127 } 128 129 if (useViewport) { 130 let requestIdleCallbackOrFallback = window.requestIdleCallback 131 // Safari has no support as of 16.3: https://webkit.org/b/164193 132 if (!requestIdleCallbackOrFallback) { 133 requestIdleCallbackOrFallback = (callback) => { 134 callback() 135 // A smarter fallback like setTimeout is not used because devices that 136 // may eventually be eligible to a Safari version supporting prefetch 137 // will be very powerful. 138 // The weakest devices that could be eligible are the 2017 iPad and 139 // the 2016 MacBook. 140 } 141 } 142 143 requestIdleCallbackOrFallback(function observeIntersection() { 144 const intersectionObserver = new IntersectionObserver((entries) => { 145 entries.forEach((entry) => { 146 if (entry.isIntersecting) { 147 const anchorElement = entry.target 148 intersectionObserver.unobserve(anchorElement) 149 preload(anchorElement.href) 150 } 151 }) 152 }) 153 154 document.querySelectorAll('a').forEach((anchorElement) => { 155 if (isPreloadable(anchorElement)) { 156 intersectionObserver.observe(anchorElement) 157 } 158 }) 159 }, { 160 timeout: 1500, 161 }) 162 } 163} 164 165function touchstartListener(event) { 166 _lastTouchTimestamp = performance.now() 167 // Chrome on Android triggers mouseover before touchcancel, so 168 // `_lastTouchTimestamp` must be assigned on touchstart to be measured 169 // on mouseover. 170 171 const anchorElement = event.target.closest('a') 172 173 if (!isPreloadable(anchorElement)) { 174 return 175 } 176 177 preload(anchorElement.href, 'high') 178} 179 180function mouseoverListener(event) { 181 if (performance.now() - _lastTouchTimestamp < DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION) { 182 return 183 } 184 185 if (!('closest' in event.target)) { 186 return 187 // Without this check sometimes an error âevent.target.closest is not a functionâ is thrown, for unknown reasons 188 // That error denotes that `event.target` isnât undefined. My best guess is that itâs the Document. 189 // 190 // Details could be gleaned from throwing such an error: 191 //throw new TypeError(`instant.page non-element event target: timeStamp=${~~event.timeStamp}, type=${event.type}, typeof=${typeof event.target}, nodeType=${event.target.nodeType}, nodeName=${event.target.nodeName}, viewport=${innerWidth}x${innerHeight}, coords=${event.clientX}x${event.clientY}, scroll=${scrollX}x${scrollY}`) 192 } 193 const anchorElement = event.target.closest('a') 194 195 if (!isPreloadable(anchorElement)) { 196 return 197 } 198 199 anchorElement.addEventListener('mouseout', mouseoutListener, {passive: true}) 200 201 _mouseoverTimer = setTimeout(() => { 202 preload(anchorElement.href, 'high') 203 _mouseoverTimer = undefined 204 }, _delayOnHover) 205} 206 207function mousedownListener(event) { 208 const anchorElement = event.target.closest('a') 209 210 if (!isPreloadable(anchorElement)) { 211 return 212 } 213 214 preload(anchorElement.href, 'high') 215} 216 217function mouseoutListener(event) { 218 if (event.relatedTarget && event.target.closest('a') == event.relatedTarget.closest('a')) { 219 return 220 } 221 222 if (_mouseoverTimer) { 223 clearTimeout(_mouseoverTimer) 224 _mouseoverTimer = undefined 225 } 226} 227 228function mousedownShortcutListener(event) { 229 if (performance.now() - _lastTouchTimestamp < DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION) { 230 return 231 } 232 233 const anchorElement = event.target.closest('a') 234 235 if (event.which > 1 || event.metaKey || event.ctrlKey) { 236 return 237 } 238 239 if (!anchorElement) { 240 return 241 } 242 243 anchorElement.addEventListener('click', function (event) { 244 if (event.detail == 1337) { 245 return 246 } 247 248 event.preventDefault() 249 }, {capture: true, passive: false, once: true}) 250 251 const customEvent = new MouseEvent('click', {view: window, bubbles: true, cancelable: false, detail: 1337}) 252 anchorElement.dispatchEvent(customEvent) 253} 254 255function isPreloadable(anchorElement) { 256 if (!anchorElement || !anchorElement.href) { 257 return 258 } 259 260 if (_useWhitelist && !('instant' in anchorElement.dataset)) { 261 return 262 } 263 264 if (anchorElement.origin != location.origin) { 265 let allowed = _allowExternalLinks || 'instant' in anchorElement.dataset 266 if (!allowed || !_chromiumMajorVersionInUserAgent) { 267 // Chromium-only: see comment on ârestrictive prefetchâ 268 return 269 } 270 } 271 272 if (!['http:', 'https:'].includes(anchorElement.protocol)) { 273 return 274 } 275 276 if (anchorElement.protocol == 'http:' && location.protocol == 'https:') { 277 return 278 } 279 280 if (!_allowQueryString && anchorElement.search && !('instant' in anchorElement.dataset)) { 281 return 282 } 283 284 if (anchorElement.hash && anchorElement.pathname + anchorElement.search == location.pathname + location.search) { 285 return 286 } 287 288 if ('noInstant' in anchorElement.dataset) { 289 return 290 } 291 292 return true 293} 294 295function preload(url, fetchPriority = 'auto') { 296 if (_preloadedList.has(url)) { 297 return 298 } 299 300 const linkElement = document.createElement('link') 301 linkElement.rel = 'prefetch' 302 linkElement.href = url 303 304 linkElement.fetchPriority = fetchPriority 305 // By default, a prefetch is loaded with a low priority. 306 // When thereâs a fair chance that this prefetch is going to be used in the 307 // near term (= after a touch/mouse event), giving it a high priority helps 308 // make the page load faster in case there are other resources loading. 309 // Prioritizing it implicitly means deprioritizing every other resource 310 // thatâs loading on the page. Due to HTML documents usually being much 311 // smaller than other resources (notably images and JavaScript), and 312 // prefetches happening once the initial page is sufficiently loaded, 313 // this theft of bandwidth should rarely be detrimental. 314 315 linkElement.as = 'document' 316 // as=document is Chromium-only and allows cross-origin prefetches to be 317 // usable for navigation. They call it ârestrictive prefetchâ and intend 318 // to remove it: https://crbug.com/1352371 319 // 320 // This document from the Chrome team dated 2022-08-10 321 // https://docs.google.com/document/d/1x232KJUIwIf-k08vpNfV85sVCRHkAxldfuIA5KOqi6M 322 // claims (I havenât tested) that data- and battery-saver modes as well as 323 // the setting to disable preloading do not disable restrictive prefetch, 324 // unlike regular prefetch. Thatâs good for prefetching on a touch/mouse 325 // event, but might be bad when prefetching every link in the viewport. 326 327 document.head.appendChild(linkElement) 328 329 _preloadedList.add(url) 330}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.