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
|
||||
// background, the toast says so and nothing is re-downloaded.
|
||||
askStatus: (worker) => SU.askCacheStatus(worker, { timeoutMs: 4000, setTimeout: (fn, ms) => setTimeout(fn, ms) }),
|
||||
onPhase: (phase) => {
|
||||
onPhase: (phase, status) => {
|
||||
if (quiet) return;
|
||||
if (_updateToast) _updateToast.remove();
|
||||
_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 });
|
||||
},
|
||||
refreshShell: ('caches' in window) ? () => SU.refreshShellInPlace({
|
||||
|
||||
@@ -100,9 +100,10 @@
|
||||
else root.navigator.serviceWorker?.getRegistration().then(send).catch(()=>{});
|
||||
},
|
||||
warm() {
|
||||
if(root.navigator.connection?.saveData || !root.navigator.serviceWorker) return;
|
||||
const idle = root.requestIdleCallback || (fn => root.setTimeout(fn,1000));
|
||||
idle(()=>root.navigator.serviceWorker.ready.then(reg=>reg.active?.postMessage({type:'WARM_ASSETS',saveData:!!root.navigator.connection?.saveData})).catch(()=>{}));
|
||||
if (!root.navigator.serviceWorker || root.navigator.onLine === false) return;
|
||||
root.navigator.serviceWorker.ready.then(reg => {
|
||||
for (const worker of new Set([reg.active, reg.waiting])) worker?.postMessage({type:'COMPLETE_ASSETS'});
|
||||
}).catch(()=>{});
|
||||
},
|
||||
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; }
|
||||
@@ -128,6 +129,8 @@
|
||||
if(event.data?.type==='SW_UPDATE_AVAILABLE') api.warm();
|
||||
});
|
||||
root.navigator.serviceWorker?.addEventListener('controllerchange',()=>{api.reportLayout();api.warm();});
|
||||
root.addEventListener?.('online',()=>api.warm());
|
||||
api.warm();
|
||||
let settings={};
|
||||
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'});
|
||||
|
||||
@@ -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');
|
||||
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.
|
||||
// (It must never be implicit — a probe that waits on a reply would hang
|
||||
// forever if the caller injected a setTimeout that never fires.)
|
||||
let precached = false;
|
||||
let precached = false, offlineStatus;
|
||||
if (waiting && askStatus) {
|
||||
try {
|
||||
const status = await askStatus(waiting);
|
||||
precached = !!(status && status.ready);
|
||||
offlineStatus = status;
|
||||
} catch { precached = false; }
|
||||
}
|
||||
if (onPhase) onPhase(precached ? 'ready' : 'downloading');
|
||||
if (onPhase) onPhase(precached ? 'ready' : 'downloading', offlineStatus);
|
||||
if (!precached && refreshShell) await refreshShell();
|
||||
|
||||
if (!guard()) throw new Error('Playing - update after this song / when paused');
|
||||
@@ -280,7 +281,25 @@
|
||||
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) {
|
||||
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);
|
||||
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-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">
|
||||
<span>
|
||||
Auto-save playlist videos
|
||||
@@ -1186,6 +1187,8 @@ async function renderSettings() {
|
||||
fold.addEventListener('toggle', () => { try { localStorage.setItem('ytpCacheFoldOpen', fold.open ? '1' : '0'); } catch { /* blocked */ } });
|
||||
}
|
||||
|
||||
window.SwUpdate?.offlineStatus().catch(() => {});
|
||||
|
||||
// ---- Populate live cache stats ----
|
||||
try {
|
||||
const res = await API.cacheList();
|
||||
|
||||
Reference in New Issue
Block a user