2026-10-13 16:58:15 +02:00
'use strict' ;
// Native history only: pages and agents cannot supply a navigation target.
2026-10-13 18:48:40 +02:00
async function performHistory ({ contents , action , allowed , current , signal , index , getSnapshot , historyBinding , timeoutMs = 10000 } = {}) {
if ( ! contents ||! [ 'back' , 'forward' , 'reload' , 'index' ]. includes ( action ) || typeof allowed !== 'function' || typeof current !== 'function' ||! Number . isFinite ( timeoutMs ) || timeoutMs < 1 || timeoutMs > 60000 || action === 'index' && ( ! Number . isSafeInteger ( index ) || index < 0 || typeof getSnapshot !== 'function' || typeof historyBinding !== 'string' )) throw Error ( 'Invalid history operation' );
2026-10-13 16:58:15 +02:00
const guard = async ( phase = 'before' )=>{ if ( signal ? . aborted ) throw Error ( 'History operation cancelled' ); if ( contents . isDestroyed ? .()) throw Error ( 'History contents unavailable' ); if ( await current ( phase ) !== true ) throw Error ( 'History context changed' ); if ( signal ? . aborted ) throw Error ( 'History operation cancelled' );};
2026-10-13 18:48:40 +02:00
const target = ()=> action === 'reload' ? contents . getURL () : contents . navigationHistory . getEntryAtIndex ( action === 'index' ? index : contents . navigationHistory . getActiveIndex () + ( action === 'back' ?- 1 : 1 )) ? . url ;
2026-10-13 16:58:15 +02:00
await guard (); const url = target ();
if ( ! url ) return { completed : false , url : contents . getURL ()};
let parsed ; try { parsed = new URL ( url );} catch { throw Error ( 'Invalid history destination' );}
if ( ! [ 'http:' , 'https:' ]. includes ( parsed . protocol ) || parsed . username || parsed . password ||! await allowed ( url )) throw Error ( 'History destination denied' );
await guard (); if ( target () !== url ) throw Error ( 'History destination changed' );
2026-10-13 18:48:40 +02:00
if ( action === 'index' && index === contents . navigationHistory . getActiveIndex ()){
if ( JSON . stringify ( getSnapshot ()) !== historyBinding || contents . getURL () !== url ) throw Error ( 'History snapshot changed' ); return { completed : true , url };
}
2026-10-13 16:58:15 +02:00
return new Promise (( resolve , reject )=>{
let settled = false , dispatched = false , checking = false ;
const cleanup = ()=>{ clearTimeout ( timer ); signal ? . removeEventListener ( 'abort' , abort ); for ( const [ name , fn ] of listeners ) contents . removeListener ( name , fn );};
const fail = error =>{ if ( settled ) return ; settled = true ; cleanup (); if ( dispatched &&! contents . isDestroyed ? .()) try { contents . stop ();} catch {} reject ( error );};
const complete = async ()=>{ if ( ! dispatched || settled || checking ) return ; checking = true ; try { await guard ( 'after' ); if ( contents . getURL () !== url ) throw Error ( 'History destination changed' ); if ( ! await allowed ( url )) throw Error ( 'History destination denied' ); await guard ( 'after' ); if ( contents . getURL () !== url ) throw Error ( 'History destination changed' ); if ( settled ) return ; settled = true ; cleanup (); resolve ({ completed : true , url });} catch ( error ){ fail ( error );} finally { checking = false ;}};
const inPage = ( _event , eventUrl , isMainFrame )=>{ if ( isMainFrame === true ){ if ( eventUrl !== url ) fail ( Error ( 'History destination changed' )); else void complete ();}};
const loadFailed = ( _event , _code , _description , _url , isMainFrame )=>{ if ( isMainFrame === true ) fail ( Error ( 'History navigation failed' ));};
const abort = ()=> fail ( Error ( 'History operation cancelled' ));
const listeners = [[ 'did-finish-load' , complete ],[ 'did-navigate-in-page' , inPage ],[ 'did-fail-load' , loadFailed ],[ 'destroyed' ,()=> fail ( Error ( 'History contents unavailable' ))]];
const timer = setTimeout (()=> fail ( Error ( 'History operation timed out' )), timeoutMs );
for ( const [ name , fn ] of listeners ) contents . on ( name , fn ); signal ? . addEventListener ( 'abort' , abort ,{ once : true });
if ( signal ? . aborted ){ abort (); return ;}
2026-10-13 18:48:40 +02:00
try { if ( action === 'index' && JSON . stringify ( getSnapshot ()) !== historyBinding ) throw Error ( 'History snapshot changed' ); if ( target () !== url ) throw Error ( 'History destination changed' ); dispatched = true ; if ( action === 'reload' ) contents . reload (); else contents . navigationHistory . goToIndex ( action === 'index' ? index : contents . navigationHistory . getActiveIndex () + ( action === 'back' ?- 1 : 1 ));} catch ( error ){ fail ( error );}
2026-10-13 16:58:15 +02:00
});
}
module . exports = { performHistory };