@zenbu-labs/pixel 0.0.0 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +161 -0
- package/dist/bootstrap.d.ts +1 -0
- package/dist/bootstrap.js +75 -0
- package/dist/context-menu.d.ts +20 -0
- package/dist/context-menu.js +57 -0
- package/dist/debug.d.ts +1 -0
- package/dist/debug.js +9 -0
- package/dist/devtools-layout.d.ts +21 -0
- package/dist/devtools-layout.js +45 -0
- package/dist/devtools.d.ts +33 -0
- package/dist/devtools.js +125 -0
- package/dist/electron/index.d.ts +3 -0
- package/dist/electron/index.js +4 -0
- package/dist/host/guest.d.ts +5 -0
- package/dist/host/guest.js +178 -0
- package/dist/host/server.d.ts +91 -0
- package/dist/host/server.js +296 -0
- package/dist/host/shell.d.ts +35 -0
- package/dist/host/shell.js +113 -0
- package/dist/host/strip.d.ts +6 -0
- package/dist/host/strip.js +63 -0
- package/dist/icons.d.ts +30 -0
- package/dist/icons.js +58 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +39 -0
- package/dist/instances.d.ts +30 -0
- package/dist/instances.js +177 -0
- package/dist/layout.d.ts +5 -0
- package/dist/layout.js +34 -0
- package/dist/popup.d.ts +21 -0
- package/dist/popup.js +47 -0
- package/dist/preload/index.d.ts +15 -0
- package/dist/preload/index.js +3 -0
- package/dist/react/colors.d.ts +3 -0
- package/dist/react/colors.js +25 -0
- package/dist/react/components.d.ts +21 -0
- package/dist/react/components.js +45 -0
- package/dist/react/devtools/app.d.ts +3 -0
- package/dist/react/devtools/app.js +89 -0
- package/dist/react/devtools/console-capture.d.ts +1 -0
- package/dist/react/devtools/console-capture.js +41 -0
- package/dist/react/devtools/controller.d.ts +45 -0
- package/dist/react/devtools/controller.js +252 -0
- package/dist/react/devtools/elements.d.ts +4 -0
- package/dist/react/devtools/elements.js +152 -0
- package/dist/react/devtools/export-profile.d.ts +2 -0
- package/dist/react/devtools/export-profile.js +225 -0
- package/dist/react/devtools/fiber-hook.d.ts +1 -0
- package/dist/react/devtools/fiber-hook.js +89 -0
- package/dist/react/devtools/log-panel.d.ts +4 -0
- package/dist/react/devtools/log-panel.js +113 -0
- package/dist/react/devtools/profiler.d.ts +11 -0
- package/dist/react/devtools/profiler.js +374 -0
- package/dist/react/devtools/store.d.ts +27 -0
- package/dist/react/devtools/store.js +61 -0
- package/dist/react/devtools/stores.d.ts +92 -0
- package/dist/react/devtools/stores.js +41 -0
- package/dist/react/devtools/theme.d.ts +35 -0
- package/dist/react/devtools/theme.js +77 -0
- package/dist/react/devtools/ui.d.ts +24 -0
- package/dist/react/devtools/ui.js +59 -0
- package/dist/react/index.d.ts +110 -0
- package/dist/react/index.js +538 -0
- package/dist/react/markdown.d.ts +25 -0
- package/dist/react/markdown.js +270 -0
- package/dist/react/native.d.ts +116 -0
- package/dist/react/native.js +56 -0
- package/dist/react/reconciler-config.d.ts +241 -0
- package/dist/react/reconciler-config.js +412 -0
- package/dist/react/styles.d.ts +88 -0
- package/dist/react/styles.js +85 -0
- package/dist/react/surface.d.ts +55 -0
- package/dist/react/surface.js +74 -0
- package/dist/registry.d.ts +53 -0
- package/dist/registry.js +193 -0
- package/dist/root.d.ts +19 -0
- package/dist/root.js +342 -0
- package/dist/sandbox.d.ts +4 -0
- package/dist/sandbox.js +84 -0
- package/dist/scale.d.ts +7 -0
- package/dist/scale.js +31 -0
- package/dist/ssh/index.d.ts +58 -0
- package/dist/ssh/index.js +445 -0
- package/dist/terminal/applescript.d.ts +2 -0
- package/dist/terminal/applescript.js +20 -0
- package/dist/terminal/detect.d.ts +10 -0
- package/dist/terminal/detect.js +34 -0
- package/dist/terminal/graphics.d.ts +9 -0
- package/dist/terminal/graphics.js +142 -0
- package/dist/terminal/index.d.ts +12 -0
- package/dist/terminal/index.js +23 -0
- package/dist/terminal/run.d.ts +2 -0
- package/dist/terminal/run.js +15 -0
- package/dist/terminal/shared.d.ts +20 -0
- package/dist/terminal/shared.js +88 -0
- package/dist/terminal/terminal.d.ts +66 -0
- package/dist/terminal/terminal.js +6 -0
- package/dist/terminal/terminals/cmux.d.ts +2 -0
- package/dist/terminal/terminals/cmux.js +98 -0
- package/dist/terminal/terminals/ghostty-script.d.ts +1 -0
- package/dist/terminal/terminals/ghostty-script.js +179 -0
- package/dist/terminal/terminals/ghostty.d.ts +2 -0
- package/dist/terminal/terminals/ghostty.js +336 -0
- package/dist/terminal/terminals/herdr.d.ts +2 -0
- package/dist/terminal/terminals/herdr.js +141 -0
- package/dist/terminal/terminals/index.d.ts +5 -0
- package/dist/terminal/terminals/index.js +26 -0
- package/dist/terminal/terminals/kitty.d.ts +2 -0
- package/dist/terminal/terminals/kitty.js +209 -0
- package/dist/terminal/terminals/supacode.d.ts +2 -0
- package/dist/terminal/terminals/supacode.js +79 -0
- package/dist/terminal/terminals/tmux.d.ts +2 -0
- package/dist/terminal/terminals/tmux.js +132 -0
- package/dist/terminal/terminals/tty7.d.ts +2 -0
- package/dist/terminal/terminals/tty7.js +58 -0
- package/dist/terminal/terminals/vscode.d.ts +2 -0
- package/dist/terminal/terminals/vscode.js +13 -0
- package/dist/terminal/terminals/wezterm.d.ts +2 -0
- package/dist/terminal/terminals/wezterm.js +59 -0
- package/dist/theme.d.ts +22 -0
- package/dist/theme.js +40 -0
- package/dist/web/browser-session.d.ts +13 -0
- package/dist/web/browser-session.js +186 -0
- package/dist/web/cursor.d.ts +1 -0
- package/dist/web/cursor.js +24 -0
- package/dist/web/devtools.d.ts +39 -0
- package/dist/web/devtools.js +300 -0
- package/dist/web/favicon.d.ts +7 -0
- package/dist/web/favicon.js +73 -0
- package/dist/web/frame-rate.d.ts +1 -0
- package/dist/web/frame-rate.js +12 -0
- package/dist/web/host.d.ts +123 -0
- package/dist/web/host.js +573 -0
- package/dist/web/input.d.ts +49 -0
- package/dist/web/input.js +468 -0
- package/dist/web/offscreen.d.ts +2 -0
- package/dist/web/offscreen.js +23 -0
- package/dist/web/paint.d.ts +11 -0
- package/dist/web/paint.js +128 -0
- package/dist/web/popup.d.ts +40 -0
- package/dist/web/popup.js +144 -0
- package/dist/web/session.d.ts +20 -0
- package/dist/web/session.js +110 -0
- package/dist/web/types.d.ts +38 -0
- package/dist/web/types.js +39 -0
- package/dist/web/zoom.d.ts +5 -0
- package/dist/web/zoom.js +36 -0
- package/dist/webview.d.ts +55 -0
- package/dist/webview.js +481 -0
- package/package.json +69 -3
- package/scripts/apparmor.sh +71 -0
- package/scripts/postinstall.mjs +150 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Session, WebContents } from "electron";
|
|
2
|
+
export interface DownloadProgress {
|
|
3
|
+
name: string;
|
|
4
|
+
savePath: string;
|
|
5
|
+
received: number;
|
|
6
|
+
total: number;
|
|
7
|
+
state: "progressing" | "done" | "failed";
|
|
8
|
+
}
|
|
9
|
+
export declare function routeThroughProxy(target: Session, rules: string): Promise<void>;
|
|
10
|
+
export declare function allowClipboardRead(contents: WebContents): void;
|
|
11
|
+
export declare function onDownloadFor(contents: WebContents, handler: (progress: DownloadProgress) => void): void;
|
|
12
|
+
export declare function configureBrowserSession(target: Session): void;
|
|
13
|
+
export declare function persistentPartition(partition: string): string;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.routeThroughProxy = routeThroughProxy;
|
|
7
|
+
exports.allowClipboardRead = allowClipboardRead;
|
|
8
|
+
exports.onDownloadFor = onDownloadFor;
|
|
9
|
+
exports.configureBrowserSession = configureBrowserSession;
|
|
10
|
+
exports.persistentPartition = persistentPartition;
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const node_stream_1 = require("node:stream");
|
|
14
|
+
const node_url_1 = require("node:url");
|
|
15
|
+
const electron_1 = require("electron");
|
|
16
|
+
const GRANTED = new Set([
|
|
17
|
+
"fullscreen",
|
|
18
|
+
"pointerLock",
|
|
19
|
+
"clipboard-sanitized-write",
|
|
20
|
+
"midi",
|
|
21
|
+
]);
|
|
22
|
+
const configured = new WeakSet();
|
|
23
|
+
const proxied = new WeakSet();
|
|
24
|
+
let webrtcGuard = false;
|
|
25
|
+
function routeThroughProxy(target, rules) {
|
|
26
|
+
proxied.add(target);
|
|
27
|
+
if (!webrtcGuard) {
|
|
28
|
+
webrtcGuard = true;
|
|
29
|
+
electron_1.app.on("web-contents-created", (_event, contents) => {
|
|
30
|
+
if (proxied.has(contents.session))
|
|
31
|
+
contents.setWebRTCIPHandlingPolicy("disable_non_proxied_udp");
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return target.setProxy({ proxyRules: rules, proxyBypassRules: "<-loopback>" });
|
|
35
|
+
}
|
|
36
|
+
const clipboardReaders = new WeakSet();
|
|
37
|
+
const SELECT_PICKER_PRELOAD = `const { webFrame } = require("electron");
|
|
38
|
+
webFrame.insertCSS("select, ::picker(select) { appearance: base-select !important }", {
|
|
39
|
+
cssOrigin: "user",
|
|
40
|
+
});
|
|
41
|
+
`;
|
|
42
|
+
let selectPreloadFile = null;
|
|
43
|
+
function selectPreloadPath() {
|
|
44
|
+
if (!selectPreloadFile) {
|
|
45
|
+
selectPreloadFile = node_path_1.default.join(electron_1.app.getPath("userData"), "pixel-select-preload.js");
|
|
46
|
+
node_fs_1.default.writeFileSync(selectPreloadFile, SELECT_PICKER_PRELOAD);
|
|
47
|
+
}
|
|
48
|
+
return selectPreloadFile;
|
|
49
|
+
}
|
|
50
|
+
function allowClipboardRead(contents) {
|
|
51
|
+
clipboardReaders.add(contents);
|
|
52
|
+
}
|
|
53
|
+
function granted(contents, permission) {
|
|
54
|
+
if (GRANTED.has(permission))
|
|
55
|
+
return true;
|
|
56
|
+
return (permission === "clipboard-read" && contents !== null && clipboardReaders.has(contents));
|
|
57
|
+
}
|
|
58
|
+
const downloadHandlers = new Map();
|
|
59
|
+
function onDownloadFor(contents, handler) {
|
|
60
|
+
downloadHandlers.set(contents.id, handler);
|
|
61
|
+
contents.once("destroyed", () => downloadHandlers.delete(contents.id));
|
|
62
|
+
}
|
|
63
|
+
function configureBrowserSession(target) {
|
|
64
|
+
if (configured.has(target))
|
|
65
|
+
return;
|
|
66
|
+
configured.add(target);
|
|
67
|
+
target.registerPreloadScript({ type: "frame", filePath: selectPreloadPath() });
|
|
68
|
+
target.setPermissionRequestHandler((contents, permission, callback) => {
|
|
69
|
+
callback(granted(contents, permission));
|
|
70
|
+
});
|
|
71
|
+
target.setPermissionCheckHandler((contents, permission) => granted(contents, permission));
|
|
72
|
+
target.webRequest.onBeforeRequest({ urls: ["file://*", "file://*/*"] }, (details, callback) => {
|
|
73
|
+
callback({ cancel: details.resourceType === "xhr" });
|
|
74
|
+
});
|
|
75
|
+
target.protocol.handle("file", async (request) => {
|
|
76
|
+
const directory = requestedDirectory(request.url);
|
|
77
|
+
if (directory)
|
|
78
|
+
return directoryListing(directory);
|
|
79
|
+
const range = request.headers.get("range");
|
|
80
|
+
const ranged = range ? rangeResponse(request.url, range) : null;
|
|
81
|
+
return ranged ?? electron_1.net.fetch(request, { bypassCustomProtocolHandlers: true });
|
|
82
|
+
});
|
|
83
|
+
target.on("will-download", (_event, item, contents) => {
|
|
84
|
+
const onDownload = downloadHandlers.get(contents.id);
|
|
85
|
+
if (!onDownload)
|
|
86
|
+
return;
|
|
87
|
+
const savePath = downloadPath(item.getFilename());
|
|
88
|
+
item.setSavePath(savePath);
|
|
89
|
+
const report = (state) => onDownload({
|
|
90
|
+
name: node_path_1.default.basename(savePath),
|
|
91
|
+
savePath,
|
|
92
|
+
received: item.getReceivedBytes(),
|
|
93
|
+
total: item.getTotalBytes(),
|
|
94
|
+
state,
|
|
95
|
+
});
|
|
96
|
+
item.on("updated", (_updated, state) => report(state === "interrupted" ? "failed" : "progressing"));
|
|
97
|
+
item.once("done", (_done, state) => report(state === "completed" ? "done" : "failed"));
|
|
98
|
+
report("progressing");
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function persistentPartition(partition) {
|
|
102
|
+
return partition.startsWith("persist:") ? partition : `persist:${partition}`;
|
|
103
|
+
}
|
|
104
|
+
function rangeResponse(url, range) {
|
|
105
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(range.trim());
|
|
106
|
+
if (!match || (!match[1] && !match[2]))
|
|
107
|
+
return null;
|
|
108
|
+
let file;
|
|
109
|
+
let size;
|
|
110
|
+
try {
|
|
111
|
+
file = (0, node_url_1.fileURLToPath)(new URL(url).href.split(/[?#]/)[0]);
|
|
112
|
+
const stat = node_fs_1.default.statSync(file);
|
|
113
|
+
if (!stat.isFile())
|
|
114
|
+
return null;
|
|
115
|
+
size = stat.size;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const start = match[1] ? Number(match[1]) : Math.max(0, size - Number(match[2]));
|
|
121
|
+
const end = match[1] && match[2] ? Math.min(Number(match[2]), size - 1) : size - 1;
|
|
122
|
+
if (start >= size || start > end) {
|
|
123
|
+
return new Response(null, {
|
|
124
|
+
status: 416,
|
|
125
|
+
headers: { "content-range": `bytes */${size}` },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const body = node_stream_1.Readable.toWeb(node_fs_1.default.createReadStream(file, { start, end }));
|
|
129
|
+
return new Response(body, {
|
|
130
|
+
status: 206,
|
|
131
|
+
headers: {
|
|
132
|
+
"accept-ranges": "bytes",
|
|
133
|
+
"content-range": `bytes ${start}-${end}/${size}`,
|
|
134
|
+
"content-length": String(end - start + 1),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function requestedDirectory(url) {
|
|
139
|
+
try {
|
|
140
|
+
const file = (0, node_url_1.fileURLToPath)(new URL(url).href.split(/[?#]/)[0]);
|
|
141
|
+
return node_fs_1.default.statSync(file).isDirectory() ? file : null;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function directoryListing(directory) {
|
|
148
|
+
const entries = node_fs_1.default
|
|
149
|
+
.readdirSync(directory, { withFileTypes: true })
|
|
150
|
+
.map((entry) => ({ name: entry.name, directory: isDirectory(directory, entry) }))
|
|
151
|
+
.sort((a, b) => a.directory === b.directory ? a.name.localeCompare(b.name) : a.directory ? -1 : 1);
|
|
152
|
+
const parent = node_path_1.default.dirname(directory);
|
|
153
|
+
const rows = entries.map((entry) => {
|
|
154
|
+
const href = (0, node_url_1.pathToFileURL)(node_path_1.default.join(directory, entry.name)).toString();
|
|
155
|
+
return `<li><a href="${escapeHtml(href)}">${escapeHtml(entry.name)}${entry.directory ? "/" : ""}</a></li>`;
|
|
156
|
+
});
|
|
157
|
+
const up = parent === directory ? "" : `<li><a href="${(0, node_url_1.pathToFileURL)(parent)}">../</a></li>`;
|
|
158
|
+
const html = `<!doctype html><meta charset="utf-8"><title>Index of ${escapeHtml(directory)}</title>
|
|
159
|
+
<style>:root{color-scheme:light dark}body{font:14px ui-monospace,Menlo,monospace;margin:2rem}h1{font-size:1rem;font-weight:600;margin:0 0 1rem}ul{list-style:none;padding:0}li{padding:2px 0}a{text-decoration:none}a:hover{text-decoration:underline}</style>
|
|
160
|
+
<h1>Index of ${escapeHtml(directory)}</h1><ul>${up}${rows.join("")}</ul>`;
|
|
161
|
+
return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
162
|
+
}
|
|
163
|
+
function isDirectory(parent, entry) {
|
|
164
|
+
if (!entry.isSymbolicLink())
|
|
165
|
+
return entry.isDirectory();
|
|
166
|
+
try {
|
|
167
|
+
return node_fs_1.default.statSync(node_path_1.default.join(parent, entry.name)).isDirectory();
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function escapeHtml(value) {
|
|
174
|
+
return value.replace(/[&<>"]/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[char]);
|
|
175
|
+
}
|
|
176
|
+
function downloadPath(filename) {
|
|
177
|
+
const dir = electron_1.app.getPath("downloads");
|
|
178
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
179
|
+
const ext = node_path_1.default.extname(filename);
|
|
180
|
+
const base = node_path_1.default.basename(filename, ext);
|
|
181
|
+
for (let i = 0;; i++) {
|
|
182
|
+
const candidate = node_path_1.default.join(dir, i === 0 ? filename : `${base} (${i})${ext}`);
|
|
183
|
+
if (!node_fs_1.default.existsSync(candidate))
|
|
184
|
+
return candidate;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cursorShapeFor(type: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cursorShapeFor = cursorShapeFor;
|
|
4
|
+
const CSS_CURSORS = new Set([
|
|
5
|
+
"default", "crosshair", "text", "wait", "help", "progress",
|
|
6
|
+
"cell", "vertical-text", "context-menu", "alias", "copy", "move",
|
|
7
|
+
"no-drop", "not-allowed", "grab", "grabbing", "zoom-in", "zoom-out",
|
|
8
|
+
"e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize",
|
|
9
|
+
"sw-resize", "w-resize", "ns-resize", "ew-resize", "nesw-resize",
|
|
10
|
+
"nwse-resize", "col-resize", "row-resize",
|
|
11
|
+
]);
|
|
12
|
+
function cursorShapeFor(type) {
|
|
13
|
+
// chromium's "pointer" is the plain arrow; its css pointer is "hand"
|
|
14
|
+
if (type === "hand")
|
|
15
|
+
return "pointer";
|
|
16
|
+
if (CSS_CURSORS.has(type))
|
|
17
|
+
return type;
|
|
18
|
+
if (type === "nodrop")
|
|
19
|
+
return "no-drop";
|
|
20
|
+
if (type.endsWith("-panning"))
|
|
21
|
+
return "all-scroll";
|
|
22
|
+
// pointer, custom, none, null, …
|
|
23
|
+
return "default";
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { WebContents } from "electron";
|
|
2
|
+
import type { Surface } from "../react";
|
|
3
|
+
import type { DevtoolsDock } from "./types";
|
|
4
|
+
import { PageInput } from "./input";
|
|
5
|
+
import type { SurfaceLayout } from "./types";
|
|
6
|
+
export type DevtoolsAction = "close" | "dock-bottom" | "dock-right";
|
|
7
|
+
export declare class DevtoolsWindow {
|
|
8
|
+
readonly input: PageInput;
|
|
9
|
+
private readonly pageContents;
|
|
10
|
+
private readonly surface;
|
|
11
|
+
private readonly window;
|
|
12
|
+
private readonly onAction;
|
|
13
|
+
private layout;
|
|
14
|
+
private dock;
|
|
15
|
+
private visible;
|
|
16
|
+
private focused;
|
|
17
|
+
private wholeSurfaceNext;
|
|
18
|
+
private readonly bitmaps;
|
|
19
|
+
private cdpAttached;
|
|
20
|
+
private destroyed;
|
|
21
|
+
private pendingPanel;
|
|
22
|
+
cursorShape: string;
|
|
23
|
+
onCursorChange: ((shape: string) => void) | null;
|
|
24
|
+
private readonly onDisplayChange;
|
|
25
|
+
constructor(pageContents: WebContents, surface: Surface, layout: SurfaceLayout, dock: DevtoolsDock, background: string, renderScale: number, onAction: (action: DevtoolsAction) => void, onClosed: () => void);
|
|
26
|
+
resize(layout: SurfaceLayout, options?: {
|
|
27
|
+
keepFrame?: boolean;
|
|
28
|
+
}): void;
|
|
29
|
+
showPanel(panel: string): void;
|
|
30
|
+
private execShowPanel;
|
|
31
|
+
setDock(dock: DevtoolsDock): void;
|
|
32
|
+
setVisible(visible: boolean): void;
|
|
33
|
+
focus(): void;
|
|
34
|
+
blur(): void;
|
|
35
|
+
close(): void;
|
|
36
|
+
private installControls;
|
|
37
|
+
private attachCdp;
|
|
38
|
+
private cdp;
|
|
39
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DevtoolsWindow = void 0;
|
|
4
|
+
const electron_1 = require("electron");
|
|
5
|
+
const cursor_1 = require("./cursor");
|
|
6
|
+
const frame_rate_1 = require("./frame-rate");
|
|
7
|
+
const input_1 = require("./input");
|
|
8
|
+
const offscreen_1 = require("./offscreen");
|
|
9
|
+
const paint_1 = require("./paint");
|
|
10
|
+
const types_1 = require("./types");
|
|
11
|
+
class DevtoolsWindow {
|
|
12
|
+
input;
|
|
13
|
+
pageContents;
|
|
14
|
+
surface;
|
|
15
|
+
window;
|
|
16
|
+
onAction;
|
|
17
|
+
layout;
|
|
18
|
+
dock;
|
|
19
|
+
visible = true;
|
|
20
|
+
focused = false;
|
|
21
|
+
wholeSurfaceNext = true;
|
|
22
|
+
bitmaps;
|
|
23
|
+
cdpAttached = false;
|
|
24
|
+
destroyed = false;
|
|
25
|
+
pendingPanel = null;
|
|
26
|
+
cursorShape = "default";
|
|
27
|
+
onCursorChange = null;
|
|
28
|
+
onDisplayChange = () => {
|
|
29
|
+
if (this.destroyed)
|
|
30
|
+
return;
|
|
31
|
+
this.window.webContents.setFrameRate(this.visible ? (0, frame_rate_1.frameRate)() : 4);
|
|
32
|
+
};
|
|
33
|
+
constructor(pageContents, surface, layout, dock, background, renderScale, onAction, onClosed) {
|
|
34
|
+
this.pageContents = pageContents;
|
|
35
|
+
this.surface = surface;
|
|
36
|
+
this.bitmaps = new paint_1.BitmapPresenter(surface);
|
|
37
|
+
this.layout = layout;
|
|
38
|
+
this.dock = dock;
|
|
39
|
+
this.onAction = onAction;
|
|
40
|
+
this.window = new electron_1.BrowserWindow({
|
|
41
|
+
...(0, types_1.cssSize)(layout.width, layout.height, layout.scale),
|
|
42
|
+
useContentSize: true,
|
|
43
|
+
backgroundColor: background,
|
|
44
|
+
show: false,
|
|
45
|
+
frame: false,
|
|
46
|
+
paintWhenInitiallyHidden: true,
|
|
47
|
+
acceptFirstMouse: true,
|
|
48
|
+
skipTaskbar: true,
|
|
49
|
+
fullscreenable: false,
|
|
50
|
+
resizable: false,
|
|
51
|
+
webPreferences: {
|
|
52
|
+
offscreen: (0, offscreen_1.offscreenPreferences)(renderScale),
|
|
53
|
+
sandbox: true,
|
|
54
|
+
nodeIntegration: false,
|
|
55
|
+
contextIsolation: true,
|
|
56
|
+
disableDialogs: true,
|
|
57
|
+
backgroundThrottling: false,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
this.input = new input_1.PageInput({
|
|
61
|
+
contents: () => this.window.webContents,
|
|
62
|
+
scale: () => this.layout.scale,
|
|
63
|
+
focus: () => this.focus(),
|
|
64
|
+
cdp: (method, params) => this.cdp(method, params),
|
|
65
|
+
});
|
|
66
|
+
this.window.webContents.setFrameRate((0, frame_rate_1.frameRate)());
|
|
67
|
+
electron_1.screen.on("display-added", this.onDisplayChange);
|
|
68
|
+
electron_1.screen.on("display-removed", this.onDisplayChange);
|
|
69
|
+
electron_1.screen.on("display-metrics-changed", this.onDisplayChange);
|
|
70
|
+
this.window.webContents.on("paint", (event, dirtyRect, image) => {
|
|
71
|
+
const shmFrame = event.softwareFrame;
|
|
72
|
+
if (!this.visible) {
|
|
73
|
+
event.texture?.release();
|
|
74
|
+
shmFrame?.release();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const presented = event.texture || shmFrame
|
|
78
|
+
? (0, paint_1.presentPaint)(this.surface, event.texture, shmFrame, image, dirtyRect, this.wholeSurfaceNext)
|
|
79
|
+
: this.bitmaps.push(image, dirtyRect, this.wholeSurfaceNext);
|
|
80
|
+
if (presented) {
|
|
81
|
+
this.wholeSurfaceNext = false;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
this.window.webContents.on("cursor-changed", (_event, type) => {
|
|
85
|
+
const shape = (0, cursor_1.cursorShapeFor)(type);
|
|
86
|
+
if (shape === this.cursorShape)
|
|
87
|
+
return;
|
|
88
|
+
this.cursorShape = shape;
|
|
89
|
+
this.onCursorChange?.(shape);
|
|
90
|
+
});
|
|
91
|
+
this.window.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
|
|
92
|
+
this.window.webContents.on("dom-ready", () => {
|
|
93
|
+
void this.installControls().catch(() => { });
|
|
94
|
+
if (this.pendingPanel) {
|
|
95
|
+
const panel = this.pendingPanel;
|
|
96
|
+
this.pendingPanel = null;
|
|
97
|
+
this.execShowPanel(panel);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
this.window.on("closed", () => {
|
|
101
|
+
this.destroyed = true;
|
|
102
|
+
this.surface.clear();
|
|
103
|
+
onClosed();
|
|
104
|
+
});
|
|
105
|
+
pageContents.setDevToolsWebContents(this.window.webContents);
|
|
106
|
+
pageContents.openDevTools({ mode: "detach" }); // not really detached since we are compositing it into one "window"
|
|
107
|
+
}
|
|
108
|
+
resize(layout, options) {
|
|
109
|
+
if (this.destroyed)
|
|
110
|
+
return;
|
|
111
|
+
if (this.layout.width === layout.width &&
|
|
112
|
+
this.layout.height === layout.height &&
|
|
113
|
+
this.layout.scale === layout.scale) {
|
|
114
|
+
this.layout = layout;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.layout = layout;
|
|
118
|
+
if (!options?.keepFrame)
|
|
119
|
+
this.surface.clear();
|
|
120
|
+
const size = (0, types_1.cssSize)(layout.width, layout.height, layout.scale);
|
|
121
|
+
this.window.setContentSize(size.width, size.height, false);
|
|
122
|
+
}
|
|
123
|
+
showPanel(panel) {
|
|
124
|
+
if (this.destroyed)
|
|
125
|
+
return;
|
|
126
|
+
const contents = this.window.webContents;
|
|
127
|
+
if (!contents.getURL().startsWith("devtools://") || contents.isLoading()) {
|
|
128
|
+
this.pendingPanel = panel;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.execShowPanel(panel);
|
|
132
|
+
}
|
|
133
|
+
execShowPanel(panel) {
|
|
134
|
+
const target = JSON.stringify(panel);
|
|
135
|
+
void this.window.webContents
|
|
136
|
+
.executeJavaScript(`(function() {
|
|
137
|
+
try { localStorage.setItem("panel-selectedTab", JSON.stringify(${target})); } catch (e) {}
|
|
138
|
+
var tries = 0;
|
|
139
|
+
var timer = setInterval(function() {
|
|
140
|
+
if (window.InspectorFrontendAPI && InspectorFrontendAPI.showPanel) {
|
|
141
|
+
InspectorFrontendAPI.showPanel(${target});
|
|
142
|
+
}
|
|
143
|
+
if (++tries >= 8) clearInterval(timer);
|
|
144
|
+
}, 400);
|
|
145
|
+
})()`, true)
|
|
146
|
+
.catch(() => { });
|
|
147
|
+
}
|
|
148
|
+
setDock(dock) {
|
|
149
|
+
if (this.destroyed || this.dock === dock)
|
|
150
|
+
return;
|
|
151
|
+
this.dock = dock;
|
|
152
|
+
void this.window.webContents
|
|
153
|
+
.executeJavaScript(`window.__pixelDevtoolsSetDock && window.__pixelDevtoolsSetDock(${JSON.stringify(dock)})`, true)
|
|
154
|
+
.catch(() => { });
|
|
155
|
+
}
|
|
156
|
+
setVisible(visible) {
|
|
157
|
+
if (this.visible === visible || this.destroyed)
|
|
158
|
+
return;
|
|
159
|
+
this.visible = visible;
|
|
160
|
+
if (visible) {
|
|
161
|
+
// one surface across every tab's inspector, so its pixels belong to whichever one drew last
|
|
162
|
+
this.wholeSurfaceNext = true;
|
|
163
|
+
this.window.webContents.setFrameRate((0, frame_rate_1.frameRate)());
|
|
164
|
+
this.window.webContents.invalidate();
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
this.window.webContents.setFrameRate(4);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
focus() {
|
|
171
|
+
if (this.focused || this.destroyed)
|
|
172
|
+
return;
|
|
173
|
+
this.focused = true;
|
|
174
|
+
this.window.focus();
|
|
175
|
+
this.window.webContents.focus();
|
|
176
|
+
void this.cdp("Emulation.setFocusEmulationEnabled", { enabled: true }).catch(() => { });
|
|
177
|
+
}
|
|
178
|
+
blur() {
|
|
179
|
+
if (!this.focused || this.destroyed)
|
|
180
|
+
return;
|
|
181
|
+
this.focused = false;
|
|
182
|
+
this.input.releaseKeys();
|
|
183
|
+
this.window.blurWebView();
|
|
184
|
+
void this.cdp("Emulation.setFocusEmulationEnabled", { enabled: false }).catch(() => { });
|
|
185
|
+
}
|
|
186
|
+
close() {
|
|
187
|
+
if (this.destroyed)
|
|
188
|
+
return;
|
|
189
|
+
electron_1.screen.off("display-added", this.onDisplayChange);
|
|
190
|
+
electron_1.screen.off("display-removed", this.onDisplayChange);
|
|
191
|
+
electron_1.screen.off("display-metrics-changed", this.onDisplayChange);
|
|
192
|
+
if (!this.pageContents.isDestroyed())
|
|
193
|
+
this.pageContents.closeDevTools();
|
|
194
|
+
this.window.destroy();
|
|
195
|
+
}
|
|
196
|
+
async installControls() {
|
|
197
|
+
await this.attachCdp();
|
|
198
|
+
await this.window.webContents.executeJavaScript(controlsScript(this.dock), true);
|
|
199
|
+
}
|
|
200
|
+
async attachCdp() {
|
|
201
|
+
if (this.cdpAttached || this.destroyed)
|
|
202
|
+
return;
|
|
203
|
+
this.window.webContents.debugger.attach("1.3");
|
|
204
|
+
this.cdpAttached = true;
|
|
205
|
+
this.window.webContents.debugger.on("message", (_event, method, params) => {
|
|
206
|
+
if (method !== "Runtime.bindingCalled")
|
|
207
|
+
return;
|
|
208
|
+
const call = params;
|
|
209
|
+
if (call.name !== "__pixelDevtools")
|
|
210
|
+
return;
|
|
211
|
+
if (call.payload === "close" || call.payload === "dock-bottom" || call.payload === "dock-right") {
|
|
212
|
+
this.onAction(call.payload);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
await this.cdp("Runtime.enable");
|
|
216
|
+
await this.cdp("Runtime.addBinding", { name: "__pixelDevtools" });
|
|
217
|
+
}
|
|
218
|
+
async cdp(method, params) {
|
|
219
|
+
if (this.destroyed)
|
|
220
|
+
return undefined;
|
|
221
|
+
await this.attachCdp();
|
|
222
|
+
return this.window.webContents.debugger.sendCommand(method, params);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.DevtoolsWindow = DevtoolsWindow;
|
|
226
|
+
function controlsScript(dock) {
|
|
227
|
+
return `(function(dock) {
|
|
228
|
+
if (window.__pixelDevtoolsSetDock) { window.__pixelDevtoolsSetDock(dock); return; }
|
|
229
|
+
var ICONS = {
|
|
230
|
+
"dock-bottom": '<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><rect x="1.5" y="2.5" width="13" height="11" rx="1.5" stroke="currentColor"/><rect x="3.5" y="8.5" width="9" height="3" fill="currentColor"/></svg>',
|
|
231
|
+
"dock-right": '<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><rect x="1.5" y="2.5" width="13" height="11" rx="1.5" stroke="currentColor"/><rect x="8.5" y="4.5" width="4" height="7" fill="currentColor"/></svg>',
|
|
232
|
+
"close": '<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>'
|
|
233
|
+
};
|
|
234
|
+
var findToolbar = function() {
|
|
235
|
+
// the drawer's tabbed pane can precede the main one in document order;
|
|
236
|
+
// mounting there puts the buttons in the drawer strip instead of the
|
|
237
|
+
// top tab bar
|
|
238
|
+
var panes = document.querySelectorAll(".main-tabbed-pane");
|
|
239
|
+
if (!panes.length) panes = document.querySelectorAll(".tabbed-pane");
|
|
240
|
+
for (var i = 0; i < panes.length; i++) {
|
|
241
|
+
var root = panes[i].shadowRoot;
|
|
242
|
+
var bar = root && root.querySelector(".tabbed-pane-right-toolbar");
|
|
243
|
+
if (bar) return { bar: bar, header: root.querySelector(".tabbed-pane-header") };
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
};
|
|
247
|
+
var buttons = {};
|
|
248
|
+
var makeButton = function(action) {
|
|
249
|
+
var b = document.createElement("button");
|
|
250
|
+
b.title = action === "close" ? "Close DevTools"
|
|
251
|
+
: action === "dock-bottom" ? "Dock to bottom" : "Dock to right";
|
|
252
|
+
b.style.cssText = "all:unset;box-sizing:border-box;width:24px;height:24px;" +
|
|
253
|
+
"display:flex;align-items:center;justify-content:center;border-radius:4px;" +
|
|
254
|
+
"color:var(--sys-color-on-surface-subtle, #5f6368);";
|
|
255
|
+
b.innerHTML = ICONS[action];
|
|
256
|
+
b.onmouseenter = function() { b.style.background = "var(--sys-color-state-hover-on-subtle, rgba(125,125,125,.15))"; };
|
|
257
|
+
b.onmouseleave = function() { b.style.background = "transparent"; };
|
|
258
|
+
b.onclick = function() { window.__pixelDevtools(action); };
|
|
259
|
+
buttons[action] = b;
|
|
260
|
+
return b;
|
|
261
|
+
};
|
|
262
|
+
var makeHost = function(id, floatCss) {
|
|
263
|
+
var host = document.createElement("div");
|
|
264
|
+
host.id = id;
|
|
265
|
+
host.style.cssText = "display:flex;align-items:center;gap:2px;flex-shrink:0;margin:0 4px;" + floatCss;
|
|
266
|
+
return host;
|
|
267
|
+
};
|
|
268
|
+
var mount = function(found) {
|
|
269
|
+
var dockHost = makeHost("__pixel-devtools-dock", found ? "" : "position:fixed;top:4px;right:132px;z-index:100000;");
|
|
270
|
+
dockHost.appendChild(makeButton("dock-bottom"));
|
|
271
|
+
dockHost.appendChild(makeButton("dock-right"));
|
|
272
|
+
// the close button goes at the end of the header row (the ⚠/gear/⋮
|
|
273
|
+
// icons are siblings of the right toolbar), right-most like the native
|
|
274
|
+
// devtools close
|
|
275
|
+
var closeHost = makeHost("__pixel-devtools-close", found ? "" : "position:fixed;top:4px;right:4px;z-index:100000;");
|
|
276
|
+
closeHost.appendChild(makeButton("close"));
|
|
277
|
+
window.__pixelDevtoolsSetDock = function(d) {
|
|
278
|
+
var active = "var(--sys-color-primary-bright, #1a73e8)";
|
|
279
|
+
var idle = "var(--sys-color-on-surface-subtle, #5f6368)";
|
|
280
|
+
buttons["dock-bottom"].style.color = d === "bottom" ? active : idle;
|
|
281
|
+
buttons["dock-right"].style.color = d === "right" ? active : idle;
|
|
282
|
+
};
|
|
283
|
+
window.__pixelDevtoolsSetDock(dock);
|
|
284
|
+
if (found) {
|
|
285
|
+
found.bar.insertBefore(dockHost, found.bar.firstChild);
|
|
286
|
+
(found.header || found.bar).appendChild(closeHost);
|
|
287
|
+
} else {
|
|
288
|
+
document.documentElement.appendChild(dockHost);
|
|
289
|
+
document.documentElement.appendChild(closeHost);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var tries = 0;
|
|
293
|
+
var timer = setInterval(function() {
|
|
294
|
+
tries++;
|
|
295
|
+
var found = findToolbar();
|
|
296
|
+
if (found) { clearInterval(timer); mount(found); }
|
|
297
|
+
else if (tries > 20) { clearInterval(timer); mount(null); }
|
|
298
|
+
}, 250);
|
|
299
|
+
})(${JSON.stringify(dock)})`;
|
|
300
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FaviconCache = void 0;
|
|
7
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const electron_1 = require("electron");
|
|
11
|
+
const RASTER_URL = /\.(png|jpe?g|webp|gif)(\?|$)/i;
|
|
12
|
+
function isIco(data) {
|
|
13
|
+
return data.length > 4 && data[0] === 0 && data[1] === 0 && data[2] === 1 && data[3] === 0;
|
|
14
|
+
}
|
|
15
|
+
class FaviconCache {
|
|
16
|
+
dir;
|
|
17
|
+
constructor(dir = node_path_1.default.join(electron_1.app.getPath("userData"), "favicons")) {
|
|
18
|
+
this.dir = dir;
|
|
19
|
+
}
|
|
20
|
+
async resolve(urls, ses) {
|
|
21
|
+
if (urls.length === 0)
|
|
22
|
+
return null;
|
|
23
|
+
const candidates = [...urls].sort((a, b) => Number(RASTER_URL.test(b)) - Number(RASTER_URL.test(a)));
|
|
24
|
+
try {
|
|
25
|
+
const fallback = new URL("/favicon.ico", urls[0]).toString();
|
|
26
|
+
if (!candidates.includes(fallback))
|
|
27
|
+
candidates.push(fallback);
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
const stem = node_path_1.default.join(this.dir, node_crypto_1.default
|
|
31
|
+
.createHash("sha1")
|
|
32
|
+
.update(`${ses.storagePath ?? ""}\0${candidates.join("\n")}`)
|
|
33
|
+
.digest("hex")
|
|
34
|
+
.slice(0, 16));
|
|
35
|
+
const cached = [`${stem}.png`, `${stem}.ico`].find((file) => node_fs_1.default.existsSync(file));
|
|
36
|
+
if (cached)
|
|
37
|
+
return cached;
|
|
38
|
+
for (const url of candidates) {
|
|
39
|
+
const file = await this.fetchDecodable(url, ses, stem);
|
|
40
|
+
if (file)
|
|
41
|
+
return file;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
async fetchDecodable(url, ses, stem) {
|
|
46
|
+
try {
|
|
47
|
+
const response = await ses.fetch(url);
|
|
48
|
+
if (!response.ok)
|
|
49
|
+
return null;
|
|
50
|
+
const data = Buffer.from(await response.arrayBuffer());
|
|
51
|
+
if (data.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
const decoded = electron_1.nativeImage.createFromBuffer(data);
|
|
54
|
+
if (!decoded.isEmpty()) {
|
|
55
|
+
const file = `${stem}.png`;
|
|
56
|
+
node_fs_1.default.mkdirSync(this.dir, { recursive: true });
|
|
57
|
+
await node_fs_1.default.promises.writeFile(file, decoded.resize({ width: 32, height: 32 }).toPNG());
|
|
58
|
+
return file;
|
|
59
|
+
}
|
|
60
|
+
if (isIco(data)) {
|
|
61
|
+
const file = `${stem}.ico`;
|
|
62
|
+
node_fs_1.default.mkdirSync(this.dir, { recursive: true });
|
|
63
|
+
await node_fs_1.default.promises.writeFile(file, data);
|
|
64
|
+
return file;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.FaviconCache = FaviconCache;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function frameRate(): number;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.frameRate = frameRate;
|
|
4
|
+
const electron_1 = require("electron");
|
|
5
|
+
function frameRate() {
|
|
6
|
+
const configured = Number(process.env.PIXEL_FPS);
|
|
7
|
+
if (Number.isFinite(configured) && configured > 0) {
|
|
8
|
+
return Math.max(1, Math.min(240, Math.round(configured)));
|
|
9
|
+
}
|
|
10
|
+
const fastest = Math.max(0, ...electron_1.screen.getAllDisplays().map((display) => display.displayFrequency));
|
|
11
|
+
return fastest > 0 ? Math.min(240, Math.round(fastest)) : 60;
|
|
12
|
+
}
|