mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
This commit re-enables the --webui functionality on windows, with the caveat that rebuild functionality is still disabled (due to deadlocks caused by reading to / writing from the same non-overlapped socket on multiple threads). I updated the UI to be aware of this, and hide the `Rebuild` button. http.Server: Remove incorrect advance() call. This was causing browsers to disconnect the websocket, as we were sending undefined bytes. build.WebServer: Re-enable on windows, but disable functionality that requires receiving messages from the client build-web: Show total times in tables
357 lines
12 KiB
JavaScript
357 lines
12 KiB
JavaScript
const domConnectionStatus = document.getElementById("connectionStatus");
|
|
const domFirefoxWebSocketBullshitExplainer = document.getElementById("firefoxWebSocketBullshitExplainer");
|
|
|
|
const domMain = document.getElementsByTagName("main")[0];
|
|
const domSummary = {
|
|
stepCount: document.getElementById("summaryStepCount"),
|
|
status: document.getElementById("summaryStatus"),
|
|
};
|
|
let domButtonRebuild = document.getElementById("buttonRebuild");
|
|
const domStepList = document.getElementById("stepList");
|
|
let domSteps = [];
|
|
|
|
let wasm_promise = fetch("main.wasm");
|
|
let wasm_exports = null;
|
|
|
|
const text_decoder = new TextDecoder();
|
|
const text_encoder = new TextEncoder();
|
|
|
|
domButtonRebuild.addEventListener("click", () => wasm_exports.rebuild());
|
|
|
|
setConnectionStatus("Loading WebAssembly...", false);
|
|
WebAssembly.instantiateStreaming(wasm_promise, {
|
|
core: {
|
|
log: function(ptr, len) {
|
|
const msg = decodeString(ptr, len);
|
|
console.log(msg);
|
|
},
|
|
panic: function (ptr, len) {
|
|
const msg = decodeString(ptr, len);
|
|
throw new Error("panic: " + msg);
|
|
},
|
|
timestamp: function () {
|
|
return BigInt(new Date());
|
|
},
|
|
hello: hello,
|
|
updateBuildStatus: updateBuildStatus,
|
|
updateStepStatus: updateStepStatus,
|
|
sendWsMessage: (ptr, len) => ws.send(new Uint8Array(wasm_exports.memory.buffer, ptr, len)),
|
|
},
|
|
fuzz: {
|
|
requestSources: fuzzRequestSources,
|
|
ready: fuzzReady,
|
|
updateStats: fuzzUpdateStats,
|
|
updateEntryPoints: fuzzUpdateEntryPoints,
|
|
updateSource: fuzzUpdateSource,
|
|
updateCoverage: fuzzUpdateCoverage,
|
|
},
|
|
time_report: {
|
|
updateCompile: timeReportUpdateCompile,
|
|
updateGeneric: timeReportUpdateGeneric,
|
|
},
|
|
}).then(function(obj) {
|
|
setConnectionStatus("Connecting to WebSocket...", true);
|
|
connectWebSocket();
|
|
|
|
wasm_exports = obj.instance.exports;
|
|
window.wasm = obj; // for debugging
|
|
});
|
|
|
|
function connectWebSocket() {
|
|
const host = document.location.host;
|
|
const pathname = document.location.pathname;
|
|
const isHttps = document.location.protocol === 'https:';
|
|
const match = host.match(/^(.+):(\d+)$/);
|
|
const defaultPort = isHttps ? 443 : 80;
|
|
const port = match ? parseInt(match[2], 10) : defaultPort;
|
|
const hostName = match ? match[1] : host;
|
|
const wsProto = isHttps ? "wss:" : "ws:";
|
|
const wsUrl = wsProto + '//' + hostName + ':' + port + pathname;
|
|
ws = new WebSocket(wsUrl);
|
|
ws.binaryType = "arraybuffer";
|
|
ws.addEventListener('message', onWebSocketMessage, false);
|
|
ws.addEventListener('error', onWebSocketClose, false);
|
|
ws.addEventListener('close', onWebSocketClose, false);
|
|
ws.addEventListener('open', onWebSocketOpen, false);
|
|
}
|
|
function onWebSocketOpen() {
|
|
setConnectionStatus("Waiting for data...", false);
|
|
}
|
|
function onWebSocketMessage(ev) {
|
|
const jsArray = new Uint8Array(ev.data);
|
|
const ptr = wasm_exports.message_begin(jsArray.length);
|
|
const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, jsArray.length);
|
|
wasmArray.set(jsArray);
|
|
wasm_exports.message_end();
|
|
}
|
|
function onWebSocketClose() {
|
|
setConnectionStatus("WebSocket connection closed. Re-connecting...", true);
|
|
ws.removeEventListener('message', onWebSocketMessage, false);
|
|
ws.removeEventListener('error', onWebSocketClose, false);
|
|
ws.removeEventListener('close', onWebSocketClose, false);
|
|
ws.removeEventListener('open', onWebSocketOpen, false);
|
|
ws = null;
|
|
setTimeout(connectWebSocket, 1000);
|
|
}
|
|
|
|
function setConnectionStatus(msg, is_websocket_connect) {
|
|
domConnectionStatus.textContent = msg;
|
|
if (msg.length > 0) {
|
|
domConnectionStatus.classList.remove("hidden");
|
|
domMain.classList.add("hidden");
|
|
} else {
|
|
domConnectionStatus.classList.add("hidden");
|
|
domMain.classList.remove("hidden");
|
|
}
|
|
if (is_websocket_connect) {
|
|
domFirefoxWebSocketBullshitExplainer.classList.remove("hidden");
|
|
} else {
|
|
domFirefoxWebSocketBullshitExplainer.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
function hello(
|
|
steps_len,
|
|
build_status,
|
|
time_report,
|
|
supports_recv,
|
|
) {
|
|
if (!supports_recv && domButtonRebuild) {
|
|
domButtonRebuild.remove();
|
|
domButtonRebuild = null;
|
|
}
|
|
|
|
domSummary.stepCount.textContent = steps_len;
|
|
updateBuildStatus(build_status);
|
|
setConnectionStatus("", false);
|
|
|
|
{
|
|
let entries = [];
|
|
for (let i = 0; i < steps_len; i += 1) {
|
|
const step_name = unwrapString(wasm_exports.stepName(i));
|
|
const code = document.createElement("code");
|
|
code.textContent = step_name;
|
|
const li = document.createElement("li");
|
|
li.appendChild(code);
|
|
entries.push(li);
|
|
}
|
|
domStepList.replaceChildren(...entries);
|
|
for (let i = 0; i < steps_len; i += 1) {
|
|
updateStepStatus(i);
|
|
}
|
|
}
|
|
|
|
if (time_report) timeReportReset(steps_len);
|
|
fuzzReset();
|
|
}
|
|
|
|
function updateBuildStatus(s) {
|
|
let text;
|
|
let active = false;
|
|
let reset_time_reports = false;
|
|
if (s == 0) {
|
|
text = "Idle";
|
|
} else if (s == 1) {
|
|
text = "Watching for changes...";
|
|
} else if (s == 2) {
|
|
text = "Running...";
|
|
active = true;
|
|
reset_time_reports = true;
|
|
} else if (s == 3) {
|
|
text = "Starting fuzzer...";
|
|
active = true;
|
|
} else {
|
|
console.log(`bad build status: ${s}`);
|
|
}
|
|
domSummary.status.textContent = text;
|
|
if (active) {
|
|
domSummary.status.classList.add("status-running");
|
|
domSummary.status.classList.remove("status-idle");
|
|
if (domButtonRebuild) {
|
|
domButtonRebuild.disabled = true;
|
|
}
|
|
} else {
|
|
domSummary.status.classList.remove("status-running");
|
|
domSummary.status.classList.add("status-idle");
|
|
if (domButtonRebuild) {
|
|
domButtonRebuild.disabled = false;
|
|
}
|
|
}
|
|
if (reset_time_reports) {
|
|
// Grey out and collapse all the time reports
|
|
for (const time_report_host of domTimeReportList.children) {
|
|
const details = time_report_host.shadowRoot.querySelector(":host > details");
|
|
details.classList.add("pending");
|
|
details.open = false;
|
|
}
|
|
}
|
|
}
|
|
function updateStepStatus(step_idx) {
|
|
const li = domStepList.children[step_idx];
|
|
const step_status = wasm_exports.stepStatus(step_idx);
|
|
li.classList.remove("step-wip", "step-success", "step-failure");
|
|
if (step_status == 0) {
|
|
// pending
|
|
} else if (step_status == 1) {
|
|
li.classList.add("step-wip");
|
|
} else if (step_status == 2) {
|
|
li.classList.add("step-success");
|
|
} else if (step_status == 3) {
|
|
li.classList.add("step-failure");
|
|
} else {
|
|
console.log(`bad step status: ${step_status}`);
|
|
}
|
|
}
|
|
|
|
function decodeString(ptr, len) {
|
|
if (len === 0) return "";
|
|
return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len));
|
|
}
|
|
function getU32Array(ptr, len) {
|
|
if (len === 0) return new Uint32Array();
|
|
return new Uint32Array(wasm_exports.memory.buffer, ptr, len);
|
|
}
|
|
function unwrapString(bigint) {
|
|
const ptr = Number(bigint & 0xffffffffn);
|
|
const len = Number(bigint >> 32n);
|
|
return decodeString(ptr, len);
|
|
}
|
|
|
|
const time_report_entry_template = document.getElementById("timeReportEntryTemplate").content;
|
|
const domTimeReport = document.getElementById("timeReport");
|
|
const domTimeReportList = document.getElementById("timeReportList");
|
|
function timeReportReset(steps_len) {
|
|
let entries = [];
|
|
for (let i = 0; i < steps_len; i += 1) {
|
|
const step_name = unwrapString(wasm_exports.stepName(i));
|
|
const host = document.createElement("div");
|
|
const shadow = host.attachShadow({ mode: "open" });
|
|
shadow.appendChild(time_report_entry_template.cloneNode(true));
|
|
shadow.querySelector(":host > details").classList.add("pending");
|
|
const slotted_name = document.createElement("code");
|
|
slotted_name.setAttribute("slot", "step-name");
|
|
slotted_name.textContent = step_name;
|
|
host.appendChild(slotted_name);
|
|
entries.push(host);
|
|
}
|
|
domTimeReportList.replaceChildren(...entries);
|
|
domTimeReport.classList.remove("hidden");
|
|
}
|
|
function timeReportUpdateCompile(
|
|
step_idx,
|
|
inner_html_ptr,
|
|
inner_html_len,
|
|
file_table_html_ptr,
|
|
file_table_html_len,
|
|
decl_table_html_ptr,
|
|
decl_table_html_len,
|
|
use_llvm,
|
|
) {
|
|
const inner_html = decodeString(inner_html_ptr, inner_html_len);
|
|
const file_table_html = decodeString(file_table_html_ptr, file_table_html_len);
|
|
const decl_table_html = decodeString(decl_table_html_ptr, decl_table_html_len);
|
|
|
|
const host = domTimeReportList.children.item(step_idx);
|
|
const shadow = host.shadowRoot;
|
|
|
|
shadow.querySelector(":host > details").classList.remove("pending", "no-llvm");
|
|
|
|
shadow.getElementById("genericReport").classList.add("hidden");
|
|
shadow.getElementById("compileReport").classList.remove("hidden");
|
|
|
|
if (!use_llvm) shadow.querySelector(":host > details").classList.add("no-llvm");
|
|
host.innerHTML = inner_html;
|
|
shadow.getElementById("fileTableBody").innerHTML = file_table_html;
|
|
shadow.getElementById("declTableBody").innerHTML = decl_table_html;
|
|
}
|
|
function timeReportUpdateGeneric(
|
|
step_idx,
|
|
inner_html_ptr,
|
|
inner_html_len,
|
|
) {
|
|
const inner_html = decodeString(inner_html_ptr, inner_html_len);
|
|
const host = domTimeReportList.children.item(step_idx);
|
|
const shadow = host.shadowRoot;
|
|
shadow.querySelector(":host > details").classList.remove("pending", "no-llvm");
|
|
shadow.getElementById("genericReport").classList.remove("hidden");
|
|
shadow.getElementById("compileReport").classList.add("hidden");
|
|
host.innerHTML = inner_html;
|
|
}
|
|
|
|
const fuzz_entry_template = document.getElementById("fuzzEntryTemplate").content;
|
|
const domFuzz = document.getElementById("fuzz");
|
|
const domFuzzStatus = document.getElementById("fuzzStatus");
|
|
const domFuzzEntries = document.getElementById("fuzzEntries");
|
|
let domFuzzInstance = null;
|
|
function fuzzRequestSources() {
|
|
domFuzzStatus.classList.remove("hidden");
|
|
domFuzzStatus.textContent = "Loading sources tarball...";
|
|
fetch("sources.tar").then(function(response) {
|
|
if (!response.ok) throw new Error("unable to download sources");
|
|
domFuzzStatus.textContent = "Parsing fuzz test sources...";
|
|
return response.arrayBuffer();
|
|
}).then(function(buffer) {
|
|
if (buffer.length === 0) throw new Error("sources.tar was empty");
|
|
const js_array = new Uint8Array(buffer);
|
|
const ptr = wasm_exports.alloc(js_array.length);
|
|
const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length);
|
|
wasm_array.set(js_array);
|
|
wasm_exports.fuzzUnpackSources(ptr, js_array.length);
|
|
domFuzzStatus.textContent = "";
|
|
domFuzzStatus.classList.add("hidden");
|
|
});
|
|
}
|
|
function fuzzReady() {
|
|
domFuzz.classList.remove("hidden");
|
|
|
|
// TODO: multiple fuzzer instances
|
|
if (domFuzzInstance !== null) return;
|
|
|
|
const host = document.createElement("div");
|
|
const shadow = host.attachShadow({ mode: "open" });
|
|
shadow.appendChild(fuzz_entry_template.cloneNode(true));
|
|
|
|
domFuzzInstance = host;
|
|
domFuzzEntries.appendChild(host);
|
|
}
|
|
function fuzzReset() {
|
|
domFuzz.classList.add("hidden");
|
|
domFuzzEntries.replaceChildren();
|
|
domFuzzInstance = null;
|
|
}
|
|
function fuzzUpdateStats(stats_html_ptr, stats_html_len) {
|
|
if (domFuzzInstance === null) throw new Error("fuzzUpdateStats called when fuzzer inactive");
|
|
const stats_html = decodeString(stats_html_ptr, stats_html_len);
|
|
const host = domFuzzInstance;
|
|
host.innerHTML = stats_html;
|
|
}
|
|
function fuzzUpdateEntryPoints(entry_points_html_ptr, entry_points_html_len) {
|
|
if (domFuzzInstance === null) throw new Error("fuzzUpdateEntryPoints called when fuzzer inactive");
|
|
const entry_points_html = decodeString(entry_points_html_ptr, entry_points_html_len);
|
|
const domEntryPointList = domFuzzInstance.shadowRoot.getElementById("entryPointList");
|
|
domEntryPointList.innerHTML = entry_points_html;
|
|
}
|
|
function fuzzUpdateSource(source_html_ptr, source_html_len) {
|
|
if (domFuzzInstance === null) throw new Error("fuzzUpdateSource called when fuzzer inactive");
|
|
const source_html = decodeString(source_html_ptr, source_html_len);
|
|
const domSourceText = domFuzzInstance.shadowRoot.getElementById("sourceText");
|
|
domSourceText.innerHTML = source_html;
|
|
domFuzzInstance.shadowRoot.getElementById("source").classList.remove("hidden");
|
|
}
|
|
function fuzzUpdateCoverage(covered_ptr, covered_len) {
|
|
if (domFuzzInstance === null) throw new Error("fuzzUpdateCoverage called when fuzzer inactive");
|
|
const shadow = domFuzzInstance.shadowRoot;
|
|
const domSourceText = shadow.getElementById("sourceText");
|
|
const covered = getU32Array(covered_ptr, covered_len);
|
|
for (let i = 0; i < domSourceText.children.length; i += 1) {
|
|
const childDom = domSourceText.children[i];
|
|
if (childDom.id != null && childDom.id[0] == "l") {
|
|
childDom.classList.add("l");
|
|
childDom.classList.remove("c");
|
|
}
|
|
}
|
|
for (const sli of covered) {
|
|
shadow.getElementById(`l${sli}`).classList.add("c");
|
|
}
|
|
}
|