diff --git a/noname/util/struct/promise-error-handler/chrome.js b/noname/util/struct/promise-error-handler/chrome.js index 29139ddc1..79b65ac4d 100644 --- a/noname/util/struct/promise-error-handler/chrome.js +++ b/noname/util/struct/promise-error-handler/chrome.js @@ -1,36 +1,49 @@ /** * 关于`Google Chrome`的异步错误处理 * - * `Chrome`所用的`v8`引擎为`Error`提供了特有的报错栈堆处理函数,用于用户自定义报错栈堆的内容。 + * 由于`v8`提供的`Error.prepareStackTrace(error, structuredStackTrace)`接口存在一些限制,导致不适合无名杀 * - * 我们用到了`Error.prepareStackTrace(error, structuredStackTrace)`这个函数,这个函数的信息可参考[这里](https://v8.dev/docs/stack-trace-api#customizing-stack-traces) + * 故我们直接解析`Error`的栈堆信息,来获取相关内容 * - * 该函数提供了结构化的栈堆信息,很幸运的是,这个结构化的栈堆能直接告诉我们报错的文件以及位置,故我们使用该函数,让异步报错能直接定位原始位置 + * ~~`Chrome`所用的`v8`引擎为`Error`提供了特有的报错栈堆处理接口,用于用户自定义报错栈堆的内容。~~ + * + * ~~我们用到了`Error.prepareStackTrace(error, structuredStackTrace)`这个接口,这个接口的信息可参考[这里](https://v8.dev/docs/stack-trace-api#customizing-stack-traces)~~ + * + * ~~该接口提供了结构化的栈堆信息,很幸运的是,这个结构化的栈堆能直接告诉我们报错的文件以及位置,故我们使用该接口,让异步报错能直接定位原始位置~~ * * @implements {PromiseErrorHandler} */ export class ChromePromiseErrorHandler { - /** - * 用于临时记录报错信息的列表,通过`Error.prepareStackTrace`更新该列表 + * ~~用于临时记录报错信息的列表,通过`Error.prepareStackTrace`更新该列表~~ * - * @type {[Error, NodeJS.CallSite[]][]} + * 现在用于存储报错过的错误信息 + * + * @type {Error[]} */ #errorList; /** * @type {typeof Error.prepareStackTrace} + * + * @deprecated */ - #originErrorPrepareStackTrace; + #_originErrorPrepareStackTrace; /** - * 初始化`Error.prepareStackTrace`,将该值赋值成我们需要的函数 + * 判断是否是v8错误栈堆用到的正则 + */ + #STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m; + + /** + * ~~初始化`Error.prepareStackTrace`,将该值赋值成我们需要的函数~~ * - * 未防止本来Error.prepareStackTrace便存在赋值的行为,我们将原始值存储,并在需要的函数中调用 + * ~~未防止本来Error.prepareStackTrace便存在赋值的行为,我们将原始值存储,并在需要的函数中调用~~ * - * > 这或许就是本体扩展化的第一步(小声) + * 初始化存储报错信息的列表 */ onLoad() { + /* this.#errorList = []; this.#originErrorPrepareStackTrace = Error.prepareStackTrace; Error.prepareStackTrace = (error, stackTraces) => { @@ -42,13 +55,19 @@ export class ChromePromiseErrorHandler { this.#errorList.push([error, stackTraces]); return result; }; + */ + this.#errorList = []; } /** - * 将原来可能的`Error.prepareStackTrace`赋值回去 + * ~~将原来可能的`Error.prepareStackTrace`赋值回去~~ + * + * @deprecated */ onUnload() { + /* Error.prepareStackTrace = this.#originErrorPrepareStackTrace; + */ } /** @@ -60,6 +79,61 @@ export class ChromePromiseErrorHandler { */ onHandle(event) { event.promise.catch((error) => { + // 如果`error`是个错误,则继续处理 + if (error instanceof Error) { + // 如果已经处理过该错误,则不再处理 + if (this.#errorList.includes(error)) return; + this.#errorList.push(error); + // 如果`error`拥有字符串形式的报错栈堆,且报错栈堆确实符合v8的stack + if (typeof error.stack === 'string' && this.#STACK_REGEXP.test(error.stack)) { + // 获取符合栈堆信息的字符串,一般来说就是从第二行开始的所有行 + // 为了处理eval的情况,故必须获取完行数 + let lines = error.stack.split('\n').filter((line) => + this.#STACK_REGEXP.test(line)); + + // 提供类型信息防止vscode报错 + /** + * @type {string | undefined} + */ + let fileName = void 0; + + /** + * @type {string | undefined} + */ + let line = void 0; + + /** + * @type {string | undefined} + */ + let column = void 0; + + // 从第一条开始遍历,一直遍历到不存在eval的位置 + for (let currentLine = 0; currentLine < lines.length; ++currentLine) { + if (/\(eval /.test(lines[currentLine])) continue; + + let formatedLine = lines[currentLine].replace(/^\s+/, '').replace(/\(eval code/g, '(').replace(/^.*?\s+/, ''); + + const location = formatedLine.match(/ (\(.+\)$)/); + if (location) formatedLine = formatedLine.replace(location[0], ''); + + const locationParts = extractLocation(location ? location[1] : formatedLine); + + fileName = ['eval', ''].includes(locationParts[0]) ? void 0 : locationParts[0]; + line = locationParts[1]; + column = locationParts[2]; + break; + } + + // @ts-ignore + window.onerror(error.message, fileName, line, column, error); + } + // 反之我们只能不考虑报错文件信息,直接调用onerror + else { + // @ts-ignore + window.onerror(error.message, void 0, void 0, void 0, error); + } + } + /* console.error(error) const result = this.#errorList.find(savedError => savedError[0] === error); if (result) { @@ -72,17 +146,42 @@ export class ChromePromiseErrorHandler { result[0] ); } + */ }); } /** - * 正式报错时便不再需要报错信息了,故直接清空列表,释放内存 + * ~~正式报错时便不再需要报错信息了,故直接清空列表,释放内存~~ + * + * @deprecated */ onErrorPrepare() { + /* this.#errorList.length = 0; + */ } } +/** + * 简易的解析报错栈堆位置信息的函数 + * + * @param {string} urlLike + * @returns {string[]} + */ +export function extractLocation(urlLike) { + // 不存在地址信息的字符串 + if (!/:/.test(urlLike)) { + return [urlLike]; + } + + // 捕获位置用到的正则 + const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/; + const parts = regExp.exec(urlLike.replace(/[()]/g, '')); + + // @ts-ignore + return [parts[1], parts[2] || void 0, parts[3] || void 0]; +} + /** * @typedef {import('../interface/promise-error-handler').PromiseErrorHandler} PromiseErrorHandler */