Resume offline preparation on launch and show cache readiness
This commit is contained in:
@@ -8690,11 +8690,11 @@ async function applyUpdate(target, { quiet = false } = {}) {
|
|||||||
// When the waiting worker already downloaded this build in the
|
// When the waiting worker already downloaded this build in the
|
||||||
// background, the toast says so and nothing is re-downloaded.
|
// background, the toast says so and nothing is re-downloaded.
|
||||||
askStatus: (worker) => SU.askCacheStatus(worker, { timeoutMs: 4000, setTimeout: (fn, ms) => setTimeout(fn, ms) }),
|
askStatus: (worker) => SU.askCacheStatus(worker, { timeoutMs: 4000, setTimeout: (fn, ms) => setTimeout(fn, ms) }),
|
||||||
onPhase: (phase) => {
|
onPhase: (phase, status) => {
|
||||||
if (quiet) return;
|
if (quiet) return;
|
||||||
if (_updateToast) _updateToast.remove();
|
if (_updateToast) _updateToast.remove();
|
||||||
_updateToast = phase === 'ready'
|
_updateToast = phase === 'ready'
|
||||||
? toast('Applying the update…', { duration: 20000 })
|
? toast(status?.offlineReady === false ? `Applying the update… Offline cache is still preparing (${status.cached}/${status.total}).` : 'Applying the update…', { duration: 20000 })
|
||||||
: toast('Downloading the update…', { duration: 305000 });
|
: toast('Downloading the update…', { duration: 305000 });
|
||||||
},
|
},
|
||||||
refreshShell: ('caches' in window) ? () => SU.refreshShellInPlace({
|
refreshShell: ('caches' in window) ? () => SU.refreshShellInPlace({
|
||||||
|
|||||||
@@ -100,9 +100,10 @@
|
|||||||
else root.navigator.serviceWorker?.getRegistration().then(send).catch(()=>{});
|
else root.navigator.serviceWorker?.getRegistration().then(send).catch(()=>{});
|
||||||
},
|
},
|
||||||
warm() {
|
warm() {
|
||||||
if(root.navigator.connection?.saveData || !root.navigator.serviceWorker) return;
|
if (!root.navigator.serviceWorker || root.navigator.onLine === false) return;
|
||||||
const idle = root.requestIdleCallback || (fn => root.setTimeout(fn,1000));
|
root.navigator.serviceWorker.ready.then(reg => {
|
||||||
idle(()=>root.navigator.serviceWorker.ready.then(reg=>reg.active?.postMessage({type:'WARM_ASSETS',saveData:!!root.navigator.connection?.saveData})).catch(()=>{}));
|
for (const worker of new Set([reg.active, reg.waiting])) worker?.postMessage({type:'COMPLETE_ASSETS'});
|
||||||
|
}).catch(()=>{});
|
||||||
},
|
},
|
||||||
onLoad(name, fn) {
|
onLoad(name, fn) {
|
||||||
if(api.loaded(name)) { Promise.resolve().then(fn).catch(error=>{if(root.CustomEvent)root.dispatchEvent?.(new root.CustomEvent('ytp-lazy-error',{detail:{group:name,error}}));}); return; }
|
if(api.loaded(name)) { Promise.resolve().then(fn).catch(error=>{if(root.CustomEvent)root.dispatchEvent?.(new root.CustomEvent('ytp-lazy-error',{detail:{group:name,error}}));}); return; }
|
||||||
@@ -128,6 +129,8 @@
|
|||||||
if(event.data?.type==='SW_UPDATE_AVAILABLE') api.warm();
|
if(event.data?.type==='SW_UPDATE_AVAILABLE') api.warm();
|
||||||
});
|
});
|
||||||
root.navigator.serviceWorker?.addEventListener('controllerchange',()=>{api.reportLayout();api.warm();});
|
root.navigator.serviceWorker?.addEventListener('controllerchange',()=>{api.reportLayout();api.warm();});
|
||||||
|
root.addEventListener?.('online',()=>api.warm());
|
||||||
|
api.warm();
|
||||||
let settings={};
|
let settings={};
|
||||||
try { settings=JSON.parse(root.localStorage.getItem('_ytpdata') || '{}').settings || {}; api.layout=settings.layout || 'classic'; } catch {}
|
try { settings=JSON.parse(root.localStorage.getItem('_ytpdata') || '{}').settings || {}; api.layout=settings.layout || 'classic'; } catch {}
|
||||||
Object.assign(doc.documentElement.dataset,{layout:api.layout,theme:settings.theme || 'dark',font:settings.fontScale || 'normal',density:settings.density || 'comfortable',perf:settings.perfMode?'on':'off',motion:settings.reduceMotion?'reduced':'full'});
|
Object.assign(doc.documentElement.dataset,{layout:api.layout,theme:settings.theme || 'dark',font:settings.fontScale || 'normal',density:settings.density || 'comfortable',perf:settings.perfMode?'on':'off',motion:settings.reduceMotion?'reduced':'full'});
|
||||||
|
|||||||
@@ -84,3 +84,14 @@ test('classic chunk registry proxies use window.YT and keep the executed singlet
|
|||||||
const singleton=root.YT.TestApi;await root.Lazy.load('feature:test');
|
const singleton=root.YT.TestApi;await root.Lazy.load('feature:test');
|
||||||
assert.equal(root.YT.TestApi,singleton);assert.equal(inserted.length,2);
|
assert.equal(root.YT.TestApi,singleton);assert.equal(inserted.length,2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('online launches request worker completion even with Save-Data and skip offline messages',async()=>{
|
||||||
|
const f=fixture(),sent=[];
|
||||||
|
const active={postMessage:m=>sent.push(m)},waiting={postMessage:m=>sent.push(m)};
|
||||||
|
f.root.navigator.connection={saveData:true};f.root.navigator.onLine=true;
|
||||||
|
f.root.navigator.serviceWorker={ready:Promise.resolve({active,waiting})};
|
||||||
|
f.root.Lazy.warm();await Promise.resolve();
|
||||||
|
assert.deepEqual(sent.map(m=>m.type),['COMPLETE_ASSETS','COMPLETE_ASSETS']);
|
||||||
|
f.root.navigator.onLine=false;f.root.Lazy.warm();await Promise.resolve();assert.equal(sent.length,2);
|
||||||
|
});
|
||||||
|
|||||||
@@ -250,14 +250,15 @@
|
|||||||
// Opt-in: without an askStatus the download path runs exactly as before.
|
// Opt-in: without an askStatus the download path runs exactly as before.
|
||||||
// (It must never be implicit — a probe that waits on a reply would hang
|
// (It must never be implicit — a probe that waits on a reply would hang
|
||||||
// forever if the caller injected a setTimeout that never fires.)
|
// forever if the caller injected a setTimeout that never fires.)
|
||||||
let precached = false;
|
let precached = false, offlineStatus;
|
||||||
if (waiting && askStatus) {
|
if (waiting && askStatus) {
|
||||||
try {
|
try {
|
||||||
const status = await askStatus(waiting);
|
const status = await askStatus(waiting);
|
||||||
precached = !!(status && status.ready);
|
precached = !!(status && status.ready);
|
||||||
|
offlineStatus = status;
|
||||||
} catch { precached = false; }
|
} catch { precached = false; }
|
||||||
}
|
}
|
||||||
if (onPhase) onPhase(precached ? 'ready' : 'downloading');
|
if (onPhase) onPhase(precached ? 'ready' : 'downloading', offlineStatus);
|
||||||
if (!precached && refreshShell) await refreshShell();
|
if (!precached && refreshShell) await refreshShell();
|
||||||
|
|
||||||
if (!guard()) throw new Error('Playing - update after this song / when paused');
|
if (!guard()) throw new Error('Playing - update after this song / when paused');
|
||||||
@@ -280,7 +281,25 @@
|
|||||||
waiting.postMessage({ type: 'SKIP_WAITING' });
|
waiting.postMessage({ type: 'SKIP_WAITING' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const SwUpdate = { applyUpdate, refreshShellInPlace, askCacheStatus, BUST_PARAM };
|
function showOfflineStatus(status) {
|
||||||
|
const el = root.document?.getElementById('offlineAssetStatus');
|
||||||
|
if (el && typeof status?.total === 'number') {
|
||||||
|
el.textContent = `${status.offlineReady ? 'Offline ready' : 'Preparing offline'} ${status.cached}/${status.total}`;
|
||||||
|
} else if (el && typeof status?.ready === 'boolean') {
|
||||||
|
el.textContent = status.ready ? 'Offline ready (legacy cache)' : 'Offline cache incomplete (legacy mode)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function offlineStatus() {
|
||||||
|
const registration = await root.navigator?.serviceWorker?.getRegistration();
|
||||||
|
const status = await askCacheStatus(registration?.active);
|
||||||
|
showOfflineStatus(status);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
root.navigator?.serviceWorker?.addEventListener?.('message', event => {
|
||||||
|
if (event.data?.type === 'OFFLINE_STATUS') showOfflineStatus(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const SwUpdate = { offlineStatus, showOfflineStatus, applyUpdate, refreshShellInPlace, askCacheStatus, BUST_PARAM };
|
||||||
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
module.exports = SwUpdate;
|
module.exports = SwUpdate;
|
||||||
|
|||||||
@@ -368,3 +368,22 @@ test('an unanswered worker status cannot fall through to an empty legacy refresh
|
|||||||
vm.runInNewContext(fs.readFileSync(__dirname+'/sw-update.js','utf8'),sandbox);
|
vm.runInNewContext(fs.readFileSync(__dirname+'/sw-update.js','utf8'),sandbox);
|
||||||
await assert.rejects(sandbox.SwUpdate.refreshShellInPlace({cachesApi:{open:async()=>({}),keys:async()=>[]},fetchFn:async()=>({ok:true,json:async()=>({buildTag:'new'})}),isShellCache:()=>true}),/Worker update status unavailable/);
|
await assert.rejects(sandbox.SwUpdate.refreshShellInPlace({cachesApi:{open:async()=>({}),keys:async()=>[]},fetchFn:async()=>({ok:true,json:async()=>({buildTag:'new'})}),isShellCache:()=>true}),/Worker update status unavailable/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('update readiness retains completion progress without blocking the ready fast path',async()=>{
|
||||||
|
const phases=[],container=fakeContainer();let downloaded=false;
|
||||||
|
const status={ready:true,offlineReady:false,cached:90,total:151,missingFiles:['/optional.js']};
|
||||||
|
await applyUpdate({reg:{waiting:{postMessage(){}}},container,askStatus:async()=>status,
|
||||||
|
onPhase:(phase,progress)=>phases.push({phase,progress}),refreshShell:async()=>{downloaded=true;},setTimeout:()=>{}});
|
||||||
|
assert.equal(downloaded,false);assert.equal(phases[0].phase,'ready');assert.deepStrictEqual(phases[0].progress,status);
|
||||||
|
});
|
||||||
|
test('offline cache status reports preparing and complete counts in Settings',()=>{
|
||||||
|
const vm=require('node:vm'),fs=require('node:fs'),element={};
|
||||||
|
const sandbox={document:{getElementById:()=>element}};
|
||||||
|
vm.runInNewContext(fs.readFileSync(__dirname+'/sw-update.js','utf8'),sandbox);
|
||||||
|
sandbox.SwUpdate.showOfflineStatus({offlineReady:false,cached:90,total:151});
|
||||||
|
assert.equal(element.textContent,'Preparing offline 90/151');
|
||||||
|
sandbox.SwUpdate.showOfflineStatus({offlineReady:true,cached:151,total:151});
|
||||||
|
assert.equal(element.textContent,'Offline ready 151/151');
|
||||||
|
sandbox.SwUpdate.showOfflineStatus({ready:true});assert.equal(element.textContent,'Offline ready (legacy cache)');
|
||||||
|
});
|
||||||
|
|||||||
@@ -843,6 +843,7 @@ async function renderSettings() {
|
|||||||
|
|
||||||
<div class="set-group">
|
<div class="set-group">
|
||||||
<div class="set-group-title">Offline cache</div>
|
<div class="set-group-title">Offline cache</div>
|
||||||
|
<div class="set-row"><span>App & features</span><small id="offlineAssetStatus" role="status">Checking offline readiness…</small></div>
|
||||||
<label class="set-row">
|
<label class="set-row">
|
||||||
<span>
|
<span>
|
||||||
Auto-save playlist videos
|
Auto-save playlist videos
|
||||||
@@ -1186,6 +1187,8 @@ async function renderSettings() {
|
|||||||
fold.addEventListener('toggle', () => { try { localStorage.setItem('ytpCacheFoldOpen', fold.open ? '1' : '0'); } catch { /* blocked */ } });
|
fold.addEventListener('toggle', () => { try { localStorage.setItem('ytpCacheFoldOpen', fold.open ? '1' : '0'); } catch { /* blocked */ } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.SwUpdate?.offlineStatus().catch(() => {});
|
||||||
|
|
||||||
// ---- Populate live cache stats ----
|
// ---- Populate live cache stats ----
|
||||||
try {
|
try {
|
||||||
const res = await API.cacheList();
|
const res = await API.cacheList();
|
||||||
|
|||||||
Reference in New Issue
Block a user