mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-16 08:06:24 +03:00
Carry native comp capture and completion integrity fixes into the reviewed component workflow. Bump the skill to 4.4.0 and engine/platform pins to 0.1.6; keep publication separate from this release candidate. AI assistance: implemented and validated with OpenAI Codex.
98 lines
6.7 KiB
JavaScript
98 lines
6.7 KiB
JavaScript
(key) => {
|
|
if (Object.hasOwn(globalThis,key)) throw Error('capture key collision');
|
|
const nodes=[...document.querySelectorAll('*')];
|
|
if(nodes.length>5000) throw Error('capture DOM exceeds 5000 elements');
|
|
const box=el=>{const b=el.getBoundingClientRect();return {x:b.x,y:b.y,w:b.width,h:b.height};};
|
|
// Computed CSS only. Split complete top-level layers, preserving quoted URL commas.
|
|
const layers=value=>{
|
|
const out=[];let start=0,depth=0,quote=null,escaped=false;
|
|
for(let i=0;i<value.length;i++){
|
|
const c=value[i];
|
|
if(escaped){escaped=false;continue;}
|
|
if(c==='\\'){escaped=true;continue;}
|
|
if(quote){if(c===quote)quote=null;continue;}
|
|
if(c==='"'||c==="'"){quote=c;continue;}
|
|
if(c==='(')depth++;else if(c===')'){if(--depth<0)return null;}
|
|
else if(c===','&&depth===0){out.push(value.slice(start,i).trim());start=i+1;}
|
|
}
|
|
if(depth||quote||escaped)return null;out.push(value.slice(start).trim());
|
|
if(out.length>16)return null;
|
|
return out.every(v=>v==='none'||/^url\("([^"\\]*)"\)$/.test(v)||(/^(repeating-)?(linear|radial|conic)-gradient\(/.test(v)&&!/(url|image-set|paint)\(/.test(v)))?out:null;
|
|
};
|
|
const state={active:null,pseudos:[]};
|
|
const computed=s=>({display:s.display,visibility:s.visibility,opacity:s.opacity,objectFit:s.objectFit,objectPosition:s.objectPosition,backgroundSize:s.backgroundSize,backgroundPosition:s.backgroundPosition});
|
|
state.scan=()=>{
|
|
const current=[...document.querySelectorAll('*')];
|
|
const sameNodes=current.length===nodes.length&¤t.every((el,i)=>el===nodes[i]);
|
|
const rows=[],unsupported=[];
|
|
for(const [index,el] of current.entries()){
|
|
const s=getComputedStyle(el),b=box(el);
|
|
if(el.shadowRoot||['IFRAME','FRAME','CANVAS','VIDEO','SVG'].includes(el.tagName.toUpperCase()))unsupported.push({kind:el.shadowRoot?'shadow-root':el.tagName.toLowerCase(),box:b});
|
|
const ancestors=[];
|
|
for(let p=el.parentElement;p;p=p.parentElement){const c=getComputedStyle(p);if(c.opacity!=='1'||c.visibility!=='visible'||c.display==='none'||c.overflow!=='visible'||c.clipPath!=='none')ancestors.push({tag:p.tagName,opacity:c.opacity,visibility:c.visibility,display:c.display,overflow:c.overflow,clipPath:c.clipPath});}
|
|
if(el instanceof HTMLImageElement&&el.currentSrc)rows.push({kind:'img',url:el.currentSrc,decoded:el.complete&&el.naturalWidth>0,supported:true,index,tag:el.tagName,elementId:el.id,box:b,computed:computed(s),ancestors});
|
|
for(const pseudo of ['', '::before','::after']){
|
|
const p=pseudo?getComputedStyle(el,pseudo):s;
|
|
const bg=p.backgroundImage;
|
|
const native=pseudo?state.pseudos.find(r=>r.index===index&&r.pseudo===pseudo):null;
|
|
const bounds=pseudo?native?.box:b;
|
|
const parts=layers(bg);
|
|
const urls=parts?.map((part,layer)=>({match:/^url\("([^"\\]*)"\)$/.exec(part),layer})).filter(r=>r.match)||[];
|
|
const unknown=bg!=='none'&&(!parts||(pseudo&&!urls.length)||(pseudo&&!bounds));
|
|
if(unknown||(pseudo&&p.content.includes('url('))){unsupported.push({kind:pseudo?'pseudo-imagery':'complex-background',box:bounds||b});continue;}
|
|
for(const r of urls)rows.push({kind:pseudo?'pseudo-background':'background',pseudo,layer:r.layer,url:new URL(r.match[1],location.href).href,decoded:null,supported:true,index,tag:el.tagName,elementId:el.id,box:bounds,computed:computed(p),ancestors:pseudo?[{tag:el.tagName,...computed(s)},...ancestors]:ancestors});
|
|
}
|
|
}
|
|
return {sameNodes,dom:document.documentElement.outerHTML,url:location.href,
|
|
viewport:{width:innerWidth,height:innerHeight,dpr:devicePixelRatio,scrollX,scrollY},
|
|
layout:current.map(box),pseudoLayout:state.pseudos,rows,unsupported,
|
|
runningAnimations:document.getAnimations().filter(a=>a.playState==='running').length};
|
|
};
|
|
// Our temporary changes can start authored transitions. Let them finish naturally.
|
|
state.settle=()=>Promise.race([
|
|
(async()=>{await Promise.all(document.getAnimations().map(a=>a.finished));await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)));return true;})(),
|
|
new Promise((_,reject)=>setTimeout(()=>reject(Error('capture intervention did not settle within 1s')),1000))
|
|
]);
|
|
state.suppressMany=(items)=>{
|
|
if(state.active)throw Error('capture intervention already active');
|
|
state.active={styles:new Map(),expected:[]};const groups=new Map(),rules=[];
|
|
for(const item of items){
|
|
const {index,kind}=item,el=nodes[index];
|
|
if(!el||el!==document.querySelectorAll('*')[index])throw Error('capture node changed');
|
|
if(kind!=='pseudo-background'&&!state.active.styles.has(el))state.active.styles.set(el,el.getAttribute('style'));
|
|
if(kind==='img'){
|
|
const nw=el.naturalWidth,nh=el.naturalHeight;
|
|
const distance=Math.ceil(Math.max(nw,nh,el.clientWidth,el.clientHeight)*Math.max(1,el.clientWidth/nw,el.clientHeight/nh)+innerWidth+innerHeight+1);
|
|
if(!Number.isFinite(distance)||distance>10000000)throw Error('image displacement exceeds capture coordinate budget');
|
|
el.style.setProperty('object-position',`${distance}px ${distance}px`,'important');
|
|
}else{
|
|
const pseudo=item.pseudo||'',id=index+pseudo;
|
|
if(!groups.has(id))groups.set(id,{index,pseudo,parts:layers(getComputedStyle(el,pseudo||null).backgroundImage),remove:new Set()});
|
|
const group=groups.get(id);
|
|
if(!group.parts||!Number.isInteger(item.layer)||!/^url\(/.test(group.parts[item.layer]||''))throw Error('capture background layer changed');
|
|
group.remove.add(item.layer);
|
|
}
|
|
}
|
|
for(const group of groups.values()){
|
|
const image=group.parts.map((v,i)=>group.remove.has(i)?'none':v).join(', ');
|
|
const el=nodes[group.index];
|
|
if(group.pseudo){
|
|
const native=state.pseudos.find(r=>r.index===group.index&&r.pseudo===group.pseudo);
|
|
if(!native?.box)throw Error('capture pseudo geometry unavailable');
|
|
rules.push(`${native.selector}${group.pseudo}{background-image:${image}!important}`);
|
|
}else el.style.setProperty('background-image',image,'important');
|
|
state.active.expected.push({index:group.index,pseudo:group.pseudo,image});
|
|
}
|
|
return {dom:document.documentElement.outerHTML,layout:state.scan().layout,rules:rules.join('\n')};
|
|
};
|
|
state.verifySuppression=()=>{
|
|
for(const e of state.active?.expected||[]){if(getComputedStyle(nodes[e.index],e.pseudo||null).backgroundImage!==e.image)throw Error('capture background suppression was overridden');}
|
|
return true;
|
|
};
|
|
state.restore=()=>{
|
|
if(state.active){for(const [el,style] of state.active.styles)style===null?el.removeAttribute('style'):el.setAttribute('style',style);state.active=null;}
|
|
};
|
|
Object.defineProperty(globalThis,key,{value:state,configurable:true});
|
|
return true;
|
|
}
|