97 lines
4.8 KiB
JavaScript
97 lines
4.8 KiB
JavaScript
/* Build-local classic-script loader. The inert manifest precedes this head script. */
|
|
(function (root) {
|
|
'use strict';
|
|
const doc = root.document;
|
|
let manifest;
|
|
try { manifest = JSON.parse(doc.getElementById('ytp-assets').textContent); }
|
|
catch { return; } // Native/static shells retain their eager script path.
|
|
const assets = new Map(), groups = new Map(), hooks = new Map();
|
|
const css = new Set([...doc.querySelectorAll('link[data-lazy-css]')].map(link => link.dataset.lazyCss));
|
|
const selected = new Map();
|
|
const url = path => selected.get(path) || (manifest.files[path]?.h ? path + '?v=' + manifest.files[path].h : path);
|
|
function reloadRequired(name) {
|
|
api.reloadRequired = true;
|
|
if (root.CustomEvent) root.dispatchEvent?.(new root.CustomEvent('ytp-reload-required', { detail: { group: name } }));
|
|
}
|
|
async function select(name) {
|
|
if (!root.caches) return;
|
|
try {
|
|
const cache = await root.caches.open('ytplayer-assets');
|
|
const response = await cache.match('/__ytp_asset_state');
|
|
const current = response && (await response.json()).current;
|
|
const built = manifest.groups[name], newer = current?.groups[name];
|
|
if (!newer || current.buildTag === manifest.buildTag) return;
|
|
if (newer.contract !== built.contract) { reloadRequired(name); return; }
|
|
for (const path of built.files) {
|
|
// Membership and contract must both match the running core's expectation.
|
|
if (!newer.files.includes(path) || !current.files[path]) continue;
|
|
const key = path + '?v=' + current.files[path].h;
|
|
const held = await cache.match(key);
|
|
if (held?.headers.get('X-Asset-Hash') === current.files[path].h) selected.set(path, key);
|
|
}
|
|
} catch { /* Private browsing/cache eviction: keep the embedded build-local map. */ }
|
|
}
|
|
function inject(path) {
|
|
if (assets.has(path)) return assets.get(path);
|
|
const style = /\.css$/.test(path);
|
|
if (style && css.has(path)) return Promise.resolve();
|
|
if (!style && !/\.js$/.test(path)) return Promise.resolve(); // workers/imports/fonts are not page scripts
|
|
const task = new Promise((resolve, reject) => {
|
|
const node = doc.createElement(style ? 'link' : 'script');
|
|
if (style) { node.rel = 'stylesheet'; node.href = url(path); }
|
|
else { node.async = false; node.src = url(path); }
|
|
node.onload = resolve;
|
|
node.onerror = () => reject(new Error('Unable to load ' + path));
|
|
doc.head.append(node);
|
|
});
|
|
assets.set(path, task);
|
|
task.catch(() => assets.delete(path));
|
|
return task;
|
|
}
|
|
function load(name) {
|
|
if (groups.has(name)) return groups.get(name);
|
|
const group = manifest.groups[name];
|
|
if (!group) return Promise.reject(new Error('Unknown asset group: ' + name));
|
|
const task = (async () => {
|
|
for (const dependency of group.dependencies || []) await load(dependency);
|
|
await select(name);
|
|
for (const path of group.files) if (!/-worker\.js$/.test(path)) await inject(path);
|
|
for (const fn of hooks.get(name) || []) await fn();
|
|
group.executed = true;
|
|
})();
|
|
groups.set(name, task);
|
|
task.catch(error => {
|
|
groups.delete(name);
|
|
if (root.CustomEvent) root.dispatchEvent?.(new root.CustomEvent('ytp-lazy-error', { detail: { group: name, error } }));
|
|
});
|
|
return task;
|
|
}
|
|
const api = {
|
|
load, prefetch: load, loaded: name => !!manifest.groups[name]?.executed, url,
|
|
manifest, layout: 'classic', reloadRequired: false,
|
|
onLoad(name, fn) { const list = hooks.get(name) || []; list.push(fn); hooks.set(name, list); },
|
|
proxy(name, methods, globalName) {
|
|
return Object.fromEntries(methods.map(method => [method, async (...args) => {
|
|
await load(name);
|
|
const target = root[globalName || name.split(':')[1]];
|
|
if (typeof target?.[method] !== 'function') throw new Error('Missing lazy method: ' + method);
|
|
return target[method](...args);
|
|
}]));
|
|
},
|
|
intent(element, name) {
|
|
if (!element) return;
|
|
for (const type of ['pointerenter', 'focus', 'touchstart']) element.addEventListener(type, () => load(name).catch(() => {}), { once: true, passive: true });
|
|
},
|
|
};
|
|
root.Lazy = api;
|
|
try { api.layout = JSON.parse(root.localStorage.getItem('_ytpdata') || '{}').settings?.layout || 'classic'; } catch {}
|
|
doc.documentElement.dataset.layout = api.layout;
|
|
const layout = manifest.groups['layout:' + api.layout] || manifest.groups['layout:classic'];
|
|
if (doc.readyState === 'loading') for (const path of layout?.files || []) {
|
|
if (!/\.css$/.test(path) || css.has(path)) continue;
|
|
// Parser-inserted CSS blocks rendering; dynamic head links do not on all WebKit versions.
|
|
css.add(path);
|
|
doc.write('<link rel="stylesheet" href="' + url(path) + '" data-lazy-css="' + path + '">');
|
|
}
|
|
})(typeof window !== 'undefined' ? window : globalThis);
|