@glittering0717/ai-browser 0.0.0-stage → 1.2.0

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.
@@ -0,0 +1,654 @@
1
+ // main/canvas_hook_source.js — 注入页面 main world 的 canvas 绘制调用记录器源码
2
+ //
3
+ // 为什么必须是 main world:`contextIsolation: true` 让 preload 只活在 isolated
4
+ // world,改自己 world 的原型截不到页面代码的调用。要看到页面画了什么,脚本必须
5
+ // 跑在页面自己的 realm 里 —— 因此用 Page.addScriptToEvaluateOnNewDocument
6
+ // (不传 worldName)+ 首个真实导航之前安装。
7
+ //
8
+ // 为什么是 tee 而不是接管:canvas 是有状态的立即模式 API,同一像素可能经历
9
+ // clip / composite / filter / 阴影 / 多次覆盖 / putImageData。包装函数必须
10
+ // 先 `Reflect.apply(original, this, args)` 让原生 API 正常执行(返回值、异常、
11
+ // this、绘制顺序一律不变),再在独立 try/catch 里记录。记录失败不影响页面。
12
+ //
13
+ // 这个模块只产字符串,不依赖 Electron,可在纯 Node 下单测。
14
+
15
+ export function buildCanvasHookSource(opts = {}) {
16
+ const binding = JSON.stringify(String(opts.bindingName || ''));
17
+ const key = JSON.stringify(String(opts.bridgeKey || ''));
18
+ const mode = JSON.stringify(String(opts.mode || 'semantic'));
19
+
20
+ return `(function () {
21
+ "use strict";
22
+ var BINDING = ${binding};
23
+ var KEY = ${key};
24
+ try {
25
+ var G = (typeof window !== "undefined") ? window : self;
26
+ if (!G) return;
27
+ if (G[KEY] && G[KEY].__aiCanvasHook) return;
28
+
29
+ var isWindow = !!(G.document && G.document.createElement);
30
+ var NAV = "nav-" + Math.random().toString(36).slice(2, 8);
31
+ var FRAME = !isWindow ? "frame-worker" : ((G.top === G) ? "frame-main" : "frame-sub");
32
+
33
+ var MAX_TEXT_CP = 4096; // 单条文本上限(Unicode code point)
34
+ var MAX_SHADER_CHARS = 16384; // 单个 shader 源码上限
35
+ var MAX_BATCH = 200; // 单批调用条数
36
+ var MAX_BATCH_BYTES = 128 * 1024;
37
+ var MAX_PATH_POINTS = 512; // 路径采样点上限,超出只保留计数和 bounds
38
+ var MAX_URL_CHARS = 2048;
39
+
40
+ var mode = ${mode};
41
+ var recording = (mode !== "off");
42
+ var report = G[BINDING];
43
+
44
+ var canvasCount = 0;
45
+ var canvasMap = new WeakMap(); // canvas 元素 -> meta
46
+ var ctxStateMap = new WeakMap(); // ctx -> state
47
+ var pending = [];
48
+ var pendingBytes = 0;
49
+ var scheduled = false;
50
+ var lastSig = null;
51
+ var lastEntry = null;
52
+
53
+ // ---- 小工具 ----------------------------------------------------------
54
+
55
+ function num(v, d) { var n = Number(v); return (typeof n === "number" && isFinite(n)) ? n : d; }
56
+ function r2(v) { return Math.round(num(v, 0) * 100) / 100; }
57
+ function strOf(v, d) { return (typeof v === "string") ? v : d; }
58
+
59
+ function hash32(s) {
60
+ var h = 5381;
61
+ for (var i = 0; i < s.length; i++) h = ((h * 33) ^ s.charCodeAt(i)) >>> 0;
62
+ return h >>> 0;
63
+ }
64
+
65
+ function fontSize(font) {
66
+ try {
67
+ var m = /([0-9.]+)\s*(px|pt|em|rem|%)?/.exec(String(font || ""));
68
+ if (m) return num(m[1], 10);
69
+ } catch (e) {}
70
+ return 10;
71
+ }
72
+
73
+ function matOf(ctx) {
74
+ try {
75
+ if (typeof ctx.getTransform === "function") {
76
+ var m = ctx.getTransform();
77
+ return [num(m.a, 1), num(m.b, 0), num(m.c, 0), num(m.d, 1), num(m.e, 0), num(m.f, 0)];
78
+ }
79
+ } catch (e) {}
80
+ return [1, 0, 0, 1, 0, 0];
81
+ }
82
+
83
+ function applyPt(m, x, y) {
84
+ return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
85
+ }
86
+
87
+ // 四角经当前 transform 变换后的 AABB(canvas backing-store 坐标)。
88
+ function rectBounds(m, x, y, w, h) {
89
+ var p0 = applyPt(m, x, y), p1 = applyPt(m, x + w, y),
90
+ p2 = applyPt(m, x + w, y + h), p3 = applyPt(m, x, y + h);
91
+ var xs = [p0[0], p1[0], p2[0], p3[0]], ys = [p0[1], p1[1], p2[1], p3[1]];
92
+ var x0 = Math.min.apply(null, xs), y0 = Math.min.apply(null, ys);
93
+ var x1 = Math.max.apply(null, xs), y1 = Math.max.apply(null, ys);
94
+ return { x: r2(x0), y: r2(y0), width: r2(x1 - x0), height: r2(y1 - y0) };
95
+ }
96
+
97
+ function boundsFromPoints(pts) {
98
+ if (!pts || !pts.length) return null;
99
+ var x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
100
+ for (var i = 0; i < pts.length; i++) {
101
+ if (pts[i][0] < x0) x0 = pts[i][0];
102
+ if (pts[i][1] < y0) y0 = pts[i][1];
103
+ if (pts[i][0] > x1) x1 = pts[i][0];
104
+ if (pts[i][1] > y1) y1 = pts[i][1];
105
+ }
106
+ if (!isFinite(x0)) return null;
107
+ return { x: r2(x0), y: r2(y0), width: r2(x1 - x0), height: r2(y1 - y0) };
108
+ }
109
+
110
+ function styleVal(v) {
111
+ if (typeof v === "string") return v.length > 200 ? v.slice(0, 200) : v;
112
+ if (typeof v === "number") return isFinite(v) ? r2(v) : null;
113
+ if (v && typeof v === "object") {
114
+ try { if (typeof G.CanvasGradient === "function" && v instanceof G.CanvasGradient) return "[gradient]"; } catch (e) {}
115
+ try { if (typeof G.CanvasPattern === "function" && v instanceof G.CanvasPattern) return "[pattern]"; } catch (e) {}
116
+ return "[object]";
117
+ }
118
+ return null;
119
+ }
120
+
121
+ function styleOf(ctx) {
122
+ var out = {};
123
+ var map = {
124
+ font: "font", textAlign: "text_align", textBaseline: "text_baseline",
125
+ direction: "direction", fillStyle: "fill_style", strokeStyle: "stroke_style",
126
+ lineWidth: "line_width", globalAlpha: "global_alpha",
127
+ globalCompositeOperation: "global_composite_operation"
128
+ };
129
+ for (var k in map) {
130
+ if (!Object.prototype.hasOwnProperty.call(map, k)) continue;
131
+ var v;
132
+ try { v = ctx[k]; } catch (e) { continue; }
133
+ if (v === undefined || v === null) continue;
134
+ var sv = styleVal(v);
135
+ if (sv === null) continue;
136
+ out[map[k]] = sv;
137
+ }
138
+ return out;
139
+ }
140
+
141
+ // ---- 上报 ------------------------------------------------------------
142
+
143
+ function flush() {
144
+ scheduled = false;
145
+ if (!pending.length) return;
146
+ var entries = pending;
147
+ pending = []; pendingBytes = 0; lastSig = null; lastEntry = null;
148
+ // 注意:本函数体位于外层模板字符串内部。在这里手写转义的引号会被模板
149
+ // 吃掉反斜杠,生成出字符串被提前闭合的坏码,注入后整页脚本直接报错。
150
+ // 因此统一用 JSON.stringify 构造载荷,完全不手写转义。
151
+ // 同理,本文件模板内部的注释不要使用反引号,否则会提前终止模板。
152
+ var objs = [];
153
+ for (var i = 0; i < entries.length; i++) {
154
+ entries[i].rec.n = entries[i].n;
155
+ objs.push(entries[i].rec);
156
+ }
157
+ if (!objs.length) return;
158
+ var payload;
159
+ try {
160
+ payload = JSON.stringify({ v: 1, nav: NAV, frame: FRAME, calls: objs });
161
+ } catch (e) { return; }
162
+ try { if (report) report(payload); } catch (e) {}
163
+ }
164
+
165
+ function schedule() {
166
+ if (scheduled) return;
167
+ scheduled = true;
168
+ try { setTimeout(flush, 0); } catch (e) { flush(); }
169
+ }
170
+
171
+ // 同一批次内签名相同的连续调用合并成 repeat_count(动画循环的主要降本手段)。
172
+ function push(rec) {
173
+ if (!recording) return;
174
+ if (!report) { try { report = G[BINDING]; } catch (e) {} }
175
+ if (!report) return;
176
+ var sig;
177
+ try { sig = JSON.stringify(rec); } catch (e) { return; }
178
+ if (!sig) return;
179
+ if (mode !== "trace" && lastEntry && lastSig === sig) {
180
+ lastEntry.n += 1;
181
+ pendingBytes += sig.length;
182
+ } else {
183
+ var entry = { rec: rec, n: 1, sig: sig, bytes: sig.length + 24 };
184
+ pending.push(entry);
185
+ pendingBytes += entry.bytes;
186
+ lastEntry = entry; lastSig = sig;
187
+ }
188
+ if (pending.length >= MAX_BATCH || pendingBytes >= MAX_BATCH_BYTES) flush();
189
+ else schedule();
190
+ }
191
+
192
+ // ---- canvas / ctx 身份 ------------------------------------------------
193
+
194
+ function metaFor(el, kind) {
195
+ var m = null;
196
+ try { m = canvasMap.get(el); } catch (e) {}
197
+ if (m) {
198
+ if (kind && kind !== "unknown" && m.kind === "unknown") m.kind = kind;
199
+ return m;
200
+ }
201
+ canvasCount += 1;
202
+ m = { id: "canvas:" + NAV + ":" + FRAME + ":" + canvasCount, kind: kind || "unknown" };
203
+ try { canvasMap.set(el, m); } catch (e) {}
204
+ try { el.__aiCanvasId = m.id; } catch (e) {}
205
+ return m;
206
+ }
207
+
208
+ function stOf(ctx, kind) {
209
+ var st = null;
210
+ try { st = ctxStateMap.get(ctx); } catch (e) {}
211
+ if (st) return st;
212
+ var cv = null;
213
+ try { cv = ctx.canvas; } catch (e) {}
214
+ var m = metaFor(cv || ctx, kind || "unknown");
215
+ st = { id: m.id, epoch: 1, w: 0, h: 0, path: { n: 0, pts: [], opaque: false }, draws: 0 };
216
+ try { ctxStateMap.set(ctx, st); } catch (e) {}
217
+ try { ctx.__aiCanvasId = m.id; } catch (e) {}
218
+ return st;
219
+ }
220
+
221
+ // canvas resize → 新 scene_epoch(旧记录的坐标体系已失效)。
222
+ function syncSize(ctx, st) {
223
+ var cv = null;
224
+ try { cv = ctx.canvas; } catch (e) {}
225
+ if (!cv) return;
226
+ var w = 0, h = 0;
227
+ try { w = num(cv.width, 0); h = num(cv.height, 0); } catch (e) {}
228
+ if (st.w === w && st.h === h) return;
229
+ var first = (st.w === 0 && st.h === 0);
230
+ st.w = w; st.h = h;
231
+ if (!first) {
232
+ st.epoch += 1;
233
+ push({ c: st.id, m: "resize", k: "state", w: w, h: h, e: st.epoch });
234
+ }
235
+ }
236
+
237
+ function addPoint(st, m, x, y) {
238
+ var p = st.path;
239
+ p.n += 1;
240
+ if (p.pts.length < MAX_PATH_POINTS) p.pts.push(applyPt(m, x, y));
241
+ }
242
+
243
+ // ---- 2D 记录 ---------------------------------------------------------
244
+
245
+ function recordText(ctx, method, args) {
246
+ var st = stOf(ctx, "2d");
247
+ syncSize(ctx, st);
248
+ var str = args[0] == null ? "" : String(args[0]);
249
+ var cps = Array.from(str);
250
+ var full = cps.length;
251
+ var out = str, tt = false;
252
+ if (full > MAX_TEXT_CP) { out = cps.slice(0, MAX_TEXT_CP).join(""); tt = true; }
253
+ var x = num(args[1], 0), y = num(args[2], 0);
254
+ var m = matOf(ctx);
255
+ var w = 0, asc = 0, desc = 0;
256
+ try {
257
+ var tm = ctx.measureText(str);
258
+ if (tm) {
259
+ w = num(tm.width, 0);
260
+ asc = num(tm.actualBoundingBoxAscent, 0);
261
+ desc = num(tm.actualBoundingBoxDescent, 0);
262
+ }
263
+ } catch (e) {}
264
+ var fs = fontSize(ctx.font);
265
+ if (!w) w = full * fs * 0.6;
266
+ var h = (asc + desc) || fs;
267
+ var align = strOf(ctx.textAlign, "start");
268
+ var left = x;
269
+ if (align === "center" || align === "middle") left = x - w / 2;
270
+ else if (align === "right" || align === "end") left = x - w;
271
+ var base = strOf(ctx.textBaseline, "alphabetic");
272
+ var top = y - asc;
273
+ if (base === "top" || base === "hanging") top = y;
274
+ else if (base === "middle") top = y - h / 2;
275
+ else if (base === "bottom" || base === "ideographic") top = y - h;
276
+ push({
277
+ c: st.id, m: method, k: "text", t: out, tt: tt, tl: full,
278
+ b: rectBounds(m, left, top, w, h), st: styleOf(ctx)
279
+ });
280
+ }
281
+
282
+ function recordRect(ctx, method, args) {
283
+ var st = stOf(ctx, "2d");
284
+ syncSize(ctx, st);
285
+ var x = num(args[0], 0), y = num(args[1], 0), w = num(args[2], 0), h = num(args[3], 0);
286
+ var m = matOf(ctx);
287
+ var rec = {
288
+ c: st.id, m: method, k: (method === "clearRect" ? "clear" : "rect"),
289
+ b: rectBounds(m, x, y, w, h), st: styleOf(ctx)
290
+ };
291
+ // 全量 clear 等价于场景重置 → 新 epoch。
292
+ if (method === "clearRect" && st.w > 0 && st.h > 0 &&
293
+ x <= 0 && y <= 0 && (x + w) >= st.w && (y + h) >= st.h) {
294
+ st.epoch += 1;
295
+ rec.e = st.epoch;
296
+ }
297
+ push(rec);
298
+ }
299
+
300
+ function imageKind(img) {
301
+ if (!img) return "unknown";
302
+ try {
303
+ var n = img.constructor && img.constructor.name;
304
+ if (n) {
305
+ n = String(n);
306
+ if (n === "HTMLImageElement") return "image";
307
+ if (n === "HTMLCanvasElement") return "canvas";
308
+ if (n === "HTMLVideoElement") return "video";
309
+ if (n === "ImageBitmap") return "imagebitmap";
310
+ if (n === "OffscreenCanvas") return "offscreen";
311
+ if (n === "SVGImageElement") return "svgimage";
312
+ return n.toLowerCase();
313
+ }
314
+ } catch (e) {}
315
+ return "unknown";
316
+ }
317
+
318
+ function recordDrawImage(ctx, args) {
319
+ var st = stOf(ctx, "2d");
320
+ syncSize(ctx, st);
321
+ var img = args[0];
322
+ var src = { kind: imageKind(img) };
323
+ try { src.width = num(img.width, 0); src.height = num(img.height, 0); } catch (e) {}
324
+ try {
325
+ var u = (img && (img.currentSrc || img.src)) ? String(img.currentSrc || img.src) : null;
326
+ if (u) {
327
+ // data: URL 只记类型和长度 —— 不复制像素。
328
+ if (u.indexOf("data:") === 0) {
329
+ src.url = "data:" + (u.split(";")[0].slice(5) || "") + ";len=" + u.length;
330
+ src.data_url = true;
331
+ } else {
332
+ src.url = u.length > MAX_URL_CHARS ? u.slice(0, MAX_URL_CHARS) : u;
333
+ }
334
+ }
335
+ } catch (e) {}
336
+ var m = matOf(ctx);
337
+ var dx, dy, dw, dh, srect = null;
338
+ if (args.length >= 9) {
339
+ srect = { x: num(args[1], 0), y: num(args[2], 0), width: num(args[3], 0), height: num(args[4], 0) };
340
+ dx = num(args[5], 0); dy = num(args[6], 0); dw = num(args[7], 0); dh = num(args[8], 0);
341
+ } else if (args.length >= 5) {
342
+ dx = num(args[1], 0); dy = num(args[2], 0); dw = num(args[3], 0); dh = num(args[4], 0);
343
+ } else {
344
+ dx = num(args[1], 0); dy = num(args[2], 0);
345
+ dw = num(src.width, 0); dh = num(src.height, 0);
346
+ }
347
+ push({
348
+ c: st.id, m: "drawImage", k: "image", src: src, srect: srect,
349
+ b: rectBounds(m, dx, dy, dw, dh), st: styleOf(ctx)
350
+ });
351
+ }
352
+
353
+ function recordPathDraw(ctx, method, args) {
354
+ var st = stOf(ctx, "2d");
355
+ syncSize(ctx, st);
356
+ var p = st.path;
357
+ // 显式 Path2D 参数的内部指令无法用标准 API 反射 —— 如实标记,不猜。
358
+ var opaque = !!(args.length && args[0] && typeof args[0] === "object") || p.opaque;
359
+ var rule = null;
360
+ for (var i = 0; i < args.length; i++) {
361
+ if (typeof args[i] === "string") { rule = String(args[i]); break; }
362
+ }
363
+ push({
364
+ c: st.id, m: method, k: "path",
365
+ p: { segments: p.n, opaque_path: opaque, approximate: true },
366
+ fill_rule: rule,
367
+ b: boundsFromPoints(p.pts),
368
+ st: styleOf(ctx)
369
+ });
370
+ }
371
+
372
+ function recordPixel(ctx, method, args) {
373
+ var st = stOf(ctx, "2d");
374
+ syncSize(ctx, st);
375
+ var id = args[0];
376
+ var w = 0, h = 0;
377
+ try { w = num(id.width, 0); h = num(id.height, 0); } catch (e) {}
378
+ push({
379
+ c: st.id, m: method, k: "pixel",
380
+ dst: { x: num(args[1], 0), y: num(args[2], 0) },
381
+ image: { width: w, height: h },
382
+ pixels_captured: false
383
+ });
384
+ }
385
+
386
+ function recordState(ctx, method, args) {
387
+ if (mode !== "trace") return;
388
+ var st = stOf(ctx, "2d");
389
+ var rec = { c: st.id, m: method, k: "state" };
390
+ if (method === "clip") rec.b = boundsFromPoints(st.path.pts);
391
+ else if (args.length && typeof args[0] === "number") rec.args = Array.prototype.slice.call(args, 0, 6).map(function (v) { return r2(v); });
392
+ push(rec);
393
+ }
394
+
395
+ // ---- WebGL 记录 ------------------------------------------------------
396
+
397
+ function glRecord(ctx, method, args) {
398
+ var st = stOf(ctx, "webgl");
399
+ syncSize(ctx, st);
400
+ if (method === "shaderSource") {
401
+ var src = args[1] == null ? "" : String(args[1]);
402
+ push({
403
+ c: st.id, m: "shaderSource", k: "webgl",
404
+ shader: {
405
+ length: src.length,
406
+ truncated: src.length > MAX_SHADER_CHARS,
407
+ hash: hash32(src),
408
+ source: src.length > MAX_SHADER_CHARS ? src.slice(0, MAX_SHADER_CHARS) : src
409
+ }
410
+ });
411
+ return;
412
+ }
413
+ if (method === "drawArrays" || method === "drawElements") {
414
+ st.draws += 1;
415
+ push({
416
+ c: st.id, m: method, k: "webgl",
417
+ gl: {
418
+ mode: num(args[0], 0),
419
+ count: num(method === "drawArrays" ? args[2] : args[1], 0),
420
+ type: method === "drawElements" ? num(args[2], 0) : undefined
421
+ }
422
+ });
423
+ return;
424
+ }
425
+ if (method === "texImage2D") {
426
+ var tw = 0, th = 0;
427
+ if (args.length >= 9) { tw = num(args[3], 0); th = num(args[4], 0); }
428
+ else if (args[1] && typeof args[1] === "object") { tw = num(args[1].width, 0); th = num(args[1].height, 0); }
429
+ push({ c: st.id, m: method, k: "webgl", texture: { width: tw, height: th } });
430
+ return;
431
+ }
432
+ if (method === "bufferData") {
433
+ var bl = 0;
434
+ try { if (args[1] && args[1].byteLength) bl = num(args[1].byteLength, 0); } catch (e) {}
435
+ push({ c: st.id, m: method, k: "webgl", buffer: { byte_length: bl } });
436
+ return;
437
+ }
438
+ if (method === "viewport") {
439
+ push({
440
+ c: st.id, m: method, k: "webgl",
441
+ b: { x: num(args[0], 0), y: num(args[1], 0), width: num(args[2], 0), height: num(args[3], 0) }
442
+ });
443
+ }
444
+ }
445
+
446
+ // ---- 原型包装 --------------------------------------------------------
447
+
448
+ function wrap(proto, name, make) {
449
+ if (!proto) return;
450
+ var orig;
451
+ try { orig = proto[name]; } catch (e) { return; }
452
+ if (typeof orig !== "function" || orig.__aiCanvasWrapper) return;
453
+ var wrapped;
454
+ try { wrapped = make(orig); } catch (e) { return; }
455
+ // 保持 length/name:有代码会检查它们。
456
+ try { Object.defineProperty(wrapped, "length", { value: orig.length, configurable: true }); } catch (e) {}
457
+ try { Object.defineProperty(wrapped, "name", { value: name, configurable: true }); } catch (e) {}
458
+ try { wrapped.__aiCanvasWrapper = true; } catch (e) {}
459
+ try { proto[name] = wrapped; } catch (e) {}
460
+ }
461
+
462
+ // 关键顺序:先原生执行,再记录。记录异常吞掉,绝不改变返回值/异常/this/顺序。
463
+ function tee(proto, name, record) {
464
+ wrap(proto, name, function (orig) {
465
+ return function () {
466
+ var r = Reflect.apply(orig, this, arguments);
467
+ try { if (recording) record(this, arguments); } catch (e) {}
468
+ return r;
469
+ };
470
+ });
471
+ }
472
+
473
+ function install2d(proto) {
474
+ if (!proto || proto.__ai2dInstalled) return;
475
+ proto.__ai2dInstalled = true;
476
+
477
+ tee(proto, "fillText", function (ctx, args) { recordText(ctx, "fillText", args); });
478
+ tee(proto, "strokeText", function (ctx, args) { recordText(ctx, "strokeText", args); });
479
+ tee(proto, "fillRect", function (ctx, args) { recordRect(ctx, "fillRect", args); });
480
+ tee(proto, "strokeRect", function (ctx, args) { recordRect(ctx, "strokeRect", args); });
481
+ tee(proto, "clearRect", function (ctx, args) { recordRect(ctx, "clearRect", args); });
482
+ tee(proto, "drawImage", function (ctx, args) { recordDrawImage(ctx, args); });
483
+ tee(proto, "fill", function (ctx, args) { recordPathDraw(ctx, "fill", args); });
484
+ tee(proto, "stroke", function (ctx, args) { recordPathDraw(ctx, "stroke", args); });
485
+ tee(proto, "putImageData", function (ctx, args) { recordPixel(ctx, "putImageData", args); });
486
+
487
+ // 路径构建:默认不逐条产出,只在 fill/stroke 时给出摘要。
488
+ wrap(proto, "beginPath", function (orig) {
489
+ return function () {
490
+ var r = Reflect.apply(orig, this, arguments);
491
+ try { var st = stOf(this, "2d"); st.path = { n: 0, pts: [], opaque: false }; } catch (e) {}
492
+ return r;
493
+ };
494
+ });
495
+ wrap(proto, "moveTo", function (orig) {
496
+ return function () {
497
+ var r = Reflect.apply(orig, this, arguments);
498
+ try { if (recording) { var st = stOf(this, "2d"); addPoint(st, matOf(this), num(arguments[0], 0), num(arguments[1], 0)); } } catch (e) {}
499
+ return r;
500
+ };
501
+ });
502
+ wrap(proto, "lineTo", function (orig) {
503
+ return function () {
504
+ var r = Reflect.apply(orig, this, arguments);
505
+ try { if (recording) { var st = stOf(this, "2d"); addPoint(st, matOf(this), num(arguments[0], 0), num(arguments[1], 0)); } } catch (e) {}
506
+ return r;
507
+ };
508
+ });
509
+ wrap(proto, "bezierCurveTo", function (orig) {
510
+ return function () {
511
+ var r = Reflect.apply(orig, this, arguments);
512
+ try { if (recording) { var st = stOf(this, "2d"); var m = matOf(this);
513
+ addPoint(st, m, num(arguments[0], 0), num(arguments[1], 0));
514
+ addPoint(st, m, num(arguments[2], 0), num(arguments[3], 0));
515
+ addPoint(st, m, num(arguments[4], 0), num(arguments[5], 0)); } } catch (e) {}
516
+ return r;
517
+ };
518
+ });
519
+ wrap(proto, "quadraticCurveTo", function (orig) {
520
+ return function () {
521
+ var r = Reflect.apply(orig, this, arguments);
522
+ try { if (recording) { var st = stOf(this, "2d"); var m = matOf(this);
523
+ addPoint(st, m, num(arguments[0], 0), num(arguments[1], 0));
524
+ addPoint(st, m, num(arguments[2], 0), num(arguments[3], 0)); } } catch (e) {}
525
+ return r;
526
+ };
527
+ });
528
+ wrap(proto, "rect", function (orig) {
529
+ return function () {
530
+ var r = Reflect.apply(orig, this, arguments);
531
+ try { if (recording) { var st = stOf(this, "2d"); var m = matOf(this);
532
+ var x = num(arguments[0], 0), y = num(arguments[1], 0), w = num(arguments[2], 0), h = num(arguments[3], 0);
533
+ addPoint(st, m, x, y); addPoint(st, m, x + w, y); addPoint(st, m, x + w, y + h); addPoint(st, m, x, y + h); } } catch (e) {}
534
+ return r;
535
+ };
536
+ });
537
+ wrap(proto, "arc", function (orig) {
538
+ return function () {
539
+ var r = Reflect.apply(orig, this, arguments);
540
+ try { if (recording) { var st = stOf(this, "2d"); var m = matOf(this);
541
+ var x = num(arguments[0], 0), y = num(arguments[1], 0), rr = Math.abs(num(arguments[2], 0));
542
+ addPoint(st, m, x - rr, y - rr); addPoint(st, m, x + rr, y + rr); } } catch (e) {}
543
+ return r;
544
+ };
545
+ });
546
+ wrap(proto, "ellipse", function (orig) {
547
+ return function () {
548
+ var r = Reflect.apply(orig, this, arguments);
549
+ try { if (recording) { var st = stOf(this, "2d"); var m = matOf(this);
550
+ var x = num(arguments[0], 0), y = num(arguments[1], 0),
551
+ rx = Math.abs(num(arguments[2], 0)), ry = Math.abs(num(arguments[3], 0));
552
+ addPoint(st, m, x - rx, y - ry); addPoint(st, m, x + rx, y + ry); } } catch (e) {}
553
+ return r;
554
+ };
555
+ });
556
+ wrap(proto, "closePath", function (orig) {
557
+ return function () {
558
+ var r = Reflect.apply(orig, this, arguments);
559
+ try { if (recording) { stOf(this, "2d").path.n += 1; } } catch (e) {}
560
+ return r;
561
+ };
562
+ });
563
+
564
+ // 状态类:只为解释 draw call 服务,默认不产出记录。
565
+ var stateMethods = ["save", "restore", "translate", "scale", "rotate", "transform",
566
+ "setTransform", "resetTransform", "clip", "setLineDash"];
567
+ for (var i = 0; i < stateMethods.length; i++) {
568
+ (function (n) {
569
+ tee(proto, n, function (ctx, args) { recordState(ctx, n, args); });
570
+ })(stateMethods[i]);
571
+ }
572
+ }
573
+
574
+ function installGL(proto) {
575
+ if (!proto || proto.__aiGLInstalled) return;
576
+ proto.__aiGLInstalled = true;
577
+ var list = ["shaderSource", "drawArrays", "drawElements", "texImage2D", "bufferData", "viewport"];
578
+ for (var i = 0; i < list.length; i++) {
579
+ (function (n) {
580
+ tee(proto, n, function (ctx, args) { glRecord(ctx, n, args); });
581
+ })(list[i]);
582
+ }
583
+ }
584
+
585
+ function wrapGetContext(proto) {
586
+ wrap(proto, "getContext", function (orig) {
587
+ return function (type) {
588
+ var ctx = Reflect.apply(orig, this, arguments);
589
+ try {
590
+ var t = String(type || "").toLowerCase();
591
+ var kind = "unknown";
592
+ if (t === "2d") kind = "2d";
593
+ else if (t === "webgl" || t === "webgl2" || t === "experimental-webgl") kind = "webgl";
594
+ else if (t === "webgpu") kind = "webgpu";
595
+ var meta = metaFor(this, kind);
596
+ var w = 0, h = 0;
597
+ try { w = num(this.width, 0); h = num(this.height, 0); } catch (e) {}
598
+ if (ctx) {
599
+ var st = stOf(ctx, kind);
600
+ st.id = meta.id; st.w = w; st.h = h;
601
+ if (kind === "2d") install2d(Object.getPrototypeOf(ctx));
602
+ else if (kind === "webgl") installGL(Object.getPrototypeOf(ctx));
603
+ }
604
+ push({ c: meta.id, m: "getContext", k: "meta", kind: kind, api: t, w: w, h: h });
605
+ } catch (e) {}
606
+ return ctx;
607
+ };
608
+ });
609
+ }
610
+
611
+ // ---- 安装 ------------------------------------------------------------
612
+
613
+ try {
614
+ if (G.HTMLCanvasElement && G.HTMLCanvasElement.prototype) wrapGetContext(G.HTMLCanvasElement.prototype);
615
+ if (G.OffscreenCanvas && G.OffscreenCanvas.prototype) wrapGetContext(G.OffscreenCanvas.prototype);
616
+ if (G.HTMLCanvasElement && typeof G.HTMLCanvasElement.prototype.transferControlToOffscreen === "function") {
617
+ wrap(G.HTMLCanvasElement.prototype, "transferControlToOffscreen", function (orig) {
618
+ return function () {
619
+ var r = Reflect.apply(orig, this, arguments);
620
+ try { push({ c: metaFor(this, "unknown").id, m: "transferControlToOffscreen", k: "meta", transferred: true }); } catch (e) {}
621
+ return r;
622
+ };
623
+ });
624
+ }
625
+ install2d(G.CanvasRenderingContext2D && G.CanvasRenderingContext2D.prototype);
626
+ install2d(G.OffscreenCanvasRenderingContext2D && G.OffscreenCanvasRenderingContext2D.prototype);
627
+ installGL(G.WebGLRenderingContext && G.WebGLRenderingContext.prototype);
628
+ installGL(G.WebGL2RenderingContext && G.WebGL2RenderingContext.prototype);
629
+ } catch (e) {}
630
+
631
+ // 不可枚举的 bridge:主进程靠它切换 mode / 手动 flush。
632
+ var bridge = {
633
+ __aiCanvasHook: true,
634
+ nav: NAV,
635
+ frame: FRAME,
636
+ setMode: function (m) { mode = String(m || "semantic"); recording = (mode !== "off"); },
637
+ getMode: function () { return mode; },
638
+ flush: flush
639
+ };
640
+ try {
641
+ Object.defineProperty(G, KEY, { value: bridge, configurable: true, enumerable: false, writable: false });
642
+ } catch (e) { try { G[KEY] = bridge; } catch (e2) {} }
643
+
644
+ try {
645
+ if (typeof G.addEventListener === "function") {
646
+ G.addEventListener("pagehide", flush);
647
+ G.addEventListener("beforeunload", flush);
648
+ }
649
+ } catch (e) {}
650
+ } catch (e) {}
651
+ })();`;
652
+ }
653
+
654
+ export default buildCanvasHookSource;