Merge pull request #1447 from IceCola97/sandbox-dev
增加沙盒内报错显示,方便调试;修复Audio|Image未定义问题
This commit is contained in:
commit
fbc72d91fa
|
@ -11,6 +11,7 @@ import { promiseErrorHandlerMap } from "../util/browser.js";
|
||||||
import { importCardPack, importCharacterPack, importExtension, importMode } from "./import.js";
|
import { importCardPack, importCharacterPack, importExtension, importMode } from "./import.js";
|
||||||
import { onload } from "./onload.js";
|
import { onload } from "./onload.js";
|
||||||
import { initializeSandboxRealms } from "../util/initRealms.js";
|
import { initializeSandboxRealms } from "../util/initRealms.js";
|
||||||
|
import { ErrorManager } from "../util/error.js";
|
||||||
|
|
||||||
// 判断是否从file协议切换到http/s协议
|
// 判断是否从file协议切换到http/s协议
|
||||||
export function canUseHttpProtocol() {
|
export function canUseHttpProtocol() {
|
||||||
|
@ -96,7 +97,7 @@ export function sendUpdate() {
|
||||||
const cp = require("child_process");
|
const cp = require("child_process");
|
||||||
cp.exec(
|
cp.exec(
|
||||||
`start /min ${__dirname}\\noname-server.exe -platform=electron`,
|
`start /min ${__dirname}\\noname-server.exe -platform=electron`,
|
||||||
(err, stdout, stderr) => {}
|
(err, stdout, stderr) => { }
|
||||||
);
|
);
|
||||||
return `http://localhost:8089/app.html?sendUpdate=true`;
|
return `http://localhost:8089/app.html?sendUpdate=true`;
|
||||||
}
|
}
|
||||||
|
@ -664,7 +665,7 @@ export async function boot() {
|
||||||
if (isFirstStartAfterUpdate && extErrorList.length) {
|
if (isFirstStartAfterUpdate && extErrorList.length) {
|
||||||
const stacktraces = extErrorList.map(e => e instanceof Error ? e.stack : String(e)).join("\n\n")
|
const stacktraces = extErrorList.map(e => e instanceof Error ? e.stack : String(e)).join("\n\n")
|
||||||
// game.saveConfig("update_first_log", stacktraces);
|
// game.saveConfig("update_first_log", stacktraces);
|
||||||
if(confirm(`扩展加载出错!是否重新载入游戏?\n本次更新可能导致了扩展出现了错误:\n\n${stacktraces}`)){
|
if (confirm(`扩展加载出错!是否重新载入游戏?\n本次更新可能导致了扩展出现了错误:\n\n${stacktraces}`)) {
|
||||||
game.reload();
|
game.reload();
|
||||||
clearTimeout(resetGameTimeout);
|
clearTimeout(resetGameTimeout);
|
||||||
return;
|
return;
|
||||||
|
@ -1021,137 +1022,141 @@ async function setOnError() {
|
||||||
|
|
||||||
window.onerror = function (msg, src, line, column, err) {
|
window.onerror = function (msg, src, line, column, err) {
|
||||||
if (promiseErrorHandler.onErrorPrepare) promiseErrorHandler.onErrorPrepare();
|
if (promiseErrorHandler.onErrorPrepare) promiseErrorHandler.onErrorPrepare();
|
||||||
const winPath = window.__dirname
|
const errorReporter = ErrorManager.getErrorReporter(err);
|
||||||
? "file:///" + (__dirname.replace(new RegExp("\\\\", "g"), "/") + "/")
|
if (errorReporter) game.print(errorReporter.report("沙盒内部执行的代码出现错误"));
|
||||||
: "";
|
else {
|
||||||
let str = `错误文件: ${
|
const winPath = window.__dirname
|
||||||
typeof src == "string"
|
? "file:///" + (__dirname.replace(new RegExp("\\\\", "g"), "/") + "/")
|
||||||
? decodeURI(src).replace(lib.assetURL, "").replace(winPath, "")
|
: "";
|
||||||
: "未知文件"
|
let str = `错误文件: ${
|
||||||
}`;
|
typeof src == "string"
|
||||||
str += `\n错误信息: ${msg}`;
|
? decodeURI(src).replace(lib.assetURL, "").replace(winPath, "")
|
||||||
const tip = lib.getErrorTip(msg);
|
: "未知文件"
|
||||||
if (tip) str += `\n错误提示: ${tip}`;
|
}`;
|
||||||
str += `\n行号: ${line}`;
|
str += `\n错误信息: ${msg}`;
|
||||||
str += `\n列号: ${column}`;
|
const tip = lib.getErrorTip(msg);
|
||||||
const version = typeof lib.version != "undefined" ? lib.version : "";
|
if (tip) str += `\n错误提示: ${tip}`;
|
||||||
const reg = /[^\d.]/;
|
str += `\n行号: ${line}`;
|
||||||
const match = version.match(reg) != null;
|
str += `\n列号: ${column}`;
|
||||||
str += "\n" + `${match ? "游戏" : "无名杀"}版本: ${version || "未知版本"}`;
|
const version = typeof lib.version != "undefined" ? lib.version : "";
|
||||||
if (match)
|
const reg = /[^\d.]/;
|
||||||
str +=
|
const match = version.match(reg) != null;
|
||||||
"\n⚠️您使用的游戏代码不是源于libccy/noname无名杀官方仓库,请自行寻找您所使用的游戏版本开发者反馈!";
|
str += "\n" + `${match ? "游戏" : "无名杀"}版本: ${version || "未知版本"}`;
|
||||||
if (_status && _status.event) {
|
if (match)
|
||||||
let evt = _status.event;
|
str +=
|
||||||
str += `\nevent.name: ${evt.name}\nevent.step: ${evt.step}`;
|
"\n⚠️您使用的游戏代码不是源于libccy/noname无名杀官方仓库,请自行寻找您所使用的游戏版本开发者反馈!";
|
||||||
// @ts-ignore
|
if (_status && _status.event) {
|
||||||
if (evt.parent)
|
let evt = _status.event;
|
||||||
str += `\nevent.parent.name: ${evt.parent.name}\nevent.parent.step: ${evt.parent.step}`;
|
str += `\nevent.name: ${evt.name}\nevent.step: ${evt.step}`;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (evt.parent && evt.parent.parent)
|
if (evt.parent)
|
||||||
str += `\nevent.parent.parent.name: ${evt.parent.parent.name}\nevent.parent.parent.step: ${evt.parent.parent.step}`;
|
str += `\nevent.parent.name: ${evt.parent.name}\nevent.parent.step: ${evt.parent.step}`;
|
||||||
if (evt.player || evt.target || evt.source || evt.skill || evt.card) {
|
// @ts-ignore
|
||||||
str += "\n-------------";
|
if (evt.parent && evt.parent.parent)
|
||||||
}
|
str += `\nevent.parent.parent.name: ${evt.parent.parent.name}\nevent.parent.parent.step: ${evt.parent.parent.step}`;
|
||||||
if (evt.player) {
|
if (evt.player || evt.target || evt.source || evt.skill || evt.card) {
|
||||||
if (lib.translate[evt.player.name])
|
str += "\n-------------";
|
||||||
str += `\nplayer: ${lib.translate[evt.player.name]}[${evt.player.name}]`;
|
}
|
||||||
else str += "\nplayer: " + evt.player.name;
|
if (evt.player) {
|
||||||
let distance = get.distance(_status.roundStart, evt.player, "absolute");
|
if (lib.translate[evt.player.name])
|
||||||
if (distance != Infinity) {
|
str += `\nplayer: ${lib.translate[evt.player.name]}[${evt.player.name}]`;
|
||||||
str += `\n座位号: ${distance + 1}`;
|
else str += "\nplayer: " + evt.player.name;
|
||||||
|
let distance = get.distance(_status.roundStart, evt.player, "absolute");
|
||||||
|
if (distance != Infinity) {
|
||||||
|
str += `\n座位号: ${distance + 1}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (evt.target) {
|
||||||
|
if (lib.translate[evt.target.name])
|
||||||
|
str += `\ntarget: ${lib.translate[evt.target.name]}[${evt.target.name}]`;
|
||||||
|
else str += "\ntarget: " + evt.target.name;
|
||||||
|
}
|
||||||
|
if (evt.source) {
|
||||||
|
if (lib.translate[evt.source.name])
|
||||||
|
str += `\nsource: ${lib.translate[evt.source.name]}[${evt.source.name}]`;
|
||||||
|
else str += "\nsource: " + evt.source.name;
|
||||||
|
}
|
||||||
|
if (evt.skill) {
|
||||||
|
if (lib.translate[evt.skill]) str += `\nskill: ${lib.translate[evt.skill]}[${evt.skill}]`;
|
||||||
|
else str += "\nskill: " + evt.skill;
|
||||||
|
}
|
||||||
|
if (evt.card) {
|
||||||
|
if (lib.translate[evt.card.name])
|
||||||
|
str += `\ncard: ${lib.translate[evt.card.name]}[${evt.card.name}]`;
|
||||||
|
else str += "\ncard: " + evt.card.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (evt.target) {
|
str += "\n-------------";
|
||||||
if (lib.translate[evt.target.name])
|
if (
|
||||||
str += `\ntarget: ${lib.translate[evt.target.name]}[${evt.target.name}]`;
|
typeof line == "number" &&
|
||||||
else str += "\ntarget: " + evt.target.name;
|
(typeof Reflect.get(game, "readFile") == "function" || location.origin != "file://")
|
||||||
}
|
) {
|
||||||
if (evt.source) {
|
const createShowCode = function (lines) {
|
||||||
if (lib.translate[evt.source.name])
|
let showCode = "";
|
||||||
str += `\nsource: ${lib.translate[evt.source.name]}[${evt.source.name}]`;
|
if (lines.length >= 10) {
|
||||||
else str += "\nsource: " + evt.source.name;
|
if (line > 4) {
|
||||||
}
|
for (let i = line - 5; i < line + 6 && i < lines.length; i++) {
|
||||||
if (evt.skill) {
|
showCode += `${i + 1}| ${line == i + 1 ? "⚠️" : ""}${lines[i]}\n`;
|
||||||
if (lib.translate[evt.skill]) str += `\nskill: ${lib.translate[evt.skill]}[${evt.skill}]`;
|
}
|
||||||
else str += "\nskill: " + evt.skill;
|
} else {
|
||||||
}
|
for (let i = 0; i < line + 6 && i < lines.length; i++) {
|
||||||
if (evt.card) {
|
showCode += `${i + 1}| ${line == i + 1 ? "⚠️" : ""}${lines[i]}\n`;
|
||||||
if (lib.translate[evt.card.name])
|
}
|
||||||
str += `\ncard: ${lib.translate[evt.card.name]}[${evt.card.name}]`;
|
|
||||||
else str += "\ncard: " + evt.card.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str += "\n-------------";
|
|
||||||
if (
|
|
||||||
typeof line == "number" &&
|
|
||||||
(typeof Reflect.get(game, "readFile") == "function" || location.origin != "file://")
|
|
||||||
) {
|
|
||||||
const createShowCode = function (lines) {
|
|
||||||
let showCode = "";
|
|
||||||
if (lines.length >= 10) {
|
|
||||||
if (line > 4) {
|
|
||||||
for (let i = line - 5; i < line + 6 && i < lines.length; i++) {
|
|
||||||
showCode += `${i + 1}| ${line == i + 1 ? "⚠️" : ""}${lines[i]}\n`;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < line + 6 && i < lines.length; i++) {
|
showCode = lines
|
||||||
showCode += `${i + 1}| ${line == i + 1 ? "⚠️" : ""}${lines[i]}\n`;
|
.map((_line, i) => `${i + 1}| ${line == i + 1 ? "⚠️" : ""}${_line}\n`)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
return showCode;
|
||||||
|
};
|
||||||
|
//协议名须和html一致(网页端防跨域),且文件是js
|
||||||
|
if (typeof src == "string" && src.startsWith(location.protocol) && src.endsWith(".js")) {
|
||||||
|
//获取代码
|
||||||
|
const codes = lib.init.reqSync(
|
||||||
|
"local:" + decodeURI(src).replace(lib.assetURL, "").replace(winPath, "")
|
||||||
|
);
|
||||||
|
if (codes) {
|
||||||
|
const lines = codes.split("\n");
|
||||||
|
str += "\n" + createShowCode(lines);
|
||||||
|
str += "\n-------------";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//解析parsex里的content fun内容(通常是技能content)
|
||||||
|
// @ts-ignore
|
||||||
|
else if (
|
||||||
|
err &&
|
||||||
|
err.stack &&
|
||||||
|
["at Object.eval [as content]", "at Proxy.content"].some((str) => {
|
||||||
|
let stackSplit1 = err.stack.split("\n")[1];
|
||||||
|
if (stackSplit1) {
|
||||||
|
return stackSplit1.trim().startsWith(str);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
const codes = _status.event.content;
|
||||||
|
if (typeof codes == "function") {
|
||||||
|
const lines = codes.toString().split("\n");
|
||||||
|
str += "\n" + createShowCode(lines);
|
||||||
|
str += "\n-------------";
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
showCode = lines
|
|
||||||
.map((_line, i) => `${i + 1}| ${line == i + 1 ? "⚠️" : ""}${_line}\n`)
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
return showCode;
|
|
||||||
};
|
|
||||||
//协议名须和html一致(网页端防跨域),且文件是js
|
|
||||||
if (typeof src == "string" && src.startsWith(location.protocol) && src.endsWith(".js")) {
|
|
||||||
//获取代码
|
|
||||||
const codes = lib.init.reqSync(
|
|
||||||
"local:" + decodeURI(src).replace(lib.assetURL, "").replace(winPath, "")
|
|
||||||
);
|
|
||||||
if (codes) {
|
|
||||||
const lines = codes.split("\n");
|
|
||||||
str += "\n" + createShowCode(lines);
|
|
||||||
str += "\n-------------";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//解析parsex里的content fun内容(通常是技能content)
|
|
||||||
// @ts-ignore
|
|
||||||
else if (
|
|
||||||
err &&
|
|
||||||
err.stack &&
|
|
||||||
["at Object.eval [as content]", "at Proxy.content"].some((str) => {
|
|
||||||
let stackSplit1 = err.stack.split("\n")[1];
|
|
||||||
if (stackSplit1) {
|
|
||||||
return stackSplit1.trim().startsWith(str);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
const codes = _status.event.content;
|
|
||||||
if (typeof codes == "function") {
|
|
||||||
const lines = codes.toString().split("\n");
|
|
||||||
str += "\n" + createShowCode(lines);
|
|
||||||
str += "\n-------------";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (err && err.stack)
|
||||||
|
str +=
|
||||||
|
"\n" +
|
||||||
|
decodeURI(err.stack)
|
||||||
|
.replace(new RegExp(lib.assetURL, "g"), "")
|
||||||
|
.replace(new RegExp(winPath, "g"), "");
|
||||||
|
alert(str);
|
||||||
|
game.print(str);
|
||||||
}
|
}
|
||||||
if (err && err.stack)
|
|
||||||
str +=
|
|
||||||
"\n" +
|
|
||||||
decodeURI(err.stack)
|
|
||||||
.replace(new RegExp(lib.assetURL, "g"), "")
|
|
||||||
.replace(new RegExp(winPath, "g"), "");
|
|
||||||
alert(str);
|
|
||||||
Reflect.set(window, "ea", Array.from(arguments));
|
Reflect.set(window, "ea", Array.from(arguments));
|
||||||
Reflect.set(window, "em", msg);
|
Reflect.set(window, "em", msg);
|
||||||
Reflect.set(window, "el", line);
|
Reflect.set(window, "el", line);
|
||||||
Reflect.set(window, "ec", column);
|
Reflect.set(window, "ec", column);
|
||||||
Reflect.set(window, "eo", err);
|
Reflect.set(window, "eo", err);
|
||||||
game.print(str);
|
|
||||||
if (promiseErrorHandler.onErrorFinish) promiseErrorHandler.onErrorFinish();
|
if (promiseErrorHandler.onErrorFinish) promiseErrorHandler.onErrorFinish();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (!lib.config.errstop && (_status && _status.event && !(_status.event.content instanceof AsyncFunction))) {
|
if (!lib.config.errstop && (_status && _status.event && !(_status.event.content instanceof AsyncFunction))) {
|
||||||
|
|
|
@ -0,0 +1,245 @@
|
||||||
|
class CodeSnippet {
|
||||||
|
/** @type {Array<CodeSnippet>} */
|
||||||
|
static #snippetStack = [];
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
#code;
|
||||||
|
/** @type {number} */
|
||||||
|
#erroff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 构造一个代码片段对象
|
||||||
|
*
|
||||||
|
* 通过 `erroff` 指定在发生错误时,错误信息指出的行与实际代码行的偏移量
|
||||||
|
* ```
|
||||||
|
* @param {string} code
|
||||||
|
* @param {number} erroff
|
||||||
|
*/
|
||||||
|
constructor(code, erroff = 0) {
|
||||||
|
this.#code = String(code);
|
||||||
|
this.#erroff = parseInt(String(erroff)) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
get code() {
|
||||||
|
return this.#code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Array<string>} */
|
||||||
|
get lines() {
|
||||||
|
return this.code.split(/\r?\n/);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 给定错误行号来获取错误代码片段
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {number} lineno
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
viewCode(lineno) {
|
||||||
|
if (!Number.isInteger(lineno))
|
||||||
|
throw new TypeError("错误行号必须是一个整数");
|
||||||
|
|
||||||
|
const index = lineno - this.#erroff;
|
||||||
|
const lines = this.lines;
|
||||||
|
const width = String(index + 4).length;
|
||||||
|
|
||||||
|
let codeView = "";
|
||||||
|
|
||||||
|
for (let i = index - 4; i < index + 5; i++) {
|
||||||
|
if (i < 0 || i >= lines.length)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
codeView += String(i + 1).padStart(width, "0");
|
||||||
|
codeView += `|${i == index ? "⚠️" : " "}${lines[i]}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 获取当前代码片段
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @type {CodeSnippet?}
|
||||||
|
*/
|
||||||
|
static get currentSnippet() {
|
||||||
|
if (!this.#snippetStack.length)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return this.#snippetStack[this.#snippetStack.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 压入一个代码片段作为当前代码片段
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {CodeSnippet} snippet
|
||||||
|
*/
|
||||||
|
static pushSnippet(snippet) {
|
||||||
|
if (!(snippet instanceof CodeSnippet))
|
||||||
|
throw new TypeError("参数必须是一个代码片段对象");
|
||||||
|
|
||||||
|
this.#snippetStack.push(snippet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 弹出当前代码片段
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @returns {CodeSnippet}
|
||||||
|
*/
|
||||||
|
static popSnippet() {
|
||||||
|
if (!this.#snippetStack.length)
|
||||||
|
throw new Error("代码片段栈为空");
|
||||||
|
|
||||||
|
// @ts-ignore // eslint好不智能哦
|
||||||
|
return this.#snippetStack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorReporter {
|
||||||
|
static #topAlert = window.alert.bind(null);
|
||||||
|
static #errorLineNoPatterns = [
|
||||||
|
/<anonymous>:(\d+):\d+\)/,
|
||||||
|
/at <anonymous>:(\d+):\d+/,
|
||||||
|
/eval:(\d+):\d+/,
|
||||||
|
/Function:(\d+):\d+/,
|
||||||
|
/:(\d+):\d+/,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @type {CodeSnippet?} */
|
||||||
|
#snippet;
|
||||||
|
/** @type {string} */
|
||||||
|
#message;
|
||||||
|
/** @type {string} */
|
||||||
|
#stack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 构造一个错误报告对象
|
||||||
|
* 以此来保存错误相关信息
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {Error} error
|
||||||
|
* @param {CodeSnippet?} snippet
|
||||||
|
*/
|
||||||
|
constructor(error, snippet = CodeSnippet.currentSnippet) {
|
||||||
|
if (!("stack" in error))
|
||||||
|
throw new TypeError("传入的对象不是一个错误对象");
|
||||||
|
|
||||||
|
this.#snippet = snippet;
|
||||||
|
this.#message = String(error);
|
||||||
|
this.#stack = String(error.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
get message() {
|
||||||
|
return this.#message;
|
||||||
|
}
|
||||||
|
|
||||||
|
get stack() {
|
||||||
|
return this.#stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
static #findLineNo = function (line) {
|
||||||
|
for (const pattern of ErrorReporter.#errorLineNoPatterns) {
|
||||||
|
const match = pattern.exec(line);
|
||||||
|
|
||||||
|
if (match)
|
||||||
|
return parseInt(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewCode() {
|
||||||
|
if(!this.#snippet)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const stack = this.#stack;
|
||||||
|
const line = stack.split("\n")[1];
|
||||||
|
const lineno = ErrorReporter.#findLineNo(line);
|
||||||
|
|
||||||
|
if (!isNaN(lineno))
|
||||||
|
return this.#snippet.viewCode(lineno);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 向用户报告错误信息
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {string} title
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
report(title) {
|
||||||
|
const codeView = this.viewCode() || "#没有代码预览#";
|
||||||
|
let errorInfo = `${title}:\n\t${this.#message}\n`;
|
||||||
|
errorInfo += `----------\n${codeView.trim()}\n`;
|
||||||
|
errorInfo += `----------\n调用堆栈:\n${this.#stack}`;
|
||||||
|
ErrorReporter.#topAlert(errorInfo);
|
||||||
|
return errorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 向用户报告错误信息
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {Error} error
|
||||||
|
* @param {string} title
|
||||||
|
*/
|
||||||
|
static reportError(error, title = "发生错误") {
|
||||||
|
new ErrorReporter(error).report(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorManager {
|
||||||
|
/** @type {WeakMap<Object, ErrorReporter>} */
|
||||||
|
static #errorReporters = new WeakMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 设置错误报告器
|
||||||
|
*
|
||||||
|
* 在报告错误时可以从此处获取错误报告器来直接报告错误
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {ErrorReporter?} reporter
|
||||||
|
*/
|
||||||
|
static setErrorReporter(obj, reporter = null) {
|
||||||
|
if (obj !== Object(obj))
|
||||||
|
throw new TypeError("参数必须是一个对象");
|
||||||
|
if (!(reporter instanceof ErrorReporter))
|
||||||
|
reporter = new ErrorReporter(obj);
|
||||||
|
|
||||||
|
ErrorManager.#errorReporters.set(obj, reporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 获取设置的错误报告器
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @returns {ErrorReporter?}
|
||||||
|
*/
|
||||||
|
static getErrorReporter(obj) {
|
||||||
|
return ErrorManager.#errorReporters.get(obj) || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
CodeSnippet,
|
||||||
|
ErrorReporter,
|
||||||
|
ErrorManager,
|
||||||
|
};
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { CodeSnippet, ErrorReporter, ErrorManager } from "./error.js";
|
||||||
|
|
||||||
// 方便开关确定沙盒的问题喵
|
// 方便开关确定沙盒的问题喵
|
||||||
// 当此处为true、debug模式为启用、设备非苹果时,沙盒生效
|
// 当此处为true、debug模式为启用、设备非苹果时,沙盒生效
|
||||||
let SANDBOX_ENABLED = true;
|
let SANDBOX_ENABLED = true;
|
||||||
|
@ -77,7 +79,7 @@ async function initializeSandboxRealms(enabled) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 传递顶级变量域、上下文执行器
|
// 传递顶级变量域、上下文执行器、错误管理器
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
iframe.contentWindow.replacedGlobal = window;
|
iframe.contentWindow.replacedGlobal = window;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -86,6 +88,8 @@ async function initializeSandboxRealms(enabled) {
|
||||||
iframe.contentWindow.replacedCI2 = ContextInvoker2;
|
iframe.contentWindow.replacedCI2 = ContextInvoker2;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
iframe.contentWindow.replacedCIC = ContextInvokerCreator;
|
iframe.contentWindow.replacedCIC = ContextInvokerCreator;
|
||||||
|
// @ts-ignore
|
||||||
|
iframe.contentWindow.replacedErrors = { CodeSnippet, ErrorReporter, ErrorManager };
|
||||||
|
|
||||||
// 重新以新的变量域载入当前脚本
|
// 重新以新的变量域载入当前脚本
|
||||||
const script = iframe.contentWindow.document.createElement("script");
|
const script = iframe.contentWindow.document.createElement("script");
|
||||||
|
@ -107,6 +111,8 @@ async function initializeSandboxRealms(enabled) {
|
||||||
delete iframe.contentWindow.replacedCI2;
|
delete iframe.contentWindow.replacedCI2;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
delete iframe.contentWindow.replacedCIC;
|
delete iframe.contentWindow.replacedCIC;
|
||||||
|
// @ts-ignore
|
||||||
|
delete iframe.contentWindow.replacedErrors;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Object.assign(SANDBOX_EXPORT, iframe.contentWindow.SANDBOX_EXPORT);
|
Object.assign(SANDBOX_EXPORT, iframe.contentWindow.SANDBOX_EXPORT);
|
||||||
|
|
|
@ -41,6 +41,13 @@ const SandboxSignal_ListMonitor = Symbol("ListMonitor");
|
||||||
const SandboxSignal_ExposeInfo = Symbol("ExposeInfo");
|
const SandboxSignal_ExposeInfo = Symbol("ExposeInfo");
|
||||||
const SandboxSignal_TryFunctionRefs = Symbol("TryFunctionRefs");
|
const SandboxSignal_TryFunctionRefs = Symbol("TryFunctionRefs");
|
||||||
|
|
||||||
|
/** @type {typeof import("./error.js").CodeSnippet} */
|
||||||
|
let CodeSnippet;
|
||||||
|
/** @type {typeof import("./error.js").ErrorReporter} */
|
||||||
|
let ErrorReporter;
|
||||||
|
/** @type {typeof import("./error.js").ErrorManager} */
|
||||||
|
let ErrorManager;
|
||||||
|
|
||||||
// 用于适配 < Chrome 84 的设备
|
// 用于适配 < Chrome 84 的设备
|
||||||
const WeakRef = window.WeakRef || class WeakRef {
|
const WeakRef = window.WeakRef || class WeakRef {
|
||||||
/**
|
/**
|
||||||
|
@ -2240,8 +2247,7 @@ class Marshal {
|
||||||
if (descriptor
|
if (descriptor
|
||||||
&& descriptor.value
|
&& descriptor.value
|
||||||
&& !descriptor.enumerable
|
&& !descriptor.enumerable
|
||||||
&& !descriptor.configurable
|
&& !descriptor.configurable)
|
||||||
&& descriptor.value.constructor === src)
|
|
||||||
cloned = function () { };
|
cloned = function () { };
|
||||||
else
|
else
|
||||||
cloned = () => { };
|
cloned = () => { };
|
||||||
|
@ -2306,12 +2312,39 @@ class Marshal {
|
||||||
|
|
||||||
if (mappedCtor) {
|
if (mappedCtor) {
|
||||||
const newError = new mappedCtor();
|
const newError = new mappedCtor();
|
||||||
const stack = String(target.stack);
|
const silentAccess = (o, p, d) => {
|
||||||
Reflect.defineProperty(newError, 'stack', {
|
try {
|
||||||
get: () => () => stack,
|
if (typeof p == "function")
|
||||||
set: () => { },
|
return p(o);
|
||||||
configurable: false,
|
else
|
||||||
});
|
return o[p];
|
||||||
|
} catch (e) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const pinValue = (o, p, v) => {
|
||||||
|
Reflect.defineProperty(o, p, {
|
||||||
|
get: () => v,
|
||||||
|
set: () => { },
|
||||||
|
configurable: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const name = String(silentAccess(target, "name", "#无法获取错误名#"));
|
||||||
|
const message = String(silentAccess(target, "message", "#无法获取错误消息#"));
|
||||||
|
const stack = String(silentAccess(target, "stack", "#无法获取调用栈#"));
|
||||||
|
const string = silentAccess(target, String, "#无法获取错误信息#");
|
||||||
|
|
||||||
|
pinValue(newError, "name", name);
|
||||||
|
pinValue(newError, "message", message);
|
||||||
|
pinValue(newError, "stack", stack);
|
||||||
|
pinValue(newError, "toString", () => string);
|
||||||
|
|
||||||
|
// 继承原本的错误信息
|
||||||
|
const errorReporter = ErrorManager.getErrorReporter(target);
|
||||||
|
ErrorManager.setErrorReporter(newError,
|
||||||
|
errorReporter || new ErrorReporter(target)); // 无论有没有都捕获当前的错误信息
|
||||||
|
|
||||||
return newError;
|
return newError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2866,7 +2899,7 @@ class Domain {
|
||||||
* 检查对象是否来自于当前的运行域
|
* 检查对象是否来自于当前的运行域
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param {Object} obj
|
* @param {Object?} obj
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isFrom(obj) {
|
isFrom(obj) {
|
||||||
|
@ -2885,7 +2918,7 @@ class Domain {
|
||||||
* 检查对象是否来自于当前的运行域的Promise
|
* 检查对象是否来自于当前的运行域的Promise
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param {Promise} promise
|
* @param {Promise?} promise
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isPromise(promise) {
|
isPromise(promise) {
|
||||||
|
@ -2902,7 +2935,7 @@ class Domain {
|
||||||
* 检查对象是否来自于当前的运行域的Error
|
* 检查对象是否来自于当前的运行域的Error
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param {Error} error
|
* @param {Error?} error
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isError(error) {
|
isError(error) {
|
||||||
|
@ -2919,7 +2952,7 @@ class Domain {
|
||||||
* 检查对象是否来自于当前的运行域的危险对象
|
* 检查对象是否来自于当前的运行域的危险对象
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param {Object} obj
|
* @param {Object?} obj
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isUnsafe(obj) {
|
isUnsafe(obj) {
|
||||||
|
@ -3144,6 +3177,10 @@ function trapMarshal(srcDomain, dstDomain, obj) {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class Sandbox {
|
class Sandbox {
|
||||||
|
// @ts-ignore
|
||||||
|
static #topWindow = window.replacedGlobal || window;
|
||||||
|
// @ts-ignore
|
||||||
|
static #topWindowHTMLElement = (window.replacedGlobal || window).HTMLElement;
|
||||||
/** @type {WeakMap<Domain, Sandbox>} */
|
/** @type {WeakMap<Domain, Sandbox>} */
|
||||||
static #domainMap = new WeakMap();
|
static #domainMap = new WeakMap();
|
||||||
/** @type {Array} */
|
/** @type {Array} */
|
||||||
|
@ -3184,6 +3221,20 @@ class Sandbox {
|
||||||
*/
|
*/
|
||||||
#freeAccess = false;
|
#freeAccess = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 当在当前scope中访问不到变量时,
|
||||||
|
* 是否允许沙盒代码可以穿透到顶级域的全局变量域中
|
||||||
|
* 去读取DOM类型的构造函数(仅读取)
|
||||||
|
* (包括Image、Audio等)
|
||||||
|
*
|
||||||
|
* 此开关有风险,请谨慎使用
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
#domAccess = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建一个新的沙盒
|
* 创建一个新的沙盒
|
||||||
*/
|
*/
|
||||||
|
@ -3421,6 +3472,40 @@ class Sandbox {
|
||||||
this.#freeAccess = !!value;
|
this.#freeAccess = !!value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 当在当前scope中访问不到变量时,
|
||||||
|
* 是否允许沙盒代码可以穿透到顶级域的全局变量域中
|
||||||
|
* 去读取DOM类型的构造函数(仅读取)
|
||||||
|
* (包括Image、Audio等)
|
||||||
|
*
|
||||||
|
* 此开关有风险,请谨慎使用
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get domAccess() {
|
||||||
|
Sandbox.#assertOperator(this);
|
||||||
|
return this.#domAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```plain
|
||||||
|
* 当在当前scope中访问不到变量时,
|
||||||
|
* 是否允许沙盒代码可以穿透到顶级域的全局变量域中
|
||||||
|
* 去读取DOM类型的构造函数(仅读取)
|
||||||
|
* (包括Image、Audio等)
|
||||||
|
*
|
||||||
|
* 此开关有风险,请谨慎使用
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
set domAccess(value) {
|
||||||
|
Sandbox.#assertOperator(this);
|
||||||
|
this.#domAccess = !!value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ```plain
|
* ```plain
|
||||||
* 向当前域注入内建对象
|
* 向当前域注入内建对象
|
||||||
|
@ -3582,6 +3667,7 @@ class Sandbox {
|
||||||
let wrappedEval;
|
let wrappedEval;
|
||||||
|
|
||||||
const raw = new thiz.#domainFunction("_", `with(_){with(window){with(${contextName}){return(${applyName}(function(${parameters}){"use strict";\n// 沙盒代码起始\n${code}\n// 沙盒代码结束\n},${contextName}.this,${argsName}))}}}`);
|
const raw = new thiz.#domainFunction("_", `with(_){with(window){with(${contextName}){return(${applyName}(function(${parameters}){"use strict";\n// 沙盒代码起始\n${code}\n// 沙盒代码结束\n},${contextName}.this,${argsName}))}}}`);
|
||||||
|
const snippet = new CodeSnippet(code, 5); // 错误信息的行号从 5 开始 (即错误信息的前 5 行是不属于 `code` 的范围)
|
||||||
|
|
||||||
const domain = thiz.#domain;
|
const domain = thiz.#domain;
|
||||||
const domainWindow = thiz.#domainWindow;
|
const domainWindow = thiz.#domainWindow;
|
||||||
|
@ -3637,6 +3723,8 @@ class Sandbox {
|
||||||
// 指定执行域
|
// 指定执行域
|
||||||
// 方便后续新的函数来继承
|
// 方便后续新的函数来继承
|
||||||
Sandbox.#executingScope.push(scope);
|
Sandbox.#executingScope.push(scope);
|
||||||
|
// 指定当前的代码片段
|
||||||
|
CodeSnippet.pushSnippet(snippet);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 传递 `this`、以及函数参数
|
// 传递 `this`、以及函数参数
|
||||||
|
@ -3652,27 +3740,17 @@ class Sandbox {
|
||||||
// 封送返回结果
|
// 封送返回结果
|
||||||
return Marshal[SandboxExposer2]
|
return Marshal[SandboxExposer2]
|
||||||
(SandboxSignal_Marshal, result, prevDomain);
|
(SandboxSignal_Marshal, result, prevDomain);
|
||||||
// } catch (e) {
|
} catch (e) {
|
||||||
// // 立即报告错误
|
// @ts-ignore
|
||||||
// const window = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow);
|
if (!domain.isError(e))
|
||||||
// // @ts-ignore
|
throw e; // 非错误对象无法读取堆栈,继续向上抛出
|
||||||
// const stack = String(e.stack);
|
|
||||||
// const line = stack.split("\n")[1];
|
// 保存当前错误信息
|
||||||
// const match = /<anonymous>:(\d+):\d+\)/.exec(line);
|
// 这样无论几次重抛都可以复现最原始的错误信息
|
||||||
// if (match) {
|
ErrorManager.setErrorReporter(e);
|
||||||
// const index = parseInt(match[1]) - 5;
|
throw e; // 继续向上抛出(由于JS不支持rethrow只能这样喵)
|
||||||
// const lines = code.split("\n");
|
|
||||||
// let codeView = "";
|
|
||||||
// for (let i = index - 4; i < index + 5; i++) {
|
|
||||||
// if (i < 0 || i >= lines.length)
|
|
||||||
// continue;
|
|
||||||
// codeView += `${i + 1}|${i == index ? "⚠️" : " "}${lines[i]}\n`;
|
|
||||||
// }
|
|
||||||
// // @ts-ignore
|
|
||||||
// window.alert(`Sandbox内执行的代码出现错误:\n${stack}\n----------\n${codeView}\n----------`);
|
|
||||||
// }
|
|
||||||
// throw e; // 不再向上抛出异常
|
|
||||||
} finally {
|
} finally {
|
||||||
|
CodeSnippet.popSnippet();
|
||||||
Sandbox.#executingScope.pop();
|
Sandbox.#executingScope.pop();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3787,10 +3865,18 @@ class Sandbox {
|
||||||
// 暴露非内建的顶级全局变量
|
// 暴露非内建的顶级全局变量
|
||||||
if (thiz.#freeAccess
|
if (thiz.#freeAccess
|
||||||
&& !Globals.isBuiltinKey(p)) {
|
&& !Globals.isBuiltinKey(p)) {
|
||||||
const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow);
|
const topWindow = Sandbox.#topWindow;
|
||||||
|
|
||||||
if (p in topWindow)
|
if (p in topWindow)
|
||||||
return trapMarshal(Domain.topDomain, thiz.#domain, topWindow[p]);
|
return trapMarshal(Domain.topDomain, thiz.#domain, topWindow[p]);
|
||||||
|
} else if (thiz.#domAccess) {
|
||||||
|
const topWindow = Sandbox.#topWindow;
|
||||||
|
const accessTarget = topWindow[p];
|
||||||
|
|
||||||
|
if (typeof accessTarget == "function"
|
||||||
|
&& "prototype" in accessTarget
|
||||||
|
&& accessTarget.prototype instanceof Sandbox.#topWindowHTMLElement)
|
||||||
|
return trapMarshal(Domain.topDomain, thiz.#domain, accessTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -3803,6 +3889,14 @@ class Sandbox {
|
||||||
&& !Globals.isBuiltinKey(p)) {
|
&& !Globals.isBuiltinKey(p)) {
|
||||||
const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow);
|
const topWindow = Domain.topDomain[SandboxExposer](SandboxSignal_GetWindow);
|
||||||
return p in topWindow;
|
return p in topWindow;
|
||||||
|
} else if (thiz.#domAccess) {
|
||||||
|
const topWindow = Sandbox.#topWindow;
|
||||||
|
const accessTarget = topWindow[p];
|
||||||
|
|
||||||
|
if (typeof accessTarget == "function"
|
||||||
|
&& "prototype" in accessTarget
|
||||||
|
&& accessTarget.prototype instanceof Sandbox.#topWindowHTMLElement)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -3974,6 +4068,16 @@ if (SANDBOX_ENABLED) {
|
||||||
// 改为此处初始化,防止多次初始化
|
// 改为此处初始化,防止多次初始化
|
||||||
Domain[SandboxExposer2](SandboxSignal_InitDomain);
|
Domain[SandboxExposer2](SandboxSignal_InitDomain);
|
||||||
|
|
||||||
|
// 获取顶级域的错误管理器
|
||||||
|
// @ts-ignore
|
||||||
|
({
|
||||||
|
CodeSnippet,
|
||||||
|
ErrorReporter,
|
||||||
|
ErrorManager,
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
= window.replacedErrors);
|
||||||
|
|
||||||
// 向顶级运行域暴露导出
|
// 向顶级运行域暴露导出
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.SANDBOX_EXPORT = {
|
window.SANDBOX_EXPORT = {
|
||||||
|
|
|
@ -583,6 +583,7 @@ function createSandbox() {
|
||||||
|
|
||||||
const box = new Sandbox();
|
const box = new Sandbox();
|
||||||
box.freeAccess = true;
|
box.freeAccess = true;
|
||||||
|
box.domAccess = true;
|
||||||
box.initBuiltins();
|
box.initBuiltins();
|
||||||
|
|
||||||
// 向沙盒提供顶级运行域的文档对象
|
// 向沙盒提供顶级运行域的文档对象
|
||||||
|
|
Loading…
Reference in New Issue