Limit the WebKit response handoff to uncontrolled visits
This commit is contained in:
@@ -125,7 +125,7 @@
|
|||||||
// 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.
|
||||||
const agent=root.navigator.userAgent || '';
|
const agent=root.navigator.userAgent || '';
|
||||||
api.captureApp=!!(manifest.appCache && root.crypto?.subtle && /AppleWebKit\//.test(agent) && !/(?:Chrome|Chromium|Edg|OPR)\//.test(agent));
|
api.captureApp=!!(manifest.appCache && !root.navigator.serviceWorker?.controller && root.crypto?.subtle && /AppleWebKit\//.test(agent) && !/(?:Chrome|Chromium|Edg|OPR)\//.test(agent));
|
||||||
if(api.captureApp) {
|
if(api.captureApp) {
|
||||||
api.appResponse=(async()=>{
|
api.appResponse=(async()=>{
|
||||||
const key=url('/app.js');
|
const key=url('/app.js');
|
||||||
|
|||||||
@@ -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, userAgent = 'AppleWebKit/605.1 Safari/605.1') {
|
function fixture(current, held = [], appCache = false, userAgent = 'AppleWebKit/605.1 Safari/605.1', controlled = false) {
|
||||||
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:{userAgent}, 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,serviceWorker:{controller:controlled?{}:null,addEventListener(){}}}, 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);
|
||||||
@@ -108,3 +108,9 @@ test('iOS Chromium-branded browsers still use their WebKit response capture',asy
|
|||||||
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;
|
||||||
assert.equal(f.root.Lazy.captureApp,true);assert.deepEqual(f.fetched,['/app.js?v=a']);
|
assert.equal(f.root.Lazy.captureApp,true);assert.deepEqual(f.fetched,['/app.js?v=a']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"results": [
|
"results": [
|
||||||
{
|
{
|
||||||
"browser": "chromium",
|
"browser": "chromium",
|
||||||
"sourceBuildTag": "22eb7414f94c",
|
"sourceBuildTag": "38131d1a6b5e",
|
||||||
"classicSharedDefaults": true,
|
"classicSharedDefaults": true,
|
||||||
"cssOrderPreserved": true,
|
"cssOrderPreserved": true,
|
||||||
"settingsSearch": true,
|
"settingsSearch": true,
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"browser": "webkit",
|
"browser": "webkit",
|
||||||
"sourceBuildTag": "22eb7414f94c",
|
"sourceBuildTag": "38131d1a6b5e",
|
||||||
"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:33445/api/version.",
|
"message": "/127.0.0.1:43269/api/recommendations?fp=163577c02a53bc40.",
|
||||||
"disconnected": true
|
"disconnected": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
"disconnected": true
|
"disconnected": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"message": "/127.0.0.1:33445/api/recommendations?fp=163577c02a53bc40.",
|
"message": "/127.0.0.1:43269/api/version.",
|
||||||
"disconnected": true
|
"disconnected": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"disconnected": true
|
"disconnected": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"message": "/127.0.0.1:33445/api/version.",
|
"message": "/127.0.0.1:43269/api/version.",
|
||||||
"disconnected": true
|
"disconnected": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"disconnected": true
|
"disconnected": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"message": "/127.0.0.1:33445/api/recommendations?fp=163577c02a53bc40.",
|
"message": "/127.0.0.1:43269/api/recommendations?fp=163577c02a53bc40.",
|
||||||
"disconnected": true
|
"disconnected": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
"disconnected": true
|
"disconnected": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"message": "/127.0.0.1:33445/api/version.",
|
"message": "/127.0.0.1:43269/api/version.",
|
||||||
"disconnected": true
|
"disconnected": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -106,3 +106,10 @@ CacheStorage or hashing work. WebKit's byte-verified owned response path is
|
|||||||
unchanged. The native fixture models the actual worker fallback decision instead
|
unchanged. The native fixture models the actual worker fallback decision instead
|
||||||
of a second page-side decision. All 221 frontend tests and 184 server tests pass;
|
of a second page-side decision. All 221 frontend tests and 184 server tests pass;
|
||||||
both-engine lazy/offline/eviction/pinning/contracts/playback smoke passes.
|
both-engine lazy/offline/eviction/pinning/contracts/playback smoke passes.
|
||||||
|
|
||||||
|
The WebKit warm prototype also paid repeated handoff/cache/hash work (warm
|
||||||
|
~98 ms versus Phase 5 83 ms). Capture now applies only to uncontrolled WebKit
|
||||||
|
visits. Controlled warm/offline/update pages use the worker's native exact-URL
|
||||||
|
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
|
||||||
|
lazy smoke pass. Cold capture and both rollback flags are unchanged.
|
||||||
|
|||||||
Reference in New Issue
Block a user