@prefetchru/prefetch 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -139,7 +139,7 @@ ESM версия автоматически определяет `nonce` чер
139
139
  // (window.Prefetch также доступен, если не занят другой библиотекой)
140
140
 
141
141
  // Версия библиотеки
142
- console.log(PrefetchRu.version) // "1.1.2"
142
+ console.log(PrefetchRu.version) // "1.1.3"
143
143
 
144
144
  // Программная предзагрузка URL
145
145
  // ВАЖНО: URL проходит те же проверки, что и автоматические ссылки
@@ -1,6 +1,6 @@
1
1
  /*!
2
- * prefetch.ru v1.1.2 (ESM) - Мгновенная загрузка страниц
2
+ * prefetch.ru v1.1.3 (ESM) - Мгновенная загрузка страниц
3
3
  * © 2026 Сергей Макаров | MIT License
4
4
  * https://prefetch.ru | https://github.com/prefetch-ru
5
5
  */
6
- var e="undefined"!=typeof window&&window.PrefetchRu&&window.PrefetchRu.__prefetchRu?window.PrefetchRu:"undefined"!=typeof window&&window.Prefetch&&window.Prefetch.__prefetchRu?window.Prefetch:function(e){var t=e&&e.getNonce;if(!(!e||e.isBrowser)||"undefined"==typeof window||"undefined"==typeof document)return{__prefetchRu:!0,version:"1.1.2",preload:function(){},destroy:function(){},refresh:function(){}};var n=new Set,r=new WeakMap,o=!1,i=0,a=[],c=new Set,s={prefetch:[],prerender:[],crossOrigin:[]},u=0,l="",d=0,f=0,h=null,p=!1,m=!1,v=null,g=!1,w=null,y=null,b=!1,L=65,E=80,x=50,T=!1,k=!1,O=!1,P=!1,A="none",S=!1,N=!1,C=!1,q=!1,R=!1,I=/(^|\/)(login|logout|auth|register|cart|basket|add|delete|remove)(\/|$|\.)/i,M=/\.(pdf|doc|docx|xls|xlsx|zip|rar|exe)($|\?)/i;function _(){if(!o){var e=document.body;if(e){l=location.origin+location.pathname+location.search,v=void 0!==window.BX?"bitrix":void 0!==window.B24||void 0!==window.BX24?"bitrix24":document.querySelector(".t-records")||void 0!==window.Tilda?"tilda":null;var t=e.dataset;if(T="prefetchAllowQueryString"in t||"instantAllowQueryString"in t,k="prefetchAllowExternalLinks"in t||"instantAllowExternalLinks"in t,O="prefetchWhitelist"in t||"instantWhitelist"in t,t.prefetchNonce&&(y=t.prefetchNonce),!y&&t.instantNonce&&(y=t.instantNonce),!m&&("prefetchSpecrules"in t||"instantSpecrules"in t)&&HTMLScriptElement.supports&&HTMLScriptElement.supports("speculationrules")){var n=t.prefetchSpecrules||t.instantSpecrules;"prerender"===n?(A="prerender",P=!0):"no"!==n&&(A="prefetch",P=!0)}S="prefetchSpecrulesFallback"in t||"instantSpecrulesFallback"in t,N="prefetchPrerenderAll"in t||"instantPrerenderAll"in t;var r=t.prefetchIntensity||t.instantIntensity;if("mousedown"===r)C=!0;else if("viewport"===r||"viewport-all"===r)("viewport-all"===r||p&&D())&&(q=!0);else if(r){var i=parseInt(r,10);!isNaN(i)&&i>=0&&(L=i)}p&&(E=Math.max(60,Math.min(L||0,150))),(R="prefetchObserveDom"in t||"instantObserveDom"in t)||"bitrix"!==v&&"tilda"!==v||(R=!0),window.addEventListener("popstate",U),window.addEventListener("hashchange",U),window.addEventListener("pageshow",W),"tilda"===v&&L<100&&(L=100);var a={capture:!0,passive:!0};if(document.addEventListener("touchstart",z,a),C?document.addEventListener("mousedown",F,a):document.addEventListener("mouseover",K,a),q&&"undefined"==typeof IntersectionObserver&&(q=!1),q)(window.requestIdleCallback||function(e){setTimeout(e,1)})(Z,{timeout:1500});R&&q&&"undefined"!=typeof MutationObserver&&function(){if(o)return;if(te)return;(te=new MutationObserver(function(e){var t=!1;e:for(var n=0;n<e.length;n++)for(var r=e[n].addedNodes,o=0;o<r.length;o++){var i=r[o];if(1===i.nodeType&&("A"===i.tagName||i.querySelector&&i.querySelector("a"))){t=!0;break e}}t&&Y&&(clearTimeout(ne),ne=setTimeout(ee,100))})).observe(document.body,{childList:!0,subtree:!0})}()}}}function D(){return!g&&("slow-2g"!==w&&"2g"!==w&&"3g"!==w)}function U(){l=location.origin+location.pathname+location.search}function W(e){e&&e.persisted&&U()}function B(e){return e?(e.nodeType&&1!==e.nodeType&&(e=e.parentElement),e&&"function"==typeof e.closest?e.closest("a"):null):null}function z(e){if(!(o||e&&!1===e.isTrusted)){d=Date.now();var t=B(e.target);if(G(t)){f&&(clearTimeout(f),f=0),h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null);var n=!1;h=function(){n=!0,f&&(clearTimeout(f),f=0),document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null},document.addEventListener("touchmove",h,{capture:!0,passive:!0,once:!0}),document.addEventListener("scroll",h,{capture:!0,passive:!0,once:!0}),f=setTimeout(function(){h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null),f=0,n||H(t.href,t)},E)}}}function K(e){if(!o&&!(e&&!1===e.isTrusted||d&&Date.now()-d<2500)){var t=B(e.target);if(t&&!r.has(t)&&G(t)){t.addEventListener("mouseleave",j,{passive:!0,once:!0});var n=setTimeout(function(){H(t.href,t),r.delete(t)},L);r.set(t,n)}}}function j(e){var t=e.currentTarget;if(t){var n=r.get(t);n&&(clearTimeout(n),r.delete(t))}}function F(e){if(!o&&!(e&&!1===e.isTrusted||"number"==typeof e.button&&(1===e.button||2===e.button)||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||d&&Date.now()-d<2500)){var t=B(e.target);G(t)&&H(t.href,t)}}function G(e){if(!e)return!1;var t=e.getAttribute("href");if(null===t||""===t.trim())return!1;if(!e.href)return!1;if(e.target&&"_self"!==e.target)return!1;if(e.hasAttribute("download"))return!1;if("noPrefetch"in e.dataset||"prefetchNo"in e.dataset)return!1;if(O&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if("http:"!==e.protocol&&"https:"!==e.protocol)return!1;if("http:"===e.protocol&&"https:"===location.protocol)return!1;if(e.origin!==location.origin&&!k&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if(e.search&&!T&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if(e.hash&&e.pathname+e.search===location.pathname+location.search)return!1;var r=e.origin+e.pathname+e.search;return r!==l&&(!n.has(r)&&(!!function(e){var t=e.href,n=e.pathname||"",r=e.hash||"";if("bitrix"===v||"bitrix24"===v){if(-1!==t.indexOf("/bitrix/")||-1!==t.indexOf("sessid="))return!1;if(e.classList.contains("bx-ajax"))return!1}if("tilda"===v&&(-1!==r.indexOf("#popup:")||-1!==r.indexOf("#rec")))return!1;return!I.test(n)&&!M.test(n)}(e)&&!!function(e){var t=e.className||"",n=e.hostname||"";return-1===t.indexOf("ym-")&&("mc.yandex.ru"!==n&&"metrika.yandex.ru"!==n&&(-1===t.indexOf("ga-")&&-1===t.indexOf("gtm-")&&("google-analytics.com"!==n&&!n.endsWith(".google-analytics.com")&&("googletagmanager.com"!==n&&!n.endsWith(".googletagmanager.com")&&(-1===t.indexOf("piwik")&&-1===t.indexOf("matomo")&&("matomo.org"!==n&&!n.endsWith(".matomo.org")&&"piwik.org"!==n&&!n.endsWith(".piwik.org")))))))}(e)))}function H(e,t){if(!o&&D()){var r=function(e){try{var t=new URL(e,location.href),n=t.origin+t.pathname+t.search;return t.hash="",{requestUrl:t.href,key:n}}catch(t){return{requestUrl:e,key:e}}}(e),c=r.requestUrl,s=r.key;if(!n.has(s)){n.size>=x&&n.delete(n.values().next().value),n.add(s);var u=function(e){return P&&"none"!==A?"prerender"!==A?A:N||O||e&&e.dataset&&("prefetchPrerender"in e.dataset||"instantPrerender"in e.dataset)?"prerender":"prefetch":"none"}(t);if(i>=4)return a.length>=50?void n.delete(s):void a.push({url:c,key:s,mode:u});Q(c,s,u)}}}function Q(e,t,n){var r=!1;try{r=new URL(e,location.href).origin!==location.origin}catch(e){}if("none"===n)m||!b||r?V(e,t):J(e,t);else{var o=!1;try{!function(e,t){var n=!1;try{n=new URL(e,location.href).origin!==location.origin}catch(e){}n&&"prerender"===t&&(t="prefetch");n?s.crossOrigin.push(e):"prerender"===t?s.prerender.push(e):s.prefetch.push(e);if(!u){var r=window.requestIdleCallback||function(e){setTimeout(e,1)};u=r($,{timeout:50})}}(e,n),o=!0}catch(e){}!S&&o||(m||!b||r?V(e,t):J(e,t))}}function X(){for(;a.length>0&&i<4;){var e=a.shift();Q(e.url,e.key,e.mode)}}function $(){if(u=0,!o){var e=document.head;if(e){var t={};if(s.prefetch.length>0&&(t.prefetch=t.prefetch||[],t.prefetch.push({source:"list",urls:s.prefetch.slice()}),s.prefetch.length=0),s.prerender.length>0&&(t.prerender=t.prerender||[],t.prerender.push({source:"list",urls:s.prerender.slice()}),s.prerender.length=0),s.crossOrigin.length>0&&(t.prefetch=t.prefetch||[],t.prefetch.push({source:"list",urls:s.crossOrigin.slice(),referrer_policy:"no-referrer",requires:["anonymous-client-ip-when-cross-origin"]}),s.crossOrigin.length=0),t.prefetch||t.prerender){var n=document.createElement("script");n.type="speculationrules",y&&(n.nonce=y),n.textContent=JSON.stringify(t),e.appendChild(n),e.removeChild(n)}}}}function J(e,t){var r=document.head;if(r){i++;var o=document.createElement("link");o.rel="prefetch",o.href=e,o.as="document";try{o.fetchPriority="low"}catch(e){}try{new URL(e,location.href).origin!==location.origin&&(o.referrerPolicy="no-referrer",o.crossOrigin="anonymous")}catch(e){}var a=setTimeout(function(){a=0,n.delete(t),c()},3e4);o.onload=c,o.onerror=function(){n.delete(t),c()},r.appendChild(o)}else n.delete(t);function c(){a&&(clearTimeout(a),a=0),o.onload=o.onerror=null,o.parentNode&&o.parentNode.removeChild(o),i--,X()}}function V(e,t){if("function"==typeof fetch){i++;var r=!1,o=null,a=0;"undefined"!=typeof AbortController&&(o=new AbortController,c.add(o),a=setTimeout(function(){try{o.abort()}catch(e){}l(!1)},5e3));var s=!1;try{s=new URL(e,location.href).origin!==location.origin}catch(e){}var u={method:"GET",cache:"force-cache",credentials:s?"omit":"same-origin",mode:s?"no-cors":"cors"};s||(u.headers={Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",Purpose:"prefetch"}),s&&(u.referrerPolicy="no-referrer"),o&&(u.signal=o.signal);try{fetch(e,u).then(function(e){l(e&&(e.ok||304===e.status||"opaque"===e.type))}).catch(function(){l(!1)})}catch(e){l(!1)}}else n.delete(t);function l(e){r||(r=!0,a&&clearTimeout(a),o&&c.delete(o),e||n.delete(t),i--,X())}}!function(){if(t)try{y=t()}catch(e){}try{var e=document.createElement("link");e.relList&&"function"==typeof e.relList.supports&&(b=e.relList.supports("prefetch"))}catch(e){}var n=navigator.userAgent,r=navigator.userAgentData;if(r){m=!1;var o="Android"===r.platform;p=r.mobile||!1;for(var i=r.brands||[],a=0;a<i.length;a++){var c=i[a];if("Chromium"===c.brand||"Google Chrome"===c.brand){parseInt(c.version,10);break}}}else{m=/iPad|iPhone/.test(n)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1;o=/Android/.test(n);p=(m||o)&&Math.min(screen.width,screen.height)<768;var s=n.match(/Chrome\/(\d+)/);s&&parseInt(s[1],10)}p&&(x=20);var u=navigator.connection;u&&(w=u.effectiveType,g=u.saveData||!1),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",_):_()}();var Y=null;function Z(){o||Y||(Y=new IntersectionObserver(function(e){e.forEach(function(e){e.isIntersecting&&(Y.unobserve(e.target),G(e.target)&&H(e.target.href,e.target))})},{rootMargin:p?"100px":"200px"}),ee())}function ee(){Y&&document.querySelectorAll("a").forEach(function(e){G(e)&&Y.observe(e)})}var te=null,ne=null;return{__prefetchRu:!0,version:"1.1.2",preload:function(e){if(function(e){return!(!e||"string"!=typeof e||!(e=e.trim())||/^\/\//.test(e)||/^(javascript|data|vbscript|file):/i.test(e)||!(/^https?:\/\//i.test(e)||/^\//.test(e)||/^\./.test(e))&&/^[a-z][a-z0-9+.-]*:/i.test(e))}(e)){var t=document.createElement("a");t.setAttribute("href",e.trim()),G(t)&&H(t.href,t)}},destroy:function(){o=!0,a.length=0,f&&(clearTimeout(f),f=0),h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null),c.forEach(function(e){try{e.abort()}catch(e){}}),c.clear(),u&&((window.cancelIdleCallback||clearTimeout)(u),u=0),s.prefetch.length=s.prerender.length=s.crossOrigin.length=0;var e={capture:!0,passive:!0};document.removeEventListener("touchstart",z,e),document.removeEventListener("mouseover",K,e),document.removeEventListener("mousedown",F,e),window.removeEventListener("popstate",U),window.removeEventListener("hashchange",U),window.removeEventListener("pageshow",W),Y&&(Y.disconnect(),Y=null),te&&(te.disconnect(),te=null)},refresh:U}}({isBrowser:"undefined"!=typeof window,getNonce:function(){var e=function(e){try{if(!e)return null;for(var t=document.getElementsByTagName("script"),n=0;n<t.length;n++){var r=t[n];if(r&&r.src&&r.src===e){var o=r.nonce||r.getAttribute("nonce")||null;if(o)return o;break}}}catch(e){}return null}(import.meta.url);if(e)return e;try{var t=document.currentScript;if(t&&t.nonce)return t.nonce}catch(e){}return null}});"undefined"!=typeof window&&(window.PrefetchRu=e,window.Prefetch||(window.Prefetch=e));export{e as Prefetch,e as default};
6
+ var e="undefined"!=typeof window&&window.PrefetchRu&&window.PrefetchRu.__prefetchRu?window.PrefetchRu:"undefined"!=typeof window&&window.Prefetch&&window.Prefetch.__prefetchRu?window.Prefetch:function(e){var t=e&&e.getNonce;if(!(!e||e.isBrowser)||"undefined"==typeof window||"undefined"==typeof document)return{__prefetchRu:!0,version:"1.1.3",preload:function(){},destroy:function(){},refresh:function(){}};var n=new Set,r=new WeakMap,i=!1,o=0,a=[],c=new Set,s={prefetch:[],prerender:[],crossOrigin:[]},u=0,l="",f=0,d=0,h=null,p=!1,v=!1,m=null,g=!1,w=null,y=null,b=null,E=!1,L=65,x=80,T=50,O=!1,k=!1,P=!1,A=!1,S="none",N=!1,q=!1,C=!1,I=!1,M=!1,R=/(^|\/)(login|logout|auth|register|cart|basket|add|delete|remove)(\/|$|\.)/i,_=/\.(pdf|doc|docx|xls|xlsx|zip|rar|exe)($|\?)/i;function D(){if(!i){var e=document.body;if(e){l=location.origin+location.pathname+location.search,m=void 0!==window.BX?"bitrix":void 0!==window.B24||void 0!==window.BX24?"bitrix24":document.querySelector(".t-records")||void 0!==window.Tilda?"tilda":null;var t=e.dataset;if(O="prefetchAllowQueryString"in t||"instantAllowQueryString"in t,k="prefetchAllowExternalLinks"in t||"instantAllowExternalLinks"in t,P="prefetchWhitelist"in t||"instantWhitelist"in t,t.prefetchNonce&&(b=t.prefetchNonce),!b&&t.instantNonce&&(b=t.instantNonce),!v&&("prefetchSpecrules"in t||"instantSpecrules"in t)&&HTMLScriptElement.supports&&HTMLScriptElement.supports("speculationrules")){var n=t.prefetchSpecrules||t.instantSpecrules;"prerender"===n?(S="prerender",A=!0):"no"!==n&&(S="prefetch",A=!0)}N="prefetchSpecrulesFallback"in t||"instantSpecrulesFallback"in t,q="prefetchPrerenderAll"in t||"instantPrerenderAll"in t;var r=t.prefetchIntensity||t.instantIntensity;if("mousedown"===r)C=!0;else if("viewport"===r||"viewport-all"===r)("viewport-all"===r||p&&W())&&(I=!0);else if(r){var o=parseInt(r,10);!isNaN(o)&&o>=0&&(L=o)}p&&(x=Math.max(60,Math.min(L||0,150))),(M="prefetchObserveDom"in t||"instantObserveDom"in t)||"bitrix"!==m&&"tilda"!==m||(M=!0),window.addEventListener("popstate",U),window.addEventListener("hashchange",U),window.addEventListener("pageshow",z),"tilda"===m&&L<100&&(L=100);var a={capture:!0,passive:!0};if(document.addEventListener("touchstart",j,a),C?document.addEventListener("mousedown",Q,a):document.addEventListener("mouseover",F,a),I&&"undefined"==typeof IntersectionObserver&&(I=!1),I)(window.requestIdleCallback||function(e){setTimeout(e,1)})(te,{timeout:1500});M&&I&&"undefined"!=typeof MutationObserver&&function(){if(i)return;if(ne)return;ne=new MutationObserver(function(e){if(ee){for(var t=[],n=0;n<e.length;n++)for(var r=e[n].addedNodes,i=0;i<r.length;i++){var o=r[i];if(1===o.nodeType)if("A"===o.tagName)t.push(o);else if(o.querySelectorAll)for(var a=o.querySelectorAll("a"),c=0;c<a.length;c++)t.push(a[c])}if(t.length>0)(window.requestIdleCallback||function(e){setTimeout(e,1)})(function(){for(var e=0;e<t.length;e++)ee&&X(t[e])&&ee.observe(t[e])})}}),ne.observe(document.body,{childList:!0,subtree:!0})}()}}}function W(){return!g&&("slow-2g"!==w&&"2g"!==w&&"3g"!==w)}function B(){y&&(w=y.effectiveType,g=y.saveData||!1)}function U(){l=location.origin+location.pathname+location.search}function z(e){e&&e.persisted&&U()}function K(e){return e?(e.nodeType&&1!==e.nodeType&&(e=e.parentElement),e&&"function"==typeof e.closest?e.closest("a"):null):null}function j(e){if(!(i||e&&!1===e.isTrusted)){f=Date.now();var t=K(e.target);if(X(t)){d&&(clearTimeout(d),d=0),h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null);var n=!1;h=function(){n=!0,d&&(clearTimeout(d),d=0),document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null},document.addEventListener("touchmove",h,{capture:!0,passive:!0,once:!0}),document.addEventListener("scroll",h,{capture:!0,passive:!0,once:!0}),d=setTimeout(function(){h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null),d=0,n||$(t.href,t)},x)}}}function F(e){if(!i&&!(e&&!1===e.isTrusted||f&&Date.now()-f<2500)){var t=K(e.target);if(t&&!r.has(t)&&X(t)){t.addEventListener("mouseleave",H,{passive:!0,once:!0});var n=setTimeout(function(){$(t.href,t),r.delete(t)},L);r.set(t,n)}}}function H(e){var t=e.currentTarget;if(t){var n=r.get(t);n&&(clearTimeout(n),r.delete(t))}}function Q(e){if(!i&&!(e&&!1===e.isTrusted||"number"==typeof e.button&&(1===e.button||2===e.button)||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||f&&Date.now()-f<2500)){var t=K(e.target);X(t)&&$(t.href,t)}}function X(e){if(!e)return!1;var t=e.getAttribute("href");if(null===t||""===t.trim())return!1;if(!e.href)return!1;if(e.target&&"_self"!==e.target)return!1;if(e.hasAttribute("download"))return!1;if("noPrefetch"in e.dataset||"prefetchNo"in e.dataset)return!1;if(P&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if("http:"!==e.protocol&&"https:"!==e.protocol)return!1;if("http:"===e.protocol&&"https:"===location.protocol)return!1;if(e.origin!==location.origin&&!k&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if(e.search&&!O&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if(e.hash&&e.pathname+e.search===location.pathname+location.search)return!1;var r=e.origin+e.pathname+e.search;return r!==l&&(!n.has(r)&&(!!function(e){var t=e.pathname||"",n=e.hash||"";if("bitrix"===m||"bitrix24"===m){if(-1!==t.indexOf("/bitrix/")||e.search&&-1!==e.search.indexOf("sessid="))return!1;if(e.classList.contains("bx-ajax"))return!1}if("tilda"===m&&(-1!==n.indexOf("#popup:")||-1!==n.indexOf("#rec")))return!1;return!R.test(t)&&!_.test(t)}(e)&&!!function(e){var t=e.className||"",n=e.hostname||"";return-1===t.indexOf("ym-")&&("mc.yandex.ru"!==n&&"metrika.yandex.ru"!==n&&(-1===t.indexOf("ga-")&&-1===t.indexOf("gtm-")&&("google-analytics.com"!==n&&!n.endsWith(".google-analytics.com")&&("googletagmanager.com"!==n&&!n.endsWith(".googletagmanager.com")&&(-1===t.indexOf("piwik")&&-1===t.indexOf("matomo")&&("matomo.org"!==n&&!n.endsWith(".matomo.org")&&"piwik.org"!==n&&!n.endsWith(".piwik.org")))))))}(e)))}function $(e,t){if(!i&&W()){var r,c,s;if(t&&t.origin)c=t.origin+t.pathname+t.search,r=t.href.split("#")[0],s=t.origin!==location.origin;else{var u=function(e){try{var t=new URL(e,location.href),n=t.origin+t.pathname+t.search;return t.hash="",{requestUrl:t.href,key:n}}catch(t){return{requestUrl:e,key:e}}}(e);r=u.requestUrl,c=u.key,s=!1;try{s=new URL(r,location.href).origin!==location.origin}catch(e){}}if(!n.has(c)){n.size>=T&&n.delete(n.values().next().value),n.add(c);var l=function(e){return A&&"none"!==S?"prerender"!==S?S:q||P||e&&e.dataset&&("prefetchPrerender"in e.dataset||"instantPrerender"in e.dataset)?"prerender":"prefetch":"none"}(t);if(o>=4)return a.length>=50?void n.delete(c):void a.push({url:r,key:c,mode:l,crossOrigin:s});G(r,c,l,s)}}}function G(e,t,n,r){if("none"===n)v||!E||r?Z(e,t,r):Y(e,t,r);else{var i=!1;try{!function(e,t,n){n&&"prerender"===t&&(t="prefetch");n?s.crossOrigin.push(e):"prerender"===t?s.prerender.push(e):s.prefetch.push(e);if(!u){var r=window.requestIdleCallback||function(e){setTimeout(e,1)};u=r(V,{timeout:50})}}(e,n,r),i=!0}catch(e){}!N&&i||(v||!E||r?Z(e,t,r):Y(e,t,r))}}function J(){for(;a.length>0&&o<4;){var e=a.shift();G(e.url,e.key,e.mode,e.crossOrigin)}}function V(){if(u=0,!i){var e=document.head;if(e){var t={};if(s.prefetch.length>0&&(t.prefetch=t.prefetch||[],t.prefetch.push({source:"list",urls:s.prefetch}),s.prefetch=[]),s.prerender.length>0&&(t.prerender=t.prerender||[],t.prerender.push({source:"list",urls:s.prerender}),s.prerender=[]),s.crossOrigin.length>0&&(t.prefetch=t.prefetch||[],t.prefetch.push({source:"list",urls:s.crossOrigin,referrer_policy:"no-referrer",requires:["anonymous-client-ip-when-cross-origin"]}),s.crossOrigin=[]),t.prefetch||t.prerender){var n=document.createElement("script");n.type="speculationrules",b&&(n.nonce=b),n.textContent=JSON.stringify(t),e.appendChild(n),e.removeChild(n)}}}}function Y(e,t,r){var i=document.head;if(i){o++;var a=document.createElement("link");a.rel="prefetch",a.href=e,a.as="document";try{a.fetchPriority="low"}catch(e){}r&&(a.referrerPolicy="no-referrer",a.crossOrigin="anonymous");var c=setTimeout(function(){c=0,n.delete(t),s()},3e4);a.onload=s,a.onerror=function(){n.delete(t),s()},i.appendChild(a)}else n.delete(t);function s(){c&&(clearTimeout(c),c=0),a.onload=a.onerror=null,a.parentNode&&a.parentNode.removeChild(a),o--,J()}}function Z(e,t,r){if("function"==typeof fetch){o++;var i=!1,a=null,s=0;"undefined"!=typeof AbortController&&(a=new AbortController,c.add(a),s=setTimeout(function(){try{a.abort()}catch(e){}l(!1)},5e3));var u={method:"GET",cache:"force-cache",credentials:r?"omit":"same-origin",mode:r?"no-cors":"cors"};r||(u.headers={Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",Purpose:"prefetch"}),r&&(u.referrerPolicy="no-referrer"),a&&(u.signal=a.signal);try{fetch(e,u).then(function(e){l(e&&(e.ok||304===e.status||"opaque"===e.type))}).catch(function(){l(!1)})}catch(e){l(!1)}}else n.delete(t);function l(e){i||(i=!0,s&&clearTimeout(s),a&&c.delete(a),e||n.delete(t),o--,J())}}!function(){if(t)try{b=t()}catch(e){}try{var e=document.createElement("link");e.relList&&"function"==typeof e.relList.supports&&(E=e.relList.supports("prefetch"))}catch(e){}var n=navigator.userAgent,r=navigator.userAgentData;r?(v=!1,p=r.mobile||!1):(v=/iPad|iPhone/.test(n)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1,p=(v||/Android/.test(n))&&Math.min(screen.width,screen.height)<768),p&&(T=20),(y=navigator.connection)&&(w=y.effectiveType,g=y.saveData||!1,"function"==typeof y.addEventListener&&y.addEventListener("change",B)),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",D):D()}();var ee=null;function te(){i||ee||(ee=new IntersectionObserver(function(e){e.forEach(function(e){e.isIntersecting&&(ee.unobserve(e.target),X(e.target)&&$(e.target.href,e.target))})},{rootMargin:p?"100px":"200px"}),function(){if(!ee)return;document.querySelectorAll("a").forEach(function(e){X(e)&&ee.observe(e)})}())}var ne=null;return{__prefetchRu:!0,version:"1.1.3",preload:function(e){if(function(e){return!(!e||"string"!=typeof e||!(e=e.trim())||/^\/\//.test(e)||/^(javascript|data|vbscript|file):/i.test(e)||!(/^https?:\/\//i.test(e)||/^\//.test(e)||/^\./.test(e))&&/^[a-z][a-z0-9+.-]*:/i.test(e))}(e)){var t=document.createElement("a");t.setAttribute("href",e.trim()),X(t)&&$(t.href,t)}},destroy:function(){i=!0,a.length=0,d&&(clearTimeout(d),d=0),h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null),c.forEach(function(e){try{e.abort()}catch(e){}}),c.clear(),u&&((window.cancelIdleCallback||clearTimeout)(u),u=0),s.prefetch.length=s.prerender.length=s.crossOrigin.length=0;var e={capture:!0,passive:!0};document.removeEventListener("touchstart",j,e),document.removeEventListener("mouseover",F,e),document.removeEventListener("mousedown",Q,e),y&&"function"==typeof y.removeEventListener&&y.removeEventListener("change",B),window.removeEventListener("popstate",U),window.removeEventListener("hashchange",U),window.removeEventListener("pageshow",z),ee&&(ee.disconnect(),ee=null),ne&&(ne.disconnect(),ne=null)},refresh:U}}({isBrowser:"undefined"!=typeof window,getNonce:function(){var e=function(e){try{if(!e)return null;for(var t=document.getElementsByTagName("script"),n=0;n<t.length;n++){var r=t[n];if(r&&r.src&&r.src===e){var i=r.nonce||r.getAttribute("nonce")||null;if(i)return i;break}}}catch(e){}return null}(import.meta.url);if(e)return e;try{var t=document.currentScript;if(t&&t.nonce)return t.nonce}catch(e){}return null}});"undefined"!=typeof window&&(window.PrefetchRu=e,window.Prefetch||(window.Prefetch=e));export{e as Prefetch,e as default};
@@ -1,6 +1,6 @@
1
1
  /*!
2
- * prefetch.ru v1.1.2 - Мгновенная загрузка страниц
2
+ * prefetch.ru v1.1.3 - Мгновенная загрузка страниц
3
3
  * © 2026 Сергей Макаров | MIT License
4
4
  * https://prefetch.ru | https://github.com/prefetch-ru
5
5
  */
6
- !function(){"use strict";if(!(window.PrefetchRu&&window.PrefetchRu.__prefetchRu||window.Prefetch&&window.Prefetch.__prefetchRu)){var e=function(e){var t=e&&e.getNonce;if(e&&!e.isBrowser||"undefined"==typeof window||"undefined"==typeof document)return{__prefetchRu:!0,version:"1.1.2",preload:function(){},destroy:function(){},refresh:function(){}};var n=new Set,r=new WeakMap,o=!1,i=0,a=[],c=new Set,s={prefetch:[],prerender:[],crossOrigin:[]},l=0,u="",d=0,f=0,h=null,p=!1,m=!1,v=null,g=!1,w=null,y=null,b=!1,L=65,E=80,x=50,T=!1,O=!1,k=!1,P=!1,S="none",A=!1,N=!1,C=!1,q=!1,I=!1,R=/(^|\/)(login|logout|auth|register|cart|basket|add|delete|remove)(\/|$|\.)/i,M=/\.(pdf|doc|docx|xls|xlsx|zip|rar|exe)($|\?)/i;function _(){if(!o){var e=document.body;if(e){u=location.origin+location.pathname+location.search,v=void 0!==window.BX?"bitrix":void 0!==window.B24||void 0!==window.BX24?"bitrix24":document.querySelector(".t-records")||void 0!==window.Tilda?"tilda":null;var t=e.dataset;if(T="prefetchAllowQueryString"in t||"instantAllowQueryString"in t,O="prefetchAllowExternalLinks"in t||"instantAllowExternalLinks"in t,k="prefetchWhitelist"in t||"instantWhitelist"in t,t.prefetchNonce&&(y=t.prefetchNonce),!y&&t.instantNonce&&(y=t.instantNonce),!m&&("prefetchSpecrules"in t||"instantSpecrules"in t)&&HTMLScriptElement.supports&&HTMLScriptElement.supports("speculationrules")){var n=t.prefetchSpecrules||t.instantSpecrules;"prerender"===n?(S="prerender",P=!0):"no"!==n&&(S="prefetch",P=!0)}A="prefetchSpecrulesFallback"in t||"instantSpecrulesFallback"in t,N="prefetchPrerenderAll"in t||"instantPrerenderAll"in t;var r=t.prefetchIntensity||t.instantIntensity;if("mousedown"===r)C=!0;else if("viewport"===r||"viewport-all"===r)("viewport-all"===r||p&&D())&&(q=!0);else if(r){var i=parseInt(r,10);!isNaN(i)&&i>=0&&(L=i)}p&&(E=Math.max(60,Math.min(L||0,150))),(I="prefetchObserveDom"in t||"instantObserveDom"in t)||"bitrix"!==v&&"tilda"!==v||(I=!0),window.addEventListener("popstate",U),window.addEventListener("hashchange",U),window.addEventListener("pageshow",W),"tilda"===v&&L<100&&(L=100);var a={capture:!0,passive:!0};document.addEventListener("touchstart",z,a),C?document.addEventListener("mousedown",F,a):document.addEventListener("mouseover",K,a),q&&"undefined"==typeof IntersectionObserver&&(q=!1),q&&(window.requestIdleCallback||function(e){setTimeout(e,1)})(Z,{timeout:1500}),I&&q&&"undefined"!=typeof MutationObserver&&(o||te||(te=new MutationObserver(function(e){var t=!1;e:for(var n=0;n<e.length;n++)for(var r=e[n].addedNodes,o=0;o<r.length;o++){var i=r[o];if(1===i.nodeType&&("A"===i.tagName||i.querySelector&&i.querySelector("a"))){t=!0;break e}}t&&Y&&(clearTimeout(ne),ne=setTimeout(ee,100))})).observe(document.body,{childList:!0,subtree:!0}))}}}function D(){return!g&&"slow-2g"!==w&&"2g"!==w&&"3g"!==w}function U(){u=location.origin+location.pathname+location.search}function W(e){e&&e.persisted&&U()}function B(e){return e?(e.nodeType&&1!==e.nodeType&&(e=e.parentElement),e&&"function"==typeof e.closest?e.closest("a"):null):null}function z(e){if(!(o||e&&!1===e.isTrusted)){d=Date.now();var t=B(e.target);if(G(t)){f&&(clearTimeout(f),f=0),h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null);var n=!1;h=function(){n=!0,f&&(clearTimeout(f),f=0),document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null},document.addEventListener("touchmove",h,{capture:!0,passive:!0,once:!0}),document.addEventListener("scroll",h,{capture:!0,passive:!0,once:!0}),f=setTimeout(function(){h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null),f=0,n||H(t.href,t)},E)}}}function K(e){if(!o&&!(e&&!1===e.isTrusted||d&&Date.now()-d<2500)){var t=B(e.target);if(t&&!r.has(t)&&G(t)){t.addEventListener("mouseleave",j,{passive:!0,once:!0});var n=setTimeout(function(){H(t.href,t),r.delete(t)},L);r.set(t,n)}}}function j(e){var t=e.currentTarget;if(t){var n=r.get(t);n&&(clearTimeout(n),r.delete(t))}}function F(e){if(!o&&!(e&&!1===e.isTrusted||"number"==typeof e.button&&(1===e.button||2===e.button)||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||d&&Date.now()-d<2500)){var t=B(e.target);G(t)&&H(t.href,t)}}function G(e){if(!e)return!1;var t=e.getAttribute("href");if(null===t||""===t.trim())return!1;if(!e.href)return!1;if(e.target&&"_self"!==e.target)return!1;if(e.hasAttribute("download"))return!1;if("noPrefetch"in e.dataset||"prefetchNo"in e.dataset)return!1;if(k&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if("http:"!==e.protocol&&"https:"!==e.protocol)return!1;if("http:"===e.protocol&&"https:"===location.protocol)return!1;if(e.origin!==location.origin&&!O&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if(e.search&&!T&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if(e.hash&&e.pathname+e.search===location.pathname+location.search)return!1;var r=e.origin+e.pathname+e.search;return r!==u&&!n.has(r)&&!!function(e){var t=e.href,n=e.pathname||"",r=e.hash||"";if("bitrix"===v||"bitrix24"===v){if(-1!==t.indexOf("/bitrix/")||-1!==t.indexOf("sessid="))return!1;if(e.classList.contains("bx-ajax"))return!1}return("tilda"!==v||-1===r.indexOf("#popup:")&&-1===r.indexOf("#rec"))&&(!R.test(n)&&!M.test(n))}(e)&&!!function(e){var t=e.className||"",n=e.hostname||"";return!(-1!==t.indexOf("ym-")||"mc.yandex.ru"===n||"metrika.yandex.ru"===n||-1!==t.indexOf("ga-")||-1!==t.indexOf("gtm-")||"google-analytics.com"===n||n.endsWith(".google-analytics.com")||"googletagmanager.com"===n||n.endsWith(".googletagmanager.com")||-1!==t.indexOf("piwik")||-1!==t.indexOf("matomo")||"matomo.org"===n||n.endsWith(".matomo.org")||"piwik.org"===n||n.endsWith(".piwik.org"))}(e)}function H(e,t){if(!o&&D()){var r=function(e){try{var t=new URL(e,location.href),n=t.origin+t.pathname+t.search;return t.hash="",{requestUrl:t.href,key:n}}catch(t){return{requestUrl:e,key:e}}}(e),c=r.requestUrl,s=r.key;if(!n.has(s)){n.size>=x&&n.delete(n.values().next().value),n.add(s);var l=function(e){return P&&"none"!==S?"prerender"!==S?S:N||k||e&&e.dataset&&("prefetchPrerender"in e.dataset||"instantPrerender"in e.dataset)?"prerender":"prefetch":"none"}(t);if(i>=4)return a.length>=50?void n.delete(s):void a.push({url:c,key:s,mode:l});Q(c,s,l)}}}function Q(e,t,n){var r=!1;try{r=new URL(e,location.href).origin!==location.origin}catch(e){}if("none"===n)m||!b||r?V(e,t):J(e,t);else{var o=!1;try{!function(e,t){var n=!1;try{n=new URL(e,location.href).origin!==location.origin}catch(e){}if(n&&"prerender"===t&&(t="prefetch"),n?s.crossOrigin.push(e):"prerender"===t?s.prerender.push(e):s.prefetch.push(e),!l){var r=window.requestIdleCallback||function(e){setTimeout(e,1)};l=r($,{timeout:50})}}(e,n),o=!0}catch(e){}!A&&o||(m||!b||r?V(e,t):J(e,t))}}function X(){for(;a.length>0&&i<4;){var e=a.shift();Q(e.url,e.key,e.mode)}}function $(){if(l=0,!o){var e=document.head;if(e){var t={};if(s.prefetch.length>0&&(t.prefetch=t.prefetch||[],t.prefetch.push({source:"list",urls:s.prefetch.slice()}),s.prefetch.length=0),s.prerender.length>0&&(t.prerender=t.prerender||[],t.prerender.push({source:"list",urls:s.prerender.slice()}),s.prerender.length=0),s.crossOrigin.length>0&&(t.prefetch=t.prefetch||[],t.prefetch.push({source:"list",urls:s.crossOrigin.slice(),referrer_policy:"no-referrer",requires:["anonymous-client-ip-when-cross-origin"]}),s.crossOrigin.length=0),t.prefetch||t.prerender){var n=document.createElement("script");n.type="speculationrules",y&&(n.nonce=y),n.textContent=JSON.stringify(t),e.appendChild(n),e.removeChild(n)}}}}function J(e,t){var r=document.head;if(r){i++;var o=document.createElement("link");o.rel="prefetch",o.href=e,o.as="document";try{o.fetchPriority="low"}catch(e){}try{new URL(e,location.href).origin!==location.origin&&(o.referrerPolicy="no-referrer",o.crossOrigin="anonymous")}catch(e){}var a=setTimeout(function(){a=0,n.delete(t),c()},3e4);o.onload=c,o.onerror=function(){n.delete(t),c()},r.appendChild(o)}else n.delete(t);function c(){a&&(clearTimeout(a),a=0),o.onload=o.onerror=null,o.parentNode&&o.parentNode.removeChild(o),i--,X()}}function V(e,t){if("function"==typeof fetch){i++;var r=!1,o=null,a=0;"undefined"!=typeof AbortController&&(o=new AbortController,c.add(o),a=setTimeout(function(){try{o.abort()}catch(e){}u(!1)},5e3));var s=!1;try{s=new URL(e,location.href).origin!==location.origin}catch(e){}var l={method:"GET",cache:"force-cache",credentials:s?"omit":"same-origin",mode:s?"no-cors":"cors"};s||(l.headers={Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",Purpose:"prefetch"}),s&&(l.referrerPolicy="no-referrer"),o&&(l.signal=o.signal);try{fetch(e,l).then(function(e){u(e&&(e.ok||304===e.status||"opaque"===e.type))}).catch(function(){u(!1)})}catch(e){u(!1)}}else n.delete(t);function u(e){r||(r=!0,a&&clearTimeout(a),o&&c.delete(o),e||n.delete(t),i--,X())}}!function(){if(t)try{y=t()}catch(e){}try{var e=document.createElement("link");e.relList&&"function"==typeof e.relList.supports&&(b=e.relList.supports("prefetch"))}catch(e){}var n=navigator.userAgent,r=navigator.userAgentData;if(r){m=!1;var o="Android"===r.platform;p=r.mobile||!1;for(var i=r.brands||[],a=0;a<i.length;a++){var c=i[a];if("Chromium"===c.brand||"Google Chrome"===c.brand){parseInt(c.version,10);break}}}else{m=/iPad|iPhone/.test(n)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1,o=/Android/.test(n),p=(m||o)&&Math.min(screen.width,screen.height)<768;var s=n.match(/Chrome\/(\d+)/);s&&parseInt(s[1],10)}p&&(x=20);var l=navigator.connection;l&&(w=l.effectiveType,g=l.saveData||!1),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",_):_()}();var Y=null;function Z(){o||Y||(Y=new IntersectionObserver(function(e){e.forEach(function(e){e.isIntersecting&&(Y.unobserve(e.target),G(e.target)&&H(e.target.href,e.target))})},{rootMargin:p?"100px":"200px"}),ee())}function ee(){Y&&document.querySelectorAll("a").forEach(function(e){G(e)&&Y.observe(e)})}var te=null,ne=null;return{__prefetchRu:!0,version:"1.1.2",preload:function(e){if(function(e){return!(!e||"string"!=typeof e||!(e=e.trim())||/^\/\//.test(e)||/^(javascript|data|vbscript|file):/i.test(e)||!(/^https?:\/\//i.test(e)||/^\//.test(e)||/^\./.test(e))&&/^[a-z][a-z0-9+.-]*:/i.test(e))}(e)){var t=document.createElement("a");t.setAttribute("href",e.trim()),G(t)&&H(t.href,t)}},destroy:function(){o=!0,a.length=0,f&&(clearTimeout(f),f=0),h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null),c.forEach(function(e){try{e.abort()}catch(e){}}),c.clear(),l&&((window.cancelIdleCallback||clearTimeout)(l),l=0),s.prefetch.length=s.prerender.length=s.crossOrigin.length=0;var e={capture:!0,passive:!0};document.removeEventListener("touchstart",z,e),document.removeEventListener("mouseover",K,e),document.removeEventListener("mousedown",F,e),window.removeEventListener("popstate",U),window.removeEventListener("hashchange",U),window.removeEventListener("pageshow",W),Y&&(Y.disconnect(),Y=null),te&&(te.disconnect(),te=null)},refresh:U}}({isBrowser:!0,getNonce:function(){try{var e=document.currentScript;if(e&&e.nonce)return e.nonce}catch(e){}return null}});window.PrefetchRu=e,window.Prefetch||(window.Prefetch=e)}}();
6
+ !function(){"use strict";if(!(window.PrefetchRu&&window.PrefetchRu.__prefetchRu||window.Prefetch&&window.Prefetch.__prefetchRu)){var e=function(e){var t=e&&e.getNonce;if(e&&!e.isBrowser||"undefined"==typeof window||"undefined"==typeof document)return{__prefetchRu:!0,version:"1.1.3",preload:function(){},destroy:function(){},refresh:function(){}};var n=new Set,r=new WeakMap,i=!1,o=0,a=[],c=new Set,s={prefetch:[],prerender:[],crossOrigin:[]},u=0,l="",d=0,f=0,h=null,p=!1,v=!1,m=null,g=!1,w=null,y=null,E=null,b=!1,L=65,x=80,T=50,O=!1,k=!1,A=!1,P=!1,S="none",N=!1,q=!1,C=!1,I=!1,M=!1,_=/(^|\/)(login|logout|auth|register|cart|basket|add|delete|remove)(\/|$|\.)/i,D=/\.(pdf|doc|docx|xls|xlsx|zip|rar|exe)($|\?)/i;function R(){if(!i){var e=document.body;if(e){l=location.origin+location.pathname+location.search,m=void 0!==window.BX?"bitrix":void 0!==window.B24||void 0!==window.BX24?"bitrix24":document.querySelector(".t-records")||void 0!==window.Tilda?"tilda":null;var t=e.dataset;if(O="prefetchAllowQueryString"in t||"instantAllowQueryString"in t,k="prefetchAllowExternalLinks"in t||"instantAllowExternalLinks"in t,A="prefetchWhitelist"in t||"instantWhitelist"in t,t.prefetchNonce&&(E=t.prefetchNonce),!E&&t.instantNonce&&(E=t.instantNonce),!v&&("prefetchSpecrules"in t||"instantSpecrules"in t)&&HTMLScriptElement.supports&&HTMLScriptElement.supports("speculationrules")){var n=t.prefetchSpecrules||t.instantSpecrules;"prerender"===n?(S="prerender",P=!0):"no"!==n&&(S="prefetch",P=!0)}N="prefetchSpecrulesFallback"in t||"instantSpecrulesFallback"in t,q="prefetchPrerenderAll"in t||"instantPrerenderAll"in t;var r=t.prefetchIntensity||t.instantIntensity;if("mousedown"===r)C=!0;else if("viewport"===r||"viewport-all"===r)("viewport-all"===r||p&&W())&&(I=!0);else if(r){var o=parseInt(r,10);!isNaN(o)&&o>=0&&(L=o)}p&&(x=Math.max(60,Math.min(L||0,150))),(M="prefetchObserveDom"in t||"instantObserveDom"in t)||"bitrix"!==m&&"tilda"!==m||(M=!0),window.addEventListener("popstate",U),window.addEventListener("hashchange",U),window.addEventListener("pageshow",z),"tilda"===m&&L<100&&(L=100);var a={capture:!0,passive:!0};document.addEventListener("touchstart",j,a),C?document.addEventListener("mousedown",Q,a):document.addEventListener("mouseover",F,a),I&&"undefined"==typeof IntersectionObserver&&(I=!1),I&&(window.requestIdleCallback||function(e){setTimeout(e,1)})(te,{timeout:1500}),M&&I&&"undefined"!=typeof MutationObserver&&(i||ne||(ne=new MutationObserver(function(e){if(ee){for(var t=[],n=0;n<e.length;n++)for(var r=e[n].addedNodes,i=0;i<r.length;i++){var o=r[i];if(1===o.nodeType)if("A"===o.tagName)t.push(o);else if(o.querySelectorAll)for(var a=o.querySelectorAll("a"),c=0;c<a.length;c++)t.push(a[c])}t.length>0&&(window.requestIdleCallback||function(e){setTimeout(e,1)})(function(){for(var e=0;e<t.length;e++)ee&&X(t[e])&&ee.observe(t[e])})}})).observe(document.body,{childList:!0,subtree:!0}))}}}function W(){return!g&&"slow-2g"!==w&&"2g"!==w&&"3g"!==w}function B(){y&&(w=y.effectiveType,g=y.saveData||!1)}function U(){l=location.origin+location.pathname+location.search}function z(e){e&&e.persisted&&U()}function K(e){return e?(e.nodeType&&1!==e.nodeType&&(e=e.parentElement),e&&"function"==typeof e.closest?e.closest("a"):null):null}function j(e){if(!(i||e&&!1===e.isTrusted)){d=Date.now();var t=K(e.target);if(X(t)){f&&(clearTimeout(f),f=0),h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null);var n=!1;h=function(){n=!0,f&&(clearTimeout(f),f=0),document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null},document.addEventListener("touchmove",h,{capture:!0,passive:!0,once:!0}),document.addEventListener("scroll",h,{capture:!0,passive:!0,once:!0}),f=setTimeout(function(){h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null),f=0,n||$(t.href,t)},x)}}}function F(e){if(!i&&!(e&&!1===e.isTrusted||d&&Date.now()-d<2500)){var t=K(e.target);if(t&&!r.has(t)&&X(t)){t.addEventListener("mouseleave",H,{passive:!0,once:!0});var n=setTimeout(function(){$(t.href,t),r.delete(t)},L);r.set(t,n)}}}function H(e){var t=e.currentTarget;if(t){var n=r.get(t);n&&(clearTimeout(n),r.delete(t))}}function Q(e){if(!i&&!(e&&!1===e.isTrusted||"number"==typeof e.button&&(1===e.button||2===e.button)||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||d&&Date.now()-d<2500)){var t=K(e.target);X(t)&&$(t.href,t)}}function X(e){if(!e)return!1;var t=e.getAttribute("href");if(null===t||""===t.trim())return!1;if(!e.href)return!1;if(e.target&&"_self"!==e.target)return!1;if(e.hasAttribute("download"))return!1;if("noPrefetch"in e.dataset||"prefetchNo"in e.dataset)return!1;if(A&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if("http:"!==e.protocol&&"https:"!==e.protocol)return!1;if("http:"===e.protocol&&"https:"===location.protocol)return!1;if(e.origin!==location.origin&&!k&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if(e.search&&!O&&!("prefetch"in e.dataset)&&!("instant"in e.dataset))return!1;if(e.hash&&e.pathname+e.search===location.pathname+location.search)return!1;var r=e.origin+e.pathname+e.search;return r!==l&&!n.has(r)&&!!function(e){var t=e.pathname||"",n=e.hash||"";if("bitrix"===m||"bitrix24"===m){if(-1!==t.indexOf("/bitrix/")||e.search&&-1!==e.search.indexOf("sessid="))return!1;if(e.classList.contains("bx-ajax"))return!1}return("tilda"!==m||-1===n.indexOf("#popup:")&&-1===n.indexOf("#rec"))&&(!_.test(t)&&!D.test(t))}(e)&&!!function(e){var t=e.className||"",n=e.hostname||"";return!(-1!==t.indexOf("ym-")||"mc.yandex.ru"===n||"metrika.yandex.ru"===n||-1!==t.indexOf("ga-")||-1!==t.indexOf("gtm-")||"google-analytics.com"===n||n.endsWith(".google-analytics.com")||"googletagmanager.com"===n||n.endsWith(".googletagmanager.com")||-1!==t.indexOf("piwik")||-1!==t.indexOf("matomo")||"matomo.org"===n||n.endsWith(".matomo.org")||"piwik.org"===n||n.endsWith(".piwik.org"))}(e)}function $(e,t){if(!i&&W()){var r,c,s;if(t&&t.origin)c=t.origin+t.pathname+t.search,r=t.href.split("#")[0],s=t.origin!==location.origin;else{var u=function(e){try{var t=new URL(e,location.href),n=t.origin+t.pathname+t.search;return t.hash="",{requestUrl:t.href,key:n}}catch(t){return{requestUrl:e,key:e}}}(e);r=u.requestUrl,c=u.key,s=!1;try{s=new URL(r,location.href).origin!==location.origin}catch(e){}}if(!n.has(c)){n.size>=T&&n.delete(n.values().next().value),n.add(c);var l=function(e){return P&&"none"!==S?"prerender"!==S?S:q||A||e&&e.dataset&&("prefetchPrerender"in e.dataset||"instantPrerender"in e.dataset)?"prerender":"prefetch":"none"}(t);if(o>=4)return a.length>=50?void n.delete(c):void a.push({url:r,key:c,mode:l,crossOrigin:s});G(r,c,l,s)}}}function G(e,t,n,r){if("none"===n)v||!b||r?Z(e,t,r):Y(e,t,r);else{var i=!1;try{!function(e,t,n){if(n&&"prerender"===t&&(t="prefetch"),n?s.crossOrigin.push(e):"prerender"===t?s.prerender.push(e):s.prefetch.push(e),!u){var r=window.requestIdleCallback||function(e){setTimeout(e,1)};u=r(V,{timeout:50})}}(e,n,r),i=!0}catch(e){}!N&&i||(v||!b||r?Z(e,t,r):Y(e,t,r))}}function J(){for(;a.length>0&&o<4;){var e=a.shift();G(e.url,e.key,e.mode,e.crossOrigin)}}function V(){if(u=0,!i){var e=document.head;if(e){var t={};if(s.prefetch.length>0&&(t.prefetch=t.prefetch||[],t.prefetch.push({source:"list",urls:s.prefetch}),s.prefetch=[]),s.prerender.length>0&&(t.prerender=t.prerender||[],t.prerender.push({source:"list",urls:s.prerender}),s.prerender=[]),s.crossOrigin.length>0&&(t.prefetch=t.prefetch||[],t.prefetch.push({source:"list",urls:s.crossOrigin,referrer_policy:"no-referrer",requires:["anonymous-client-ip-when-cross-origin"]}),s.crossOrigin=[]),t.prefetch||t.prerender){var n=document.createElement("script");n.type="speculationrules",E&&(n.nonce=E),n.textContent=JSON.stringify(t),e.appendChild(n),e.removeChild(n)}}}}function Y(e,t,r){var i=document.head;if(i){o++;var a=document.createElement("link");a.rel="prefetch",a.href=e,a.as="document";try{a.fetchPriority="low"}catch(e){}r&&(a.referrerPolicy="no-referrer",a.crossOrigin="anonymous");var c=setTimeout(function(){c=0,n.delete(t),s()},3e4);a.onload=s,a.onerror=function(){n.delete(t),s()},i.appendChild(a)}else n.delete(t);function s(){c&&(clearTimeout(c),c=0),a.onload=a.onerror=null,a.parentNode&&a.parentNode.removeChild(a),o--,J()}}function Z(e,t,r){if("function"==typeof fetch){o++;var i=!1,a=null,s=0;"undefined"!=typeof AbortController&&(a=new AbortController,c.add(a),s=setTimeout(function(){try{a.abort()}catch(e){}l(!1)},5e3));var u={method:"GET",cache:"force-cache",credentials:r?"omit":"same-origin",mode:r?"no-cors":"cors"};r||(u.headers={Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",Purpose:"prefetch"}),r&&(u.referrerPolicy="no-referrer"),a&&(u.signal=a.signal);try{fetch(e,u).then(function(e){l(e&&(e.ok||304===e.status||"opaque"===e.type))}).catch(function(){l(!1)})}catch(e){l(!1)}}else n.delete(t);function l(e){i||(i=!0,s&&clearTimeout(s),a&&c.delete(a),e||n.delete(t),o--,J())}}!function(){if(t)try{E=t()}catch(e){}try{var e=document.createElement("link");e.relList&&"function"==typeof e.relList.supports&&(b=e.relList.supports("prefetch"))}catch(e){}var n=navigator.userAgent,r=navigator.userAgentData;r?(v=!1,p=r.mobile||!1):(v=/iPad|iPhone/.test(n)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1,p=(v||/Android/.test(n))&&Math.min(screen.width,screen.height)<768),p&&(T=20),(y=navigator.connection)&&(w=y.effectiveType,g=y.saveData||!1,"function"==typeof y.addEventListener&&y.addEventListener("change",B)),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",R):R()}();var ee=null;function te(){i||ee||(ee=new IntersectionObserver(function(e){e.forEach(function(e){e.isIntersecting&&(ee.unobserve(e.target),X(e.target)&&$(e.target.href,e.target))})},{rootMargin:p?"100px":"200px"}))&&document.querySelectorAll("a").forEach(function(e){X(e)&&ee.observe(e)})}var ne=null;return{__prefetchRu:!0,version:"1.1.3",preload:function(e){if(function(e){return!(!e||"string"!=typeof e||!(e=e.trim())||/^\/\//.test(e)||/^(javascript|data|vbscript|file):/i.test(e)||!(/^https?:\/\//i.test(e)||/^\//.test(e)||/^\./.test(e))&&/^[a-z][a-z0-9+.-]*:/i.test(e))}(e)){var t=document.createElement("a");t.setAttribute("href",e.trim()),X(t)&&$(t.href,t)}},destroy:function(){i=!0,a.length=0,f&&(clearTimeout(f),f=0),h&&(document.removeEventListener("touchmove",h,!0),document.removeEventListener("scroll",h,!0),h=null),c.forEach(function(e){try{e.abort()}catch(e){}}),c.clear(),u&&((window.cancelIdleCallback||clearTimeout)(u),u=0),s.prefetch.length=s.prerender.length=s.crossOrigin.length=0;var e={capture:!0,passive:!0};document.removeEventListener("touchstart",j,e),document.removeEventListener("mouseover",F,e),document.removeEventListener("mousedown",Q,e),y&&"function"==typeof y.removeEventListener&&y.removeEventListener("change",B),window.removeEventListener("popstate",U),window.removeEventListener("hashchange",U),window.removeEventListener("pageshow",z),ee&&(ee.disconnect(),ee=null),ne&&(ne.disconnect(),ne=null)},refresh:U}}({isBrowser:!0,getNonce:function(){try{var e=document.currentScript;if(e&&e.nonce)return e.nonce}catch(e){}return null}});window.PrefetchRu=e,window.Prefetch||(window.Prefetch=e)}}();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prefetchru/prefetch",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Мгновенная загрузка страниц для российского веба. Instant page loading for Russian web.",
5
5
  "main": "prefetch.js",
6
6
  "module": "prefetch.esm.js",
package/prefetch.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * prefetch.ru v1.1.2 (ESM) - Мгновенная загрузка страниц
2
+ * prefetch.ru v1.1.3 (ESM) - Мгновенная загрузка страниц
3
3
  * © 2026 Сергей Макаров | MIT License
4
4
  * https://prefetch.ru | https://github.com/prefetch-ru
5
5
  */
@@ -19,7 +19,7 @@ function createPrefetchCore(options) {
19
19
  if (!isBrowser || typeof window === 'undefined' || typeof document === 'undefined') {
20
20
  return {
21
21
  __prefetchRu: true,
22
- version: '1.1.2',
22
+ version: '1.1.3',
23
23
  preload: function () {},
24
24
  destroy: function () {},
25
25
  refresh: function () {}
@@ -57,6 +57,9 @@ function createPrefetchCore(options) {
57
57
  var saveData = false;
58
58
  var connType = null;
59
59
 
60
+ // v1.1.3: ссылка на navigator.connection для снятия listener в destroy()
61
+ var conn = null;
62
+
60
63
  // CSP / поддержка
61
64
  var scriptNonce = null;
62
65
  var supportsLinkPrefetch = false;
@@ -102,40 +105,29 @@ function createPrefetchCore(options) {
102
105
  var ua = navigator.userAgent;
103
106
  var uaData = navigator.userAgentData; // v1.0.12: UA-CH API (более надёжно в долгосрочной перспективе)
104
107
 
105
- // Определяем устройство
106
- // v1.0.12: используем userAgentData если доступен, иначе fallback на UA
108
+ // v1.1.3: UA-CH API если доступен, иначе fallback на UA
107
109
  if (uaData) {
108
110
  // UA-CH API: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData
109
111
  isIOS = false; // UA-CH пока не поддерживается на iOS
110
- var isAndroid = uaData.platform === 'Android';
111
112
  isMobile = uaData.mobile || false;
112
- // Chromium версия через brands
113
- var brands = uaData.brands || [];
114
- for (var i = 0; i < brands.length; i++) {
115
- var b = brands[i];
116
- if (b.brand === 'Chromium' || b.brand === 'Google Chrome') {
117
- parseInt(b.version, 10);
118
- break
119
- }
120
- }
121
113
  } else {
122
114
  // Fallback на традиционный UA sniffing
123
115
  isIOS =
124
116
  /iPad|iPhone/.test(ua) ||
125
117
  (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
126
- var isAndroid = /Android/.test(ua);
127
- isMobile = (isIOS || isAndroid) && Math.min(screen.width, screen.height) < 768;
128
- // Chromium версия из UA
129
- var cm = ua.match(/Chrome\/(\d+)/);
130
- if (cm) parseInt(cm[1], 10);
118
+ isMobile = (isIOS || /Android/.test(ua)) && Math.min(screen.width, screen.height) < 768;
131
119
  }
132
120
  if (isMobile) maxPreloads = 20;
133
121
 
134
122
  // Сеть
135
- var conn = navigator.connection;
123
+ conn = navigator.connection;
136
124
  if (conn) {
137
125
  connType = conn.effectiveType;
138
126
  saveData = conn.saveData || false;
127
+ // v1.1.3: реактивное обновление при смене типа соединения
128
+ if (typeof conn.addEventListener === 'function') {
129
+ conn.addEventListener('change', onConnectionChange);
130
+ }
139
131
  }
140
132
 
141
133
  // Ждём DOM
@@ -268,6 +260,14 @@ function createPrefetchCore(options) {
268
260
  return true
269
261
  }
270
262
 
263
+ // v1.1.3: реактивное обновление состояния сети
264
+ function onConnectionChange() {
265
+ if (conn) {
266
+ connType = conn.effectiveType;
267
+ saveData = conn.saveData || false;
268
+ }
269
+ }
270
+
271
271
  // v1.0.11: обновление currentKey при навигации (SPA-гибриды, pushState)
272
272
  function updateCurrentKey() {
273
273
  currentKey = location.origin + location.pathname + location.search;
@@ -438,14 +438,13 @@ function createPrefetchCore(options) {
438
438
  }
439
439
 
440
440
  function checkPlatform(a) {
441
- var href = a.href;
442
-
443
441
  // v1.0.11: используем свойства <a> напрямую вместо new URL() (perf)
444
442
  var pathname = a.pathname || '';
445
443
  var hash = a.hash || '';
446
444
 
447
445
  if (platform === 'bitrix' || platform === 'bitrix24') {
448
- if (href.indexOf('/bitrix/') !== -1 || href.indexOf('sessid=') !== -1) return false
446
+ // v1.1.3: проверяем pathname и search вместо href (точнее, без ложных срабатываний на домене)
447
+ if (pathname.indexOf('/bitrix/') !== -1 || (a.search && a.search.indexOf('sessid=') !== -1)) return false
449
448
  if (a.classList.contains('bx-ajax')) return false
450
449
  }
451
450
 
@@ -519,10 +518,20 @@ function createPrefetchCore(options) {
519
518
  if (disabled) return
520
519
  if (!isNetworkOk()) return
521
520
 
522
- // v1.0.12: парсим URL один раз вместо двух
523
- var parsed = parseUrl(url);
524
- var requestUrl = parsed.requestUrl;
525
- var key = parsed.key;
521
+ // v1.1.3: если a реальный DOM-элемент, берём key и crossOrigin из его свойств
522
+ // без лишнего new URL() (parseUrl нужен только для публичного API с произвольным url)
523
+ var requestUrl, key, isCrossOrigin;
524
+ if (a && a.origin) {
525
+ key = a.origin + a.pathname + a.search;
526
+ requestUrl = a.href.split('#')[0]; // убираем hash для запроса
527
+ isCrossOrigin = a.origin !== location.origin;
528
+ } else {
529
+ var parsed = parseUrl(url);
530
+ requestUrl = parsed.requestUrl;
531
+ key = parsed.key;
532
+ isCrossOrigin = false;
533
+ try { isCrossOrigin = new URL(requestUrl, location.href).origin !== location.origin; } catch (e) {}
534
+ }
526
535
 
527
536
  if (preloaded.has(key)) return
528
537
  if (preloaded.size >= maxPreloads) {
@@ -537,26 +546,20 @@ function createPrefetchCore(options) {
537
546
  // v1.0.11: лимит очереди — отбрасываем новые при переполнении
538
547
  // v1.0.11: при drop удаляем ключ (иначе URL "навсегда" считается прогретым)
539
548
  if (queue.length >= maxQueue) { preloaded.delete(key); return }
540
- queue.push({ url: requestUrl, key: key, mode: mode });
549
+ queue.push({ url: requestUrl, key: key, mode: mode, crossOrigin: isCrossOrigin });
541
550
  return
542
551
  }
543
552
 
544
- doPreload(requestUrl, key, mode);
553
+ doPreload(requestUrl, key, mode, isCrossOrigin);
545
554
  }
546
555
 
547
- function doPreload(requestUrl, key, mode) {
548
- // v1.1.1: определяем cross-origin для выбора метода
549
- var isCrossOrigin = false;
550
- try {
551
- var u = new URL(requestUrl, location.href);
552
- isCrossOrigin = u.origin !== location.origin;
553
- } catch (e) {}
554
-
556
+ // v1.1.3: isCrossOrigin передаётся через цепочку вызовов (без повторного new URL())
557
+ function doPreload(requestUrl, key, mode, isCrossOrigin) {
555
558
  if (mode !== 'none') {
556
559
  // v1.0.11: try/catch для preloadSpec — при строгом CSP/Trusted Types может выбросить исключение
557
560
  var specOk = false;
558
561
  try {
559
- preloadSpec(requestUrl, mode);
562
+ preloadSpec(requestUrl, mode, isCrossOrigin);
560
563
  specOk = true;
561
564
  } catch (e) {
562
565
  // Ошибка в Speculation Rules — fallback обязателен
@@ -566,32 +569,25 @@ function createPrefetchCore(options) {
566
569
  // По умолчанию fallback отключён для избежания двойного трафика
567
570
  if (specRulesFallback || !specOk) {
568
571
  // v1.1.1: cross-origin всегда через fetch (no-cors), <link> требует CORS headers
569
- if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key);
570
- else preloadLink(requestUrl, key);
572
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin);
573
+ else preloadLink(requestUrl, key, isCrossOrigin);
571
574
  }
572
575
  return
573
576
  }
574
577
 
575
578
  // v1.1.1: cross-origin всегда через fetch (no-cors), <link crossorigin=anonymous> требует CORS
576
- if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key);
577
- else preloadLink(requestUrl, key);
579
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin);
580
+ else preloadLink(requestUrl, key, isCrossOrigin);
578
581
  }
579
582
 
580
583
  function processQueue() {
581
584
  while (queue.length > 0 && inFlight < maxInFlight) {
582
585
  var item = queue.shift();
583
- doPreload(item.url, item.key, item.mode);
586
+ doPreload(item.url, item.key, item.mode, item.crossOrigin);
584
587
  }
585
588
  }
586
589
 
587
- function preloadSpec(url, mode) {
588
- // v1.0.13: для cross-origin проверяем и модифицируем правила
589
- var isCrossOrigin = false;
590
- try {
591
- var u = new URL(url, location.href);
592
- isCrossOrigin = u.origin !== location.origin;
593
- } catch (e) {}
594
-
590
+ function preloadSpec(url, mode, isCrossOrigin) {
595
591
  // v1.0.13: для cross-origin никогда не делаем prerender (только prefetch)
596
592
  if (isCrossOrigin && mode === 'prerender') {
597
593
  mode = 'prefetch';
@@ -625,18 +621,19 @@ function createPrefetchCore(options) {
625
621
 
626
622
  var rules = {};
627
623
 
624
+ // v1.1.3: передаём массив напрямую и создаём новый (без лишнего .slice())
628
625
  // Same-origin prefetch
629
626
  if (specBuffer.prefetch.length > 0) {
630
627
  rules.prefetch = rules.prefetch || [];
631
- rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch.slice() });
632
- specBuffer.prefetch.length = 0;
628
+ rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch });
629
+ specBuffer.prefetch = [];
633
630
  }
634
631
 
635
632
  // Same-origin prerender
636
633
  if (specBuffer.prerender.length > 0) {
637
634
  rules.prerender = rules.prerender || [];
638
- rules.prerender.push({ source: 'list', urls: specBuffer.prerender.slice() });
639
- specBuffer.prerender.length = 0;
635
+ rules.prerender.push({ source: 'list', urls: specBuffer.prerender });
636
+ specBuffer.prerender = [];
640
637
  }
641
638
 
642
639
  // Cross-origin (только prefetch, с privacy requirements)
@@ -644,11 +641,11 @@ function createPrefetchCore(options) {
644
641
  rules.prefetch = rules.prefetch || [];
645
642
  rules.prefetch.push({
646
643
  source: 'list',
647
- urls: specBuffer.crossOrigin.slice(),
644
+ urls: specBuffer.crossOrigin,
648
645
  referrer_policy: 'no-referrer',
649
646
  requires: ['anonymous-client-ip-when-cross-origin']
650
647
  });
651
- specBuffer.crossOrigin.length = 0;
648
+ specBuffer.crossOrigin = [];
652
649
  }
653
650
 
654
651
  // Если нет правил — выходим
@@ -663,7 +660,7 @@ function createPrefetchCore(options) {
663
660
  head.removeChild(s);
664
661
  }
665
662
 
666
- function preloadLink(url, key) {
663
+ function preloadLink(url, key, isCrossOrigin) {
667
664
  var head = document.head;
668
665
  // v1.0.10: если head недоступен, откатываем ключ
669
666
  if (!head) { preloaded.delete(key); return }
@@ -677,13 +674,10 @@ function createPrefetchCore(options) {
677
674
  try { l.fetchPriority = 'low'; } catch (e) {}
678
675
 
679
676
  // v1.0.11: для cross-origin: referrerPolicy + crossOrigin
680
- try {
681
- var u = new URL(url, location.href);
682
- if (u.origin !== location.origin) {
683
- l.referrerPolicy = 'no-referrer';
684
- l.crossOrigin = 'anonymous'; // не отправлять cookies на внешние домены
685
- }
686
- } catch (e) {}
677
+ if (isCrossOrigin) {
678
+ l.referrerPolicy = 'no-referrer';
679
+ l.crossOrigin = 'anonymous'; // не отправлять cookies на внешние домены
680
+ }
687
681
 
688
682
  // v1.0.13: safety timeout — предохранитель если onload/onerror не сработают
689
683
  // (экзотические браузеры, сетевые ошибки без событий)
@@ -707,7 +701,7 @@ function createPrefetchCore(options) {
707
701
  head.appendChild(l);
708
702
  }
709
703
 
710
- function preloadFetch(url, key) {
704
+ function preloadFetch(url, key, isCrossOrigin) {
711
705
  // v1.0.10: если fetch недоступен, откатываем ключ
712
706
  if (typeof fetch !== 'function') { preloaded.delete(key); return }
713
707
 
@@ -740,12 +734,6 @@ function createPrefetchCore(options) {
740
734
  }, 5000);
741
735
  }
742
736
 
743
- // v1.0.13: определяем cross-origin для корректных настроек fetch
744
- var isCrossOrigin = false;
745
- try {
746
- isCrossOrigin = new URL(url, location.href).origin !== location.origin;
747
- } catch (e) {}
748
-
749
737
  var opts = {
750
738
  method: 'GET',
751
739
  cache: 'force-cache',
@@ -815,30 +803,36 @@ function createPrefetchCore(options) {
815
803
 
816
804
  // Mutation Observer
817
805
  var mutObserver = null;
818
- var mutTimer = null;
819
806
 
820
807
  function startMutationObserver() {
821
808
  // v1.0.11: защита от вызова после destroy()
822
809
  if (disabled) return
823
810
  if (mutObserver) return
824
811
  mutObserver = new MutationObserver(function (muts) {
825
- // v1.0.13: оптимизация — обычный цикл вместо Array.from + querySelector вместо querySelectorAll
826
- var hasLinks = false;
827
- outer: for (var i = 0; i < muts.length; i++) {
812
+ if (!vpObserver) return
813
+ // v1.1.3: собираем только новые ссылки из addedNodes (не пересканируем весь DOM)
814
+ var pending = [];
815
+ for (var i = 0; i < muts.length; i++) {
828
816
  var nodes = muts[i].addedNodes;
829
817
  for (var j = 0; j < nodes.length; j++) {
830
818
  var n = nodes[j];
831
- if (n.nodeType === 1) {
832
- if (n.tagName === 'A' || (n.querySelector && n.querySelector('a'))) {
833
- hasLinks = true;
834
- break outer
835
- }
819
+ if (n.nodeType !== 1) continue
820
+ if (n.tagName === 'A') {
821
+ pending.push(n);
822
+ } else if (n.querySelectorAll) {
823
+ var links = n.querySelectorAll('a');
824
+ for (var k = 0; k < links.length; k++) pending.push(links[k]);
836
825
  }
837
826
  }
838
827
  }
839
- if (hasLinks && vpObserver) {
840
- clearTimeout(mutTimer);
841
- mutTimer = setTimeout(observeLinks, 100);
828
+ // v1.1.3: обрабатываем через rIC чтобы не блокировать UI при массовых вставках DOM
829
+ if (pending.length > 0) {
830
+ var rIC = window.requestIdleCallback || function (cb) { setTimeout(cb, 1); };
831
+ rIC(function () {
832
+ for (var i = 0; i < pending.length; i++) {
833
+ if (vpObserver && canPreload(pending[i])) vpObserver.observe(pending[i]);
834
+ }
835
+ });
842
836
  }
843
837
  });
844
838
  mutObserver.observe(document.body, { childList: true, subtree: true });
@@ -899,6 +893,11 @@ function createPrefetchCore(options) {
899
893
  document.removeEventListener('mouseover', onMouseOver, opts);
900
894
  document.removeEventListener('mousedown', onMouseDown, opts);
901
895
 
896
+ // v1.1.3: снимаем listener сети
897
+ if (conn && typeof conn.removeEventListener === 'function') {
898
+ conn.removeEventListener('change', onConnectionChange);
899
+ }
900
+
902
901
  // v1.0.11: снимаем слушатели навигации
903
902
  window.removeEventListener('popstate', updateCurrentKey);
904
903
  window.removeEventListener('hashchange', updateCurrentKey);
@@ -918,7 +917,7 @@ function createPrefetchCore(options) {
918
917
  // Публичный API
919
918
  var api = {
920
919
  __prefetchRu: true,
921
- version: '1.1.2',
920
+ version: '1.1.3',
922
921
  preload: function (url) {
923
922
  // v1.0.11: валидация URL + прогон через canPreload() (консистентность с авто-режимом)
924
923
  if (!isValidPrefetchUrl(url)) return
package/prefetch.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * prefetch.ru v1.1.2 - Мгновенная загрузка страниц
2
+ * prefetch.ru v1.1.3 - Мгновенная загрузка страниц
3
3
  * © 2026 Сергей Макаров | MIT License
4
4
  * https://prefetch.ru | https://github.com/prefetch-ru
5
5
  */
@@ -22,7 +22,7 @@
22
22
  if (!isBrowser || typeof window === 'undefined' || typeof document === 'undefined') {
23
23
  return {
24
24
  __prefetchRu: true,
25
- version: '1.1.2',
25
+ version: '1.1.3',
26
26
  preload: function () {},
27
27
  destroy: function () {},
28
28
  refresh: function () {}
@@ -60,6 +60,9 @@
60
60
  var saveData = false;
61
61
  var connType = null;
62
62
 
63
+ // v1.1.3: ссылка на navigator.connection для снятия listener в destroy()
64
+ var conn = null;
65
+
63
66
  // CSP / поддержка
64
67
  var scriptNonce = null;
65
68
  var supportsLinkPrefetch = false;
@@ -105,40 +108,29 @@
105
108
  var ua = navigator.userAgent;
106
109
  var uaData = navigator.userAgentData; // v1.0.12: UA-CH API (более надёжно в долгосрочной перспективе)
107
110
 
108
- // Определяем устройство
109
- // v1.0.12: используем userAgentData если доступен, иначе fallback на UA
111
+ // v1.1.3: UA-CH API если доступен, иначе fallback на UA
110
112
  if (uaData) {
111
113
  // UA-CH API: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData
112
114
  isIOS = false; // UA-CH пока не поддерживается на iOS
113
- var isAndroid = uaData.platform === 'Android';
114
115
  isMobile = uaData.mobile || false;
115
- // Chromium версия через brands
116
- var brands = uaData.brands || [];
117
- for (var i = 0; i < brands.length; i++) {
118
- var b = brands[i];
119
- if (b.brand === 'Chromium' || b.brand === 'Google Chrome') {
120
- parseInt(b.version, 10);
121
- break
122
- }
123
- }
124
116
  } else {
125
117
  // Fallback на традиционный UA sniffing
126
118
  isIOS =
127
119
  /iPad|iPhone/.test(ua) ||
128
120
  (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
129
- var isAndroid = /Android/.test(ua);
130
- isMobile = (isIOS || isAndroid) && Math.min(screen.width, screen.height) < 768;
131
- // Chromium версия из UA
132
- var cm = ua.match(/Chrome\/(\d+)/);
133
- if (cm) parseInt(cm[1], 10);
121
+ isMobile = (isIOS || /Android/.test(ua)) && Math.min(screen.width, screen.height) < 768;
134
122
  }
135
123
  if (isMobile) maxPreloads = 20;
136
124
 
137
125
  // Сеть
138
- var conn = navigator.connection;
126
+ conn = navigator.connection;
139
127
  if (conn) {
140
128
  connType = conn.effectiveType;
141
129
  saveData = conn.saveData || false;
130
+ // v1.1.3: реактивное обновление при смене типа соединения
131
+ if (typeof conn.addEventListener === 'function') {
132
+ conn.addEventListener('change', onConnectionChange);
133
+ }
142
134
  }
143
135
 
144
136
  // Ждём DOM
@@ -271,6 +263,14 @@
271
263
  return true
272
264
  }
273
265
 
266
+ // v1.1.3: реактивное обновление состояния сети
267
+ function onConnectionChange() {
268
+ if (conn) {
269
+ connType = conn.effectiveType;
270
+ saveData = conn.saveData || false;
271
+ }
272
+ }
273
+
274
274
  // v1.0.11: обновление currentKey при навигации (SPA-гибриды, pushState)
275
275
  function updateCurrentKey() {
276
276
  currentKey = location.origin + location.pathname + location.search;
@@ -441,14 +441,13 @@
441
441
  }
442
442
 
443
443
  function checkPlatform(a) {
444
- var href = a.href;
445
-
446
444
  // v1.0.11: используем свойства <a> напрямую вместо new URL() (perf)
447
445
  var pathname = a.pathname || '';
448
446
  var hash = a.hash || '';
449
447
 
450
448
  if (platform === 'bitrix' || platform === 'bitrix24') {
451
- if (href.indexOf('/bitrix/') !== -1 || href.indexOf('sessid=') !== -1) return false
449
+ // v1.1.3: проверяем pathname и search вместо href (точнее, без ложных срабатываний на домене)
450
+ if (pathname.indexOf('/bitrix/') !== -1 || (a.search && a.search.indexOf('sessid=') !== -1)) return false
452
451
  if (a.classList.contains('bx-ajax')) return false
453
452
  }
454
453
 
@@ -522,10 +521,20 @@
522
521
  if (disabled) return
523
522
  if (!isNetworkOk()) return
524
523
 
525
- // v1.0.12: парсим URL один раз вместо двух
526
- var parsed = parseUrl(url);
527
- var requestUrl = parsed.requestUrl;
528
- var key = parsed.key;
524
+ // v1.1.3: если a реальный DOM-элемент, берём key и crossOrigin из его свойств
525
+ // без лишнего new URL() (parseUrl нужен только для публичного API с произвольным url)
526
+ var requestUrl, key, isCrossOrigin;
527
+ if (a && a.origin) {
528
+ key = a.origin + a.pathname + a.search;
529
+ requestUrl = a.href.split('#')[0]; // убираем hash для запроса
530
+ isCrossOrigin = a.origin !== location.origin;
531
+ } else {
532
+ var parsed = parseUrl(url);
533
+ requestUrl = parsed.requestUrl;
534
+ key = parsed.key;
535
+ isCrossOrigin = false;
536
+ try { isCrossOrigin = new URL(requestUrl, location.href).origin !== location.origin; } catch (e) {}
537
+ }
529
538
 
530
539
  if (preloaded.has(key)) return
531
540
  if (preloaded.size >= maxPreloads) {
@@ -540,26 +549,20 @@
540
549
  // v1.0.11: лимит очереди — отбрасываем новые при переполнении
541
550
  // v1.0.11: при drop удаляем ключ (иначе URL "навсегда" считается прогретым)
542
551
  if (queue.length >= maxQueue) { preloaded.delete(key); return }
543
- queue.push({ url: requestUrl, key: key, mode: mode });
552
+ queue.push({ url: requestUrl, key: key, mode: mode, crossOrigin: isCrossOrigin });
544
553
  return
545
554
  }
546
555
 
547
- doPreload(requestUrl, key, mode);
556
+ doPreload(requestUrl, key, mode, isCrossOrigin);
548
557
  }
549
558
 
550
- function doPreload(requestUrl, key, mode) {
551
- // v1.1.1: определяем cross-origin для выбора метода
552
- var isCrossOrigin = false;
553
- try {
554
- var u = new URL(requestUrl, location.href);
555
- isCrossOrigin = u.origin !== location.origin;
556
- } catch (e) {}
557
-
559
+ // v1.1.3: isCrossOrigin передаётся через цепочку вызовов (без повторного new URL())
560
+ function doPreload(requestUrl, key, mode, isCrossOrigin) {
558
561
  if (mode !== 'none') {
559
562
  // v1.0.11: try/catch для preloadSpec — при строгом CSP/Trusted Types может выбросить исключение
560
563
  var specOk = false;
561
564
  try {
562
- preloadSpec(requestUrl, mode);
565
+ preloadSpec(requestUrl, mode, isCrossOrigin);
563
566
  specOk = true;
564
567
  } catch (e) {
565
568
  // Ошибка в Speculation Rules — fallback обязателен
@@ -569,32 +572,25 @@
569
572
  // По умолчанию fallback отключён для избежания двойного трафика
570
573
  if (specRulesFallback || !specOk) {
571
574
  // v1.1.1: cross-origin всегда через fetch (no-cors), <link> требует CORS headers
572
- if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key);
573
- else preloadLink(requestUrl, key);
575
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin);
576
+ else preloadLink(requestUrl, key, isCrossOrigin);
574
577
  }
575
578
  return
576
579
  }
577
580
 
578
581
  // v1.1.1: cross-origin всегда через fetch (no-cors), <link crossorigin=anonymous> требует CORS
579
- if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key);
580
- else preloadLink(requestUrl, key);
582
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin);
583
+ else preloadLink(requestUrl, key, isCrossOrigin);
581
584
  }
582
585
 
583
586
  function processQueue() {
584
587
  while (queue.length > 0 && inFlight < maxInFlight) {
585
588
  var item = queue.shift();
586
- doPreload(item.url, item.key, item.mode);
589
+ doPreload(item.url, item.key, item.mode, item.crossOrigin);
587
590
  }
588
591
  }
589
592
 
590
- function preloadSpec(url, mode) {
591
- // v1.0.13: для cross-origin проверяем и модифицируем правила
592
- var isCrossOrigin = false;
593
- try {
594
- var u = new URL(url, location.href);
595
- isCrossOrigin = u.origin !== location.origin;
596
- } catch (e) {}
597
-
593
+ function preloadSpec(url, mode, isCrossOrigin) {
598
594
  // v1.0.13: для cross-origin никогда не делаем prerender (только prefetch)
599
595
  if (isCrossOrigin && mode === 'prerender') {
600
596
  mode = 'prefetch';
@@ -628,18 +624,19 @@
628
624
 
629
625
  var rules = {};
630
626
 
627
+ // v1.1.3: передаём массив напрямую и создаём новый (без лишнего .slice())
631
628
  // Same-origin prefetch
632
629
  if (specBuffer.prefetch.length > 0) {
633
630
  rules.prefetch = rules.prefetch || [];
634
- rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch.slice() });
635
- specBuffer.prefetch.length = 0;
631
+ rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch });
632
+ specBuffer.prefetch = [];
636
633
  }
637
634
 
638
635
  // Same-origin prerender
639
636
  if (specBuffer.prerender.length > 0) {
640
637
  rules.prerender = rules.prerender || [];
641
- rules.prerender.push({ source: 'list', urls: specBuffer.prerender.slice() });
642
- specBuffer.prerender.length = 0;
638
+ rules.prerender.push({ source: 'list', urls: specBuffer.prerender });
639
+ specBuffer.prerender = [];
643
640
  }
644
641
 
645
642
  // Cross-origin (только prefetch, с privacy requirements)
@@ -647,11 +644,11 @@
647
644
  rules.prefetch = rules.prefetch || [];
648
645
  rules.prefetch.push({
649
646
  source: 'list',
650
- urls: specBuffer.crossOrigin.slice(),
647
+ urls: specBuffer.crossOrigin,
651
648
  referrer_policy: 'no-referrer',
652
649
  requires: ['anonymous-client-ip-when-cross-origin']
653
650
  });
654
- specBuffer.crossOrigin.length = 0;
651
+ specBuffer.crossOrigin = [];
655
652
  }
656
653
 
657
654
  // Если нет правил — выходим
@@ -666,7 +663,7 @@
666
663
  head.removeChild(s);
667
664
  }
668
665
 
669
- function preloadLink(url, key) {
666
+ function preloadLink(url, key, isCrossOrigin) {
670
667
  var head = document.head;
671
668
  // v1.0.10: если head недоступен, откатываем ключ
672
669
  if (!head) { preloaded.delete(key); return }
@@ -680,13 +677,10 @@
680
677
  try { l.fetchPriority = 'low'; } catch (e) {}
681
678
 
682
679
  // v1.0.11: для cross-origin: referrerPolicy + crossOrigin
683
- try {
684
- var u = new URL(url, location.href);
685
- if (u.origin !== location.origin) {
686
- l.referrerPolicy = 'no-referrer';
687
- l.crossOrigin = 'anonymous'; // не отправлять cookies на внешние домены
688
- }
689
- } catch (e) {}
680
+ if (isCrossOrigin) {
681
+ l.referrerPolicy = 'no-referrer';
682
+ l.crossOrigin = 'anonymous'; // не отправлять cookies на внешние домены
683
+ }
690
684
 
691
685
  // v1.0.13: safety timeout — предохранитель если onload/onerror не сработают
692
686
  // (экзотические браузеры, сетевые ошибки без событий)
@@ -710,7 +704,7 @@
710
704
  head.appendChild(l);
711
705
  }
712
706
 
713
- function preloadFetch(url, key) {
707
+ function preloadFetch(url, key, isCrossOrigin) {
714
708
  // v1.0.10: если fetch недоступен, откатываем ключ
715
709
  if (typeof fetch !== 'function') { preloaded.delete(key); return }
716
710
 
@@ -743,12 +737,6 @@
743
737
  }, 5000);
744
738
  }
745
739
 
746
- // v1.0.13: определяем cross-origin для корректных настроек fetch
747
- var isCrossOrigin = false;
748
- try {
749
- isCrossOrigin = new URL(url, location.href).origin !== location.origin;
750
- } catch (e) {}
751
-
752
740
  var opts = {
753
741
  method: 'GET',
754
742
  cache: 'force-cache',
@@ -818,30 +806,36 @@
818
806
 
819
807
  // Mutation Observer
820
808
  var mutObserver = null;
821
- var mutTimer = null;
822
809
 
823
810
  function startMutationObserver() {
824
811
  // v1.0.11: защита от вызова после destroy()
825
812
  if (disabled) return
826
813
  if (mutObserver) return
827
814
  mutObserver = new MutationObserver(function (muts) {
828
- // v1.0.13: оптимизация — обычный цикл вместо Array.from + querySelector вместо querySelectorAll
829
- var hasLinks = false;
830
- outer: for (var i = 0; i < muts.length; i++) {
815
+ if (!vpObserver) return
816
+ // v1.1.3: собираем только новые ссылки из addedNodes (не пересканируем весь DOM)
817
+ var pending = [];
818
+ for (var i = 0; i < muts.length; i++) {
831
819
  var nodes = muts[i].addedNodes;
832
820
  for (var j = 0; j < nodes.length; j++) {
833
821
  var n = nodes[j];
834
- if (n.nodeType === 1) {
835
- if (n.tagName === 'A' || (n.querySelector && n.querySelector('a'))) {
836
- hasLinks = true;
837
- break outer
838
- }
822
+ if (n.nodeType !== 1) continue
823
+ if (n.tagName === 'A') {
824
+ pending.push(n);
825
+ } else if (n.querySelectorAll) {
826
+ var links = n.querySelectorAll('a');
827
+ for (var k = 0; k < links.length; k++) pending.push(links[k]);
839
828
  }
840
829
  }
841
830
  }
842
- if (hasLinks && vpObserver) {
843
- clearTimeout(mutTimer);
844
- mutTimer = setTimeout(observeLinks, 100);
831
+ // v1.1.3: обрабатываем через rIC чтобы не блокировать UI при массовых вставках DOM
832
+ if (pending.length > 0) {
833
+ var rIC = window.requestIdleCallback || function (cb) { setTimeout(cb, 1); };
834
+ rIC(function () {
835
+ for (var i = 0; i < pending.length; i++) {
836
+ if (vpObserver && canPreload(pending[i])) vpObserver.observe(pending[i]);
837
+ }
838
+ });
845
839
  }
846
840
  });
847
841
  mutObserver.observe(document.body, { childList: true, subtree: true });
@@ -902,6 +896,11 @@
902
896
  document.removeEventListener('mouseover', onMouseOver, opts);
903
897
  document.removeEventListener('mousedown', onMouseDown, opts);
904
898
 
899
+ // v1.1.3: снимаем listener сети
900
+ if (conn && typeof conn.removeEventListener === 'function') {
901
+ conn.removeEventListener('change', onConnectionChange);
902
+ }
903
+
905
904
  // v1.0.11: снимаем слушатели навигации
906
905
  window.removeEventListener('popstate', updateCurrentKey);
907
906
  window.removeEventListener('hashchange', updateCurrentKey);
@@ -921,7 +920,7 @@
921
920
  // Публичный API
922
921
  var api = {
923
922
  __prefetchRu: true,
924
- version: '1.1.2',
923
+ version: '1.1.3',
925
924
  preload: function (url) {
926
925
  // v1.0.11: валидация URL + прогон через canPreload() (консистентность с авто-режимом)
927
926
  if (!isValidPrefetchUrl(url)) return
package/src/core.js CHANGED
@@ -49,11 +49,13 @@ export function createPrefetchCore(options) {
49
49
 
50
50
  var isMobile = false
51
51
  var isIOS = false
52
- var chromiumVer = null
53
52
  var platform = null
54
53
  var saveData = false
55
54
  var connType = null
56
55
 
56
+ // v1.1.3: ссылка на navigator.connection для снятия listener в destroy()
57
+ var conn = null
58
+
57
59
  // CSP / поддержка
58
60
  var scriptNonce = null
59
61
  var supportsLinkPrefetch = false
@@ -99,40 +101,29 @@ export function createPrefetchCore(options) {
99
101
  var ua = navigator.userAgent
100
102
  var uaData = navigator.userAgentData // v1.0.12: UA-CH API (более надёжно в долгосрочной перспективе)
101
103
 
102
- // Определяем устройство
103
- // v1.0.12: используем userAgentData если доступен, иначе fallback на UA
104
+ // v1.1.3: UA-CH API если доступен, иначе fallback на UA
104
105
  if (uaData) {
105
106
  // UA-CH API: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData
106
107
  isIOS = false // UA-CH пока не поддерживается на iOS
107
- var isAndroid = uaData.platform === 'Android'
108
108
  isMobile = uaData.mobile || false
109
- // Chromium версия через brands
110
- var brands = uaData.brands || []
111
- for (var i = 0; i < brands.length; i++) {
112
- var b = brands[i]
113
- if (b.brand === 'Chromium' || b.brand === 'Google Chrome') {
114
- chromiumVer = parseInt(b.version, 10)
115
- break
116
- }
117
- }
118
109
  } else {
119
110
  // Fallback на традиционный UA sniffing
120
111
  isIOS =
121
112
  /iPad|iPhone/.test(ua) ||
122
113
  (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
123
- var isAndroid = /Android/.test(ua)
124
- isMobile = (isIOS || isAndroid) && Math.min(screen.width, screen.height) < 768
125
- // Chromium версия из UA
126
- var cm = ua.match(/Chrome\/(\d+)/)
127
- if (cm) chromiumVer = parseInt(cm[1], 10)
114
+ isMobile = (isIOS || /Android/.test(ua)) && Math.min(screen.width, screen.height) < 768
128
115
  }
129
116
  if (isMobile) maxPreloads = 20
130
117
 
131
118
  // Сеть
132
- var conn = navigator.connection
119
+ conn = navigator.connection
133
120
  if (conn) {
134
121
  connType = conn.effectiveType
135
122
  saveData = conn.saveData || false
123
+ // v1.1.3: реактивное обновление при смене типа соединения
124
+ if (typeof conn.addEventListener === 'function') {
125
+ conn.addEventListener('change', onConnectionChange)
126
+ }
136
127
  }
137
128
 
138
129
  // Ждём DOM
@@ -265,6 +256,14 @@ export function createPrefetchCore(options) {
265
256
  return true
266
257
  }
267
258
 
259
+ // v1.1.3: реактивное обновление состояния сети
260
+ function onConnectionChange() {
261
+ if (conn) {
262
+ connType = conn.effectiveType
263
+ saveData = conn.saveData || false
264
+ }
265
+ }
266
+
268
267
  // v1.0.11: обновление currentKey при навигации (SPA-гибриды, pushState)
269
268
  function updateCurrentKey() {
270
269
  currentKey = location.origin + location.pathname + location.search
@@ -435,14 +434,13 @@ export function createPrefetchCore(options) {
435
434
  }
436
435
 
437
436
  function checkPlatform(a) {
438
- var href = a.href
439
-
440
437
  // v1.0.11: используем свойства <a> напрямую вместо new URL() (perf)
441
438
  var pathname = a.pathname || ''
442
439
  var hash = a.hash || ''
443
440
 
444
441
  if (platform === 'bitrix' || platform === 'bitrix24') {
445
- if (href.indexOf('/bitrix/') !== -1 || href.indexOf('sessid=') !== -1) return false
442
+ // v1.1.3: проверяем pathname и search вместо href (точнее, без ложных срабатываний на домене)
443
+ if (pathname.indexOf('/bitrix/') !== -1 || (a.search && a.search.indexOf('sessid=') !== -1)) return false
446
444
  if (a.classList.contains('bx-ajax')) return false
447
445
  }
448
446
 
@@ -516,10 +514,20 @@ export function createPrefetchCore(options) {
516
514
  if (disabled) return
517
515
  if (!isNetworkOk()) return
518
516
 
519
- // v1.0.12: парсим URL один раз вместо двух
520
- var parsed = parseUrl(url)
521
- var requestUrl = parsed.requestUrl
522
- var key = parsed.key
517
+ // v1.1.3: если a реальный DOM-элемент, берём key и crossOrigin из его свойств
518
+ // без лишнего new URL() (parseUrl нужен только для публичного API с произвольным url)
519
+ var requestUrl, key, isCrossOrigin
520
+ if (a && a.origin) {
521
+ key = a.origin + a.pathname + a.search
522
+ requestUrl = a.href.split('#')[0] // убираем hash для запроса
523
+ isCrossOrigin = a.origin !== location.origin
524
+ } else {
525
+ var parsed = parseUrl(url)
526
+ requestUrl = parsed.requestUrl
527
+ key = parsed.key
528
+ isCrossOrigin = false
529
+ try { isCrossOrigin = new URL(requestUrl, location.href).origin !== location.origin } catch (e) {}
530
+ }
523
531
 
524
532
  if (preloaded.has(key)) return
525
533
  if (preloaded.size >= maxPreloads) {
@@ -534,26 +542,20 @@ export function createPrefetchCore(options) {
534
542
  // v1.0.11: лимит очереди — отбрасываем новые при переполнении
535
543
  // v1.0.11: при drop удаляем ключ (иначе URL "навсегда" считается прогретым)
536
544
  if (queue.length >= maxQueue) { preloaded.delete(key); return }
537
- queue.push({ url: requestUrl, key: key, mode: mode })
545
+ queue.push({ url: requestUrl, key: key, mode: mode, crossOrigin: isCrossOrigin })
538
546
  return
539
547
  }
540
548
 
541
- doPreload(requestUrl, key, mode)
549
+ doPreload(requestUrl, key, mode, isCrossOrigin)
542
550
  }
543
551
 
544
- function doPreload(requestUrl, key, mode) {
545
- // v1.1.1: определяем cross-origin для выбора метода
546
- var isCrossOrigin = false
547
- try {
548
- var u = new URL(requestUrl, location.href)
549
- isCrossOrigin = u.origin !== location.origin
550
- } catch (e) {}
551
-
552
+ // v1.1.3: isCrossOrigin передаётся через цепочку вызовов (без повторного new URL())
553
+ function doPreload(requestUrl, key, mode, isCrossOrigin) {
552
554
  if (mode !== 'none') {
553
555
  // v1.0.11: try/catch для preloadSpec — при строгом CSP/Trusted Types может выбросить исключение
554
556
  var specOk = false
555
557
  try {
556
- preloadSpec(requestUrl, mode)
558
+ preloadSpec(requestUrl, mode, isCrossOrigin)
557
559
  specOk = true
558
560
  } catch (e) {
559
561
  // Ошибка в Speculation Rules — fallback обязателен
@@ -563,32 +565,25 @@ export function createPrefetchCore(options) {
563
565
  // По умолчанию fallback отключён для избежания двойного трафика
564
566
  if (specRulesFallback || !specOk) {
565
567
  // v1.1.1: cross-origin всегда через fetch (no-cors), <link> требует CORS headers
566
- if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key)
567
- else preloadLink(requestUrl, key)
568
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin)
569
+ else preloadLink(requestUrl, key, isCrossOrigin)
568
570
  }
569
571
  return
570
572
  }
571
573
 
572
574
  // v1.1.1: cross-origin всегда через fetch (no-cors), <link crossorigin=anonymous> требует CORS
573
- if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key)
574
- else preloadLink(requestUrl, key)
575
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin)
576
+ else preloadLink(requestUrl, key, isCrossOrigin)
575
577
  }
576
578
 
577
579
  function processQueue() {
578
580
  while (queue.length > 0 && inFlight < maxInFlight) {
579
581
  var item = queue.shift()
580
- doPreload(item.url, item.key, item.mode)
582
+ doPreload(item.url, item.key, item.mode, item.crossOrigin)
581
583
  }
582
584
  }
583
585
 
584
- function preloadSpec(url, mode) {
585
- // v1.0.13: для cross-origin проверяем и модифицируем правила
586
- var isCrossOrigin = false
587
- try {
588
- var u = new URL(url, location.href)
589
- isCrossOrigin = u.origin !== location.origin
590
- } catch (e) {}
591
-
586
+ function preloadSpec(url, mode, isCrossOrigin) {
592
587
  // v1.0.13: для cross-origin никогда не делаем prerender (только prefetch)
593
588
  if (isCrossOrigin && mode === 'prerender') {
594
589
  mode = 'prefetch'
@@ -622,18 +617,19 @@ export function createPrefetchCore(options) {
622
617
 
623
618
  var rules = {}
624
619
 
620
+ // v1.1.3: передаём массив напрямую и создаём новый (без лишнего .slice())
625
621
  // Same-origin prefetch
626
622
  if (specBuffer.prefetch.length > 0) {
627
623
  rules.prefetch = rules.prefetch || []
628
- rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch.slice() })
629
- specBuffer.prefetch.length = 0
624
+ rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch })
625
+ specBuffer.prefetch = []
630
626
  }
631
627
 
632
628
  // Same-origin prerender
633
629
  if (specBuffer.prerender.length > 0) {
634
630
  rules.prerender = rules.prerender || []
635
- rules.prerender.push({ source: 'list', urls: specBuffer.prerender.slice() })
636
- specBuffer.prerender.length = 0
631
+ rules.prerender.push({ source: 'list', urls: specBuffer.prerender })
632
+ specBuffer.prerender = []
637
633
  }
638
634
 
639
635
  // Cross-origin (только prefetch, с privacy requirements)
@@ -641,11 +637,11 @@ export function createPrefetchCore(options) {
641
637
  rules.prefetch = rules.prefetch || []
642
638
  rules.prefetch.push({
643
639
  source: 'list',
644
- urls: specBuffer.crossOrigin.slice(),
640
+ urls: specBuffer.crossOrigin,
645
641
  referrer_policy: 'no-referrer',
646
642
  requires: ['anonymous-client-ip-when-cross-origin']
647
643
  })
648
- specBuffer.crossOrigin.length = 0
644
+ specBuffer.crossOrigin = []
649
645
  }
650
646
 
651
647
  // Если нет правил — выходим
@@ -660,7 +656,7 @@ export function createPrefetchCore(options) {
660
656
  head.removeChild(s)
661
657
  }
662
658
 
663
- function preloadLink(url, key) {
659
+ function preloadLink(url, key, isCrossOrigin) {
664
660
  var head = document.head
665
661
  // v1.0.10: если head недоступен, откатываем ключ
666
662
  if (!head) { preloaded.delete(key); return }
@@ -674,13 +670,10 @@ export function createPrefetchCore(options) {
674
670
  try { l.fetchPriority = 'low' } catch (e) {}
675
671
 
676
672
  // v1.0.11: для cross-origin: referrerPolicy + crossOrigin
677
- try {
678
- var u = new URL(url, location.href)
679
- if (u.origin !== location.origin) {
680
- l.referrerPolicy = 'no-referrer'
681
- l.crossOrigin = 'anonymous' // не отправлять cookies на внешние домены
682
- }
683
- } catch (e) {}
673
+ if (isCrossOrigin) {
674
+ l.referrerPolicy = 'no-referrer'
675
+ l.crossOrigin = 'anonymous' // не отправлять cookies на внешние домены
676
+ }
684
677
 
685
678
  // v1.0.13: safety timeout — предохранитель если onload/onerror не сработают
686
679
  // (экзотические браузеры, сетевые ошибки без событий)
@@ -704,7 +697,7 @@ export function createPrefetchCore(options) {
704
697
  head.appendChild(l)
705
698
  }
706
699
 
707
- function preloadFetch(url, key) {
700
+ function preloadFetch(url, key, isCrossOrigin) {
708
701
  // v1.0.10: если fetch недоступен, откатываем ключ
709
702
  if (typeof fetch !== 'function') { preloaded.delete(key); return }
710
703
 
@@ -737,12 +730,6 @@ export function createPrefetchCore(options) {
737
730
  }, 5000)
738
731
  }
739
732
 
740
- // v1.0.13: определяем cross-origin для корректных настроек fetch
741
- var isCrossOrigin = false
742
- try {
743
- isCrossOrigin = new URL(url, location.href).origin !== location.origin
744
- } catch (e) {}
745
-
746
733
  var opts = {
747
734
  method: 'GET',
748
735
  cache: 'force-cache',
@@ -812,30 +799,36 @@ export function createPrefetchCore(options) {
812
799
 
813
800
  // Mutation Observer
814
801
  var mutObserver = null
815
- var mutTimer = null
816
802
 
817
803
  function startMutationObserver() {
818
804
  // v1.0.11: защита от вызова после destroy()
819
805
  if (disabled) return
820
806
  if (mutObserver) return
821
807
  mutObserver = new MutationObserver(function (muts) {
822
- // v1.0.13: оптимизация — обычный цикл вместо Array.from + querySelector вместо querySelectorAll
823
- var hasLinks = false
824
- outer: for (var i = 0; i < muts.length; i++) {
808
+ if (!vpObserver) return
809
+ // v1.1.3: собираем только новые ссылки из addedNodes (не пересканируем весь DOM)
810
+ var pending = []
811
+ for (var i = 0; i < muts.length; i++) {
825
812
  var nodes = muts[i].addedNodes
826
813
  for (var j = 0; j < nodes.length; j++) {
827
814
  var n = nodes[j]
828
- if (n.nodeType === 1) {
829
- if (n.tagName === 'A' || (n.querySelector && n.querySelector('a'))) {
830
- hasLinks = true
831
- break outer
832
- }
815
+ if (n.nodeType !== 1) continue
816
+ if (n.tagName === 'A') {
817
+ pending.push(n)
818
+ } else if (n.querySelectorAll) {
819
+ var links = n.querySelectorAll('a')
820
+ for (var k = 0; k < links.length; k++) pending.push(links[k])
833
821
  }
834
822
  }
835
823
  }
836
- if (hasLinks && vpObserver) {
837
- clearTimeout(mutTimer)
838
- mutTimer = setTimeout(observeLinks, 100)
824
+ // v1.1.3: обрабатываем через rIC чтобы не блокировать UI при массовых вставках DOM
825
+ if (pending.length > 0) {
826
+ var rIC = window.requestIdleCallback || function (cb) { setTimeout(cb, 1) }
827
+ rIC(function () {
828
+ for (var i = 0; i < pending.length; i++) {
829
+ if (vpObserver && canPreload(pending[i])) vpObserver.observe(pending[i])
830
+ }
831
+ })
839
832
  }
840
833
  })
841
834
  mutObserver.observe(document.body, { childList: true, subtree: true })
@@ -896,6 +889,11 @@ export function createPrefetchCore(options) {
896
889
  document.removeEventListener('mouseover', onMouseOver, opts)
897
890
  document.removeEventListener('mousedown', onMouseDown, opts)
898
891
 
892
+ // v1.1.3: снимаем listener сети
893
+ if (conn && typeof conn.removeEventListener === 'function') {
894
+ conn.removeEventListener('change', onConnectionChange)
895
+ }
896
+
899
897
  // v1.0.11: снимаем слушатели навигации
900
898
  window.removeEventListener('popstate', updateCurrentKey)
901
899
  window.removeEventListener('hashchange', updateCurrentKey)