'use strict'; // Native history only: pages and agents cannot supply a navigation target. 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'); 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');}; const target=()=>action==='reload'?contents.getURL():contents.navigationHistory.getEntryAtIndex(action==='index'?index:contents.navigationHistory.getActiveIndex()+(action==='back'?-1:1))?.url; 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'); if(action==='index'&&index===contents.navigationHistory.getActiveIndex()){ if(JSON.stringify(getSnapshot())!==historyBinding||contents.getURL()!==url)throw Error('History snapshot changed');return {completed:true,url}; } 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;} 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);} }); } module.exports={performHistory};