Files

56 lines
4.5 KiB
JavaScript

'use strict';
const {randomUUID}=require('node:crypto');
// Native ownership only: renderer tab labels never own or replace a WebContents.
function createTabRegistry({createView,attach=()=>{},detach=()=>{},dispose=()=>{},maxTabs=32}={}) {
if(typeof createView!=='function'||![attach,detach,dispose].every(fn=>typeof fn==='function')||!Number.isSafeInteger(maxTabs)||maxTabs<1||maxTabs>128)throw TypeError('Invalid native tab registry');
const tabs=new Map(),byContents=new Map();let activeId=null,activation=0,policyVersion=0,paused=true,closed=false;
const requireTab=id=>{const tab=tabs.get(id);if(!tab||tab.contents.isDestroyed())throw Error('Unknown native tab');return tab;};
const invalidate=tab=>{tab.navigationId++;tab.security.visualApproval=null;tab.security.visualToken='';tab.security.visualRevision=-1;tab.security.visualScanner?.reset();};
function create({group=''}={}) {
if(closed||tabs.size>=maxTabs)throw Error('Native tab capacity reached');
if(typeof group!=='string'||group.length>100)throw TypeError('Invalid tab group');
const view=createView(),contents=view?.webContents;
if(!contents||!Number.isSafeInteger(contents.id)||contents.isDestroyed()||byContents.has(contents.id))throw Error('Invalid native tab view');
const tab={id:randomUUID(),view,group,navigationId:0,title:'New Tab',color:'#edf1f6',startPage:true,security:{visualApproval:null,visualToken:'',visualRevision:-1,visualPending:null,visualBlocked:null,contentBlocked:null,visualScanner:null}};
Object.defineProperty(tab,'contents',{value:contents,enumerable:false,writable:false,configurable:false});
view.setVisible(false);tabs.set(tab.id,tab);byContents.set(contents.id,tab);
if(!activeId)activate(tab.id);return tab;
}
function activate(id) {
const next=requireTab(id);if(id===activeId)return next;
const previous=tabs.get(activeId);if(previous){if(!previous.contents.isDestroyed())previous.view.setVisible(false);detach(previous.view);}
activeId=id;activation++;attach(next.view);next.view.setVisible(false);return next;
}
function remove(id) {
const tab=tabs.get(id);if(!tab)throw Error('Unknown native tab');const wasActive=id===activeId;invalidate(tab);if(!tab.contents.isDestroyed())tab.view.setVisible(false);
tabs.delete(id);byContents.delete(tab.contents.id);
if(wasActive){if(!tab.contents.isDestroyed())detach(tab.view);activeId=null;activation++;}
try{dispose(tab);}finally{if(!tab.contents.isDestroyed())tab.contents.close({waitForBeforeUnload:false});}
if(wasActive&&!closed){const next=[...tabs.values()].find(item=>!item.contents.isDestroyed());if(next)activate(next.id);}
}
function context(id=activeId,{foreground=true}={}) {
const tab=requireTab(id);return Object.freeze({tabId:tab.id,webContents:tab.contents,navigationId:tab.navigationId,url:tab.contents.getURL(),policyVersion,activation:foreground?activation:null,foreground,paused});
}
function current(token,{expectedURL=token?.url}={}) {
const tab=token&&tabs.get(token.tabId);
return !!tab&&!paused&&!token.paused&&!tab.contents.isDestroyed()&&tab.contents===token.webContents&&tab.navigationId===token.navigationId&&policyVersion===token.policyVersion&&tab.contents.getURL()===expectedURL&&(!token.foreground||(activeId===tab.id&&activation===token.activation));
}
function quarantine() {
paused=true;activation++;
for(const tab of tabs.values()){invalidate(tab);if(!tab.contents.isDestroyed()){tab.contents.stop();tab.view.setVisible(false);tab.security.developerToolsGuard?.close();tab.contents.closeDevTools?.();}}
}
function resume(version) {
if(!Number.isSafeInteger(version)||version<1||version<policyVersion)throw Error('Invalid tab policy generation');
if(version!==policyVersion)for(const tab of tabs.values())invalidate(tab);
policyVersion=version;paused=false;
}
return {create,activate,remove,context,current,quarantine,resume,
get:requireTab,active:()=>activeId?requireTab(activeId):null,
fromContents:id=>byContents.get(id)||null,all:()=>[...tabs.values()],
navigation:id=>invalidate(requireTab(id)),
close:()=>{closed=true;quarantine();for(const id of [...tabs.keys()])remove(id);},
summary:()=>({activeTabId:activeId,tabs:[...tabs.values()].map(tab=>({id:tab.id,group:tab.group,title:tab.title,url:tab.contents.getURL(),startPage:tab.startPage,color:tab.color,canGoBack:tab.contents.navigationHistory?.canGoBack()||false,canGoForward:tab.contents.navigationHistory?.canGoForward()||false}))})};
}
module.exports={createTabRegistry};