Package
@effector/router (core)
What happened?
Один нативный back() при активном history.block (дефолтный historyAdapter) уходит в бесконечный цикл и вешает приложение. Никакой навигации со стороны приложения при этом не происходит.
Механизм: retryNativeFx (transition.retry(), dist ~index.js:724) переприменяет удержанный POP сырым history.go, не обёрнутым в runWithoutBlocking. Активный blocker (subscribeHistoryFx, ~index.js:969) перехватывает этот же retry-go как новую нативную навигацию → откатывает его → снова retry → бесконечно.
Ожидается: retry() должен обходить blocker (как runWithoutBlocking для push/replace), либо blocker должен приостанавливаться на время retry. Один back() должен дать один POP.
Фактически: бесконечный go(-1)/go(1) пинг-понг между двумя локациями; history.length стабилен; push/replace = 0 (навигация приложения не участвует).
Связано с #109 (там потеря POP при конкурирующем replace) — вероятно, это более фундаментальная причина: конкурирующая навигация не нужна.
Logs / screenshots
goNeg = 34, goPos = 35, POPSTATE = 69, push = 0, replace = 0 // anyPushOrReplace = false; растёт ~47 popstate/сек
Reproduction
Один self-contained файл (зависимости с CDN), запуск в реальном браузере:
<!doctype html><html><head><meta charset=utf-8><title>@effector/router 1.2.0 back() re-entrancy</title></head><body>
<pre id=out>running…</pre>
<script type=module>
import { createRouter, createRoute, historyAdapter } from 'https://esm.sh/@effector/router@1.2.0';
import { createBrowserHistory } from 'https://esm.sh/history@5.3.0';
const out=document.getElementById('out');
const c={push:0,replace:0,goNeg:0,goPos:0,pop:0}; const log=[];
const H=window.history, _p=H.pushState,_r=H.replaceState,_g=H.go,_b=H.back;
H.pushState=function(...a){c.push++;if(log.length<400)log.push('PUSH '+a[2]);return _p.apply(this,a);};
H.replaceState=function(...a){c.replace++;if(log.length<400)log.push('REPLACE '+a[2]);return _r.apply(this,a);};
H.go=function(d){d<0?c.goNeg++:d>0&&c.goPos++;if(log.length<400)log.push('GO('+d+') @'+location.pathname);return _g.call(this,d);};
H.back=function(){if(log.length<400)log.push('history.back() @'+location.pathname);return _b.call(this);};
window.addEventListener('popstate',()=>{c.pop++;if(log.length<400)log.push('POPSTATE @'+location.pathname);});
const history=createBrowserHistory();
const routeA=createRoute({path:'/a'}), routeB=createRoute({path:'/b'});
const router=createRouter({base:'',routes:[routeA,routeB]});
router.setHistory(historyAdapter(history));
const s=ms=>new Promise(r=>setTimeout(r,ms));
(async()=>{ await s(150); routeA.open(); await s(150); routeB.open(); await s(250);
c.push=c.replace=c.goNeg=c.goPos=c.pop=0; log.length=0;
window.history.back(); // единственный триггер, дальше никакой навигации
await s(1500);
out.textContent=JSON.stringify({afterPath:location.pathname,counts:{...c},anyPushOrReplace:c.push>0||c.replace>0,logSample:log.slice(0,26)},null,2);
})();
</script></body></html>
Запуск: python3 -m http.server 8099 --bind 127.0.0.1 → открыть http://127.0.0.1:8099/.
Шаги: один createRouter + createBrowserHistory + дефолтный historyAdapter, роуты /a,/b; собрать /a → /b через route.open; затем один window.history.back() — больше ничего.
Environment
@effector/router: 1.2.0
history: 5.3.0
- Браузер: Chromium (реальный)
createMemoryHistory этот дефект НЕ ловит: там back синхронный, нет popstate-revert.
Logs / screenshots
Лог одного цикла:
history.back() @/b
POPSTATE @/a // back закоммитился в /a
GO(1) @/a // blocker: revert -> /b
POPSTATE @/b
GO(-1) @/b // retryNativeFx: transition.retry() -> /a
POPSTATE @/a
GO(1) @/a ... GO(-1) @/b ... // бесконечно
Package
@effector/router (core)
What happened?
Один нативный
back()при активномhistory.block(дефолтныйhistoryAdapter) уходит в бесконечный цикл и вешает приложение. Никакой навигации со стороны приложения при этом не происходит.Механизм:
retryNativeFx(transition.retry(), dist~index.js:724) переприменяет удержанный POP сырымhistory.go, не обёрнутым вrunWithoutBlocking. Активный blocker (subscribeHistoryFx,~index.js:969) перехватывает этот же retry-goкак новую нативную навигацию → откатывает его → сноваretry→ бесконечно.Ожидается:
retry()должен обходить blocker (какrunWithoutBlockingдляpush/replace), либо blocker должен приостанавливаться на время retry. Одинback()должен дать одинPOP.Фактически: бесконечный
go(-1)/go(1)пинг-понг между двумя локациями;history.lengthстабилен;push/replace = 0(навигация приложения не участвует).Связано с #109 (там потеря POP при конкурирующем
replace) — вероятно, это более фундаментальная причина: конкурирующая навигация не нужна.Logs / screenshots
Reproduction
Один self-contained файл (зависимости с CDN), запуск в реальном браузере:
Запуск:
python3 -m http.server 8099 --bind 127.0.0.1→ открытьhttp://127.0.0.1:8099/.Шаги: один
createRouter+createBrowserHistory+ дефолтныйhistoryAdapter, роуты/a,/b; собрать/a → /bчерезroute.open; затем одинwindow.history.back()— больше ничего.Environment
@effector/router:1.2.0history:5.3.0createMemoryHistoryэтот дефект НЕ ловит: тамbackсинхронный, нет popstate-revert.Logs / screenshots
Лог одного цикла: