gui/pkg/webview/diagnostics.go
Virgil a0cad39fbb
Some checks failed
Security Scan / security (push) Failing after 22s
Test / test (push) Successful in 1m25s
feat(gui): add webview diagnostics and tray fallback
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-02 13:29:46 +00:00

157 lines
4.4 KiB
Go

package webview
import (
"encoding/json"
"fmt"
"strings"
)
func jsQuote(v string) string {
b, _ := json.Marshal(v)
return string(b)
}
func computedStyleScript(selector string) string {
sel := jsQuote(selector)
return fmt.Sprintf(`(function(){
const el = document.querySelector(%s);
if (!el) return null;
const style = window.getComputedStyle(el);
const out = {};
for (let i = 0; i < style.length; i++) {
const key = style[i];
out[key] = style.getPropertyValue(key);
}
return out;
})()`, sel)
}
func highlightScript(selector, colour string) string {
sel := jsQuote(selector)
if colour == "" {
colour = "#ff9800"
}
col := jsQuote(colour)
return fmt.Sprintf(`(function(){
const el = document.querySelector(%s);
if (!el) return false;
if (el.__coreHighlightOrigOutline === undefined) {
el.__coreHighlightOrigOutline = el.style.outline || "";
}
el.style.outline = "3px solid " + %s;
el.style.outlineOffset = "2px";
try { el.scrollIntoView({block: "center", inline: "center", behavior: "smooth"}); } catch (e) {}
return true;
})()`, sel, col)
}
func performanceScript() string {
return `(function(){
const nav = performance.getEntriesByType("navigation")[0] || {};
const paints = performance.getEntriesByType("paint");
const firstPaint = paints.find((entry) => entry.name === "first-paint");
const firstContentfulPaint = paints.find((entry) => entry.name === "first-contentful-paint");
const memory = performance.memory || {};
return {
navigationStart: nav.startTime || 0,
domContentLoaded: nav.domContentLoadedEventEnd || 0,
loadEventEnd: nav.loadEventEnd || 0,
firstPaint: firstPaint ? firstPaint.startTime : 0,
firstContentfulPaint: firstContentfulPaint ? firstContentfulPaint.startTime : 0,
usedJSHeapSize: memory.usedJSHeapSize || 0,
totalJSHeapSize: memory.totalJSHeapSize || 0
};
})()`
}
func resourcesScript() string {
return `(function(){
return performance.getEntriesByType("resource").map((entry) => ({
name: entry.name,
entryType: entry.entryType,
initiatorType: entry.initiatorType,
startTime: entry.startTime,
duration: entry.duration,
transferSize: entry.transferSize || 0,
encodedBodySize: entry.encodedBodySize || 0,
decodedBodySize: entry.decodedBodySize || 0
}));
})()`
}
func networkInitScript() string {
return `(function(){
if (window.__coreNetworkLog) return true;
window.__coreNetworkLog = [];
const log = (entry) => { window.__coreNetworkLog.push(entry); };
const originalFetch = window.fetch;
if (originalFetch) {
window.fetch = async function(input, init) {
const request = typeof input === "string" ? input : (input && input.url) ? input.url : "";
const method = (init && init.method) || (input && input.method) || "GET";
const started = Date.now();
try {
const response = await originalFetch.call(this, input, init);
log({
url: response.url || request,
method: method,
status: response.status,
ok: response.ok,
resource: "fetch",
timestamp: started
});
return response;
} catch (error) {
log({
url: request,
method: method,
error: String(error),
resource: "fetch",
timestamp: started
});
throw error;
}
};
}
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this.__coreMethod = method;
this.__coreUrl = url;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
const started = Date.now();
this.addEventListener("loadend", () => {
log({
url: this.__coreUrl || "",
method: this.__coreMethod || "GET",
status: this.status || 0,
ok: this.status >= 200 && this.status < 400,
resource: "xhr",
timestamp: started
});
});
return originalSend.apply(this, arguments);
};
return true;
})()`
}
func networkClearScript() string {
return `(function(){
window.__coreNetworkLog = [];
return true;
})()`
}
func networkLogScript(limit int) string {
if limit <= 0 {
return `(window.__coreNetworkLog || [])`
}
return fmt.Sprintf(`(window.__coreNetworkLog || []).slice(-%d)`, limit)
}
func normalizeWhitespace(s string) string {
return strings.TrimSpace(s)
}