feat(bridge): deep-inspect HTTP endpoint + recursive DOM inspector #task-264
This commit is contained in:
@@ -5,3 +5,4 @@
|
||||
| 001 | 08:00~09:17 | 승인 실행 메커니즘 연구 + step-type별 VS Code 명령 분기 구현 | included in 002 | 🔧 |
|
||||
| 002 | 09:21~15:07 | SDK 승인 명령 미등록 확정 + Renderer DOM Click 구현 | `4497e96` | 🔧 |
|
||||
| 003 | 15:32~17:59 | Renderer v3 deep DOM traversal (iframe/webview/shadow 관통) | `32bf5ae` | 🔧 |
|
||||
| 004 | 18:08~18:23 | Deep inspect HTTP endpoint (/deep-inspect) + 렌더러 재귀 인스펙터 | | 🔧 |
|
||||
|
||||
@@ -411,6 +411,10 @@ let observerHttpServer = null;
|
||||
const pendingResponses = new Map();
|
||||
// Click trigger: extension sets this, renderer polls and clicks button
|
||||
let clickTrigger = null;
|
||||
// Deep inspect trigger: curl sets this, renderer picks it up and POSTs results back
|
||||
let deepInspectRequested = false;
|
||||
let deepInspectResult = null;
|
||||
let deepInspectWaiters = [];
|
||||
/** Derive a deterministic port from project name (range 10000-60000) */
|
||||
function getDeterministicPort(name) {
|
||||
let hash = 0;
|
||||
@@ -507,6 +511,65 @@ function startObserverHttpBridge() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
// GET /deep-inspect — trigger deep DOM inspection from renderer
|
||||
if (req.method === 'GET' && url.pathname === '/deep-inspect') {
|
||||
deepInspectRequested = true;
|
||||
logToFile('[HTTP] deep-inspect triggered — waiting for renderer result...');
|
||||
// Wait up to 10s for renderer to POST result
|
||||
const timeout = setTimeout(() => {
|
||||
deepInspectWaiters = deepInspectWaiters.filter(w => w !== waiter);
|
||||
if (deepInspectResult) {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(deepInspectResult));
|
||||
}
|
||||
else {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ status: 'timeout', message: 'Renderer did not respond in 10s. Is the v3 script loaded?' }));
|
||||
}
|
||||
}, 10000);
|
||||
const waiter = (data) => {
|
||||
clearTimeout(timeout);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(data));
|
||||
};
|
||||
deepInspectWaiters.push(waiter);
|
||||
return;
|
||||
}
|
||||
// GET /deep-inspect-trigger — renderer polls this
|
||||
if (req.method === 'GET' && url.pathname === '/deep-inspect-trigger') {
|
||||
const requested = deepInspectRequested;
|
||||
deepInspectRequested = false;
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ inspect: requested }));
|
||||
return;
|
||||
}
|
||||
// POST /deep-inspect-result — renderer posts inspection results here
|
||||
if (req.method === 'POST' && url.pathname === '/deep-inspect-result') {
|
||||
let body = '';
|
||||
req.on('data', (c) => body += c);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
deepInspectResult = data;
|
||||
logToFile(`[HTTP] deep-inspect result received (${body.length} bytes)`);
|
||||
// Write to file for reference
|
||||
const inspectFile = path.join(bridgePath, 'deep-inspect-result.json');
|
||||
fs.writeFileSync(inspectFile, JSON.stringify(data, null, 2));
|
||||
// Notify waiters
|
||||
const waiters = [...deepInspectWaiters];
|
||||
deepInspectWaiters = [];
|
||||
waiters.forEach(w => w(data));
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ ok: true }));
|
||||
}
|
||||
catch (e) {
|
||||
logToFile(`[HTTP] deep-inspect-result parse error: ${e.message}`);
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
// GET /ping — health check
|
||||
if (url.pathname === '/ping') {
|
||||
res.writeHead(200);
|
||||
@@ -642,54 +705,132 @@ function generateApprovalObserverScript(_port) {
|
||||
}catch(e){}
|
||||
}
|
||||
|
||||
// ── DOM Structure Dump (one-time on startup) ──
|
||||
function dumpDOMStructure(){
|
||||
if(_domDumped)return;
|
||||
_domDumped=true;
|
||||
// ── Deep DOM Inspector (recursive, POSTs results to bridge) ──
|
||||
function runDeepInspect(){
|
||||
var result={timestamp:new Date().toISOString(),windowURL:window.location.href,windowOrigin:window.location.origin,windowProtocol:window.location.protocol,framesCount:window.frames.length,nodes:[]};
|
||||
log('DEEP-INSPECT: starting recursive DOM analysis...');
|
||||
|
||||
function inspectDoc(doc,depth,label){
|
||||
var node={label:label,depth:depth,accessible:true,url:'',buttons:[],roleBtns:[],iframes:[],webviews:[],shadowDOMs:0,totalElements:0};
|
||||
if(!doc){node.accessible=false;node.error='null document';result.nodes.push(node);return;}
|
||||
try{node.url=(doc.URL||doc.documentURI||'unknown').substring(0,200);}catch(e){node.url='blocked';}
|
||||
try{node.title=(doc.title||'').substring(0,100);}catch(e){}
|
||||
try{node.readyState=doc.readyState;}catch(e){}
|
||||
|
||||
// CSP
|
||||
try{
|
||||
// Count iframes
|
||||
var iframes=document.querySelectorAll('iframe');
|
||||
log('DOM-DUMP: '+iframes.length+' iframes found');
|
||||
for(var i=0;i<iframes.length;i++){
|
||||
var f=iframes[i];
|
||||
var fInfo=' iframe#'+i+' class="'+f.className.substring(0,50)+'" src="'+(f.src||'').substring(0,80)+'"';
|
||||
var csp=doc.querySelectorAll('meta[http-equiv="Content-Security-Policy"]');
|
||||
if(csp.length>0){node.csp=[];for(var c=0;c<csp.length;c++){node.csp.push((csp[c].content||'').substring(0,200));}}
|
||||
}catch(e){}
|
||||
|
||||
try{
|
||||
var idoc=f.contentDocument;
|
||||
if(idoc){
|
||||
var btns=idoc.querySelectorAll('button');
|
||||
fInfo+=' → ACCESSIBLE ('+btns.length+' buttons)';
|
||||
// Dump first 5 button texts
|
||||
for(var b=0;b<Math.min(5,btns.length);b++){
|
||||
log(' btn['+b+']: "'+((btns[b].textContent||'').trim()).substring(0,40)+'"');
|
||||
var allEls=doc.querySelectorAll('*');
|
||||
node.totalElements=allEls.length;
|
||||
// Buttons
|
||||
var btns=doc.querySelectorAll('button');
|
||||
for(var i=0;i<btns.length;i++){
|
||||
var b=btns[i];
|
||||
var txt=(b.textContent||'').trim().substring(0,80);
|
||||
if(!txt)continue;
|
||||
var cls=(b.className||'').substring(0,60);
|
||||
var disabled=b.disabled;
|
||||
var hidden=b.hidden||false;
|
||||
try{if(!b.offsetParent&&b.style.display!=='fixed')hidden=true;}catch(e){}
|
||||
var aria=b.getAttribute('aria-label')||'';
|
||||
var ttl=b.getAttribute('title')||'';
|
||||
node.buttons.push({text:txt,class:cls,disabled:disabled,hidden:hidden,aria:aria,title:ttl});
|
||||
}
|
||||
// role=button
|
||||
var rbs=doc.querySelectorAll('[role="button"]');
|
||||
for(var r=0;r<rbs.length;r++){
|
||||
if(rbs[r].tagName==='BUTTON')continue;
|
||||
var rtxt=(rbs[r].textContent||'').trim().substring(0,60);
|
||||
node.roleBtns.push({tag:rbs[r].tagName.toLowerCase(),text:rtxt});
|
||||
}
|
||||
}catch(e){
|
||||
fInfo+=' → BLOCKED ('+e.message.substring(0,40)+')';
|
||||
// Shadow DOMs
|
||||
for(var s=0;s<allEls.length;s++){
|
||||
var sr=allEls[s].shadowRoot;
|
||||
if(sr){node.shadowDOMs++;inspectDoc(sr,depth+1,'shadow(<'+allEls[s].tagName.toLowerCase()+' class="'+(allEls[s].className||'').substring(0,30)+'">)');}
|
||||
}
|
||||
log(fInfo);
|
||||
// Iframes
|
||||
var ifs=doc.querySelectorAll('iframe');
|
||||
for(var fi=0;fi<ifs.length;fi++){
|
||||
var f=ifs[fi];
|
||||
var finfo={index:fi,class:(f.className||'').substring(0,60),src:(f.src||'').substring(0,150),id:f.id||'',sandbox:f.getAttribute('sandbox')||'',allow:f.getAttribute('allow')||'',accessible:false,cwExists:false,cwFrames:0};
|
||||
try{
|
||||
var idoc=f.contentDocument||(f.contentWindow&&f.contentWindow.document);
|
||||
if(idoc){finfo.accessible=true;inspectDoc(idoc,depth+1,'iframe#'+fi+'('+finfo.class.substring(0,30)+')');
|
||||
}else{finfo.error='contentDocument=null';}
|
||||
}catch(e){finfo.error=e.message.substring(0,80);}
|
||||
try{var cw=f.contentWindow;if(cw){finfo.cwExists=true;finfo.cwFrames=cw.length;try{finfo.cwLocation=cw.location.href;}catch(e2){finfo.cwLocation='blocked: '+e2.message.substring(0,40);}}}
|
||||
catch(e){}
|
||||
node.iframes.push(finfo);
|
||||
}
|
||||
// Count webview elements
|
||||
// Webviews
|
||||
var wvs=doc.querySelectorAll('webview');
|
||||
for(var wi=0;wi<wvs.length;wi++){
|
||||
var wv=wvs[wi];
|
||||
var winfo={index:wi,src:(wv.src||'').substring(0,150),class:(wv.className||'').substring(0,60),partition:wv.getAttribute('partition')||'',preload:wv.getAttribute('preload')||'',nodeintegration:wv.getAttribute('nodeintegration')||'',webpreferences:wv.getAttribute('webpreferences')||'',hasExecJS:typeof wv.executeJavaScript==='function',contentDocAccessible:false};
|
||||
try{var wdoc=wv.contentDocument;if(wdoc){winfo.contentDocAccessible=true;inspectDoc(wdoc,depth+1,'webview#'+wi+'.contentDocument');}}catch(e){winfo.contentDocError=e.message.substring(0,60);}
|
||||
node.webviews.push(winfo);
|
||||
}
|
||||
}catch(e){node.error=e.message;}
|
||||
result.nodes.push(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
inspectDoc(document,0,'MainDocument');
|
||||
|
||||
// Webview executeJavaScript probe (async)
|
||||
var webviews=document.querySelectorAll('webview');
|
||||
log('DOM-DUMP: '+webviews.length+' <webview> elements');
|
||||
for(var w=0;w<webviews.length;w++){
|
||||
var wInfo=' webview#'+w+' src="'+(webviews[w].src||'').substring(0,80)+'"';
|
||||
var probesPending=webviews.length;
|
||||
result.webviewProbes=[];
|
||||
if(probesPending===0)postResults();
|
||||
for(var pw=0;pw<webviews.length;pw++){
|
||||
(function(wv,idx){
|
||||
if(typeof wv.executeJavaScript!=='function'){result.webviewProbes.push({index:idx,error:'executeJavaScript not available'});probesPending--;if(probesPending<=0)postResults();return;}
|
||||
try{
|
||||
var wdoc=webviews[w].contentDocument;
|
||||
if(wdoc)wInfo+=' → ACCESSIBLE';
|
||||
else wInfo+=' → null contentDocument';
|
||||
}catch(e){wInfo+=' → BLOCKED ('+e.message.substring(0,40)+')';}
|
||||
log(wInfo);
|
||||
wv.executeJavaScript('(function(){var btns=document.querySelectorAll("button");var allEls=document.querySelectorAll("*");var ifs=document.querySelectorAll("iframe");var wvs=document.querySelectorAll("webview");var btnArr=[];for(var i=0;i<btns.length;i++){var b=btns[i];var txt=(b.textContent||"").trim();var cls=(b.className||"").substring(0,50);var dis=b.disabled;var hid=b.hidden||!b.offsetParent;btnArr.push({text:txt.substring(0,60),class:cls,disabled:dis,hidden:hid,aria:b.getAttribute("aria-label")||"",title:b.getAttribute("title")||""});}var rbs=document.querySelectorAll("[role=button]");var rbArr=[];for(var j=0;j<rbs.length;j++){if(rbs[j].tagName!=="BUTTON")rbArr.push({tag:rbs[j].tagName.toLowerCase(),text:(rbs[j].textContent||"").trim().substring(0,40)});}var sc=0;for(var k=0;k<allEls.length;k++){if(allEls[k].shadowRoot)sc++;}return JSON.stringify({url:document.URL,title:document.title,totalElements:allEls.length,buttons:btnArr,roleBtns:rbArr,iframes:ifs.length,webviews:wvs.length,shadowDOMs:sc});})()')
|
||||
.then(function(r){
|
||||
try{var d=JSON.parse(r);result.webviewProbes.push({index:idx,success:true,data:d});log('DEEP-INSPECT: webview#'+idx+' probe OK: '+d.buttons.length+' buttons, '+d.totalElements+' elements');}catch(e){result.webviewProbes.push({index:idx,parseError:e.message,raw:r});}
|
||||
probesPending--;if(probesPending<=0)postResults();
|
||||
})
|
||||
.catch(function(e){
|
||||
result.webviewProbes.push({index:idx,execError:e.message});
|
||||
log('DEEP-INSPECT: webview#'+idx+' execJS error: '+e.message);
|
||||
probesPending--;if(probesPending<=0)postResults();
|
||||
});
|
||||
}catch(e){
|
||||
result.webviewProbes.push({index:idx,callError:e.message});
|
||||
probesPending--;if(probesPending<=0)postResults();
|
||||
}
|
||||
// Main doc buttons
|
||||
var mainBtns=document.querySelectorAll('button');
|
||||
log('DOM-DUMP: '+mainBtns.length+' buttons in main document');
|
||||
for(var m=0;m<Math.min(10,mainBtns.length);m++){
|
||||
log(' main-btn['+m+']: "'+((mainBtns[m].textContent||'').trim()).substring(0,50)+'"');
|
||||
})(webviews[pw],pw);
|
||||
}
|
||||
// Report to bridge
|
||||
fetch(BASE+'/ping?dom_dump='+iframes.length+'f_'+webviews.length+'wv_'+mainBtns.length+'btn').catch(function(){});
|
||||
}catch(e){log('DOM-DUMP error: '+e.message);}
|
||||
|
||||
function postResults(){
|
||||
var summary='nodes='+result.nodes.length;
|
||||
var totalBtns=0;for(var n=0;n<result.nodes.length;n++)totalBtns+=result.nodes[n].buttons.length;
|
||||
summary+=' totalButtons='+totalBtns+' webviewProbes='+result.webviewProbes.length;
|
||||
log('DEEP-INSPECT complete: '+summary);
|
||||
// Also log buttons from each node
|
||||
for(var n2=0;n2<result.nodes.length;n2++){
|
||||
var nd=result.nodes[n2];
|
||||
if(nd.buttons.length>0){
|
||||
log(' '+nd.label+': '+nd.buttons.length+' buttons');
|
||||
for(var bi=0;bi<Math.min(15,nd.buttons.length);bi++){
|
||||
log(' ['+bi+'] "'+nd.buttons[bi].text+'"'+(nd.buttons[bi].disabled?' DISABLED':'')+(nd.buttons[bi].hidden?' HIDDEN':''));
|
||||
}
|
||||
}
|
||||
}
|
||||
// POST to bridge
|
||||
fetch(BASE+'/deep-inspect-result',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(result)})
|
||||
.then(function(){log('DEEP-INSPECT results posted to bridge');})
|
||||
.catch(function(e){log('DEEP-INSPECT post error: '+e.message);});
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-dump on startup (3s delay)
|
||||
function dumpDOMStructure(){runDeepInspect();}
|
||||
|
||||
// ── Port Discovery: async fetch()-based (sync XHR blocked in Electron renderer) ──
|
||||
var HARDCODED_PORT=${_port};
|
||||
@@ -975,6 +1116,14 @@ function generateApprovalObserverScript(_port) {
|
||||
// FALLBACK: periodic scan every 3s for any missed mutations
|
||||
setInterval(scheduleScan,3000);
|
||||
|
||||
// ── DEEP-INSPECT POLLING: curl→Bridge→Renderer→Results ──
|
||||
setInterval(function(){
|
||||
if(!_ready||!BASE)return;
|
||||
fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||
if(d.inspect){log('🔍 Deep inspect triggered via HTTP');runDeepInspect();}
|
||||
}).catch(function(){});
|
||||
},2000);
|
||||
|
||||
// ── TRIGGER-CLICK: Extension→Renderer bridge for programmatic button clicks ──
|
||||
// Extension sets clickTrigger via tryApprovalStrategies → renderer polls and clicks
|
||||
// v3: uses deepFindButtons() to traverse iframes, webviews, shadow DOMs
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -385,6 +385,11 @@ const pendingResponses = new Map<string, { approved: boolean } | null>();
|
||||
// Click trigger: extension sets this, renderer polls and clicks button
|
||||
let clickTrigger: { action: 'approve' | 'reject'; timestamp: number } | null = null;
|
||||
|
||||
// Deep inspect trigger: curl sets this, renderer picks it up and POSTs results back
|
||||
let deepInspectRequested = false;
|
||||
let deepInspectResult: any = null;
|
||||
let deepInspectWaiters: Array<(data: any) => void> = [];
|
||||
|
||||
/** Derive a deterministic port from project name (range 10000-60000) */
|
||||
function getDeterministicPort(name: string): number {
|
||||
let hash = 0;
|
||||
@@ -475,6 +480,65 @@ function startObserverHttpBridge(): Promise<number> {
|
||||
return;
|
||||
}
|
||||
|
||||
// GET /deep-inspect — trigger deep DOM inspection from renderer
|
||||
if (req.method === 'GET' && url.pathname === '/deep-inspect') {
|
||||
deepInspectRequested = true;
|
||||
logToFile('[HTTP] deep-inspect triggered — waiting for renderer result...');
|
||||
// Wait up to 10s for renderer to POST result
|
||||
const timeout = setTimeout(() => {
|
||||
deepInspectWaiters = deepInspectWaiters.filter(w => w !== waiter);
|
||||
if (deepInspectResult) {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(deepInspectResult));
|
||||
} else {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ status: 'timeout', message: 'Renderer did not respond in 10s. Is the v3 script loaded?' }));
|
||||
}
|
||||
}, 10000);
|
||||
const waiter = (data: any) => {
|
||||
clearTimeout(timeout);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(data));
|
||||
};
|
||||
deepInspectWaiters.push(waiter);
|
||||
return;
|
||||
}
|
||||
|
||||
// GET /deep-inspect-trigger — renderer polls this
|
||||
if (req.method === 'GET' && url.pathname === '/deep-inspect-trigger') {
|
||||
const requested = deepInspectRequested;
|
||||
deepInspectRequested = false;
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ inspect: requested }));
|
||||
return;
|
||||
}
|
||||
|
||||
// POST /deep-inspect-result — renderer posts inspection results here
|
||||
if (req.method === 'POST' && url.pathname === '/deep-inspect-result') {
|
||||
let body = '';
|
||||
req.on('data', (c: string) => body += c);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
deepInspectResult = data;
|
||||
logToFile(`[HTTP] deep-inspect result received (${body.length} bytes)`);
|
||||
// Write to file for reference
|
||||
const inspectFile = path.join(bridgePath, 'deep-inspect-result.json');
|
||||
fs.writeFileSync(inspectFile, JSON.stringify(data, null, 2));
|
||||
// Notify waiters
|
||||
const waiters = [...deepInspectWaiters];
|
||||
deepInspectWaiters = [];
|
||||
waiters.forEach(w => w(data));
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ ok: true }));
|
||||
} catch (e: any) {
|
||||
logToFile(`[HTTP] deep-inspect-result parse error: ${e.message}`);
|
||||
res.writeHead(400); res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// GET /ping — health check
|
||||
if (url.pathname === '/ping') {
|
||||
res.writeHead(200); res.end('pong');
|
||||
@@ -614,54 +678,132 @@ function generateApprovalObserverScript(_port: number): string {
|
||||
}catch(e){}
|
||||
}
|
||||
|
||||
// ── DOM Structure Dump (one-time on startup) ──
|
||||
function dumpDOMStructure(){
|
||||
if(_domDumped)return;
|
||||
_domDumped=true;
|
||||
// ── Deep DOM Inspector (recursive, POSTs results to bridge) ──
|
||||
function runDeepInspect(){
|
||||
var result={timestamp:new Date().toISOString(),windowURL:window.location.href,windowOrigin:window.location.origin,windowProtocol:window.location.protocol,framesCount:window.frames.length,nodes:[]};
|
||||
log('DEEP-INSPECT: starting recursive DOM analysis...');
|
||||
|
||||
function inspectDoc(doc,depth,label){
|
||||
var node={label:label,depth:depth,accessible:true,url:'',buttons:[],roleBtns:[],iframes:[],webviews:[],shadowDOMs:0,totalElements:0};
|
||||
if(!doc){node.accessible=false;node.error='null document';result.nodes.push(node);return;}
|
||||
try{node.url=(doc.URL||doc.documentURI||'unknown').substring(0,200);}catch(e){node.url='blocked';}
|
||||
try{node.title=(doc.title||'').substring(0,100);}catch(e){}
|
||||
try{node.readyState=doc.readyState;}catch(e){}
|
||||
|
||||
// CSP
|
||||
try{
|
||||
// Count iframes
|
||||
var iframes=document.querySelectorAll('iframe');
|
||||
log('DOM-DUMP: '+iframes.length+' iframes found');
|
||||
for(var i=0;i<iframes.length;i++){
|
||||
var f=iframes[i];
|
||||
var fInfo=' iframe#'+i+' class="'+f.className.substring(0,50)+'" src="'+(f.src||'').substring(0,80)+'"';
|
||||
var csp=doc.querySelectorAll('meta[http-equiv="Content-Security-Policy"]');
|
||||
if(csp.length>0){node.csp=[];for(var c=0;c<csp.length;c++){node.csp.push((csp[c].content||'').substring(0,200));}}
|
||||
}catch(e){}
|
||||
|
||||
try{
|
||||
var idoc=f.contentDocument;
|
||||
if(idoc){
|
||||
var btns=idoc.querySelectorAll('button');
|
||||
fInfo+=' → ACCESSIBLE ('+btns.length+' buttons)';
|
||||
// Dump first 5 button texts
|
||||
for(var b=0;b<Math.min(5,btns.length);b++){
|
||||
log(' btn['+b+']: "'+((btns[b].textContent||'').trim()).substring(0,40)+'"');
|
||||
var allEls=doc.querySelectorAll('*');
|
||||
node.totalElements=allEls.length;
|
||||
// Buttons
|
||||
var btns=doc.querySelectorAll('button');
|
||||
for(var i=0;i<btns.length;i++){
|
||||
var b=btns[i];
|
||||
var txt=(b.textContent||'').trim().substring(0,80);
|
||||
if(!txt)continue;
|
||||
var cls=(b.className||'').substring(0,60);
|
||||
var disabled=b.disabled;
|
||||
var hidden=b.hidden||false;
|
||||
try{if(!b.offsetParent&&b.style.display!=='fixed')hidden=true;}catch(e){}
|
||||
var aria=b.getAttribute('aria-label')||'';
|
||||
var ttl=b.getAttribute('title')||'';
|
||||
node.buttons.push({text:txt,class:cls,disabled:disabled,hidden:hidden,aria:aria,title:ttl});
|
||||
}
|
||||
// role=button
|
||||
var rbs=doc.querySelectorAll('[role="button"]');
|
||||
for(var r=0;r<rbs.length;r++){
|
||||
if(rbs[r].tagName==='BUTTON')continue;
|
||||
var rtxt=(rbs[r].textContent||'').trim().substring(0,60);
|
||||
node.roleBtns.push({tag:rbs[r].tagName.toLowerCase(),text:rtxt});
|
||||
}
|
||||
}catch(e){
|
||||
fInfo+=' → BLOCKED ('+e.message.substring(0,40)+')';
|
||||
// Shadow DOMs
|
||||
for(var s=0;s<allEls.length;s++){
|
||||
var sr=allEls[s].shadowRoot;
|
||||
if(sr){node.shadowDOMs++;inspectDoc(sr,depth+1,'shadow(<'+allEls[s].tagName.toLowerCase()+' class="'+(allEls[s].className||'').substring(0,30)+'">)');}
|
||||
}
|
||||
log(fInfo);
|
||||
// Iframes
|
||||
var ifs=doc.querySelectorAll('iframe');
|
||||
for(var fi=0;fi<ifs.length;fi++){
|
||||
var f=ifs[fi];
|
||||
var finfo={index:fi,class:(f.className||'').substring(0,60),src:(f.src||'').substring(0,150),id:f.id||'',sandbox:f.getAttribute('sandbox')||'',allow:f.getAttribute('allow')||'',accessible:false,cwExists:false,cwFrames:0};
|
||||
try{
|
||||
var idoc=f.contentDocument||(f.contentWindow&&f.contentWindow.document);
|
||||
if(idoc){finfo.accessible=true;inspectDoc(idoc,depth+1,'iframe#'+fi+'('+finfo.class.substring(0,30)+')');
|
||||
}else{finfo.error='contentDocument=null';}
|
||||
}catch(e){finfo.error=e.message.substring(0,80);}
|
||||
try{var cw=f.contentWindow;if(cw){finfo.cwExists=true;finfo.cwFrames=cw.length;try{finfo.cwLocation=cw.location.href;}catch(e2){finfo.cwLocation='blocked: '+e2.message.substring(0,40);}}}
|
||||
catch(e){}
|
||||
node.iframes.push(finfo);
|
||||
}
|
||||
// Count webview elements
|
||||
// Webviews
|
||||
var wvs=doc.querySelectorAll('webview');
|
||||
for(var wi=0;wi<wvs.length;wi++){
|
||||
var wv=wvs[wi];
|
||||
var winfo={index:wi,src:(wv.src||'').substring(0,150),class:(wv.className||'').substring(0,60),partition:wv.getAttribute('partition')||'',preload:wv.getAttribute('preload')||'',nodeintegration:wv.getAttribute('nodeintegration')||'',webpreferences:wv.getAttribute('webpreferences')||'',hasExecJS:typeof wv.executeJavaScript==='function',contentDocAccessible:false};
|
||||
try{var wdoc=wv.contentDocument;if(wdoc){winfo.contentDocAccessible=true;inspectDoc(wdoc,depth+1,'webview#'+wi+'.contentDocument');}}catch(e){winfo.contentDocError=e.message.substring(0,60);}
|
||||
node.webviews.push(winfo);
|
||||
}
|
||||
}catch(e){node.error=e.message;}
|
||||
result.nodes.push(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
inspectDoc(document,0,'MainDocument');
|
||||
|
||||
// Webview executeJavaScript probe (async)
|
||||
var webviews=document.querySelectorAll('webview');
|
||||
log('DOM-DUMP: '+webviews.length+' <webview> elements');
|
||||
for(var w=0;w<webviews.length;w++){
|
||||
var wInfo=' webview#'+w+' src="'+(webviews[w].src||'').substring(0,80)+'"';
|
||||
var probesPending=webviews.length;
|
||||
result.webviewProbes=[];
|
||||
if(probesPending===0)postResults();
|
||||
for(var pw=0;pw<webviews.length;pw++){
|
||||
(function(wv,idx){
|
||||
if(typeof wv.executeJavaScript!=='function'){result.webviewProbes.push({index:idx,error:'executeJavaScript not available'});probesPending--;if(probesPending<=0)postResults();return;}
|
||||
try{
|
||||
var wdoc=webviews[w].contentDocument;
|
||||
if(wdoc)wInfo+=' → ACCESSIBLE';
|
||||
else wInfo+=' → null contentDocument';
|
||||
}catch(e){wInfo+=' → BLOCKED ('+e.message.substring(0,40)+')';}
|
||||
log(wInfo);
|
||||
wv.executeJavaScript('(function(){var btns=document.querySelectorAll("button");var allEls=document.querySelectorAll("*");var ifs=document.querySelectorAll("iframe");var wvs=document.querySelectorAll("webview");var btnArr=[];for(var i=0;i<btns.length;i++){var b=btns[i];var txt=(b.textContent||"").trim();var cls=(b.className||"").substring(0,50);var dis=b.disabled;var hid=b.hidden||!b.offsetParent;btnArr.push({text:txt.substring(0,60),class:cls,disabled:dis,hidden:hid,aria:b.getAttribute("aria-label")||"",title:b.getAttribute("title")||""});}var rbs=document.querySelectorAll("[role=button]");var rbArr=[];for(var j=0;j<rbs.length;j++){if(rbs[j].tagName!=="BUTTON")rbArr.push({tag:rbs[j].tagName.toLowerCase(),text:(rbs[j].textContent||"").trim().substring(0,40)});}var sc=0;for(var k=0;k<allEls.length;k++){if(allEls[k].shadowRoot)sc++;}return JSON.stringify({url:document.URL,title:document.title,totalElements:allEls.length,buttons:btnArr,roleBtns:rbArr,iframes:ifs.length,webviews:wvs.length,shadowDOMs:sc});})()')
|
||||
.then(function(r){
|
||||
try{var d=JSON.parse(r);result.webviewProbes.push({index:idx,success:true,data:d});log('DEEP-INSPECT: webview#'+idx+' probe OK: '+d.buttons.length+' buttons, '+d.totalElements+' elements');}catch(e){result.webviewProbes.push({index:idx,parseError:e.message,raw:r});}
|
||||
probesPending--;if(probesPending<=0)postResults();
|
||||
})
|
||||
.catch(function(e){
|
||||
result.webviewProbes.push({index:idx,execError:e.message});
|
||||
log('DEEP-INSPECT: webview#'+idx+' execJS error: '+e.message);
|
||||
probesPending--;if(probesPending<=0)postResults();
|
||||
});
|
||||
}catch(e){
|
||||
result.webviewProbes.push({index:idx,callError:e.message});
|
||||
probesPending--;if(probesPending<=0)postResults();
|
||||
}
|
||||
// Main doc buttons
|
||||
var mainBtns=document.querySelectorAll('button');
|
||||
log('DOM-DUMP: '+mainBtns.length+' buttons in main document');
|
||||
for(var m=0;m<Math.min(10,mainBtns.length);m++){
|
||||
log(' main-btn['+m+']: "'+((mainBtns[m].textContent||'').trim()).substring(0,50)+'"');
|
||||
})(webviews[pw],pw);
|
||||
}
|
||||
// Report to bridge
|
||||
fetch(BASE+'/ping?dom_dump='+iframes.length+'f_'+webviews.length+'wv_'+mainBtns.length+'btn').catch(function(){});
|
||||
}catch(e){log('DOM-DUMP error: '+e.message);}
|
||||
|
||||
function postResults(){
|
||||
var summary='nodes='+result.nodes.length;
|
||||
var totalBtns=0;for(var n=0;n<result.nodes.length;n++)totalBtns+=result.nodes[n].buttons.length;
|
||||
summary+=' totalButtons='+totalBtns+' webviewProbes='+result.webviewProbes.length;
|
||||
log('DEEP-INSPECT complete: '+summary);
|
||||
// Also log buttons from each node
|
||||
for(var n2=0;n2<result.nodes.length;n2++){
|
||||
var nd=result.nodes[n2];
|
||||
if(nd.buttons.length>0){
|
||||
log(' '+nd.label+': '+nd.buttons.length+' buttons');
|
||||
for(var bi=0;bi<Math.min(15,nd.buttons.length);bi++){
|
||||
log(' ['+bi+'] "'+nd.buttons[bi].text+'"'+(nd.buttons[bi].disabled?' DISABLED':'')+(nd.buttons[bi].hidden?' HIDDEN':''));
|
||||
}
|
||||
}
|
||||
}
|
||||
// POST to bridge
|
||||
fetch(BASE+'/deep-inspect-result',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(result)})
|
||||
.then(function(){log('DEEP-INSPECT results posted to bridge');})
|
||||
.catch(function(e){log('DEEP-INSPECT post error: '+e.message);});
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-dump on startup (3s delay)
|
||||
function dumpDOMStructure(){runDeepInspect();}
|
||||
|
||||
// ── Port Discovery: async fetch()-based (sync XHR blocked in Electron renderer) ──
|
||||
var HARDCODED_PORT=${_port};
|
||||
@@ -947,6 +1089,14 @@ function generateApprovalObserverScript(_port: number): string {
|
||||
// FALLBACK: periodic scan every 3s for any missed mutations
|
||||
setInterval(scheduleScan,3000);
|
||||
|
||||
// ── DEEP-INSPECT POLLING: curl→Bridge→Renderer→Results ──
|
||||
setInterval(function(){
|
||||
if(!_ready||!BASE)return;
|
||||
fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||
if(d.inspect){log('🔍 Deep inspect triggered via HTTP');runDeepInspect();}
|
||||
}).catch(function(){});
|
||||
},2000);
|
||||
|
||||
// ── TRIGGER-CLICK: Extension→Renderer bridge for programmatic button clicks ──
|
||||
// Extension sets clickTrigger via tryApprovalStrategies → renderer polls and clicks
|
||||
// v3: uses deepFindButtons() to traverse iframes, webviews, shadow DOMs
|
||||
|
||||
367
tools/deep_iframe_inspector.js
Normal file
367
tools/deep_iframe_inspector.js
Normal file
@@ -0,0 +1,367 @@
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Deep Iframe Inspector for Antigravity
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Usage: Paste this entire script into the AG Renderer DevTools console
|
||||
// (Ctrl+Shift+I → Console tab in the main workbench window)
|
||||
//
|
||||
// This script recursively inspects ALL iframes, webview elements,
|
||||
// and shadow DOMs to map the complete DOM tree including:
|
||||
// - iframe nesting depth and origin info
|
||||
// - webview elements and their executeJavaScript availability
|
||||
// - shadow DOM roots
|
||||
// - button inventory at each level
|
||||
// - Content Security Policy headers
|
||||
// - Cross-origin accessibility status
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
(function deepIframeInspector() {
|
||||
'use strict';
|
||||
|
||||
const SEP = '─'.repeat(70);
|
||||
const results = [];
|
||||
let nodeCount = 0;
|
||||
|
||||
function log(msg) {
|
||||
console.log('[DEEP-INSPECT] ' + msg);
|
||||
results.push(msg);
|
||||
}
|
||||
|
||||
function indent(depth) {
|
||||
return ' '.repeat(depth);
|
||||
}
|
||||
|
||||
// ── Inspect a single document context ──
|
||||
function inspectDocument(doc, depth, label) {
|
||||
nodeCount++;
|
||||
const id = `node_${nodeCount}`;
|
||||
const pfx = indent(depth);
|
||||
|
||||
log(`${pfx}┌─ ${label} (depth=${depth})`);
|
||||
|
||||
if (!doc) {
|
||||
log(`${pfx}│ ⛔ document is null/undefined`);
|
||||
log(`${pfx}└─ END ${label}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic doc info
|
||||
try {
|
||||
log(`${pfx}│ URL: ${(doc.URL || doc.documentURI || 'unknown').substring(0, 120)}`);
|
||||
log(`${pfx}│ title: "${(doc.title || '').substring(0, 80)}"`);
|
||||
log(`${pfx}│ readyState: ${doc.readyState}`);
|
||||
log(`${pfx}│ domain: ${doc.domain || 'N/A'}`);
|
||||
} catch (e) {
|
||||
log(`${pfx}│ ⛔ Cannot read doc properties: ${e.message}`);
|
||||
}
|
||||
|
||||
// CSP meta tags
|
||||
try {
|
||||
const cspMetas = doc.querySelectorAll('meta[http-equiv="Content-Security-Policy"]');
|
||||
if (cspMetas.length > 0) {
|
||||
for (let i = 0; i < cspMetas.length; i++) {
|
||||
log(`${pfx}│ CSP[${i}]: ${(cspMetas[i].content || '').substring(0, 150)}`);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// Count key elements
|
||||
try {
|
||||
const allEls = doc.querySelectorAll('*');
|
||||
const buttons = doc.querySelectorAll('button');
|
||||
const inputs = doc.querySelectorAll('input[type="button"],input[type="submit"]');
|
||||
const anchors = doc.querySelectorAll('a[role="button"]');
|
||||
const iframes = doc.querySelectorAll('iframe');
|
||||
const webviews = doc.querySelectorAll('webview');
|
||||
const divWithRole = doc.querySelectorAll('[role="button"]');
|
||||
|
||||
log(`${pfx}│ Elements: total=${allEls.length} buttons=${buttons.length} roleBtn=${divWithRole.length} iframes=${iframes.length} webviews=${webviews.length}`);
|
||||
|
||||
// Dump ALL buttons (not just first 10)
|
||||
if (buttons.length > 0) {
|
||||
log(`${pfx}│ ── Buttons ──`);
|
||||
const maxShow = Math.min(30, buttons.length);
|
||||
for (let i = 0; i < maxShow; i++) {
|
||||
const b = buttons[i];
|
||||
const txt = (b.textContent || '').trim().substring(0, 60);
|
||||
const cls = (b.className || '').substring(0, 50);
|
||||
const disabled = b.disabled ? ' [DISABLED]' : '';
|
||||
const hidden = (b.hidden || !b.offsetParent) ? ' [HIDDEN]' : '';
|
||||
const ariaLabel = b.getAttribute('aria-label') || '';
|
||||
const title = b.getAttribute('title') || '';
|
||||
log(`${pfx}│ btn[${i}]: "${txt}"${disabled}${hidden} class="${cls}" aria="${ariaLabel}" title="${title}"`);
|
||||
}
|
||||
if (buttons.length > maxShow) {
|
||||
log(`${pfx}│ ... +${buttons.length - maxShow} more buttons`);
|
||||
}
|
||||
}
|
||||
|
||||
// Dump role="button" elements that aren't actual buttons
|
||||
if (divWithRole.length > 0) {
|
||||
log(`${pfx}│ ── role="button" elements ──`);
|
||||
const maxShow = Math.min(15, divWithRole.length);
|
||||
for (let i = 0; i < maxShow; i++) {
|
||||
const el = divWithRole[i];
|
||||
if (el.tagName === 'BUTTON') continue; // skip actual buttons
|
||||
const txt = (el.textContent || '').trim().substring(0, 60);
|
||||
const tag = el.tagName.toLowerCase();
|
||||
log(`${pfx}│ roleBtn[${i}]: <${tag}> "${txt}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse into shadow DOMs
|
||||
let shadowCount = 0;
|
||||
for (let i = 0; i < allEls.length; i++) {
|
||||
const sr = allEls[i].shadowRoot;
|
||||
if (sr) {
|
||||
shadowCount++;
|
||||
const tag = allEls[i].tagName.toLowerCase();
|
||||
const cls = (allEls[i].className || '').substring(0, 40);
|
||||
inspectDocument(sr, depth + 1, `ShadowRoot of <${tag} class="${cls}">`);
|
||||
}
|
||||
}
|
||||
if (shadowCount === 0) {
|
||||
log(`${pfx}│ (no shadow DOMs found)`);
|
||||
}
|
||||
|
||||
// Recurse into iframes
|
||||
for (let i = 0; i < iframes.length; i++) {
|
||||
const f = iframes[i];
|
||||
const src = (f.src || '').substring(0, 120);
|
||||
const cls = (f.className || '').substring(0, 50);
|
||||
const id = f.id || '';
|
||||
const sandbox = f.getAttribute('sandbox') || 'none';
|
||||
const allow = f.getAttribute('allow') || '';
|
||||
|
||||
let fLabel = `<iframe#${i}> id="${id}" class="${cls}" src="${src}" sandbox="${sandbox}"`;
|
||||
|
||||
try {
|
||||
const idoc = f.contentDocument || (f.contentWindow && f.contentWindow.document);
|
||||
if (idoc) {
|
||||
inspectDocument(idoc, depth + 1, fLabel + ' [ACCESSIBLE]');
|
||||
} else {
|
||||
log(`${pfx}│ ${fLabel} → contentDocument=null`);
|
||||
// Try contentWindow info
|
||||
try {
|
||||
const cw = f.contentWindow;
|
||||
log(`${pfx}│ contentWindow exists: ${!!cw}`);
|
||||
if (cw) {
|
||||
log(`${pfx}│ contentWindow.length (sub-frames): ${cw.length}`);
|
||||
try { log(`${pfx}│ contentWindow.location: ${cw.location.href}`); } catch (e2) {
|
||||
log(`${pfx}│ contentWindow.location: ⛔ ${e2.message.substring(0, 60)}`);
|
||||
}
|
||||
}
|
||||
} catch (e2) {}
|
||||
}
|
||||
} catch (e) {
|
||||
log(`${pfx}│ ${fLabel} → ⛔ BLOCKED: ${e.message.substring(0, 80)}`);
|
||||
// Still try to get some info about the blocked iframe
|
||||
try {
|
||||
const cw = f.contentWindow;
|
||||
log(`${pfx}│ contentWindow exists: ${!!cw}`);
|
||||
if (cw) {
|
||||
log(`${pfx}│ contentWindow.length (sub-frames): ${cw.length}`);
|
||||
}
|
||||
} catch (e2) {
|
||||
log(`${pfx}│ contentWindow also blocked: ${e2.message.substring(0, 60)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse into webview elements
|
||||
for (let i = 0; i < webviews.length; i++) {
|
||||
const wv = webviews[i];
|
||||
const src = (wv.src || '').substring(0, 120);
|
||||
const cls = (wv.className || '').substring(0, 50);
|
||||
const partition = wv.getAttribute('partition') || '';
|
||||
const preload = wv.getAttribute('preload') || '';
|
||||
const nodeInteg = wv.getAttribute('nodeintegration') || '';
|
||||
const nodeIntegSub = wv.getAttribute('nodeintegrationinsubframes') || '';
|
||||
const webpref = wv.getAttribute('webpreferences') || '';
|
||||
|
||||
log(`${pfx}│ ── <webview#${i}> ──`);
|
||||
log(`${pfx}│ src: ${src}`);
|
||||
log(`${pfx}│ class: ${cls}`);
|
||||
log(`${pfx}│ partition: ${partition}`);
|
||||
log(`${pfx}│ preload: ${preload}`);
|
||||
log(`${pfx}│ nodeintegration: ${nodeInteg}`);
|
||||
log(`${pfx}│ webpreferences: ${webpref}`);
|
||||
|
||||
// Try contentDocument
|
||||
try {
|
||||
const wdoc = wv.contentDocument;
|
||||
if (wdoc) {
|
||||
inspectDocument(wdoc, depth + 1, `<webview#${i}> contentDocument [ACCESSIBLE]`);
|
||||
} else {
|
||||
log(`${pfx}│ contentDocument: null`);
|
||||
}
|
||||
} catch (e) {
|
||||
log(`${pfx}│ contentDocument: ⛔ ${e.message.substring(0, 60)}`);
|
||||
}
|
||||
|
||||
// Check executeJavaScript availability
|
||||
log(`${pfx}│ executeJavaScript: ${typeof wv.executeJavaScript}`);
|
||||
if (typeof wv.executeJavaScript === 'function') {
|
||||
log(`${pfx}│ 🔑 executeJavaScript IS AVAILABLE — attempting probe...`);
|
||||
try {
|
||||
wv.executeJavaScript(`
|
||||
(function() {
|
||||
var btns = document.querySelectorAll('button');
|
||||
var allEls = document.querySelectorAll('*');
|
||||
var iframes = document.querySelectorAll('iframe');
|
||||
var webviews = document.querySelectorAll('webview');
|
||||
var btnTexts = [];
|
||||
for (var i = 0; i < btns.length; i++) {
|
||||
var txt = (btns[i].textContent || '').trim();
|
||||
var disabled = btns[i].disabled ? ' [DISABLED]' : '';
|
||||
var hidden = (btns[i].hidden || !btns[i].offsetParent) ? ' [HIDDEN]' : '';
|
||||
var cls = (btns[i].className || '').substring(0, 40);
|
||||
btnTexts.push('"' + txt.substring(0, 50) + '"' + disabled + hidden + ' cls=' + cls);
|
||||
}
|
||||
// Also check for role=button
|
||||
var roleBtns = document.querySelectorAll('[role="button"]');
|
||||
var roleBtnTexts = [];
|
||||
for (var j = 0; j < roleBtns.length; j++) {
|
||||
if (roleBtns[j].tagName !== 'BUTTON') {
|
||||
roleBtnTexts.push('<' + roleBtns[j].tagName.toLowerCase() + '> "' + (roleBtns[j].textContent || '').trim().substring(0, 40) + '"');
|
||||
}
|
||||
}
|
||||
|
||||
// Check for shadow DOMs
|
||||
var shadowCount = 0;
|
||||
for (var k = 0; k < allEls.length; k++) {
|
||||
if (allEls[k].shadowRoot) shadowCount++;
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
url: document.URL,
|
||||
title: document.title,
|
||||
totalElements: allEls.length,
|
||||
buttons: btns.length,
|
||||
buttonTexts: btnTexts.slice(0, 30),
|
||||
roleBtns: roleBtnTexts.slice(0, 15),
|
||||
iframes: iframes.length,
|
||||
webviews: webviews.length,
|
||||
shadowDOMs: shadowCount
|
||||
});
|
||||
})()
|
||||
`).then(function(result) {
|
||||
try {
|
||||
const data = JSON.parse(result);
|
||||
console.log('[DEEP-INSPECT] 📦 webview#' + i + ' INTERNAL PROBE:');
|
||||
console.log('[DEEP-INSPECT] URL: ' + data.url);
|
||||
console.log('[DEEP-INSPECT] title: ' + data.title);
|
||||
console.log('[DEEP-INSPECT] Elements: total=' + data.totalElements + ' buttons=' + data.buttons + ' iframes=' + data.iframes + ' webviews=' + data.webviews + ' shadowDOMs=' + data.shadowDOMs);
|
||||
if (data.buttonTexts.length > 0) {
|
||||
console.log('[DEEP-INSPECT] ── Buttons inside webview ──');
|
||||
data.buttonTexts.forEach(function(t, idx) {
|
||||
console.log('[DEEP-INSPECT] btn[' + idx + ']: ' + t);
|
||||
});
|
||||
}
|
||||
if (data.roleBtns.length > 0) {
|
||||
console.log('[DEEP-INSPECT] ── role="button" inside webview ──');
|
||||
data.roleBtns.forEach(function(t, idx) {
|
||||
console.log('[DEEP-INSPECT] roleBtn[' + idx + ']: ' + t);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[DEEP-INSPECT] raw result: ' + result);
|
||||
}
|
||||
}).catch(function(e) {
|
||||
console.log('[DEEP-INSPECT] ⛔ executeJavaScript FAILED: ' + e.message);
|
||||
});
|
||||
} catch (e) {
|
||||
log(`${pfx}│ ⛔ executeJavaScript call threw: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Try getWebContentsId (Electron-specific)
|
||||
try {
|
||||
if (typeof wv.getWebContentsId === 'function') {
|
||||
log(`${pfx}│ webContentsId: ${wv.getWebContentsId()}`);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(`${pfx}│ ⛔ Error during inspection: ${e.message}`);
|
||||
}
|
||||
|
||||
log(`${pfx}└─ END ${label}`);
|
||||
}
|
||||
|
||||
// ── Window hierarchy ──
|
||||
function inspectWindowHierarchy() {
|
||||
log(SEP);
|
||||
log('📐 Window Hierarchy');
|
||||
log(SEP);
|
||||
log(`window.location.href: ${window.location.href}`);
|
||||
log(`window.location.origin: ${window.location.origin}`);
|
||||
log(`window.location.protocol: ${window.location.protocol}`);
|
||||
log(`window.frames.length: ${window.frames.length}`);
|
||||
log(`window === window.top: ${window === window.top}`);
|
||||
log(`window === window.parent: ${window === window.parent}`);
|
||||
log(`navigator.userAgent: ${navigator.userAgent.substring(0, 120)}`);
|
||||
|
||||
// Check if we're in Electron
|
||||
try {
|
||||
log(`process.type: ${typeof process !== 'undefined' ? process.type : 'N/A'}`);
|
||||
log(`process.versions.electron: ${typeof process !== 'undefined' && process.versions ? process.versions.electron : 'N/A'}`);
|
||||
} catch (e) {
|
||||
log(`process info: N/A (${e.message.substring(0, 40)})`);
|
||||
}
|
||||
|
||||
// Check webFrame (Electron renderer API)
|
||||
try {
|
||||
if (typeof require === 'function') {
|
||||
const { webFrame } = require('electron');
|
||||
log(`webFrame available: ${!!webFrame}`);
|
||||
if (webFrame) {
|
||||
log(`webFrame.routingId: ${webFrame.routingId}`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log(`electron.webFrame: N/A (${e.message.substring(0, 40)})`);
|
||||
}
|
||||
|
||||
// Check webContents access via remote/electron
|
||||
try {
|
||||
if (typeof require === 'function') {
|
||||
const electron = require('electron');
|
||||
const ipcRenderer = electron.ipcRenderer;
|
||||
log(`ipcRenderer available: ${!!ipcRenderer}`);
|
||||
}
|
||||
} catch (e) {
|
||||
log(`electron.ipcRenderer: N/A (${e.message.substring(0, 40)})`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Main ──
|
||||
log('');
|
||||
log('═'.repeat(70));
|
||||
log(' DEEP IFRAME INSPECTOR — Antigravity DOM Analysis');
|
||||
log(' Timestamp: ' + new Date().toISOString());
|
||||
log('═'.repeat(70));
|
||||
log('');
|
||||
|
||||
inspectWindowHierarchy();
|
||||
|
||||
log('');
|
||||
log(SEP);
|
||||
log('📄 Document Tree (recursive)');
|
||||
log(SEP);
|
||||
|
||||
inspectDocument(document, 0, 'Main Document');
|
||||
|
||||
log('');
|
||||
log(SEP);
|
||||
log(`✅ Inspection complete. ${nodeCount} document contexts inspected.`);
|
||||
log(SEP);
|
||||
|
||||
// Summary
|
||||
log('');
|
||||
log('📋 SUMMARY:');
|
||||
log(' Copy all [DEEP-INSPECT] lines from this console');
|
||||
log(' Webview executeJavaScript probe results will appear AFTER this summary (async)');
|
||||
|
||||
return results;
|
||||
})();
|
||||
Reference in New Issue
Block a user