From a2b47cf6c820f531cb65b4d3f27bb7c024eb4efe Mon Sep 17 00:00:00 2001 From: SuperMonster003 Date: Thu, 20 Nov 2025 16:38:56 +0800 Subject: [PATCH] =?UTF-8?q?6.7.0=20-=20Alpha11=20-=20=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=8F=B0=E7=9B=B8=E5=85=B3=E6=96=B9=E6=B3=95=E6=89=93=E5=8D=B0?= =?UTF-8?q?=E5=B7=A8=E5=9E=8B=E6=95=B0=E6=8D=AE=E6=97=B6=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E4=B8=8A=E9=99=90=E9=80=82=E5=BD=93=E7=9C=81=E7=95=A5=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=86=85=E5=AE=B9=E4=BB=A5=E9=99=8D=E4=BD=8E=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E8=80=97=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changelog/lang_zh-Hans.json | 1 + .../runtime/api/augment/util/Inspect.kt | 207 +++++++++++++----- .../autojs/runtime/api/augment/util/Util.kt | 105 +++++++-- version.properties | 6 +- 4 files changed, 248 insertions(+), 71 deletions(-) diff --git a/.changelog/lang_zh-Hans.json b/.changelog/lang_zh-Hans.json index 4b1d70ff..9982b75e 100644 --- a/.changelog/lang_zh-Hans.json +++ b/.changelog/lang_zh-Hans.json @@ -69,6 +69,7 @@ "android.graphics.Paint#setColor 支持正常解析 ColorInt/ColorHex/ColorName 等颜色参数", "app.launchDual/startDualActivity 等方法提升执行效率并增强兼容性", "内置模块相关方法实参类型的异常消息增加类型摘要信息", + "控制台相关方法打印巨型数据时根据上限适当省略部分内容以降低打印耗时", "控制台浮动窗口倒计时起始值由 6 秒增加到 9 秒", "控制台浮动窗口内部实现进行无锁化及队列化处理以提升其参数设置效率与成功率", "控制台浮动窗口背景色彩行为相关 API (透明度/着色/基色) 更符合安卓设计规范 _[`issue #458`](http://issues.autojs6.com/458)_", diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/augment/util/Inspect.kt b/app/src/main/java/org/autojs/autojs/runtime/api/augment/util/Inspect.kt index 6d0439f8..5f26382a 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/augment/util/Inspect.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/augment/util/Inspect.kt @@ -9,6 +9,7 @@ import org.autojs.autojs.extension.ArrayExtensions.toNativeObject import org.autojs.autojs.extension.FlexibleArray.Companion.component1 import org.autojs.autojs.extension.FlexibleArray.Companion.component2 import org.autojs.autojs.extension.ScriptableExtensions.prop +import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire import org.autojs.autojs.rhino.ProxyObject import org.autojs.autojs.rhino.ProxyObject.Companion.AUGMENTED_CUSTOM_TO_STRING_KEY import org.autojs.autojs.rhino.ProxyObject.Companion.AUGMENTED_OBJECT_KEY @@ -27,6 +28,8 @@ import org.autojs.autojs.util.RhinoUtils import org.autojs.autojs.util.RhinoUtils.callFunction import org.autojs.autojs.util.RhinoUtils.callPrototypeFunction import org.autojs.autojs.util.RhinoUtils.callToStringFunction +import org.autojs.autojs.util.RhinoUtils.coerceBoolean +import org.autojs.autojs.util.RhinoUtils.coerceIntNumber import org.autojs.autojs.util.RhinoUtils.javaArrayToString import org.autojs.autojs.util.RhinoUtils.js_array_isArray import org.autojs.autojs.util.RhinoUtils.js_json_stringify @@ -52,8 +55,8 @@ import org.mozilla.javascript.TopLevel.Builtins import org.mozilla.javascript.Undefined.isUndefined import org.mozilla.javascript.Wrapper import org.mozilla.javascript.regexp.NativeRegExp +import java.util.* import java.util.function.Supplier -import kotlin.math.roundToInt /** * Created by SuperMonster003 on Jun 10, 2024. @@ -206,6 +209,8 @@ object Inspect : Augmentable(), Invokable { "null" to "bold", ) + private val DIGITS_REGEX = Regex("\\d+") + /** * Echos the value of a value. Trys to print the value out * in the best way possible given the different types. @@ -222,34 +227,46 @@ object Inspect : Augmentable(), Invokable { val opt = when { options.isJsNullish() -> Options() options is NativeObject -> Options().also { opt -> - val optionShowHidden = options.prop("showHidden") - if (!optionShowHidden.isJsNullish()) { - opt.showHidden = Context.toBoolean(optionShowHidden) - } - val optionDepth = options.prop("depth") - if (!optionDepth.isJsNullish()) { - opt.depth = Context.toNumber(optionDepth).roundToInt() - } - val optionColors = options.prop("colors") - if (!optionColors.isJsNullish()) { - opt.colors = Context.toBoolean(optionColors) - } + options.inquire("showHidden", transformer = ::coerceBoolean)?.let { opt.showHidden = it } + options.inquire("depth", transformer = ::coerceIntNumber)?.let { opt.depth = it } + options.inquire("colors", transformer = ::coerceBoolean)?.let { opt.colors = it } + options.inquire("maxArrayItems", transformer = ::coerceIntNumber)?.let { opt.maxArrayItems = it } + options.inquire("maxObjectKeys", transformer = ::coerceIntNumber)?.let { opt.maxObjectKeys = it } + options.inquire("maxStringLength", transformer = ::coerceIntNumber)?.let { opt.maxStringLength = it } + options.inquire("customInspect", transformer = ::coerceBoolean)?.let { opt.customInspect = it } } else -> throw WrappedIllegalArgumentException("Argument \"options\" ${options.jsBrief()} for util.inspect must be a JavaScript Object") } - val ctx = Ctx().also { - it.showHidden = opt.showHidden - it.depth = opt.depth - it.colors = opt.colors - it.stylize = when (it.colors) { - true -> { str, styleType -> - styles[styleType]?.let { style -> - colors[style]?.let { color -> - color.joinToString(str) { c -> "\u001b[${c}m" } - } ?: throw WrappedIllegalArgumentException("Invalid style type: $styleType") - } ?: str + val ctx = Ctx().also { c -> + opt.showHidden?.let { c.showHidden = it } + opt.depth?.let { c.depth = it } + opt.colors?.let { c.colors = it } + opt.customInspect?.let { c.customInspect = it } + opt.maxArrayItems?.let { c.maxArrayItems = it } + opt.maxObjectKeys?.let { c.maxObjectKeys = it } + opt.maxStringLength?.let { c.maxStringLength = it } + + // Pre-compute style cache to avoid constructing escape sequences every time during stylize. + // zh-CN: 预计算样式缓存, 避免每次 stylize 都构造转义序列. + if (c.colors) { + val cache = HashMap>(styles.size) + styles.forEach { (styleType, colorName) -> + colors[colorName]?.let { codes -> + val prefix = "\u001b[${codes.first()}m" + val suffix = "\u001b[${codes.last()}m" + cache[styleType] = prefix to suffix + } } - else -> { str, _ -> str } + c.styleCache = cache + c.stylize = { str, styleType -> + run { + val st = styleType ?: return@run str + val pair = c.styleCache[st] ?: return@run str + pair.first + str + pair.second + } + } + } else { + c.stylize = { str, _ -> str } } } return formatValue(ctx, obj, ctx.depth) @@ -272,12 +289,16 @@ object Inspect : Augmentable(), Invokable { if (value is Scriptable && value !is NativeJavaPackage) runCatching { (value.prop(StringReadable.KEY) as? BaseFunction)?.let { f -> - return Context.toString(callFunction(f, null, value, arrayOf())) + val value = Context.toString(callFunction(f, null, value, arrayOf())) + return formatValue(ctx, value, recurseTimes) } } // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it + // Check that value is an object with an inspect function on it. + // zh-CN: + // 为用户指定的 inspect 函数提供一个钩子. + // 检查 value 是否为带有 inspect 函数的对象. // noinspection JSIncompatibleTypesComparison if (ctx.customInspect && value is Scriptable && value.isJsNonNullObject()) { @@ -285,7 +306,7 @@ object Inspect : Augmentable(), Invokable { if (inspectFunc is BaseFunction) { val ret = callFunction(inspectFunc, null, value, arrayOf(recurseTimes, ctx)) return when { - isStringRhino(ret) -> Context.toString(ret) + isStringRhino(ret) -> formatValue(ctx, Context.toString(ret), recurseTimes) else -> formatValue(ctx, ret, recurseTimes) } } @@ -301,8 +322,15 @@ object Inspect : Augmentable(), Invokable { } } - // Primitive types cannot have properties + // Primitive types cannot have properties. + // zh-CN: 原始类型不能有属性. formatPrimitive(ctx, value).takeUnless { it.isJsNullish() }?.let { + // String truncation (avoid huge overhead caused by overlong strings). + // zh-CN: 字符串截断 (避免超长字符串造成巨大开销). + if (value is String && ctx.maxStringLength > 0 && it.length > ctx.maxStringLength) { + val cut = it.substring(0, ctx.maxStringLength) + return ctx.stylize("$cut... (${it.length - cut.length} more chars)", "string") + } return Context.toString(it) } @@ -314,7 +342,7 @@ object Inspect : Augmentable(), Invokable { if (value is NativeJavaPackage) return value.toString() if (value is NativeJavaClass) return value.toString() if (value.javaClass.isArray) return javaArrayToString(value) - if (value !is ScriptableObject) return Context.toString(value) + if (value !is ScriptableObject) return formatValue(ctx, Context.toString(value), recurseTimes) if (value is ProxyObject) return formatValue(ctx, value[AUGMENTED_OBJECT_KEY], recurseTimes) // @Hint by SuperMonster003 on Apr 16, 2024. @@ -372,6 +400,7 @@ object Inspect : Augmentable(), Invokable { } // Look up the keys of the object. + // zh-CN: 查找对象的键. var keys: NativeArray = js_object_keys(value) val visibleKeys = newNativeObject().also { o -> keys.forEach { o.put(Context.toString(it), o, true) } @@ -380,13 +409,15 @@ object Inspect : Augmentable(), Invokable { keys = js_object_getOwnPropertyNames(value) } - // IE doesn't make error fields non-enumerable - // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + // IE doesn't make error fields non-enumerable. + // zh-CN: IE 不会使错误字段不可枚举. + // @Reference to http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx if (isErrorRhino(value) && (keys.contains("message") || keys.contains("description"))) { return formatError(value) } // Some type of object without properties can be shortcut. + // zh-CN: 某些没有属性的对象类型可以简化处理. if (keys.isEmpty) { when (value) { is BaseFunction -> return formatBaseFunction(ctx, value) @@ -400,28 +431,33 @@ object Inspect : Augmentable(), Invokable { var array = false var braces = listOf("{", "}") - // Make Array say that they are Array + // Make Array say that they are Array. + // zh-CN: 让数组表明它们是数组. if (js_array_isArray(value)) { array = true braces = listOf("[", "]") } - // Make functions say that they are functions + // Make functions say that they are functions. + // zh-CN: 让函数表明它们是函数. if (value is BaseFunction) { base = " ${getBaseFunctionFormattedName(value)}" } - // Make RegExps say that they are RegExps + // Make RegExps say that they are RegExps. + // zh-CN: 让正则表达式表明它们是正则表达式. if (value is NativeRegExp) { base = " ${getRegExpFormattedName(value)}" } - // Make dates with properties first say the date + // Make dates with properties first say the date. + // zh-CN: 让带属性的日期先说明它们是日期. if (value is NativeDate) { base = " ${getDateFormattedName(value, true)}" } - // Make error with a message first say the error + // Make error with a message first say the error. + // zh-CN: 让带消息的错误先说明它们是错误. if (value is NativeError) { base = " ${getErrorFormattedName(value)}" } @@ -437,13 +473,21 @@ object Inspect : Augmentable(), Invokable { } } + // Identity-based collection, O(1) contains/add. + // zh-CN: 基于身份的集合, O(1) contains/add. ctx.seen.add(value) val output: List = if (array) { formatArray(ctx, value as NativeArray, recurseTimes, visibleKeys, keys) } else { try { - keys.map { key -> formatProperty(ctx, value, recurseTimes, visibleKeys, Context.toString(key), false) } + // Object properties limit. + // zh-CN: 对象属性上限. + val limit = if (ctx.maxObjectKeys > 0) ctx.maxObjectKeys else Int.MAX_VALUE + keys.asSequence() + .take(limit) + .map { key -> formatProperty(ctx, value, recurseTimes, visibleKeys, Context.toString(key), false) } + .toList() } catch (err: Exception) { if (isJavaObjectRhino(value)) { RhinoUtils.unwrap(value).let { @@ -453,15 +497,18 @@ object Inspect : Augmentable(), Invokable { } return toJavaObjectString(value) } - return try { + val value = try { Context.toString(value).ifEmpty { "" } } catch (e: Exception) { Context.toString(callPrototypeFunction(Builtins.Object, "toString", value)) } + return formatValue(ctx, value, recurseTimes) } } - ctx.seen.removeLastOrNull() + // With Set, just remove directly. + // zh-CN: 与 Set 搭配, 直接移除即可. + ctx.seen.remove(value) return reduceToSingleString(output, base, braces) } @@ -484,16 +531,54 @@ object Inspect : Augmentable(), Invokable { private fun formatArray(ctx: Ctx, value: NativeArray, recurseTimes: Int?, visibleKeys: NativeObject, keys: NativeArray): List { val output = mutableListOf() - for (i in 0 until value.length) { + + // Array elements upper limit display (head/tail summary). + // zh-CN: 数组元素上限显示 (head/tail 摘要). + val len = value.length.toInt() + val max = if (ctx.maxArrayItems > 0) ctx.maxArrayItems else Int.MAX_VALUE + // First 100. + // zh-CN: 前 100. + val head = minOf(len, minOf(max, 100)) + val tail = when { + // When exceeding, show last 20. + // zh-CN: 多出时, 末尾展示 20. + len > max -> minOf(20, max - head).coerceAtLeast(0) + else -> 0 + } + + // Show head first. + // zh-CN: 先展示 head. + for (i in 0 until head) { val key = Context.toString(i) output += when (js_object_hasOwnProperty(value, key)) { true -> formatProperty(ctx, value, recurseTimes, visibleKeys, key, true) else -> "" } } + + // Middle summary. + // zh-CN: 中间摘要. + if (len > head + tail) { + val omitted = len - head - tail + output += ctx.stylize("... $omitted more items ...", "special") + } + + // Then show tail. + // zh-CN: 再展示 tail. + for (i in (len - tail) until len) { + if (i < head) continue + val key = Context.toString(i) + output += when (js_object_hasOwnProperty(value, key)) { + true -> formatProperty(ctx, value, recurseTimes, visibleKeys, key, true) + else -> "" + } + } + + // Append non-numeric keys. + // zh-CN: 补充非数字键. keys.forEach { key -> val niceKey = Context.toString(key) - if (!niceKey.matches(Regex("\\d+"))) { + if (!DIGITS_REGEX.matches(niceKey)) { output.add(formatProperty(ctx, value, recurseTimes, visibleKeys, niceKey, true)) } } @@ -524,11 +609,16 @@ object Inspect : Augmentable(), Invokable { } if (str.isEmpty()) { - if (ctx.seen.indexOf(desc.prop("value")) < 0) { + val v = desc.prop("value") + // Use identity Set to detect cycles, avoid O(n) indexOf. + // zh-CN: 使用身份 Set 检测循环, 避免 O(n) indexOf. + if (v is ScriptableObject && ctx.seen.contains(v)) { + str = ctx.stylize("[Circular]", "special") + } else { str = if (recurseTimes == null) { - formatValue(ctx, desc.prop("value"), null) + formatValue(ctx, v, null) } else { - formatValue(ctx, desc.prop("value"), recurseTimes - 1) + formatValue(ctx, v, recurseTimes - 1) } if (str.contains("\n")) { str = if (array) { @@ -537,12 +627,10 @@ object Inspect : Augmentable(), Invokable { str.split("\n").joinToString("\n") { " $it" } } } - } else { - str = ctx.stylize("[Circular]", "special") } } if (name.isJsNullish()) { - if (array && key.matches(Regex("\\d+"))) { + if (array && DIGITS_REGEX.matches(key)) { return str } name = Context.toString(js_json_stringify(key)) @@ -637,9 +725,13 @@ object Inspect : Augmentable(), Invokable { } class Options { - var colors = false - var depth: Int = 2 - var showHidden = false + var colors: Boolean? = null + var depth: Int? = null + var showHidden: Boolean? = null + var customInspect: Boolean? = null + var maxArrayItems: Int? = null + var maxObjectKeys: Int? = null + var maxStringLength: Int? = null } class Ctx { @@ -647,7 +739,18 @@ object Inspect : Augmentable(), Invokable { var depth: Int = 2 var showHidden = false var customInspect = false - val seen = mutableListOf() + var maxArrayItems: Int = 10_000 + var maxObjectKeys: Int = 10_000 + var maxStringLength: Int = 100_000 + + // Identity-based collection to avoid O(n^2). + // zh-CN: 以对象身份比较的集合, 避免 O(n^2). + val seen: MutableSet = Collections.newSetFromMap(IdentityHashMap()) + + // Pre-compute style cache to avoid constructing escape sequences every time during stylize. + // zh-CN: 预计算样式缓存, 避免每次 stylize 都构造转义序列. + var styleCache: Map> = emptyMap() + lateinit var stylize: (str: String, styleType: String?) -> String } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/augment/util/Util.kt b/app/src/main/java/org/autojs/autojs/runtime/api/augment/util/Util.kt index ebbfc5ec..a6f8cb76 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/augment/util/Util.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/augment/util/Util.kt @@ -20,6 +20,9 @@ import org.autojs.autojs.extension.AnyExtensions.isJsSymbol import org.autojs.autojs.extension.AnyExtensions.isJsUndefined import org.autojs.autojs.extension.AnyExtensions.jsBrief import org.autojs.autojs.extension.FlexibleArray +import org.autojs.autojs.extension.FlexibleArray.Companion.component1 +import org.autojs.autojs.extension.FlexibleArray.Companion.component2 +import org.autojs.autojs.extension.FlexibleArray.Companion.component3 import org.autojs.autojs.extension.NumberExtensions.jsString import org.autojs.autojs.extension.ScriptableExtensions.defineProp import org.autojs.autojs.extension.ScriptableExtensions.prop @@ -343,31 +346,101 @@ object Util : Augmentable() { @JvmStatic @RhinoFunctionBody fun formatRhino(vararg args: Any?): String { - var index = 1 if (args.isEmpty()) return "" - val first = args.first() - if (first !is String) return args.joinToString(" ") { inspectRhino(it) } + val first = args[0] + if (first !is String) { + return args.joinToString(" ") { inspectRhino(it) } + } - var str = first.replace(Regex("%[sdj%]")) { matchResult -> - if (matchResult.value == "%%") return@replace "%" - if (index >= args.size) return@replace matchResult.value + val argsSize = args.size + var index = 1 + val fmt = first + val len = fmt.length + // Estimate some margin to reduce expansion. + // zh-CN: 预估一些余量, 减少扩容. + val sb = StringBuilder(len + 16) - when (matchResult.value) { - "%s" -> Context.toString(args[index++]) - "%d" -> Context.toNumber(args[index++]).jsString - "%j" -> try { - Context.toString(js_json_stringify(args[index++])) - } catch (e: Exception) { - "[Circular]" + var i = 0 + while (i < len) { + val ch = fmt[i] + if (ch != '%') { + sb.append(ch) + i++ + continue + } + + // '%' at last char -> append '%'. + if (i + 1 >= len) { + sb.append('%') + i++ + continue + } + + when (val spec = fmt[i + 1]) { + '%' -> { + sb.append('%') + i += 2 + } + 's' -> { + if (index >= argsSize) { + // Keep the placeholder as is when no corresponding parameter exists. + // zh-CN: 没有对应参数, 保持占位符原样. + sb.append('%').append('s') + } else { + sb.append(Context.toString(args[index++])) + } + i += 2 + } + 'd' -> { + if (index >= argsSize) { + sb.append('%').append('d') + } else { + sb.append(Context.toNumber(args[index++]).jsString) + } + i += 2 + } + 'j' -> { + if (index >= argsSize) { + sb.append('%').append('j') + } else { + try { + sb.append(Context.toString(js_json_stringify(args[index++]))) + } catch (_: Throwable) { + sb.append("[Circular]") + } + } + i += 2 + } + else -> { + // Keep unknown placeholders as is. + // zh-CN: 未知占位符, 按原样输出. + sb.append('%').append(spec) + i += 2 } - else -> matchResult.value } } - args.copyOfRange(index, args.size).forEach { x -> str += " ${inspectRhino(x)}" } + val formatted = inspectRhino(sb.toString()) - return str + return when { + index >= argsSize -> formatted + else -> { + // Append remaining parameters, separated by spaces. + // zh-CN: 追加剩余参数, 以空格分隔. + val out = StringBuilder(formatted.length + 1 + (argsSize - index) * 8) + out.append(formatted) + out.append(' ') + // Append the first one first, then add spaces for the rest to avoid checking leading spaces in the loop. + // zh-CN: 为避免在循环中判断前导空格, 这里先追加第一个, 再对剩余的加空格. + out.append(inspectRhino(args[index++])) + while (index < argsSize) { + out.append(' ') + out.append(inspectRhino(args[index++])) + } + out.toString() + } + } } @JvmStatic diff --git a/version.properties b/version.properties index b22ceea8..04aba8d5 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ -#Wed Nov 19 13:23:33 CST 2025 -BUILD_TIME=1763529813026 +#Thu Nov 20 16:13:13 CST 2025 +BUILD_TIME=1763626393791 COMPILE_SDK_VERSION=36 IMAGE_QUANT_CMAKE_VERSION=3.22.1 IMAGE_QUANT_NDK_VERSION=26.1.10909125 @@ -27,6 +27,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13 RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3 TARGET_SDK_VERSION=36 TARGET_SDK_VERSION_INRT=29 -VERSION_BUILD=3498 +VERSION_BUILD=3502 VERSION_NAME=6.7.0 Alpha11 VSCODE_EXT_REQUIRED_VERSION=1.0.8