Keep native Chromium loading and prevent stale browser cache entries

This commit is contained in:
Jonathan Sykes
2026-10-08 09:43:51 +08:00
parent c4a512bea6
commit 57edb410c3
11 changed files with 179 additions and 23 deletions

View File

@@ -27,11 +27,12 @@
return {bytes,digest}; return {bytes,digest};
} }
async function start() { async function start() {
if(!manifest.appCache || !root.crypto?.subtle)return external(key); if(!manifest.appCache || !root.crypto?.subtle || (!root.Lazy.captureApp&&!root.navigator?.serviceWorker?.controller))return external(key);
const expected=manifest.files['/app.js'].h; const expected=manifest.files['/app.js'].h;
let cache; let cache;
try { cache=await root.caches?.open('ytplayer-assets'); } catch {} try { cache=await root.caches?.open('ytplayer-assets'); } catch {}
let response=await cache?.match(key); let response=await cache?.match(key);
if(!root.Lazy.captureApp && response?.ok && response.headers.get('X-Asset-Hash')===expected)return external(key);
if(!response || response.headers.get('X-Asset-Hash')!==expected) { if(!response || response.headers.get('X-Asset-Hash')!==expected) {
try { response=await (root.Lazy.appResponse || root.fetch(key,{credentials:'same-origin'})); } catch {} try { response=await (root.Lazy.appResponse || root.fetch(key,{credentials:'same-origin'})); } catch {}
} }
@@ -56,6 +57,7 @@
// CacheStorage may share a full quota with saved music. Never remove data // CacheStorage may share a full quota with saved music. Never remove data
// to make room; boot from these verified bytes even if caching is refused. // to make room; boot from these verified bytes even if caching is refused.
try { await cache?.put(key,response); } catch(error) { root.console.warn('[app-cache]',error.message); } try { await cache?.put(key,response); } catch(error) { root.console.warn('[app-cache]',error.message); }
if(!root.Lazy.captureApp)return external(key);
const node=doc.createElement('script');node.textContent=new root.TextDecoder().decode(bytes); const node=doc.createElement('script');node.textContent=new root.TextDecoder().decode(bytes);
execute(node); execute(node);
} }

View File

@@ -3,14 +3,14 @@ const assert=require('node:assert/strict');
const {webcrypto,createHash}=require('node:crypto'); const {webcrypto,createHash}=require('node:crypto');
const vm=require('node:vm'); const vm=require('node:vm');
const {readFileSync}=require('node:fs'); const {readFileSync}=require('node:fs');
function fixture({cached=false,tampered=false,enabled=true,loading=false,quota=false,insecure=false,previous=false,contract=1}={}){ function fixture({cached=false,tampered=false,enabled=true,loading=false,quota=false,insecure=false,previous=false,contract=1,capture=true,controller=true}={}){
const source='window.appRuns=(window.appRuns||0)+1; window.boot=()=>window.bootRuns=(window.bootRuns||0)+1; document.addEventListener("DOMContentLoaded",boot);',hash=createHash('sha256').update(source).digest('hex'), key='/app.js?v='+hash.slice(0,10); const source='window.appRuns=(window.appRuns||0)+1; window.boot=()=>window.bootRuns=(window.bootRuns||0)+1; document.addEventListener("DOMContentLoaded",boot);',hash=createHash('sha256').update(source).digest('hex'), key='/app.js?v='+hash.slice(0,10);
const oldSource='window.oldApp=true;'+source, oldHash=createHash('sha256').update(oldSource).digest('hex').slice(0,10), oldKey='/app.js?v='+oldHash; const oldSource='window.oldApp=true;'+source, oldHash=createHash('sha256').update(oldSource).digest('hex').slice(0,10), oldKey='/app.js?v='+oldHash;
const held=new Map(), appended=[],listeners={};let calls=0; const held=new Map(), appended=[],listeners={};let calls=0;
const response=()=>new Response(tampered?'bad':source,{headers:{'X-Asset-Hash':hash.slice(0,10)}}); const response=()=>new Response(tampered?'bad':source,{headers:{'X-Asset-Hash':hash.slice(0,10)}});
if(cached)held.set(key,response()); if(cached)held.set(key,response());
if(previous){held.set(oldKey,new Response(oldSource,{headers:{'X-Asset-Hash':oldHash}}));held.set('/__ytp_asset_state',Response.json({previous:{files:{'/app.js':{h:oldHash}},groups:{core:{contract,files:['/app.js']}}}}));} if(previous){held.set(oldKey,new Response(oldSource,{headers:{'X-Asset-Hash':oldHash}}));held.set('/__ytp_asset_state',Response.json({previous:{files:{'/app.js':{h:oldHash}},groups:{core:{contract,files:['/app.js']}}}}));}
const root={crypto:insecure?undefined:webcrypto,TextDecoder,Uint8Array,btoa,console,AssetSyncCore:require('./asset-sync-core'),navigator:{serviceWorker:{controller:{}}},Lazy:{manifest:{groups:{core:{contract:1,files:['/app.js']}},appCache:enabled,files:{'/app.js':{h:hash.slice(0,10)}}},url:()=>key},fetch:async()=>{calls++;return previous?new Response(oldSource,{headers:{'X-Asset-Hash':oldHash}}):response();},caches:{open:async()=>({match:async k=>held.get(k)?.clone(),put:async(k,r)=>{if(quota)throw Error('quota');held.set(k,r.clone());}})}}; const root={crypto:insecure?undefined:webcrypto,TextDecoder,Uint8Array,btoa,console,AssetSyncCore:require('./asset-sync-core'),navigator:{serviceWorker:{controller:controller?{}:null}},Lazy:{captureApp:capture,manifest:{groups:{core:{contract:1,files:['/app.js']}},appCache:enabled,files:{'/app.js':{h:hash.slice(0,10)}}},url:()=>key},fetch:async()=>{calls++;return previous?new Response(oldSource,{headers:{'X-Asset-Hash':oldHash}}):response();},caches:{open:async()=>({match:async k=>held.get(k)?.clone(),put:async(k,r)=>{if(quota)throw Error('quota');held.set(k,r.clone());}})}};
const doc=root.document={readyState:loading?'loading':'complete',querySelector:()=>({content:"script-src 'self' 'sha256-"+Buffer.from(hash,'hex').toString('base64')+"'"}),addEventListener:(name,fn)=>(listeners[name] ||= []).push(fn),createElement:()=>({remove(){this.removed=true;}}),head:{append(node){appended.push(node);if(node.textContent)vm.runInContext(node.textContent,context);else {vm.runInContext(node.src===oldKey?oldSource:source,context);queueMicrotask(()=>node.onload());}}}}; const doc=root.document={readyState:loading?'loading':'complete',querySelector:()=>({content:"script-src 'self' 'sha256-"+Buffer.from(hash,'hex').toString('base64')+"'"}),addEventListener:(name,fn)=>(listeners[name] ||= []).push(fn),createElement:()=>({remove(){this.removed=true;}}),head:{append(node){appended.push(node);if(node.textContent)vm.runInContext(node.textContent,context);else {vm.runInContext(node.src===oldKey?oldSource:source,context);queueMicrotask(()=>node.onload());}}}};
root.window=root;const context=vm.createContext(root); root.window=root;const context=vm.createContext(root);
vm.runInContext(readFileSync(require.resolve('./app-bootstrap.js'),'utf8'),context); vm.runInContext(readFileSync(require.resolve('./app-bootstrap.js'),'utf8'),context);
@@ -61,3 +61,16 @@ test('a feature contract change also prevents stale core execution',async()=>{
const state=await f.held.get('/__ytp_asset_state').json();state.previous.groups['feature:test']={contract:1,files:[]};f.held.set('/__ytp_asset_state',Response.json(state)); const state=await f.held.get('/__ytp_asset_state').json();state.previous.groups['feature:test']={contract:1,files:[]};f.held.set('/__ytp_asset_state',Response.json(state));
await assert.rejects(f.root.AppBootstrap.ready,/hash/);assert.equal(f.root.appRuns,undefined); await assert.rejects(f.root.AppBootstrap.ready,/hash/);assert.equal(f.root.appRuns,undefined);
}); });
test('native-cache engines use their external script and keep normal code-cache behavior',async()=>{
const f=fixture({capture:false,controller:false});await f.root.AppBootstrap.ready;assert.equal(f.calls(),0);assert.equal(f.appended[0].src,f.key);assert.equal(f.root.bootRuns,1);
});
test('native-cache engines still reject an incompatible retained core',async()=>{
const f=fixture({capture:false,previous:true,contract:2});await assert.rejects(f.root.AppBootstrap.ready,/hash/);assert.equal(f.root.appRuns,undefined);
});
test('native-cache engines use the validated current cache entry without decoding its body',async()=>{
const f=fixture({capture:false,cached:true});await f.root.AppBootstrap.ready;assert.equal(f.calls(),0);assert.equal(f.appended[0].src,f.key);assert.equal(f.root.bootRuns,1);
});

View File

@@ -15,10 +15,10 @@ test('staged same-contract responses use verified N-1 without poisoning the new
const e=environment(); const cache=await e.storage.open(core.CACHE); const e=environment(); const cache=await e.storage.open(core.CACHE);
const old={buildTag:'old',files:{'/extra.js':{h:'old'}},groups:{core:{files:[],contract:1},'feature:extra':{files:['/extra.js'],contract:1,background:true}}}; const old={buildTag:'old',files:{'/extra.js':{h:'old'}},groups:{core:{files:[],contract:1},'feature:extra':{files:['/extra.js'],contract:1,background:true}}};
e.m.groups.core.contract=1; e.m.groups['feature:extra']={files:['/extra.js'],contract:1,background:true}; e.m.files['/extra.js']={h:'new'}; e.m.groups.core.contract=1; e.m.groups['feature:extra']={files:['/extra.js'],contract:1,background:true}; e.m.files['/extra.js']={h:'new'};
await cache.put(core.STATE,Response.json({current:old})); await cache.put('/extra.js?v=old',new Response('old body',{headers:{'X-Asset-Hash':'old'}})); await cache.put(core.STATE,Response.json({current:old})); await cache.put('/extra.js?v=old',new Response('old body',{headers:{'X-Asset-Hash':'old','Cache-Control':'public, max-age=31536000, immutable','Content-Encoding':'gzip','Content-Length':'9'}}));
await e.dispatch('install'); await e.dispatch('activate'); await e.dispatch('install'); await e.dispatch('activate');
assert.equal(await cache.match('/extra.js?v=new'),undefined); assert.equal(await cache.match('/extra.js?v=new'),undefined);
const stale=await e.request('/extra.js?v=new'); assert.equal(await stale.text(),'old body');assert.equal(stale.headers.get('X-Asset-Hash'),'old');assert.equal(await cache.match('/extra.js?v=new'),undefined); const stale=await e.request('/extra.js?v=new'); assert.equal(await stale.text(),'old body');assert.equal(stale.headers.get('X-Asset-Hash'),'old');assert.equal(stale.headers.get('Cache-Control'),'no-store');assert.equal(stale.headers.get('Content-Encoding'),null);assert.equal(stale.headers.get('Content-Length'),null);assert.equal(await cache.match('/extra.js?v=new'),undefined);
await e.dispatch('message',{data:{type:'WARM_ASSETS',saveData:true}}); assert.equal(await cache.match('/extra.js?v=new'),undefined); await e.dispatch('message',{data:{type:'WARM_ASSETS',saveData:true}}); assert.equal(await cache.match('/extra.js?v=new'),undefined);
await e.dispatch('message',{data:{type:'WARM_ASSETS',saveData:false}}); assert.equal((await cache.match('/extra.js?v=new')).headers.get('X-Asset-Hash'),'new'); await e.dispatch('message',{data:{type:'WARM_ASSETS',saveData:false}}); assert.equal((await cache.match('/extra.js?v=new')).headers.get('X-Asset-Hash'),'new');
assert.equal((await e.request('/extra.js?v=new')).headers.get('X-Asset-Hash'),'new'); assert.equal((await e.request('/extra.js?v=new')).headers.get('X-Asset-Hash'),'new');

View File

@@ -124,7 +124,9 @@
}; };
// Own this response once. Classic-script and fetch preloads do not share // Own this response once. Classic-script and fetch preloads do not share
// their cached bodies in WebKit; the later bootstrap consumes this promise. // their cached bodies in WebKit; the later bootstrap consumes this promise.
if(manifest.appCache && root.crypto?.subtle) { const agent=root.navigator.userAgent || '';
api.captureApp=!!(manifest.appCache && root.crypto?.subtle && /AppleWebKit\//.test(agent) && !/(?:Chrome|Chromium|Edg|OPR)\//.test(agent));
if(api.captureApp) {
api.appResponse=(async()=>{ api.appResponse=(async()=>{
const key=url('/app.js'); const key=url('/app.js');
try { try {
@@ -134,6 +136,9 @@
return root.fetch(key,{credentials:'same-origin'}); return root.fetch(key,{credentials:'same-origin'});
})(); })();
api.appResponse.catch(()=>{}); api.appResponse.catch(()=>{});
} else if(manifest.appCache) {
// Preserve native script/code-cache reuse in engines without the WebKit gap.
const preload=doc.createElement('link');preload.rel='preload';preload.as='script';preload.href=url('/app.js');doc.head.append(preload);
} }
root.Lazy = api; root.Lazy = api;
root.navigator.serviceWorker?.addEventListener('message',event=>{ root.navigator.serviceWorker?.addEventListener('message',event=>{

View File

@@ -2,12 +2,12 @@ const { test } = require('node:test');
const assert = require('node:assert/strict'); const assert = require('node:assert/strict');
const { readFileSync } = require('node:fs'); const { readFileSync } = require('node:fs');
const vm = require('node:vm'); const vm = require('node:vm');
function fixture(current, held = [], appCache = false) { function fixture(current, held = [], appCache = false, userAgent = 'AppleWebKit/605.1 Safari/605.1') {
const inserted = [], listeners = {}, writes = [], fetched = []; const inserted = [], listeners = {}, writes = [], fetched = [];
const manifest = { buildTag: 'own', appCache, groups: { core: { files: [] }, 'layout:classic': { files: ['/classic.css'] }, 'layout:glass-stage': { files: ['/glass.css', '/one.js', '/two.js'] }, 'feature:test': { contract:1, files: ['/one.js', '/two.js'] } }, files: { '/app.js':{h:'a'}, '/classic.css': {h:'c'}, '/glass.css':{h:'g'}, '/one.js':{h:'1'}, '/two.js':{h:'2'} } }; const manifest = { buildTag: 'own', appCache, groups: { core: { files: [] }, 'layout:classic': { files: ['/classic.css'] }, 'layout:glass-stage': { files: ['/glass.css', '/one.js', '/two.js'] }, 'feature:test': { contract:1, files: ['/one.js', '/two.js'] } }, files: { '/app.js':{h:'a'}, '/classic.css': {h:'c'}, '/glass.css':{h:'g'}, '/one.js':{h:'1'}, '/two.js':{h:'2'} } };
const doc = { readyState:'loading', documentElement: { dataset:{} }, getElementById: () => ({ textContent:JSON.stringify(manifest) }), querySelectorAll: () => [], createElement: tag => ({ tagName:tag.toUpperCase() }), write: value => writes.push(value), addEventListener: (t,f) => listeners[t]=f }; const doc = { readyState:'loading', documentElement: { dataset:{} }, getElementById: () => ({ textContent:JSON.stringify(manifest) }), querySelectorAll: () => [], createElement: tag => ({ tagName:tag.toUpperCase() }), write: value => writes.push(value), addEventListener: (t,f) => listeners[t]=f };
doc.head = { append: node => { inserted.push(node); queueMicrotask(() => node.onload?.()); } }; doc.head = { append: node => { inserted.push(node); queueMicrotask(() => node.onload?.()); } };
const root = { document:doc, localStorage:{ getItem:() => '{"settings":{"layout":"glass-stage"}}' }, console, Promise, URL, crypto:{subtle:{}}, fetch:async key=>{fetched.push(key);return new Response('app');}, setTimeout, clearTimeout, navigator:{}, addEventListener(){} }; const root = { document:doc, localStorage:{ getItem:() => '{"settings":{"layout":"glass-stage"}}' }, console, Promise, URL, crypto:{subtle:{}}, fetch:async key=>{fetched.push(key);return new Response('app');}, setTimeout, clearTimeout, navigator:{userAgent}, addEventListener(){} };
if (current) root.caches = { open: async () => ({ match: async key => key === '/__ytp_asset_state' ? new Response(JSON.stringify({current})) : held.includes(key) ? new Response('', {headers:{'X-Asset-Hash':key.split('=')[1]}}) : undefined }) }; if (current) root.caches = { open: async () => ({ match: async key => key === '/__ytp_asset_state' ? new Response(JSON.stringify({current})) : held.includes(key) ? new Response('', {headers:{'X-Asset-Hash':key.split('=')[1]}}) : undefined }) };
root.window=root; root.globalThis=root; root.window=root; root.globalThis=root;
vm.runInNewContext(readFileSync(require.resolve('./lazy.js'),'utf8'), root); vm.runInNewContext(readFileSync(require.resolve('./lazy.js'),'utf8'), root);
@@ -97,3 +97,14 @@ test('head capture is cache-first even before a worker controls the page',async(
const f=fixture(fixture().manifest,['/app.js?v=a'],true);const response=await f.root.Lazy.appResponse; const f=fixture(fixture().manifest,['/app.js?v=a'],true);const response=await f.root.Lazy.appResponse;
assert.equal(response.headers.get('X-Asset-Hash'),'a');assert.deepEqual(f.fetched,[]); assert.equal(response.headers.get('X-Asset-Hash'),'a');assert.deepEqual(f.fetched,[]);
}); });
test('Chromium retains native script loading and only preloads the current app URL',async()=>{
const f=fixture(null,[],true,'AppleWebKit/537.36 Chrome/140.0 Safari/537.36');
assert.equal(f.root.Lazy.captureApp,false);assert.equal(f.root.Lazy.appResponse,undefined);assert.deepEqual(f.fetched,[]);
assert.equal(f.inserted.length,1);assert.equal(f.inserted[0].rel,'preload');assert.equal(f.inserted[0].as,'script');assert.equal(f.inserted[0].href,'/app.js?v=a');
});
test('iOS Chromium-branded browsers still use their WebKit response capture',async()=>{
const f=fixture(null,[],true,'AppleWebKit/605.1 CriOS/140.0 Mobile Safari/605.1');await f.root.Lazy.appResponse;
assert.equal(f.root.Lazy.captureApp,true);assert.deepEqual(f.fetched,['/app.js?v=a']);
});

View File

@@ -576,7 +576,13 @@ async function assetFetch(request, clientId) {
if(state.current.files[path]?.h === h) { if(state.current.files[path]?.h === h) {
const previousKey=AssetSyncCore.fallback(state.current,state.previous,path); const previousKey=AssetSyncCore.fallback(state.current,state.previous,path);
const previousResponse=previousKey && await cache.match(previousKey); const previousResponse=previousKey && await cache.match(previousKey);
if(previousResponse && previousResponse.headers.get('X-Asset-Hash')===state.previous.files[path].h) return previousResponse; if(previousResponse && previousResponse.headers.get('X-Asset-Hash')===state.previous.files[path].h) {
// A stale body returned under a new URL must not poison the browser's
// resource cache either. Its decoded stream has no transport encoding.
const headers=new Headers(previousResponse.headers);headers.set('Cache-Control','no-store');
for(const name of ['Content-Encoding','Content-Length','Transfer-Encoding'])headers.delete(name);
return new Response(previousResponse.body,{status:previousResponse.status,headers});
}
} }
try { const r = await fetch(key); if(r.ok && r.headers.get('X-Asset-Hash') !== h) return new Response('Asset version unavailable',{status:409}); if(r.ok && request.method !== 'HEAD') await cache.put(key,r.clone()); return r; } catch { return new Response('Offline',{status:503}); } try { const r = await fetch(key); if(r.ok && r.headers.get('X-Asset-Hash') !== h) return new Response('Asset version unavailable',{status:409}); if(r.ok && request.method !== 'HEAD') await cache.put(key,r.clone()); return r; } catch { return new Response('Offline',{status:503}); }
} }

View File

@@ -354,3 +354,6 @@ when diagnosing historical regressions.
Phase 6 counts its fetch-owned app.js response in initial JS/CSS bytes. Since the Phase 6 counts its fetch-owned app.js response in initial JS/CSS bytes. Since the
bootstrap consumes that response asynchronously, DOMContentLoaded no longer means bootstrap consumes that response asynchronously, DOMContentLoaded no longer means
app execution is complete; compare the existing actual boot-done landmark. app execution is complete; compare the existing actual boot-done landmark.
`--scenario warm,offline,update-js,update-css,update-feature` runs a regression
sweep without repeating an already-completed cold acceptance run.

View File

@@ -709,6 +709,8 @@ async function main() {
if (options.browser === 'all' || options.browser === 'chromium') browsers.push(pw.chromium); if (options.browser === 'all' || options.browser === 'chromium') browsers.push(pw.chromium);
if (options.browser === 'all' || options.browser === 'webkit') browsers.push(pw.webkit); if (options.browser === 'all' || options.browser === 'webkit') browsers.push(pw.webkit);
const requestedScenarios=new Set(options.scenario.split(','));
const wants=name=>requestedScenarios.has('all')||requestedScenarios.has(name);
const profiles = []; const profiles = [];
if (options.profile === 'all' || options.profile === 'lte') profiles.push('lte'); if (options.profile === 'all' || options.profile === 'lte') profiles.push('lte');
if (options.profile === 'all' || options.profile === 'lossy') profiles.push('lossy'); if (options.profile === 'all' || options.profile === 'lossy') profiles.push('lossy');
@@ -773,7 +775,7 @@ async function main() {
console.log(`Proxy listening on port ${proxy.port} (mode: ${proxyProfile})`); console.log(`Proxy listening on port ${proxy.port} (mode: ${proxyProfile})`);
// 1. Cold Scenario // 1. Cold Scenario
if (options.scenario === 'all' || options.scenario === 'cold') { if (wants('cold')) {
console.log(` [Scenario: Cold Load] running ${options.runs} iterations...`); console.log(` [Scenario: Cold Load] running ${options.runs} iterations...`);
const coldRuns = []; const coldRuns = [];
for (let r = 0; r < options.runs; r++) { for (let r = 0; r < options.runs; r++) {
@@ -781,7 +783,7 @@ async function main() {
const res = await runColdScenario({ browserType: bType, server, proxy, profile: prof }); const res = await runColdScenario({ browserType: bType, server, proxy, profile: prof });
if(options.assertCold)assertColdAssets(res.requests); if(options.assertCold)assertColdAssets(res.requests);
coldRuns.push(res); coldRuns.push(res);
console.log(`done (${res.wireBytes} bytes, ${res.requestCount} reqs, boot=${res.bootDone}ms)`); console.log(`done (${res.wireBytes} bytes, ${res.requestCount} reqs, fcp=${res.fcp}ms, boot=${res.bootDone}ms)`);
} }
results.browsers[bName][prof].cold = { results.browsers[bName][prof].cold = {
@@ -807,7 +809,7 @@ async function main() {
} }
// 2. Warm Scenario // 2. Warm Scenario
if (options.scenario === 'all' || options.scenario === 'warm') { if (wants('warm')) {
console.log(` [Scenario: Warm Load] running ${options.runs} iterations...`); console.log(` [Scenario: Warm Load] running ${options.runs} iterations...`);
const warmRuns = []; const warmRuns = [];
for (let r = 0; r < options.runs; r++) { for (let r = 0; r < options.runs; r++) {
@@ -829,7 +831,7 @@ async function main() {
} }
// 3. Offline Scenario // 3. Offline Scenario
if (options.scenario === 'all' || options.scenario === 'offline') { if (wants('offline')) {
console.log(` [Scenario: Offline Reload] running ${options.runs} iterations...`); console.log(` [Scenario: Offline Reload] running ${options.runs} iterations...`);
const offRuns = []; const offRuns = [];
for (let r = 0; r < options.runs; r++) { for (let r = 0; r < options.runs; r++) {
@@ -851,7 +853,7 @@ async function main() {
} }
// 4. Update N -> N+1 (JS) // 4. Update N -> N+1 (JS)
if (options.scenario === 'all' || options.scenario === 'update-js') { if (wants('update-js')) {
console.log(` [Scenario: Update N->N+1 (1-line app.js)] running ${options.runs} iterations...`); console.log(` [Scenario: Update N->N+1 (1-line app.js)] running ${options.runs} iterations...`);
const updJsRuns = []; const updJsRuns = [];
for (let r = 0; r < options.runs; r++) { for (let r = 0; r < options.runs; r++) {
@@ -878,7 +880,7 @@ async function main() {
} }
// 5. Update N -> N+1 (CSS) // 5. Update N -> N+1 (CSS)
if (options.scenario === 'all' || options.scenario === 'update-css') { if (wants('update-css')) {
console.log(` [Scenario: Update N->N+1 (1-line theme-glass.css)] running ${options.runs} iterations...`); console.log(` [Scenario: Update N->N+1 (1-line theme-glass.css)] running ${options.runs} iterations...`);
const updCssRuns = []; const updCssRuns = [];
for (let r = 0; r < options.runs; r++) { for (let r = 0; r < options.runs; r++) {
@@ -906,7 +908,7 @@ async function main() {
}; };
} }
if ((options.scenario === 'all' || options.scenario === 'update-feature') && existsSync(path.join(treeBase,'presenter.js'))) { if ((wants('update-feature')) && existsSync(path.join(treeBase,'presenter.js'))) {
console.log(` [Scenario: Update split-out presenter, including idle cache fill] running ${options.runs} iterations...`); console.log(` [Scenario: Update split-out presenter, including idle cache fill] running ${options.runs} iterations...`);
const runs=[]; const runs=[];
for(let r=0;r<options.runs;r++) { for(let r=0;r<options.runs;r++) {

View File

@@ -2,7 +2,7 @@
"results": [ "results": [
{ {
"browser": "chromium", "browser": "chromium",
"sourceBuildTag": "41a7ba2c29a3", "sourceBuildTag": "562f4031bcb7",
"classicSharedDefaults": true, "classicSharedDefaults": true,
"cssOrderPreserved": true, "cssOrderPreserved": true,
"settingsSearch": true, "settingsSearch": true,
@@ -28,7 +28,7 @@
}, },
{ {
"browser": "webkit", "browser": "webkit",
"sourceBuildTag": "41a7ba2c29a3", "sourceBuildTag": "562f4031bcb7",
"classicSharedDefaults": true, "classicSharedDefaults": true,
"cssOrderPreserved": true, "cssOrderPreserved": true,
"settingsSearch": true, "settingsSearch": true,
@@ -56,7 +56,7 @@
"disconnected": true "disconnected": true
}, },
{ {
"message": "/127.0.0.1:36117/api/version.", "message": "/127.0.0.1:33967/api/version.",
"disconnected": true "disconnected": true
}, },
{ {
@@ -64,7 +64,7 @@
"disconnected": true "disconnected": true
}, },
{ {
"message": "/127.0.0.1:36117/api/recommendations?fp=163577c02a53bc40.", "message": "/127.0.0.1:33967/api/recommendations?fp=163577c02a53bc40.",
"disconnected": true "disconnected": true
}, },
{ {
@@ -72,7 +72,7 @@
"disconnected": true "disconnected": true
}, },
{ {
"message": "/127.0.0.1:36117/api/version.", "message": "/127.0.0.1:33967/api/version.",
"disconnected": true "disconnected": true
}, },
{ {
@@ -80,7 +80,7 @@
"disconnected": true "disconnected": true
}, },
{ {
"message": "/127.0.0.1:36117/api/recommendations?fp=163577c02a53bc40.", "message": "/127.0.0.1:33967/api/recommendations?fp=163577c02a53bc40.",
"disconnected": true "disconnected": true
}, },
{ {
@@ -88,7 +88,7 @@
"disconnected": true "disconnected": true
}, },
{ {
"message": "/127.0.0.1:36117/api/version.", "message": "/127.0.0.1:33967/api/version.",
"disconnected": true "disconnected": true
} }
] ]

View File

@@ -0,0 +1,101 @@
{
"date": "2026-10-08T01:29:49.903Z",
"frontendCommit": null,
"sourceRoot": null,
"sourceCommit": "c4a512bea63da4c9c875501038727768080496c1",
"frontendBuildTag": "5fe61861d759",
"commit": "c4a512bea63da4c9c875501038727768080496c1",
"runsConfigured": 5,
"browsers": {
"chromium": {
"lte": {
"warm": {
"requestCount": {
"median": 4,
"min": 3,
"max": 5,
"values": [
5,
4,
3,
4,
3
]
},
"wireBytes": {
"median": 7869,
"min": 97,
"max": 7869,
"values": [
7869,
7869,
97,
7869,
7858
]
},
"fcp": {
"median": 140,
"min": 120,
"max": 152,
"values": [
152,
148,
120,
120,
140
]
},
"lcp": {
"median": 148,
"min": 140,
"max": 156,
"values": [
152,
148,
148,
156,
140
]
},
"domContentLoaded": {
"median": 398,
"min": 391,
"max": 406,
"values": [
404,
398,
396,
406,
391
]
},
"bootDone": {
"median": 414,
"min": 411,
"max": 420,
"values": [
418,
414,
411,
420,
412
]
},
"longTaskTotal": {
"median": 62,
"min": 57,
"max": 65,
"values": [
65,
59,
63,
57,
62
]
}
}
}
}
}
}

View File

@@ -85,3 +85,16 @@ contract to match the running shell's expectations; a core or feature contract
change rejects it. Both-engine lazy smoke now changes app bytes, evicts the new change rejects it. Both-engine lazy smoke now changes app bytes, evicts the new
entry, boots N-1 offline, then repairs the current entry before the existing entry, boots N-1 offline, then repairs the current entry before the existing
feature pinning/contract/playback checks. No source in app.js changed. feature pinning/contract/playback checks. No source in app.js changed.
The all-engine inline prototype's completed five-run Chromium/LTE profile had
cold boot 6748 ms, warm boot 458 ms (Phase 5: 410 ms), offline boot 204 ms, core
update 870 ms. This was a real warm regression, so the full sweep was interrupted
before accepting its results. Raw output: perf/.tmp/phase6-inline-chromium-profile.log.
The native-script controlled variant measured warm 414 ms over five samples.
Shipping capture is therefore restricted to WebKit (including iOS CriOS); other
engines preload and retain their native classic-script/code-cache path. Normal
native cache hits avoid decoding/rehashing; retained core fallback still enforces
all group contracts. The new worker marks stale responses no-store and strips
decoded transport headers, preventing browser resource-cache poisoning under a
new URL. CacheStorage's exact keys and previous entries stay unchanged.