diff --git a/app/src/androidTest/java/io/legado/app/AndroidJsTest.kt b/app/src/androidTest/java/io/legado/app/AndroidJsTest.kt index 5a6227b0a..f2d236472 100644 --- a/app/src/androidTest/java/io/legado/app/AndroidJsTest.kt +++ b/app/src/androidTest/java/io/legado/app/AndroidJsTest.kt @@ -1,7 +1,7 @@ package io.legado.app import cn.hutool.core.lang.JarClassLoader -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import dalvik.system.DexClassLoader import org.intellij.lang.annotations.Language @@ -69,7 +69,7 @@ class AndroidJsTest { @Test fun testMap() { val map = hashMapOf("id" to "3242532321") - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["result"] = map @Language("js") val jsMap = "$=result;id=$.id;id" diff --git a/app/src/androidTest/java/io/legado/app/MigrationTest.kt b/app/src/androidTest/java/io/legado/app/MigrationTest.kt index 3bf1c00c9..dbac4197a 100644 --- a/app/src/androidTest/java/io/legado/app/MigrationTest.kt +++ b/app/src/androidTest/java/io/legado/app/MigrationTest.kt @@ -21,7 +21,7 @@ class MigrationTest { ) - @Rule + @get:Rule val helper: MigrationTestHelper = MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), AppDatabase::class.java.canonicalName, @@ -32,7 +32,7 @@ class MigrationTest { @Throws(IOException::class) fun migrateAll() { // Create earliest version of the database. - helper.createDatabase(TEST_DB, 30).apply { + helper.createDatabase(TEST_DB, 50).apply { close() } diff --git a/app/src/main/java/io/legado/app/data/entities/BaseSource.kt b/app/src/main/java/io/legado/app/data/entities/BaseSource.kt index 51af04fb4..fa6e74af0 100644 --- a/app/src/main/java/io/legado/app/data/entities/BaseSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/BaseSource.kt @@ -1,7 +1,7 @@ package io.legado.app.data.entities import cn.hutool.crypto.symmetric.AES -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import io.legado.app.constant.AppConst import io.legado.app.constant.AppLog @@ -231,16 +231,15 @@ interface BaseSource : JsExtensions { * 执行JS */ @Throws(Exception::class) - fun evalJS(jsStr: String, bindingsConfig: SimpleBindings.() -> Unit = {}): Any? { - val bindings = SimpleBindings() + fun evalJS(jsStr: String, bindingsConfig: ScriptBindings.() -> Unit = {}): Any? { + val bindings = ScriptBindings() bindings.apply(bindingsConfig) bindings["java"] = this bindings["source"] = this bindings["baseUrl"] = getKey() bindings["cookie"] = CookieStore bindings["cache"] = CacheManager - val context = RhinoScriptEngine.getScriptContext(bindings) - val scope = RhinoScriptEngine.getRuntimeScope(context) + val scope = RhinoScriptEngine.getRuntimeScope(bindings) getShareScope()?.let { scope.prototype = it } diff --git a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt index 6e5c900d8..1b6068fb3 100644 --- a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt +++ b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt @@ -3,7 +3,7 @@ package io.legado.app.help.book import android.net.Uri -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import io.legado.app.constant.AppLog import io.legado.app.constant.BookSourceType @@ -273,7 +273,7 @@ fun Book.getExportFileName(suffix: String): String { if (jsStr.isNullOrBlank()) { return "$name 作者:${getRealAuthor()}.$suffix" } - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["epubIndex"] = ""// 兼容老版本,修复可能存在的错误 bindings["name"] = name bindings["author"] = getRealAuthor() @@ -297,7 +297,7 @@ fun Book.getExportFileName( if (jsStr.isNullOrBlank()) { return default } - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["name"] = name bindings["author"] = getRealAuthor() bindings["epubIndex"] = epubIndex @@ -327,7 +327,7 @@ fun Book.readSimulating(): Boolean { } fun tryParesExportFileName(jsStr: String): Boolean { - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["name"] = "name" bindings["author"] = "author" bindings["epubIndex"] = "epubIndex" diff --git a/app/src/main/java/io/legado/app/model/SharedJsScope.kt b/app/src/main/java/io/legado/app/model/SharedJsScope.kt index ec7a0d2ec..8539d2a18 100644 --- a/app/src/main/java/io/legado/app/model/SharedJsScope.kt +++ b/app/src/main/java/io/legado/app/model/SharedJsScope.kt @@ -1,7 +1,7 @@ package io.legado.app.model import com.google.gson.reflect.TypeToken -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import io.legado.app.exception.NoStackTraceException import io.legado.app.help.http.newCallStrResponse @@ -33,7 +33,7 @@ object SharedJsScope { var scope = scopeMap[key]?.get() if (scope == null) { scope = RhinoScriptEngine.run { - getRuntimeScope(getScriptContext(SimpleBindings())) + getRuntimeScope(ScriptBindings()) } if (jsLib.isJsonObject()) { val jsMap: Map = GSON.fromJson( diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt index 02721ff7b..b076af9c4 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt @@ -2,7 +2,7 @@ package io.legado.app.model.analyzeRule import android.text.TextUtils import androidx.annotation.Keep -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.data.entities.BaseBook @@ -21,7 +21,6 @@ import io.legado.app.utils.isJson import io.legado.app.utils.printOnDebug import io.legado.app.utils.splitNotBlank import io.legado.app.utils.stackTraceStr -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout import org.apache.commons.text.StringEscapeUtils @@ -746,7 +745,7 @@ class AnalyzeRule( * 执行JS */ fun evalJS(jsStr: String, result: Any? = null): Any? { - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["java"] = this bindings["cookie"] = CookieStore bindings["cache"] = CacheManager @@ -758,8 +757,7 @@ class AnalyzeRule( bindings["title"] = chapter?.title bindings["src"] = content bindings["nextChapterUrl"] = nextChapterUrl - val context = RhinoScriptEngine.getScriptContext(bindings) - val scope = RhinoScriptEngine.getRuntimeScope(context) + val scope = RhinoScriptEngine.getRuntimeScope(bindings) source?.getShareScope()?.let { scope.prototype = it } diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index 7220194e7..fb026abf6 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt @@ -6,7 +6,7 @@ import androidx.annotation.Keep import androidx.media3.common.MediaItem import cn.hutool.core.util.HexUtil import com.bumptech.glide.load.model.GlideUrl -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import io.legado.app.constant.AppConst.UA_NAME import io.legado.app.constant.AppPattern @@ -266,7 +266,7 @@ class AnalyzeUrl( * 执行JS */ fun evalJS(jsStr: String, result: Any? = null): Any? { - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["java"] = this bindings["baseUrl"] = baseUrl bindings["cookie"] = CookieStore @@ -278,8 +278,7 @@ class AnalyzeUrl( bindings["book"] = ruleData as? Book bindings["source"] = source bindings["result"] = result - val context = RhinoScriptEngine.getScriptContext(bindings) - val scope = RhinoScriptEngine.getRuntimeScope(context) + val scope = RhinoScriptEngine.getRuntimeScope(bindings) source?.getShareScope()?.let { scope.prototype = it } diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index 8693fe459..a603c08c0 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -3,7 +3,7 @@ package io.legado.app.model.localBook import android.net.Uri import android.util.Base64 import androidx.documentfile.provider.DocumentFile -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import io.legado.app.R import io.legado.app.constant.* @@ -291,7 +291,7 @@ object LocalBook { AppConfig.bookImportFileName + "\nJSON.stringify({author:author,name:name})" //在脚本中定义如何分解文件名成书名、作者名 val jsonStr = RhinoScriptEngine.run { - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["src"] = tempFileName eval(js, bindings) }.toString() diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt index e7a9132e3..49c4d0416 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt @@ -1,7 +1,7 @@ package io.legado.app.model.webBook import android.text.TextUtils -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import io.legado.app.R import io.legado.app.data.entities.Book @@ -127,7 +127,7 @@ object BookChapterList { Debug.log(book.origin, "◇目录总数:${list.size}") coroutineContext.ensureActive() val formatJs = tocRule.formatJs - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["gInt"] = 0 list.forEachIndexed { index, bookChapter -> bookChapter.index = index diff --git a/app/src/main/java/io/legado/app/utils/RegexExtensions.kt b/app/src/main/java/io/legado/app/utils/RegexExtensions.kt index 8de851098..c345292a4 100644 --- a/app/src/main/java/io/legado/app/utils/RegexExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/RegexExtensions.kt @@ -1,7 +1,7 @@ package io.legado.app.utils import androidx.core.os.postDelayed -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import io.legado.app.exception.RegexTimeoutException import io.legado.app.help.CrashHandler @@ -32,7 +32,7 @@ fun CharSequence.replace(regex: Regex, replacement: String, timeout: Long): Stri while (matcher.find()) { if (isJs) { val jsResult = RhinoScriptEngine.run { - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["result"] = matcher.group() eval(replacement1, bindings) }.toString() diff --git a/app/src/test/java/io/legado/app/JsTest.kt b/app/src/test/java/io/legado/app/JsTest.kt index 58784ddca..df7e1c4e1 100644 --- a/app/src/test/java/io/legado/app/JsTest.kt +++ b/app/src/test/java/io/legado/app/JsTest.kt @@ -1,6 +1,6 @@ package io.legado.app -import com.script.SimpleBindings +import com.script.ScriptBindings import com.script.rhino.RhinoScriptEngine import io.legado.app.data.entities.BookChapter import org.intellij.lang.annotations.Language @@ -28,7 +28,7 @@ class JsTest { @Test fun testMap() { val map = hashMapOf("id" to "3242532321") - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["result"] = map @Language("js") val jsMap = "$=result;id=$.id;id" @@ -43,7 +43,7 @@ class JsTest { @Test fun testFor() { val scope = RhinoScriptEngine.run { - val scope = getRuntimeScope(getScriptContext(SimpleBindings())) + val scope = getRuntimeScope(ScriptBindings()) eval(printJs, scope) scope } @@ -86,7 +86,7 @@ class JsTest { s[2].substr(0,n); """.trimIndent() val x = RhinoScriptEngine.run { - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["result"] = "筳彩涫第七百一十四章 人头树鮺舦綸" eval(js, bindings) } @@ -97,7 +97,7 @@ class JsTest { @Test fun chapterText() { val chapter = BookChapter(title = "xxxyyy") - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["chapter"] = chapter @Language("js") val js = "chapter.title" @@ -108,7 +108,7 @@ class JsTest { @Test fun javaListForEach() { val list = arrayListOf(1, 2, 3) - val bindings = SimpleBindings() + val bindings = ScriptBindings() bindings["list"] = list @Language("js") val js = """ @@ -120,4 +120,16 @@ class JsTest { Assert.assertEquals(result, 6.0) } + @Test + fun typeofString() { + val bindings = ScriptBindings() + @Language("js") + val js = """ + s = "" + String() + typeof s + """.trimIndent() + val result = RhinoScriptEngine.eval(js, bindings) + Assert.assertEquals(result, "string") + } + } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 908bccc9f..f57a59328 100644 --- a/gradle.properties +++ b/gradle.properties @@ -45,4 +45,5 @@ android.nonTransitiveRClass=true CronetVersion=123.0.6312.80 CronetMainVersion=123.0.0.0 android.injected.testOnly=false -android.nonFinalResIds=true \ No newline at end of file +android.nonFinalResIds=true +android.injected.androidTest.leaveApksInstalledAfterRun=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cd9e63f3b..d011b9e5b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,6 +37,7 @@ protobufJavalite = "4.26.1" quickChineseTransfer = "0.2.13" room = "2.6.1" splitties = "3.0.0" +rhino = "1.7.15" activity = "1.8.2" kotlinxSerialization = "1.6.3" @@ -104,6 +105,7 @@ protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version. quick-chinese-transfer-core = { module = "com.github.liuyueyi.quick-chinese-transfer:quick-transfer-core", version.ref = "quickChineseTransfer" } + room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } @@ -148,6 +150,9 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinxSerialization" } liveeventbus = { module = "com.github.michaellee123:LiveEventBus", version.ref = "liveeventbus" } material = { module = "com.google.android.material:material", version.ref = "material" } + +mozilla-rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } + renderscript-intrinsics-replacement-toolkit = { module = "com.github.android:renderscript-intrinsics-replacement-toolkit", version = "b6363490c3" } zxing-lite = { module = "com.github.jenly1314:zxing-lite", version.ref = "zxingLite" } diff --git a/modules/rhino/build.gradle b/modules/rhino/build.gradle index 1890ee4d5..8ae10e567 100644 --- a/modules/rhino/build.gradle +++ b/modules/rhino/build.gradle @@ -35,7 +35,8 @@ android { } dependencies { - api(fileTree(dir: 'lib', include: ['rhino-1.7.14.jar'])) +// api(fileTree(dir: 'lib', include: ['rhino-1.7.14.jar'])) + api libs.mozilla.rhino implementation(libs.kotlinx.coroutines.core) diff --git a/modules/rhino/src/main/java/com/script/AbstractScriptEngine.kt b/modules/rhino/src/main/java/com/script/AbstractScriptEngine.kt index d0a8c6289..09bd068c3 100644 --- a/modules/rhino/src/main/java/com/script/AbstractScriptEngine.kt +++ b/modules/rhino/src/main/java/com/script/AbstractScriptEngine.kt @@ -81,6 +81,11 @@ abstract class AbstractScriptEngine(val bindings: Bindings? = null) : ScriptEngi return this.eval(script, getScriptContext(bindings)) } + @Throws(ScriptException::class) + override fun eval(script: String, bindings: ScriptBindings): Any? { + return this.eval(script, getRuntimeScope(bindings)) + } + @Throws(ScriptException::class) override fun eval(reader: Reader): Any? { return this.eval(reader, context) diff --git a/modules/rhino/src/main/java/com/script/ScriptBindings.kt b/modules/rhino/src/main/java/com/script/ScriptBindings.kt new file mode 100644 index 000000000..341ea7903 --- /dev/null +++ b/modules/rhino/src/main/java/com/script/ScriptBindings.kt @@ -0,0 +1,42 @@ +package com.script + +import org.mozilla.javascript.Context +import org.mozilla.javascript.NativeObject +import org.mozilla.javascript.ScriptableObject + +class ScriptBindings : NativeObject() { + + companion object { + private val topLevelScope: ScriptableObject by lazy { + val cx = Context.enter() + try { + cx.initStandardObjects() + } finally { + Context.exit() + } + } + } + + init { + prototype = topLevelScope + } + + operator fun set(key: String, value: Any?) { + Context.enter() + try { + put(key, this, Context.javaToJS(value, this)) + } finally { + Context.exit() + } + } + + operator fun set(index: Int, value: Any?) { + Context.enter() + try { + put(index, this, Context.javaToJS(value, this)) + } finally { + Context.exit() + } + } + +} diff --git a/modules/rhino/src/main/java/com/script/ScriptEngine.kt b/modules/rhino/src/main/java/com/script/ScriptEngine.kt index 2a3d7e76d..bd8b81d67 100644 --- a/modules/rhino/src/main/java/com/script/ScriptEngine.kt +++ b/modules/rhino/src/main/java/com/script/ScriptEngine.kt @@ -45,9 +45,14 @@ interface ScriptEngine { @Throws(ScriptException::class) fun eval(script: String, bindings: Bindings): Any? + @Throws(ScriptException::class) + fun eval(script: String, bindings: ScriptBindings): Any? + @Throws(ScriptException::class) fun eval(script: String, context: ScriptContext): Any? + fun getRuntimeScope(bindings: ScriptBindings): Scriptable + fun getRuntimeScope(context: ScriptContext): Scriptable fun getScriptContext(bindings: Bindings): ScriptContext diff --git a/modules/rhino/src/main/java/com/script/rhino/RhinoScriptEngine.kt b/modules/rhino/src/main/java/com/script/rhino/RhinoScriptEngine.kt index 4b3b62371..5fcaabc9a 100644 --- a/modules/rhino/src/main/java/com/script/rhino/RhinoScriptEngine.kt +++ b/modules/rhino/src/main/java/com/script/rhino/RhinoScriptEngine.kt @@ -53,8 +53,8 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable { private val indexedProps: MutableMap private val implementor: InterfaceImplementor - fun eval(js: String, bindingsConfig: SimpleBindings.() -> Unit = {}): Any? { - val bindings = SimpleBindings() + fun eval(js: String, bindingsConfig: ScriptBindings.() -> Unit = {}): Any? { + val bindings = ScriptBindings() bindings.apply(bindingsConfig) return eval(js, bindings) } @@ -249,6 +249,16 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable { return newScope } + override fun getRuntimeScope(bindings: ScriptBindings): ScriptBindings { + val cx = Context.enter() + try { + bindings.prototype = cx.initStandardObjects() + } finally { + Context.exit() + } + return bindings + } + @Throws(ScriptException::class) override fun compile(script: String): CompiledScript { return this.compile(StringReader(script) as Reader)