v8Cache: initial add

This commit is contained in:
CanadaHonk 2023-01-26 23:08:57 +00:00
parent 02cce39e44
commit e1bb814c88
4 changed files with 159 additions and 2 deletions

52
gluon.d.ts vendored
View File

@ -1,3 +1,55 @@
type V8CacheBuildOptions = {
/**
* Path to save the V8 Cache to. Defaults to v8Cache.json in Gluon's browser data.
*/
path?: string,
/**
* Use eager compilation.
* @default true
*/
eager?: Boolean,
/**
* URLs of scripts to cache. Defaults to automatically detecting currently loaded scripts in the page.
*/
urls?: string[],
/**
* Reload the page to force script compilation.
* @default true
*/
reload?: Boolean,
/**
* Include preload scripts in automatic detection.
* @default true
*/
includePreload?: Boolean
};
type V8CacheApi = {
/** Build a V8 Cache. */
build(
/** Build options. */
options?: V8CacheBuildOptions
),
/** Load a V8 Cache. */
load(
/**
* Path to load. Defaults to v8Cache.json in Gluon's browser data.
*/
path?: string
): Promise<boolean>
/** Check if a V8 Cache exists with a given path. */
exists(
/** Path to check. */
path: string
): Promise<Boolean>
};
type PageApi = {
/**
* Evaluate a string or function in the web context.

102
src/api/v8Cache.js Normal file
View File

@ -0,0 +1,102 @@
import { join, sep } from 'path';
import { access, writeFile, readFile } from 'fs/promises';
import { log } from '../lib/logger.js';
export default async (CDP, evaluate, { browserType, dataPath }) => {
if (browserType !== 'chromium') { // current implementation is for chromium-based only
const warning = () => log(`Warning: V8 Cache API is only for Chromium (running on ${browserType})`);
return {
build: () => {},
load: () => false,
exists: () => false
};
}
await CDP.send('Page.enable');
const getDefaultPath = () => join(dataPath, 'v8Cache.json');
const getScriptUrls = async (includePreload = true) => (await evaluate(`[...document.querySelectorAll('script[src]${includePreload ? ', link[as=script]' : ''}')].map(x => x.src ?? x.href).join(';')`)).split(';');
const build = async ({ path = getDefaultPath(), eager = true, urls = [], reload = true, includePreload = true, finishOnLoad = true } = {}) => {
const startTime = performance.now();
log('v8Cache: beginning cache build...');
urls ??= await getScriptUrls(includePreload);
log(`v8Cache: found ${urls.length} scripts`);
const cache = await new Promise(async resolve => {
let produced = [], done = false;
const unhook = CDP.on('Page.compilationCacheProduced', ({ params: { url, data }}) => {
// console.log('produced', url);
produced.push({ url, data });
process.stdout.write(`v8Cache: caching... (${produced.length}/${urls.length})\r`);
if (produced.length >= urls.length) {
done = true;
unhook();
resolve(produced);
}
});
await CDP.send('Page.produceCompilationCache', {
scripts: urls.map(url => ({ url, eager }))
});
if (reload) {
if (finishOnLoad) CDP.on('Page.frameStoppedLoading', async () => {
// console.log('loaded');
await new Promise(res => setTimeout(res, 2000));
if (done) return;
log('v8Cache: forcing caching to end early as loading is done');
unhook();
resolve(produced);
}, true);
log('v8Cache: reloading to force script loads...');
await CDP.send('Page.reload');
}
process.stdout.write(`v8Cache: starting cache...\r`);
});
log(`v8Cache: cached ${cache.length}/${urls.length} scripts in ${(performance.now() - startTime).toFixed(2)}ms`);
const raw = JSON.stringify(cache, null, 2);
await writeFile(path, raw);
log(`v8Cache: saved to .../${path.split(sep).slice(-3).join('/')} (${(Buffer.byteLength(raw, 'utf8') / 1024 / 1024).toFixed(2)}MB)`);
};
const exists = (path = getDefaultPath()) => access(path).then(() => true).catch(() => false);
const load = async (path = getDefaultPath()) => {
if (!await exists(path)) return false;
const startTime = performance.now();
const cache = JSON.parse(await readFile(path, 'utf8'));
for (const entry of cache) {
// console.log('loaded', entry);
CDP.send('Page.addCompilationCache', entry);
}
log(`v8Cache: loaded ${cache.length} scripts in ${(performance.now() - startTime).toFixed(2)}ms`);
return true;
};
// try to load default ASAP
load();
return {
build,
load,
exists
};
};

View File

@ -206,7 +206,8 @@ const startBrowser = async (url, { windowSize, forceBrowser, forceEngine }) => {
localUrl,
openingLocal,
closeHandlers,
browserType
browserType,
dataPath
});
return Window;

View File

@ -5,6 +5,7 @@ import LocalCDP from '../lib/local/cdp.js';
import IdleApi from '../api/idle.js';
import ControlsApi from '../api/controls.js';
import V8CacheApi from '../api/v8Cache.js';
const acquireTarget = async (CDP, filter = () => true) => {
let target;
@ -25,7 +26,7 @@ const acquireTarget = async (CDP, filter = () => true) => {
})).sessionId;
};
export default async (CDP, proc, injectionType = 'browser', { browserName, browserType, openingLocal, localUrl, url, closeHandlers }) => {
export default async (CDP, proc, injectionType = 'browser', { dataPath, browserName, browserType, openingLocal, localUrl, url, closeHandlers }) => {
let pageLoadCallback, pageLoadPromise = new Promise(res => pageLoadCallback = res);
let frameLoadCallback = () => {}, onWindowMessage = () => {};
CDP.onMessage(msg => {
@ -125,6 +126,7 @@ export default async (CDP, proc, injectionType = 'browser', { browserName, brows
Window.idle = await IdleApi(Window.cdp, { browserType, closeHandlers });
Window.controls = await ControlsApi(Window.cdp);
Window.v8Cache = await V8CacheApi(Window.cdp, evalInWindow, { browserType, dataPath });
return Window;
};