@prefetchru/prefetch 1.1.1 → 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.1"
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.1 (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.1",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,E=65,L=80,x=50,T=!1,k=!1,O=!1,P=!1,A="none",S=!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){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&&(E=i)}p&&(L=Math.max(60,Math.min(E||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&&E<100&&(E=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});I&&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)},L)}}}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)},E);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!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 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){if("none"===n)m||!b?V(e,t):J(e,t);else{var r=!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),r=!0}catch(e){}!S&&r||(m||!b?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.1",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.1 - Мгновенная загрузка страниц
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.1",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,O=!1,k=!1,P=!1,S="none",A=!1,N=!1,C=!1,q=!1,I=!1,M=/(^|\/)(login|logout|auth|register|cart|basket|add|delete|remove)(\/|$|\.)/i,R=/\.(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,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(){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(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!==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}return("tilda"!==v||-1===r.indexOf("#popup:")&&-1===r.indexOf("#rec"))&&(!M.test(n)&&!R.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"!==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:u});Q(c,s,u)}}}function Q(e,t,n){if("none"===n)m||!b?V(e,t):J(e,t);else{var r=!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),!u){var r=window.requestIdleCallback||function(e){setTimeout(e,1)};u=r($,{timeout:50})}}(e,n),r=!0}catch(e){}!A&&r||(m||!b?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.1",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:!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.1",
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.1 (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.1',
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,19 +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) {
556
+ // v1.1.3: isCrossOrigin передаётся через цепочку вызовов (без повторного new URL())
557
+ function doPreload(requestUrl, key, mode, isCrossOrigin) {
548
558
  if (mode !== 'none') {
549
559
  // v1.0.11: try/catch для preloadSpec — при строгом CSP/Trusted Types может выбросить исключение
550
560
  var specOk = false;
551
561
  try {
552
- preloadSpec(requestUrl, mode);
562
+ preloadSpec(requestUrl, mode, isCrossOrigin);
553
563
  specOk = true;
554
564
  } catch (e) {
555
565
  // Ошибка в Speculation Rules — fallback обязателен
@@ -558,31 +568,26 @@ function createPrefetchCore(options) {
558
568
  // v1.0.11: fallback только если явно включён ИЛИ если SpecRules не удался
559
569
  // По умолчанию fallback отключён для избежания двойного трафика
560
570
  if (specRulesFallback || !specOk) {
561
- if (isIOS || !supportsLinkPrefetch) preloadFetch(requestUrl, key);
562
- else preloadLink(requestUrl, key);
571
+ // v1.1.1: cross-origin всегда через fetch (no-cors), <link> требует CORS headers
572
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin);
573
+ else preloadLink(requestUrl, key, isCrossOrigin);
563
574
  }
564
575
  return
565
576
  }
566
577
 
567
- if (isIOS || !supportsLinkPrefetch) preloadFetch(requestUrl, key);
568
- else preloadLink(requestUrl, key);
578
+ // v1.1.1: cross-origin всегда через fetch (no-cors), <link crossorigin=anonymous> требует CORS
579
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin);
580
+ else preloadLink(requestUrl, key, isCrossOrigin);
569
581
  }
570
582
 
571
583
  function processQueue() {
572
584
  while (queue.length > 0 && inFlight < maxInFlight) {
573
585
  var item = queue.shift();
574
- doPreload(item.url, item.key, item.mode);
586
+ doPreload(item.url, item.key, item.mode, item.crossOrigin);
575
587
  }
576
588
  }
577
589
 
578
- function preloadSpec(url, mode) {
579
- // v1.0.13: для cross-origin проверяем и модифицируем правила
580
- var isCrossOrigin = false;
581
- try {
582
- var u = new URL(url, location.href);
583
- isCrossOrigin = u.origin !== location.origin;
584
- } catch (e) {}
585
-
590
+ function preloadSpec(url, mode, isCrossOrigin) {
586
591
  // v1.0.13: для cross-origin никогда не делаем prerender (только prefetch)
587
592
  if (isCrossOrigin && mode === 'prerender') {
588
593
  mode = 'prefetch';
@@ -616,18 +621,19 @@ function createPrefetchCore(options) {
616
621
 
617
622
  var rules = {};
618
623
 
624
+ // v1.1.3: передаём массив напрямую и создаём новый (без лишнего .slice())
619
625
  // Same-origin prefetch
620
626
  if (specBuffer.prefetch.length > 0) {
621
627
  rules.prefetch = rules.prefetch || [];
622
- rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch.slice() });
623
- specBuffer.prefetch.length = 0;
628
+ rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch });
629
+ specBuffer.prefetch = [];
624
630
  }
625
631
 
626
632
  // Same-origin prerender
627
633
  if (specBuffer.prerender.length > 0) {
628
634
  rules.prerender = rules.prerender || [];
629
- rules.prerender.push({ source: 'list', urls: specBuffer.prerender.slice() });
630
- specBuffer.prerender.length = 0;
635
+ rules.prerender.push({ source: 'list', urls: specBuffer.prerender });
636
+ specBuffer.prerender = [];
631
637
  }
632
638
 
633
639
  // Cross-origin (только prefetch, с privacy requirements)
@@ -635,11 +641,11 @@ function createPrefetchCore(options) {
635
641
  rules.prefetch = rules.prefetch || [];
636
642
  rules.prefetch.push({
637
643
  source: 'list',
638
- urls: specBuffer.crossOrigin.slice(),
644
+ urls: specBuffer.crossOrigin,
639
645
  referrer_policy: 'no-referrer',
640
646
  requires: ['anonymous-client-ip-when-cross-origin']
641
647
  });
642
- specBuffer.crossOrigin.length = 0;
648
+ specBuffer.crossOrigin = [];
643
649
  }
644
650
 
645
651
  // Если нет правил — выходим
@@ -654,7 +660,7 @@ function createPrefetchCore(options) {
654
660
  head.removeChild(s);
655
661
  }
656
662
 
657
- function preloadLink(url, key) {
663
+ function preloadLink(url, key, isCrossOrigin) {
658
664
  var head = document.head;
659
665
  // v1.0.10: если head недоступен, откатываем ключ
660
666
  if (!head) { preloaded.delete(key); return }
@@ -668,13 +674,10 @@ function createPrefetchCore(options) {
668
674
  try { l.fetchPriority = 'low'; } catch (e) {}
669
675
 
670
676
  // v1.0.11: для cross-origin: referrerPolicy + crossOrigin
671
- try {
672
- var u = new URL(url, location.href);
673
- if (u.origin !== location.origin) {
674
- l.referrerPolicy = 'no-referrer';
675
- l.crossOrigin = 'anonymous'; // не отправлять cookies на внешние домены
676
- }
677
- } catch (e) {}
677
+ if (isCrossOrigin) {
678
+ l.referrerPolicy = 'no-referrer';
679
+ l.crossOrigin = 'anonymous'; // не отправлять cookies на внешние домены
680
+ }
678
681
 
679
682
  // v1.0.13: safety timeout — предохранитель если onload/onerror не сработают
680
683
  // (экзотические браузеры, сетевые ошибки без событий)
@@ -698,7 +701,7 @@ function createPrefetchCore(options) {
698
701
  head.appendChild(l);
699
702
  }
700
703
 
701
- function preloadFetch(url, key) {
704
+ function preloadFetch(url, key, isCrossOrigin) {
702
705
  // v1.0.10: если fetch недоступен, откатываем ключ
703
706
  if (typeof fetch !== 'function') { preloaded.delete(key); return }
704
707
 
@@ -731,12 +734,6 @@ function createPrefetchCore(options) {
731
734
  }, 5000);
732
735
  }
733
736
 
734
- // v1.0.13: определяем cross-origin для корректных настроек fetch
735
- var isCrossOrigin = false;
736
- try {
737
- isCrossOrigin = new URL(url, location.href).origin !== location.origin;
738
- } catch (e) {}
739
-
740
737
  var opts = {
741
738
  method: 'GET',
742
739
  cache: 'force-cache',
@@ -806,30 +803,36 @@ function createPrefetchCore(options) {
806
803
 
807
804
  // Mutation Observer
808
805
  var mutObserver = null;
809
- var mutTimer = null;
810
806
 
811
807
  function startMutationObserver() {
812
808
  // v1.0.11: защита от вызова после destroy()
813
809
  if (disabled) return
814
810
  if (mutObserver) return
815
811
  mutObserver = new MutationObserver(function (muts) {
816
- // v1.0.13: оптимизация — обычный цикл вместо Array.from + querySelector вместо querySelectorAll
817
- var hasLinks = false;
818
- 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++) {
819
816
  var nodes = muts[i].addedNodes;
820
817
  for (var j = 0; j < nodes.length; j++) {
821
818
  var n = nodes[j];
822
- if (n.nodeType === 1) {
823
- if (n.tagName === 'A' || (n.querySelector && n.querySelector('a'))) {
824
- hasLinks = true;
825
- break outer
826
- }
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]);
827
825
  }
828
826
  }
829
827
  }
830
- if (hasLinks && vpObserver) {
831
- clearTimeout(mutTimer);
832
- 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
+ });
833
836
  }
834
837
  });
835
838
  mutObserver.observe(document.body, { childList: true, subtree: true });
@@ -890,6 +893,11 @@ function createPrefetchCore(options) {
890
893
  document.removeEventListener('mouseover', onMouseOver, opts);
891
894
  document.removeEventListener('mousedown', onMouseDown, opts);
892
895
 
896
+ // v1.1.3: снимаем listener сети
897
+ if (conn && typeof conn.removeEventListener === 'function') {
898
+ conn.removeEventListener('change', onConnectionChange);
899
+ }
900
+
893
901
  // v1.0.11: снимаем слушатели навигации
894
902
  window.removeEventListener('popstate', updateCurrentKey);
895
903
  window.removeEventListener('hashchange', updateCurrentKey);
@@ -909,7 +917,7 @@ function createPrefetchCore(options) {
909
917
  // Публичный API
910
918
  var api = {
911
919
  __prefetchRu: true,
912
- version: '1.1.1',
920
+ version: '1.1.3',
913
921
  preload: function (url) {
914
922
  // v1.0.11: валидация URL + прогон через canPreload() (консистентность с авто-режимом)
915
923
  if (!isValidPrefetchUrl(url)) return
package/prefetch.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * prefetch.ru v1.1.1 - Мгновенная загрузка страниц
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.1',
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,19 +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) {
559
+ // v1.1.3: isCrossOrigin передаётся через цепочку вызовов (без повторного new URL())
560
+ function doPreload(requestUrl, key, mode, isCrossOrigin) {
551
561
  if (mode !== 'none') {
552
562
  // v1.0.11: try/catch для preloadSpec — при строгом CSP/Trusted Types может выбросить исключение
553
563
  var specOk = false;
554
564
  try {
555
- preloadSpec(requestUrl, mode);
565
+ preloadSpec(requestUrl, mode, isCrossOrigin);
556
566
  specOk = true;
557
567
  } catch (e) {
558
568
  // Ошибка в Speculation Rules — fallback обязателен
@@ -561,31 +571,26 @@
561
571
  // v1.0.11: fallback только если явно включён ИЛИ если SpecRules не удался
562
572
  // По умолчанию fallback отключён для избежания двойного трафика
563
573
  if (specRulesFallback || !specOk) {
564
- if (isIOS || !supportsLinkPrefetch) preloadFetch(requestUrl, key);
565
- else preloadLink(requestUrl, key);
574
+ // v1.1.1: cross-origin всегда через fetch (no-cors), <link> требует CORS headers
575
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin);
576
+ else preloadLink(requestUrl, key, isCrossOrigin);
566
577
  }
567
578
  return
568
579
  }
569
580
 
570
- if (isIOS || !supportsLinkPrefetch) preloadFetch(requestUrl, key);
571
- else preloadLink(requestUrl, key);
581
+ // v1.1.1: cross-origin всегда через fetch (no-cors), <link crossorigin=anonymous> требует CORS
582
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin);
583
+ else preloadLink(requestUrl, key, isCrossOrigin);
572
584
  }
573
585
 
574
586
  function processQueue() {
575
587
  while (queue.length > 0 && inFlight < maxInFlight) {
576
588
  var item = queue.shift();
577
- doPreload(item.url, item.key, item.mode);
589
+ doPreload(item.url, item.key, item.mode, item.crossOrigin);
578
590
  }
579
591
  }
580
592
 
581
- function preloadSpec(url, mode) {
582
- // v1.0.13: для cross-origin проверяем и модифицируем правила
583
- var isCrossOrigin = false;
584
- try {
585
- var u = new URL(url, location.href);
586
- isCrossOrigin = u.origin !== location.origin;
587
- } catch (e) {}
588
-
593
+ function preloadSpec(url, mode, isCrossOrigin) {
589
594
  // v1.0.13: для cross-origin никогда не делаем prerender (только prefetch)
590
595
  if (isCrossOrigin && mode === 'prerender') {
591
596
  mode = 'prefetch';
@@ -619,18 +624,19 @@
619
624
 
620
625
  var rules = {};
621
626
 
627
+ // v1.1.3: передаём массив напрямую и создаём новый (без лишнего .slice())
622
628
  // Same-origin prefetch
623
629
  if (specBuffer.prefetch.length > 0) {
624
630
  rules.prefetch = rules.prefetch || [];
625
- rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch.slice() });
626
- specBuffer.prefetch.length = 0;
631
+ rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch });
632
+ specBuffer.prefetch = [];
627
633
  }
628
634
 
629
635
  // Same-origin prerender
630
636
  if (specBuffer.prerender.length > 0) {
631
637
  rules.prerender = rules.prerender || [];
632
- rules.prerender.push({ source: 'list', urls: specBuffer.prerender.slice() });
633
- specBuffer.prerender.length = 0;
638
+ rules.prerender.push({ source: 'list', urls: specBuffer.prerender });
639
+ specBuffer.prerender = [];
634
640
  }
635
641
 
636
642
  // Cross-origin (только prefetch, с privacy requirements)
@@ -638,11 +644,11 @@
638
644
  rules.prefetch = rules.prefetch || [];
639
645
  rules.prefetch.push({
640
646
  source: 'list',
641
- urls: specBuffer.crossOrigin.slice(),
647
+ urls: specBuffer.crossOrigin,
642
648
  referrer_policy: 'no-referrer',
643
649
  requires: ['anonymous-client-ip-when-cross-origin']
644
650
  });
645
- specBuffer.crossOrigin.length = 0;
651
+ specBuffer.crossOrigin = [];
646
652
  }
647
653
 
648
654
  // Если нет правил — выходим
@@ -657,7 +663,7 @@
657
663
  head.removeChild(s);
658
664
  }
659
665
 
660
- function preloadLink(url, key) {
666
+ function preloadLink(url, key, isCrossOrigin) {
661
667
  var head = document.head;
662
668
  // v1.0.10: если head недоступен, откатываем ключ
663
669
  if (!head) { preloaded.delete(key); return }
@@ -671,13 +677,10 @@
671
677
  try { l.fetchPriority = 'low'; } catch (e) {}
672
678
 
673
679
  // v1.0.11: для cross-origin: referrerPolicy + crossOrigin
674
- try {
675
- var u = new URL(url, location.href);
676
- if (u.origin !== location.origin) {
677
- l.referrerPolicy = 'no-referrer';
678
- l.crossOrigin = 'anonymous'; // не отправлять cookies на внешние домены
679
- }
680
- } catch (e) {}
680
+ if (isCrossOrigin) {
681
+ l.referrerPolicy = 'no-referrer';
682
+ l.crossOrigin = 'anonymous'; // не отправлять cookies на внешние домены
683
+ }
681
684
 
682
685
  // v1.0.13: safety timeout — предохранитель если onload/onerror не сработают
683
686
  // (экзотические браузеры, сетевые ошибки без событий)
@@ -701,7 +704,7 @@
701
704
  head.appendChild(l);
702
705
  }
703
706
 
704
- function preloadFetch(url, key) {
707
+ function preloadFetch(url, key, isCrossOrigin) {
705
708
  // v1.0.10: если fetch недоступен, откатываем ключ
706
709
  if (typeof fetch !== 'function') { preloaded.delete(key); return }
707
710
 
@@ -734,12 +737,6 @@
734
737
  }, 5000);
735
738
  }
736
739
 
737
- // v1.0.13: определяем cross-origin для корректных настроек fetch
738
- var isCrossOrigin = false;
739
- try {
740
- isCrossOrigin = new URL(url, location.href).origin !== location.origin;
741
- } catch (e) {}
742
-
743
740
  var opts = {
744
741
  method: 'GET',
745
742
  cache: 'force-cache',
@@ -809,30 +806,36 @@
809
806
 
810
807
  // Mutation Observer
811
808
  var mutObserver = null;
812
- var mutTimer = null;
813
809
 
814
810
  function startMutationObserver() {
815
811
  // v1.0.11: защита от вызова после destroy()
816
812
  if (disabled) return
817
813
  if (mutObserver) return
818
814
  mutObserver = new MutationObserver(function (muts) {
819
- // v1.0.13: оптимизация — обычный цикл вместо Array.from + querySelector вместо querySelectorAll
820
- var hasLinks = false;
821
- 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++) {
822
819
  var nodes = muts[i].addedNodes;
823
820
  for (var j = 0; j < nodes.length; j++) {
824
821
  var n = nodes[j];
825
- if (n.nodeType === 1) {
826
- if (n.tagName === 'A' || (n.querySelector && n.querySelector('a'))) {
827
- hasLinks = true;
828
- break outer
829
- }
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]);
830
828
  }
831
829
  }
832
830
  }
833
- if (hasLinks && vpObserver) {
834
- clearTimeout(mutTimer);
835
- 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
+ });
836
839
  }
837
840
  });
838
841
  mutObserver.observe(document.body, { childList: true, subtree: true });
@@ -893,6 +896,11 @@
893
896
  document.removeEventListener('mouseover', onMouseOver, opts);
894
897
  document.removeEventListener('mousedown', onMouseDown, opts);
895
898
 
899
+ // v1.1.3: снимаем listener сети
900
+ if (conn && typeof conn.removeEventListener === 'function') {
901
+ conn.removeEventListener('change', onConnectionChange);
902
+ }
903
+
896
904
  // v1.0.11: снимаем слушатели навигации
897
905
  window.removeEventListener('popstate', updateCurrentKey);
898
906
  window.removeEventListener('hashchange', updateCurrentKey);
@@ -912,7 +920,7 @@
912
920
  // Публичный API
913
921
  var api = {
914
922
  __prefetchRu: true,
915
- version: '1.1.1',
923
+ version: '1.1.3',
916
924
  preload: function (url) {
917
925
  // v1.0.11: валидация URL + прогон через canPreload() (консистентность с авто-режимом)
918
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,19 +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) {
552
+ // v1.1.3: isCrossOrigin передаётся через цепочку вызовов (без повторного new URL())
553
+ function doPreload(requestUrl, key, mode, isCrossOrigin) {
545
554
  if (mode !== 'none') {
546
555
  // v1.0.11: try/catch для preloadSpec — при строгом CSP/Trusted Types может выбросить исключение
547
556
  var specOk = false
548
557
  try {
549
- preloadSpec(requestUrl, mode)
558
+ preloadSpec(requestUrl, mode, isCrossOrigin)
550
559
  specOk = true
551
560
  } catch (e) {
552
561
  // Ошибка в Speculation Rules — fallback обязателен
@@ -555,31 +564,26 @@ export function createPrefetchCore(options) {
555
564
  // v1.0.11: fallback только если явно включён ИЛИ если SpecRules не удался
556
565
  // По умолчанию fallback отключён для избежания двойного трафика
557
566
  if (specRulesFallback || !specOk) {
558
- if (isIOS || !supportsLinkPrefetch) preloadFetch(requestUrl, key)
559
- else preloadLink(requestUrl, key)
567
+ // v1.1.1: cross-origin всегда через fetch (no-cors), <link> требует CORS headers
568
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin)
569
+ else preloadLink(requestUrl, key, isCrossOrigin)
560
570
  }
561
571
  return
562
572
  }
563
573
 
564
- if (isIOS || !supportsLinkPrefetch) preloadFetch(requestUrl, key)
565
- else preloadLink(requestUrl, key)
574
+ // v1.1.1: cross-origin всегда через fetch (no-cors), <link crossorigin=anonymous> требует CORS
575
+ if (isIOS || !supportsLinkPrefetch || isCrossOrigin) preloadFetch(requestUrl, key, isCrossOrigin)
576
+ else preloadLink(requestUrl, key, isCrossOrigin)
566
577
  }
567
578
 
568
579
  function processQueue() {
569
580
  while (queue.length > 0 && inFlight < maxInFlight) {
570
581
  var item = queue.shift()
571
- doPreload(item.url, item.key, item.mode)
582
+ doPreload(item.url, item.key, item.mode, item.crossOrigin)
572
583
  }
573
584
  }
574
585
 
575
- function preloadSpec(url, mode) {
576
- // v1.0.13: для cross-origin проверяем и модифицируем правила
577
- var isCrossOrigin = false
578
- try {
579
- var u = new URL(url, location.href)
580
- isCrossOrigin = u.origin !== location.origin
581
- } catch (e) {}
582
-
586
+ function preloadSpec(url, mode, isCrossOrigin) {
583
587
  // v1.0.13: для cross-origin никогда не делаем prerender (только prefetch)
584
588
  if (isCrossOrigin && mode === 'prerender') {
585
589
  mode = 'prefetch'
@@ -613,18 +617,19 @@ export function createPrefetchCore(options) {
613
617
 
614
618
  var rules = {}
615
619
 
620
+ // v1.1.3: передаём массив напрямую и создаём новый (без лишнего .slice())
616
621
  // Same-origin prefetch
617
622
  if (specBuffer.prefetch.length > 0) {
618
623
  rules.prefetch = rules.prefetch || []
619
- rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch.slice() })
620
- specBuffer.prefetch.length = 0
624
+ rules.prefetch.push({ source: 'list', urls: specBuffer.prefetch })
625
+ specBuffer.prefetch = []
621
626
  }
622
627
 
623
628
  // Same-origin prerender
624
629
  if (specBuffer.prerender.length > 0) {
625
630
  rules.prerender = rules.prerender || []
626
- rules.prerender.push({ source: 'list', urls: specBuffer.prerender.slice() })
627
- specBuffer.prerender.length = 0
631
+ rules.prerender.push({ source: 'list', urls: specBuffer.prerender })
632
+ specBuffer.prerender = []
628
633
  }
629
634
 
630
635
  // Cross-origin (только prefetch, с privacy requirements)
@@ -632,11 +637,11 @@ export function createPrefetchCore(options) {
632
637
  rules.prefetch = rules.prefetch || []
633
638
  rules.prefetch.push({
634
639
  source: 'list',
635
- urls: specBuffer.crossOrigin.slice(),
640
+ urls: specBuffer.crossOrigin,
636
641
  referrer_policy: 'no-referrer',
637
642
  requires: ['anonymous-client-ip-when-cross-origin']
638
643
  })
639
- specBuffer.crossOrigin.length = 0
644
+ specBuffer.crossOrigin = []
640
645
  }
641
646
 
642
647
  // Если нет правил — выходим
@@ -651,7 +656,7 @@ export function createPrefetchCore(options) {
651
656
  head.removeChild(s)
652
657
  }
653
658
 
654
- function preloadLink(url, key) {
659
+ function preloadLink(url, key, isCrossOrigin) {
655
660
  var head = document.head
656
661
  // v1.0.10: если head недоступен, откатываем ключ
657
662
  if (!head) { preloaded.delete(key); return }
@@ -665,13 +670,10 @@ export function createPrefetchCore(options) {
665
670
  try { l.fetchPriority = 'low' } catch (e) {}
666
671
 
667
672
  // v1.0.11: для cross-origin: referrerPolicy + crossOrigin
668
- try {
669
- var u = new URL(url, location.href)
670
- if (u.origin !== location.origin) {
671
- l.referrerPolicy = 'no-referrer'
672
- l.crossOrigin = 'anonymous' // не отправлять cookies на внешние домены
673
- }
674
- } catch (e) {}
673
+ if (isCrossOrigin) {
674
+ l.referrerPolicy = 'no-referrer'
675
+ l.crossOrigin = 'anonymous' // не отправлять cookies на внешние домены
676
+ }
675
677
 
676
678
  // v1.0.13: safety timeout — предохранитель если onload/onerror не сработают
677
679
  // (экзотические браузеры, сетевые ошибки без событий)
@@ -695,7 +697,7 @@ export function createPrefetchCore(options) {
695
697
  head.appendChild(l)
696
698
  }
697
699
 
698
- function preloadFetch(url, key) {
700
+ function preloadFetch(url, key, isCrossOrigin) {
699
701
  // v1.0.10: если fetch недоступен, откатываем ключ
700
702
  if (typeof fetch !== 'function') { preloaded.delete(key); return }
701
703
 
@@ -728,12 +730,6 @@ export function createPrefetchCore(options) {
728
730
  }, 5000)
729
731
  }
730
732
 
731
- // v1.0.13: определяем cross-origin для корректных настроек fetch
732
- var isCrossOrigin = false
733
- try {
734
- isCrossOrigin = new URL(url, location.href).origin !== location.origin
735
- } catch (e) {}
736
-
737
733
  var opts = {
738
734
  method: 'GET',
739
735
  cache: 'force-cache',
@@ -803,30 +799,36 @@ export function createPrefetchCore(options) {
803
799
 
804
800
  // Mutation Observer
805
801
  var mutObserver = null
806
- var mutTimer = null
807
802
 
808
803
  function startMutationObserver() {
809
804
  // v1.0.11: защита от вызова после destroy()
810
805
  if (disabled) return
811
806
  if (mutObserver) return
812
807
  mutObserver = new MutationObserver(function (muts) {
813
- // v1.0.13: оптимизация — обычный цикл вместо Array.from + querySelector вместо querySelectorAll
814
- var hasLinks = false
815
- 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++) {
816
812
  var nodes = muts[i].addedNodes
817
813
  for (var j = 0; j < nodes.length; j++) {
818
814
  var n = nodes[j]
819
- if (n.nodeType === 1) {
820
- if (n.tagName === 'A' || (n.querySelector && n.querySelector('a'))) {
821
- hasLinks = true
822
- break outer
823
- }
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])
824
821
  }
825
822
  }
826
823
  }
827
- if (hasLinks && vpObserver) {
828
- clearTimeout(mutTimer)
829
- 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
+ })
830
832
  }
831
833
  })
832
834
  mutObserver.observe(document.body, { childList: true, subtree: true })
@@ -887,6 +889,11 @@ export function createPrefetchCore(options) {
887
889
  document.removeEventListener('mouseover', onMouseOver, opts)
888
890
  document.removeEventListener('mousedown', onMouseDown, opts)
889
891
 
892
+ // v1.1.3: снимаем listener сети
893
+ if (conn && typeof conn.removeEventListener === 'function') {
894
+ conn.removeEventListener('change', onConnectionChange)
895
+ }
896
+
890
897
  // v1.0.11: снимаем слушатели навигации
891
898
  window.removeEventListener('popstate', updateCurrentKey)
892
899
  window.removeEventListener('hashchange', updateCurrentKey)