104 lines
5.4 KiB
JavaScript
104 lines
5.4 KiB
JavaScript
// Runs before any application script. No real account, Rust IPC, or remote API.
|
|
export function installBrowserFixture() {
|
|
const counters = { commands: {}, fileReads: 0, webglContexts: 0, longTasks: [] };
|
|
window.__ARTCRAFT_COUNTERS__ = counters;
|
|
const getContext = HTMLCanvasElement.prototype.getContext;
|
|
HTMLCanvasElement.prototype.getContext = function (type, ...args) {
|
|
if (type === "webgl" || type === "webgl2" || type === "experimental-webgl") counters.webglContexts++;
|
|
return getContext.call(this, type, ...args);
|
|
};
|
|
const readAsDataURL = FileReader.prototype.readAsDataURL;
|
|
FileReader.prototype.readAsDataURL = function (...args) {
|
|
counters.fileReads++;
|
|
return readAsDataURL.apply(this, args);
|
|
};
|
|
if (PerformanceObserver.supportedEntryTypes.includes("longtask")) {
|
|
new PerformanceObserver((list) => {
|
|
counters.longTasks.push(...list.getEntries().map(({ startTime, duration }) => ({ startTime, duration })));
|
|
}).observe({ type: "longtask", buffered: true });
|
|
}
|
|
let callbackId = 0;
|
|
const callbacks = new Map();
|
|
window.__TAURI_INTERNALS__ = {
|
|
metadata: { currentWindow: { label: "main" }, currentWebview: { label: "main" } },
|
|
transformCallback(callback) { callbacks.set(++callbackId, callback); return callbackId; },
|
|
unregisterCallback(id) { callbacks.delete(id); },
|
|
convertFileSrc(path) { return path; },
|
|
async invoke(command, args) {
|
|
counters.commands[command] = (counters.commands[command] ?? 0) + 1;
|
|
if (command === "plugin:event|listen") return args.handler;
|
|
if (command === "plugin:window|inner_size") return { width: 1440, height: 1000 };
|
|
if (command === "plugin:window|scale_factor") return 1;
|
|
if (command === "storyteller_get_login_session_command") return {
|
|
username: "performance_fixture", user_token: "u_performance_fixture",
|
|
};
|
|
if (command === "get_task_queue_command") return { payload: { tasks: [] } };
|
|
// Use the bundled model overlay, as the app does when the listing is unavailable.
|
|
if (command === "list_image_models_command" || command === "list_video_models_command") throw new Error("Offline model listing fixture");
|
|
if (command === "plugin:app|version") return "0.0.0-benchmark";
|
|
if (command === "plugin:app|name") return "ArtCraft Benchmark";
|
|
if (command === "plugin:os|platform") return "macos";
|
|
if (command.startsWith("plugin:http|")) throw new Error("Network disabled in performance fixture");
|
|
return { success: true, payload: {} };
|
|
},
|
|
};
|
|
window.__TAURI_EVENT_PLUGIN_INTERNALS__ = { unregisterListener() {} };
|
|
}
|
|
|
|
export async function seedDrawingScene() {
|
|
const { scene, Node } = window.__ARTCRAFT_BENCH__;
|
|
scene.getState().RESET();
|
|
scene.getState().setBaseImageInfo({ url: "", mediaToken: "fixture-blank", isBlankCanvas: true, blankCanvasWidth: 1024, blankCanvasHeight: 1024 });
|
|
// Deterministic noisy pixels resist PNG compression, like imported artwork.
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = canvas.height = 1024;
|
|
const context = canvas.getContext("2d");
|
|
const pixels = context.createImageData(1024, 1024);
|
|
let random = 12345;
|
|
for (let i = 0; i < pixels.data.length; i += 4) {
|
|
random = (Math.imul(random, 1664525) + 1013904223) >>> 0;
|
|
pixels.data[i] = random & 255;
|
|
pixels.data[i + 1] = (random >>> 8) & 255;
|
|
pixels.data[i + 2] = (random >>> 16) & 255;
|
|
pixels.data[i + 3] = 255;
|
|
}
|
|
context.putImageData(pixels, 0, 0);
|
|
const blob = await new Promise((resolve) => canvas.toBlob(resolve));
|
|
const drawNodes = [];
|
|
for (let i = 0; i < 4; i++) {
|
|
const node = new Node({ id: `fixture-image-${i}`, type: "image", x: i * 220, y: i * 100, width: 400, height: 400, fill: "transparent" });
|
|
await node.setImageFromFile(new File([blob], `fixture-${i}.png`, { type: "image/png" }));
|
|
drawNodes.push(node);
|
|
}
|
|
for (let i = 0; i < 80; i++) {
|
|
drawNodes.push({
|
|
id: `fixture-line-${i}`, type: "line", draggable: true, stroke: "#7766ee", strokeWidth: 3,
|
|
points: Array.from({ length: 200 }, (_, j) => j % 2 === 0 ? j * 4 : i * 10 + Math.sin(j) * 30),
|
|
});
|
|
}
|
|
scene.setState({ drawNodes, selectedNodeIds: [drawNodes[0].id] });
|
|
scene.getState().saveState();
|
|
return { images: 4, imageSize: 1024, imageBytesEach: blob.size, strokes: 80, coordinatesPerStroke: 200 };
|
|
}
|
|
|
|
export async function measureTabChange(target) {
|
|
const counters = window.__ARTCRAFT_COUNTERS__;
|
|
const readsBefore = counters.fileReads;
|
|
const start = performance.now();
|
|
const success = await window.__ARTCRAFT_BENCH__.tabs.getState().setActiveTab(target);
|
|
if (!success) throw new Error(`Tab change to ${target} failed`);
|
|
const stateReadyMs = performance.now() - start;
|
|
const ready = () => target === "2D"
|
|
? !!document.querySelector(".konvajs-content canvas")
|
|
: !!Array.from(document.querySelectorAll("h1")).find((h) => h.textContent.includes("craft"));
|
|
while (!ready()) {
|
|
if (performance.now() - start > 15000) throw new Error(`Tab ${target} never rendered`);
|
|
await new Promise(requestAnimationFrame);
|
|
}
|
|
// A rendering opportunity after the target DOM committed, not GPU completion.
|
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
const paintMs = performance.now() - start;
|
|
performance.measure(`artcraft:tab:${target}`, { start, end: performance.now() });
|
|
return { stateReadyMs, paintMs, fileReads: counters.fileReads - readsBefore };
|
|
}
|