(tabId, frameId, monkey)
| 381 | }; |
| 382 | |
| 383 | const _injectScript = (tabId, frameId, monkey) => { |
| 384 | let allFrames = !!monkey.mAllFrames; |
| 385 | let world = monkey.mWorld === 'ISOLATED' ? 'ISOLATED' : 'MAIN'; |
| 386 | let finalCode = buildFinalCode(monkey); |
| 387 | |
| 388 | // 必须先等 ISOLATED 桥接器就位(MAIN/ISOLATED 模式都需要: |
| 389 | // ISOLATED 模式下 user script 也通过 postMessage 走桥接,统一通道)。 |
| 390 | // 否则 user script 启动期的早期日志/@require 请求会丢失。 |
| 391 | let bridgeReady = _injectLogBridge(tabId, allFrames, frameId) || Promise.resolve(); |
| 392 | |
| 393 | const exec = (worldOption, isFallback) => { |
| 394 | try { |
| 395 | chrome.scripting.executeScript({ |
| 396 | target: _buildTarget(tabId, allFrames, frameId), |
| 397 | func: function (code) { |
| 398 | try { (0, eval)(code); } catch (e) { |
| 399 | // 注入阶段的 eval 错误也走桥接 |
| 400 | try { |
| 401 | window.postMessage({ |
| 402 | __fh_monkey_log: true, |
| 403 | payload: { level: 'error', msg: 'inject eval error: ' + ((e && e.stack) || e), time: Date.now() } |
| 404 | }, '*'); |
| 405 | } catch (_) {} |
| 406 | } |
| 407 | }, |
| 408 | args: [finalCode], |
| 409 | world: worldOption, |
| 410 | injectImmediately: true |
| 411 | }).catch((err) => { |
| 412 | if (!isFallback && worldOption === 'MAIN') { |
| 413 | exec('ISOLATED', true); |
| 414 | } else { |
| 415 | // 最终失败也回报一条日志 |
| 416 | log({ |
| 417 | id: monkey.id, name: monkey.mName || '', level: 'error', |
| 418 | msg: 'inject failed: ' + ((err && err.message) || err), |
| 419 | url: '(tab ' + tabId + ', frame ' + (frameId || 0) + ')', time: Date.now() |
| 420 | }); |
| 421 | } |
| 422 | }); |
| 423 | } catch (e) { |
| 424 | if (!isFallback && worldOption === 'MAIN') exec('ISOLATED', true); |
| 425 | } |
| 426 | }; |
| 427 | bridgeReady.then(() => exec(world, false), () => exec(world, false)); |
| 428 | }; |
| 429 | |
| 430 | const injectMonkey = (tabId, frameId, monkey) => { |
| 431 | let allFrames = !!monkey.mAllFrames; |
no test coverage detected