Restore native boot scheduling without an extra core request

This commit is contained in:
Jonathan Sykes
2026-10-08 11:25:03 +08:00
parent 9408ee07e5
commit 4b1f5204f8
12 changed files with 136 additions and 97 deletions

View File

@@ -1,65 +0,0 @@
/* Cache the single page-owned app response before it registers the worker. */
(function (root) {
'use strict';
const doc=root.document, manifest=root.Lazy.manifest, key=root.Lazy.url('/app.js');
let domReady=doc.readyState==='complete', appSawDom=false;
doc.addEventListener('DOMContentLoaded',()=>{domReady=true;appSawDom=typeof root.boot==='function';},{once:true});
function execute(node) {
doc.head.append(node);
node.remove();
delete root.Lazy.appResponse;
// app.js keeps its original DOMContentLoaded listener. Async response
// consumption can finish after that event; boot exactly once in that case.
if(domReady)root.boot();
}
async function external(url) {
await new Promise((resolve,reject)=>{
const node=doc.createElement('script');node.src=url;
node.onload=()=>{if(domReady&&!appSawDom)root.boot();resolve();};node.onerror=()=>reject(Error('Unable to load player'));
doc.head.append(node);
});
}
async function verified(response, expected) {
const bytes=await response.clone().arrayBuffer();
const digest=new Uint8Array(await root.crypto.subtle.digest('SHA-256',bytes));
const hex=Array.from(digest,b=>b.toString(16).padStart(2,'0')).join('');
if(hex.slice(0,10)!==expected)throw Error('Player body hash mismatch');
return {bytes,digest};
}
async function start() {
if(!manifest.appCache || !root.crypto?.subtle || !root.Lazy.captureApp)return external(key);
const expected=manifest.files['/app.js'].h;
let cache;
try { cache=await root.caches?.open('ytplayer-assets'); } catch {}
let response=await cache?.match(key);
if(!response || response.headers.get('X-Asset-Hash')!==expected) {
try { response=await (root.Lazy.appResponse || root.fetch(key,{credentials:'same-origin'})); } catch {}
}
if(!response?.ok || response.headers.get('X-Asset-Hash')!==expected) {
// Preserve staged N-1 boot after an individual cache eviction. Only the
// controller can serve this old URL safely; never fetch stale URLs bare.
const state=cache && await root.AssetSyncCore.state(cache);
const previousKey=state && root.AssetSyncCore.fallback(manifest,state.previous,'/app.js');
const previous=previousKey && await cache.match(previousKey);
const previousHash=state?.previous?.files['/app.js']?.h;
const contractsMatch=state?.previous && Object.entries(manifest.groups).every(([name,group])=>state.previous.groups[name]?.contract===group.contract);
if(contractsMatch && root.navigator?.serviceWorker?.controller && previous?.ok && previous.headers.get('X-Asset-Hash')===previousHash) {
await verified(previous,previousHash);
delete root.Lazy.appResponse;
return external(previousKey);
}
throw Error('Player asset hash mismatch');
}
const {bytes,digest}=await verified(response,expected);
const permission="'sha256-"+root.btoa(String.fromCharCode(...digest))+"'";
if(!doc.querySelector('meta[http-equiv="Content-Security-Policy"]').content.includes(permission))throw Error('Player body hash mismatch');
// 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.
try { await cache?.put(key,response); } catch(error) { root.console.warn('[app-cache]',error.message); }
const node=doc.createElement('script');node.textContent=new root.TextDecoder().decode(bytes);
execute(node);
}
const ready=start();
root.AppBootstrap={ready};
ready.catch(error=>root.console.error('[app-cache]',error.message));
})(typeof window!=='undefined'?window:globalThis);

View File

@@ -3,18 +3,18 @@ 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,capture=true,controller=true}={}){ function fixture({cached=false,tampered=false,enabled=true,loading=false,quota=false,insecure=false,previous=false,contract=1,capture=true,controller=true,parser=false}={}){
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=[],writes=[],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: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 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 {queueMicrotask(async()=>{if(previous && node.src===key){const state=await held.get('/__ytp_asset_state').clone().json();if(!root.AssetSyncCore.fallback(root.Lazy.manifest,state.previous,'/app.js'))return node.onerror();vm.runInContext(oldSource,context);}else vm.runInContext(node.src===oldKey?oldSource:source,context);node.onload();});}}}}; const doc=root.document={readyState:loading?'loading':'complete',currentScript:parser?{}:null,write:value=>{writes.push(value);vm.runInContext(source,context);},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 {queueMicrotask(async()=>{if(previous && node.src===key){const state=await held.get('/__ytp_asset_state').clone().json();if(!root.AssetSyncCore.fallback(root.Lazy.manifest,state.previous,'/app.js'))return node.onerror();vm.runInContext(oldSource,context);}else vm.runInContext(node.src===oldKey?oldSource:source,context);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('./section-rail.js'),'utf8'),context);
return {root,held,appended,listeners,key,calls:()=>calls,doc,response,oldKey}; return {root,held,appended,writes,listeners,key,calls:()=>calls,doc,response,oldKey};
} }
test('cold boot caches verified app before execution so worker skips its download',async()=>{ test('cold boot caches verified app before execution so worker skips its download',async()=>{
const f=fixture();await f.root.AppBootstrap.ready;assert.equal(f.calls(),1);assert.ok(f.held.has(f.key));assert.equal(f.root.appRuns,1);assert.equal(f.root.bootRuns,1);assert.equal(f.appended[0].src,undefined);assert.equal(f.appended[0].removed,true); const f=fixture();await f.root.AppBootstrap.ready;assert.equal(f.calls(),1);assert.ok(f.held.has(f.key));assert.equal(f.root.appRuns,1);assert.equal(f.root.bootRuns,1);assert.equal(f.appended[0].src,undefined);assert.equal(f.appended[0].removed,true);
@@ -74,3 +74,11 @@ test('native-cache engines still reject an incompatible retained core',async()=>
test('native-cache engines use the validated current cache entry without decoding its body',async()=>{ 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); 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);
}); });
test('native parser boot preserves classic evaluation before the original DOM event',async()=>{
const f=fixture({capture:false,loading:true,parser:true});
assert.equal(f.root.appRuns,1);assert.equal(f.root.bootRuns,undefined);
assert.deepEqual(f.writes,['<script src="'+f.key+'"></script>']);assert.equal(f.appended.length,0);
for(const fn of f.listeners.DOMContentLoaded)fn();await f.root.AppBootstrap.ready;
assert.equal(f.root.bootRuns,1);assert.equal(f.calls(),0);
});

View File

@@ -30,8 +30,8 @@ test('Phase 4 preserves the exact player, continuity, tracking and queue bodies'
test('eager shell and Settings definitions load before app state and defer reading it',()=>{ test('eager shell and Settings definitions load before app state and defer reading it',()=>{
const html=readFileSync(join(__dirname,'index.html'),'utf8'); const html=readFileSync(join(__dirname,'index.html'),'utf8');
for(const file of ['shell-core.js','views-core.js','section-rail.js']) { for(const file of ['shell-core.js','views-core.js','section-rail.js']) {
assert.ok(html.indexOf('src="'+file+'"')<html.indexOf('src="app-bootstrap.js"')); assert.ok(html.indexOf('src="'+file+'"')<=html.indexOf('src="section-rail.js"'));
const vm=require('node:vm');const context=vm.createContext({window:{},document:{}}); const vm=require('node:vm');const doc={readyState:'complete',addEventListener(){},createElement:()=>({}),head:{append(){}}};const globals={document:doc,Lazy:{manifest:{appCache:false},url:()=>'/app.js'},console};globals.window=globals;const context=vm.createContext(globals);
// Evaluation before any data/$/els/Player declaration must be safe. // Evaluation before any data/$/els/Player declaration must be safe.
vm.runInContext(readFileSync(join(__dirname,file),'utf8'),context); vm.runInContext(readFileSync(join(__dirname,file),'utf8'),context);
} }

View File

@@ -1,7 +1,7 @@
{ {
"groups": { "groups": {
"core": { "core": {
"contract": 1, "contract": 2,
"eager": true, "eager": true,
"files": [ "files": [
"/index.html", "/index.html",
@@ -60,8 +60,7 @@
"/device-db.js", "/device-db.js",
"/shell-core.js", "/shell-core.js",
"/views-core.js", "/views-core.js",
"/section-rail.js", "/section-rail.js"
"/app-bootstrap.js"
], ],
"background": false "background": false
}, },

File diff suppressed because one or more lines are too long

View File

@@ -138,7 +138,7 @@
api.appResponse.catch(()=>{}); api.appResponse.catch(()=>{});
} else if(manifest.appCache) { } else if(manifest.appCache) {
// Preserve native script/code-cache reuse in engines without the WebKit gap. // 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); const preload=doc.createElement('link');preload.rel='preload';preload.as='script';preload.fetchPriority='low';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

@@ -102,7 +102,7 @@ test('head capture is cache-first even before a worker controls the page',async(
test('Chromium retains native script loading and only preloads the current app URL',async()=>{ 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'); 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.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'); 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');assert.equal(f.inserted[0].fetchPriority,'low');
}); });
test('iOS Chromium-branded browsers still use their WebKit response capture',async()=>{ 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; const f=fixture(null,[],true,'AppleWebKit/605.1 CriOS/140.0 Mobile Safari/605.1');await f.root.Lazy.appResponse;
@@ -112,5 +112,5 @@ test('iOS Chromium-branded browsers still use their WebKit response capture',asy
test('controlled WebKit visits use worker-cached native app scripts without a response handoff',async()=>{ test('controlled WebKit visits use worker-cached native app scripts without a response handoff',async()=>{
const f=fixture(null,[],true,'AppleWebKit/605.1 Safari/605.1',true); const f=fixture(null,[],true,'AppleWebKit/605.1 Safari/605.1',true);
assert.equal(f.root.Lazy.captureApp,false);assert.equal(f.root.Lazy.appResponse,undefined);assert.deepEqual(f.fetched,[]); assert.equal(f.root.Lazy.captureApp,false);assert.equal(f.root.Lazy.appResponse,undefined);assert.deepEqual(f.fetched,[]);
assert.equal(f.inserted[0].as,'script');assert.equal(f.inserted[0].href,'/app.js?v=a'); assert.equal(f.inserted[0].as,'script');assert.equal(f.inserted[0].href,'/app.js?v=a');assert.equal(f.inserted[0].fetchPriority,'low');
}); });

View File

@@ -245,3 +245,77 @@ const SectionRail = (() => {
return { boot, refresh, setFolded, isFolded }; return { boot, refresh, setFolded, isFolded };
})(); })();
/* Cache the single page-owned app response before it registers the worker. */
(function (root) {
'use strict';
const doc=root.document, manifest=root.Lazy.manifest, key=root.Lazy.url('/app.js');
let domReady=doc.readyState==='complete', appSawDom=false;
doc.addEventListener('DOMContentLoaded',()=>{domReady=true;appSawDom=typeof root.boot==='function';},{once:true});
function execute(node) {
doc.head.append(node);
node.remove();
delete root.Lazy.appResponse;
// app.js keeps its original DOMContentLoaded listener. Async response
// consumption can finish after that event; boot exactly once in that case.
if(domReady)root.boot();
}
async function external(url) {
await new Promise((resolve,reject)=>{
const node=doc.createElement('script');node.src=url;
node.onload=()=>{if(domReady&&!appSawDom)root.boot();resolve();};node.onerror=()=>reject(Error('Unable to load player'));
doc.head.append(node);
});
}
function native() {
if(doc.readyState!=='loading' || !doc.currentScript)return external(key);
return new Promise((resolve,reject)=>{
doc.addEventListener('DOMContentLoaded',()=>typeof root.boot==='function'?resolve():reject(Error('Unable to load player')),{once:true});
const escaped=key.replaceAll('&','&amp;').replaceAll('"','&quot;').replaceAll('<','&lt;');
doc.write('<script src="'+escaped+'"></script>');
});
}
async function verified(response, expected) {
const bytes=await response.clone().arrayBuffer();
const digest=new Uint8Array(await root.crypto.subtle.digest('SHA-256',bytes));
const hex=Array.from(digest,b=>b.toString(16).padStart(2,'0')).join('');
if(hex.slice(0,10)!==expected)throw Error('Player body hash mismatch');
return {bytes,digest};
}
async function start() {
if(!manifest.appCache || !root.crypto?.subtle || !root.Lazy.captureApp)return native();
const expected=manifest.files['/app.js'].h;
let cache;
try { cache=await root.caches?.open('ytplayer-assets'); } catch {}
let response=await cache?.match(key);
if(!response || response.headers.get('X-Asset-Hash')!==expected) {
try { response=await (root.Lazy.appResponse || root.fetch(key,{credentials:'same-origin'})); } catch {}
}
if(!response?.ok || response.headers.get('X-Asset-Hash')!==expected) {
// Preserve staged N-1 boot after an individual cache eviction. Only the
// controller can serve this old URL safely; never fetch stale URLs bare.
const state=cache && await root.AssetSyncCore.state(cache);
const previousKey=state && root.AssetSyncCore.fallback(manifest,state.previous,'/app.js');
const previous=previousKey && await cache.match(previousKey);
const previousHash=state?.previous?.files['/app.js']?.h;
const contractsMatch=state?.previous && Object.entries(manifest.groups).every(([name,group])=>state.previous.groups[name]?.contract===group.contract);
if(contractsMatch && root.navigator?.serviceWorker?.controller && previous?.ok && previous.headers.get('X-Asset-Hash')===previousHash) {
await verified(previous,previousHash);
delete root.Lazy.appResponse;
return external(previousKey);
}
throw Error('Player asset hash mismatch');
}
const {bytes,digest}=await verified(response,expected);
const permission="'sha256-"+root.btoa(String.fromCharCode(...digest))+"'";
if(!doc.querySelector('meta[http-equiv="Content-Security-Policy"]').content.includes(permission))throw Error('Player body hash mismatch');
// 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.
try { await cache?.put(key,response); } catch(error) { root.console.warn('[app-cache]',error.message); }
const node=doc.createElement('script');node.textContent=new root.TextDecoder().decode(bytes);
execute(node);
}
const ready=start();
root.AppBootstrap={ready};
ready.catch(error=>root.console.error('[app-cache]',error.message));
})(typeof window!=='undefined'?window:globalThis);

View File

@@ -32,7 +32,7 @@ test('assets.json describes the complete existing shell exactly once with eager
const { groups } = JSON.parse(readFileSync(join(__dirname, 'assets.json'), 'utf8')); const { groups } = JSON.parse(readFileSync(join(__dirname, 'assets.json'), 'utf8'));
const described = new Set(); const described = new Set();
for (const [name, group] of Object.entries(groups)) { for (const [name, group] of Object.entries(groups)) {
assert.equal(group.contract, 1, `${name} initial contract`); assert.equal(group.contract, name === 'core' ? 2 : 1, `${name} contract (core boot packing changed)`);
assert.equal(typeof group.eager, 'boolean'); assert.equal(typeof group.eager, 'boolean');
for (const asset of group.files) { for (const asset of group.files) {
assert.ok(!described.has(asset), `${asset} has one group`); assert.ok(!described.has(asset), `${asset} has one group`);
@@ -100,12 +100,15 @@ test('classic first-visit style preload is discovered before the head loader wit
}); });
test('the eager cache bootstrap stays last and retains app.js in core and legacy shell',()=>{ test('the last core script owns boot without an extra request or unsafe stale packing',()=>{
const {groups}=JSON.parse(readFileSync(join(__dirname,'assets.json'),'utf8')); const {groups}=JSON.parse(readFileSync(join(__dirname,'assets.json'),'utf8'));
assert.ok(groups.core.files.includes('/app-bootstrap.js')); assert.equal(groups.core.contract,2,'old core packing cannot fall back without the loader');
assert.ok(groups.core.files.includes('/app.js')); assert.ok(groups.core.files.includes('/app.js'));
assert.ok(listed.has('/app-bootstrap.js')&&listed.has('/app.js')); assert.ok(groups.core.files.includes('/section-rail.js'));
assert.ok(html.indexOf('src="app-bootstrap.js"')>html.indexOf('src="section-rail.js"')); assert.equal(groups.core.files.includes('/app-bootstrap.js'),false);
assert.equal(listed.has('/app-bootstrap.js'),false);
const scripts=[...html.matchAll(/<script src="([^"]+)"/g)].map(m=>m[1]);
assert.equal(scripts.at(-1),'section-rail.js');
assert.ok(html.includes("'sha256-__APP_SCRIPT_HASH__'")); assert.ok(html.includes("'sha256-__APP_SCRIPT_HASH__'"));
assert.ok(readFileSync(join(__dirname,'lazy.js'),'utf8').includes("api.appResponse=(async()=>")); assert.ok(readFileSync(join(__dirname,'section-rail.js'),'utf8').includes('root.AppBootstrap={ready}'));
}); });

View File

@@ -146,7 +146,6 @@ const SHELL = [
'/share-external.js', '/share-external.js',
'/stats-view.js', '/stats-view.js',
'/app.js', '/app.js',
'/app-bootstrap.js',
'/manifest.webmanifest', '/manifest.webmanifest',
'/icons/icon-192.png', '/icons/icon-192.png',
'/icons/icon-512.png', '/icons/icon-512.png',

View File

@@ -2,7 +2,7 @@
"results": [ "results": [
{ {
"browser": "chromium", "browser": "chromium",
"sourceBuildTag": "38131d1a6b5e", "sourceBuildTag": "31617662d2f0",
"classicSharedDefaults": true, "classicSharedDefaults": true,
"cssOrderPreserved": true, "cssOrderPreserved": true,
"settingsSearch": true, "settingsSearch": true,
@@ -28,7 +28,7 @@
}, },
{ {
"browser": "webkit", "browser": "webkit",
"sourceBuildTag": "38131d1a6b5e", "sourceBuildTag": "31617662d2f0",
"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:43269/api/recommendations?fp=163577c02a53bc40.", "message": "/127.0.0.1:38489/api/version.",
"disconnected": true "disconnected": true
}, },
{ {
@@ -64,7 +64,7 @@
"disconnected": true "disconnected": true
}, },
{ {
"message": "/127.0.0.1:43269/api/version.", "message": "/127.0.0.1:38489/api/recommendations?fp=163577c02a53bc40.",
"disconnected": true "disconnected": true
}, },
{ {
@@ -72,7 +72,7 @@
"disconnected": true "disconnected": true
}, },
{ {
"message": "/127.0.0.1:43269/api/version.", "message": "/127.0.0.1:38489/api/version.",
"disconnected": true "disconnected": true
}, },
{ {
@@ -80,7 +80,7 @@
"disconnected": true "disconnected": true
}, },
{ {
"message": "/127.0.0.1:43269/api/recommendations?fp=163577c02a53bc40.", "message": "/127.0.0.1:38489/api/recommendations?fp=163577c02a53bc40.",
"disconnected": true "disconnected": true
}, },
{ {
@@ -88,7 +88,7 @@
"disconnected": true "disconnected": true
}, },
{ {
"message": "/127.0.0.1:43269/api/version.", "message": "/127.0.0.1:38489/api/version.",
"disconnected": true "disconnected": true
} }
] ]

View File

@@ -41,9 +41,9 @@ and is diagnostic only; the shipping fix must retain selected-layout behavior.
The modern synchronizer already uses default fetch caching. Changing it to The modern synchronizer already uses default fetch caching. Changing it to
force-cache has no effect: every WebKit sample still transfers app.js twice. force-cache has no effect: every WebKit sample still transfers app.js twice.
The page and worker cannot reuse these HTTP-cache responses in this harness. The page and worker cannot reuse these HTTP-cache responses in this harness.
A same-build page handoff can reuse the page HTTP cache instead. Worker validation The next hypothesis was a same-build page handoff reusing the page HTTP cache.
must check both the X-Asset-Hash header and SHA-256 of the decoded bytes. It must The prototype checked both the X-Asset-Hash header and decoded-body SHA-256, and
strip compressed transport headers before constructing the cached Response. stripped compressed transport headers before constructing the cached Response.
Updates and legacy migration must continue through their existing worker path. Updates and legacy migration must continue through their existing worker path.
The shipping CSS fix is a preload, not an unconditional stylesheet. Its three-run The shipping CSS fix is a preload, not an unconditional stylesheet. Its three-run
@@ -113,3 +113,25 @@ visits. Controlled warm/offline/update pages use the worker's native exact-URL
script path, including its strengthened contract guard. A failing head-loader script path, including its strengthened contract guard. A failing head-loader
test precedes this change; all 222 frontend and 184 server tests and both-engine test precedes this change; all 222 frontend and 184 server tests and both-engine
lazy smoke pass. Cold capture and both rollback flags are unchanged. lazy smoke pass. Cold capture and both rollback flags are unchanged.
The final native-path review caught a new Chromium paint regression: five-run
FCP 1616/2328 ms (LTE/lossy), despite faster boot. A contemporary Phase 5 LTE
control measured 1360 ms over three runs. Lowering the new app preload priority
reduced FCP to 1396 ms (three runs). Native parser execution restores app's
original DOMContentLoaded boot boundary. A rejected no-preload controlled-page
trial worsened WebKit warm boot to 98 ms; native cache preloading remains.
The standalone helper adds a CacheStorage script read to native startup. Packing
the loader into the existing final core script, section-rail.js, measured Chrome
warm 425 ms versus contemporary Phase 5 426 ms (five runs); WebKit measured
95 ms versus 88 ms, so zero warm overhead is not yet established. The merged
three-run Chrome LTE cold trial measured FCP 1384 ms and boot 6512 ms. Core's
contract becomes 2 because an old section script lacks this loader and cannot
safely substitute for it. No Phase 5 SHELL file is removed; the experimental
Phase 6 standalone helper is removed. Native-order, exact final-script packing
and contract tests precede implementation. All 223 frontend and 184 server tests
and both-engine lazy/offline/eviction/pinning/contracts/playback smoke pass.
The intermediate native-auto-priority full dataset is retained as a rejected
trial; it is not the final acceptance result. Source directories named in trial
JSONs are dirty diagnostic worktrees of 9408ee0 with exactly the changes above.