This commit is contained in:
Horis
2024-07-31 17:42:19 +08:00
parent beb561a052
commit 7e6a1e4749
18 changed files with 117 additions and 40 deletions
@@ -1,7 +1,7 @@
package io.legado.app package io.legado.app
import cn.hutool.core.lang.JarClassLoader import cn.hutool.core.lang.JarClassLoader
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import dalvik.system.DexClassLoader import dalvik.system.DexClassLoader
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
@@ -69,7 +69,7 @@ class AndroidJsTest {
@Test @Test
fun testMap() { fun testMap() {
val map = hashMapOf("id" to "3242532321") val map = hashMapOf("id" to "3242532321")
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["result"] = map bindings["result"] = map
@Language("js") @Language("js")
val jsMap = "$=result;id=$.id;id" val jsMap = "$=result;id=$.id;id"
@@ -21,7 +21,7 @@ class MigrationTest {
) )
@Rule @get:Rule
val helper: MigrationTestHelper = MigrationTestHelper( val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(), InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName, AppDatabase::class.java.canonicalName,
@@ -32,7 +32,7 @@ class MigrationTest {
@Throws(IOException::class) @Throws(IOException::class)
fun migrateAll() { fun migrateAll() {
// Create earliest version of the database. // Create earliest version of the database.
helper.createDatabase(TEST_DB, 30).apply { helper.createDatabase(TEST_DB, 50).apply {
close() close()
} }
@@ -1,7 +1,7 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import cn.hutool.crypto.symmetric.AES import cn.hutool.crypto.symmetric.AES
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
@@ -231,16 +231,15 @@ interface BaseSource : JsExtensions {
* 执行JS * 执行JS
*/ */
@Throws(Exception::class) @Throws(Exception::class)
fun evalJS(jsStr: String, bindingsConfig: SimpleBindings.() -> Unit = {}): Any? { fun evalJS(jsStr: String, bindingsConfig: ScriptBindings.() -> Unit = {}): Any? {
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings.apply(bindingsConfig) bindings.apply(bindingsConfig)
bindings["java"] = this bindings["java"] = this
bindings["source"] = this bindings["source"] = this
bindings["baseUrl"] = getKey() bindings["baseUrl"] = getKey()
bindings["cookie"] = CookieStore bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager bindings["cache"] = CacheManager
val context = RhinoScriptEngine.getScriptContext(bindings) val scope = RhinoScriptEngine.getRuntimeScope(bindings)
val scope = RhinoScriptEngine.getRuntimeScope(context)
getShareScope()?.let { getShareScope()?.let {
scope.prototype = it scope.prototype = it
} }
@@ -3,7 +3,7 @@
package io.legado.app.help.book package io.legado.app.help.book
import android.net.Uri import android.net.Uri
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.constant.BookSourceType import io.legado.app.constant.BookSourceType
@@ -273,7 +273,7 @@ fun Book.getExportFileName(suffix: String): String {
if (jsStr.isNullOrBlank()) { if (jsStr.isNullOrBlank()) {
return "$name 作者:${getRealAuthor()}.$suffix" return "$name 作者:${getRealAuthor()}.$suffix"
} }
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["epubIndex"] = ""// 兼容老版本,修复可能存在的错误 bindings["epubIndex"] = ""// 兼容老版本,修复可能存在的错误
bindings["name"] = name bindings["name"] = name
bindings["author"] = getRealAuthor() bindings["author"] = getRealAuthor()
@@ -297,7 +297,7 @@ fun Book.getExportFileName(
if (jsStr.isNullOrBlank()) { if (jsStr.isNullOrBlank()) {
return default return default
} }
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["name"] = name bindings["name"] = name
bindings["author"] = getRealAuthor() bindings["author"] = getRealAuthor()
bindings["epubIndex"] = epubIndex bindings["epubIndex"] = epubIndex
@@ -327,7 +327,7 @@ fun Book.readSimulating(): Boolean {
} }
fun tryParesExportFileName(jsStr: String): Boolean { fun tryParesExportFileName(jsStr: String): Boolean {
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["name"] = "name" bindings["name"] = "name"
bindings["author"] = "author" bindings["author"] = "author"
bindings["epubIndex"] = "epubIndex" bindings["epubIndex"] = "epubIndex"
@@ -1,7 +1,7 @@
package io.legado.app.model package io.legado.app.model
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.http.newCallStrResponse import io.legado.app.help.http.newCallStrResponse
@@ -33,7 +33,7 @@ object SharedJsScope {
var scope = scopeMap[key]?.get() var scope = scopeMap[key]?.get()
if (scope == null) { if (scope == null) {
scope = RhinoScriptEngine.run { scope = RhinoScriptEngine.run {
getRuntimeScope(getScriptContext(SimpleBindings())) getRuntimeScope(ScriptBindings())
} }
if (jsLib.isJsonObject()) { if (jsLib.isJsonObject()) {
val jsMap: Map<String, String> = GSON.fromJson( val jsMap: Map<String, String> = GSON.fromJson(
@@ -2,7 +2,7 @@ package io.legado.app.model.analyzeRule
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.Keep import androidx.annotation.Keep
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.data.entities.BaseBook 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.printOnDebug
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.stackTraceStr import io.legado.app.utils.stackTraceStr
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import org.apache.commons.text.StringEscapeUtils import org.apache.commons.text.StringEscapeUtils
@@ -746,7 +745,7 @@ class AnalyzeRule(
* 执行JS * 执行JS
*/ */
fun evalJS(jsStr: String, result: Any? = null): Any? { fun evalJS(jsStr: String, result: Any? = null): Any? {
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["java"] = this bindings["java"] = this
bindings["cookie"] = CookieStore bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager bindings["cache"] = CacheManager
@@ -758,8 +757,7 @@ class AnalyzeRule(
bindings["title"] = chapter?.title bindings["title"] = chapter?.title
bindings["src"] = content bindings["src"] = content
bindings["nextChapterUrl"] = nextChapterUrl bindings["nextChapterUrl"] = nextChapterUrl
val context = RhinoScriptEngine.getScriptContext(bindings) val scope = RhinoScriptEngine.getRuntimeScope(bindings)
val scope = RhinoScriptEngine.getRuntimeScope(context)
source?.getShareScope()?.let { source?.getShareScope()?.let {
scope.prototype = it scope.prototype = it
} }
@@ -6,7 +6,7 @@ import androidx.annotation.Keep
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import cn.hutool.core.util.HexUtil import cn.hutool.core.util.HexUtil
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import io.legado.app.constant.AppConst.UA_NAME import io.legado.app.constant.AppConst.UA_NAME
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
@@ -266,7 +266,7 @@ class AnalyzeUrl(
* 执行JS * 执行JS
*/ */
fun evalJS(jsStr: String, result: Any? = null): Any? { fun evalJS(jsStr: String, result: Any? = null): Any? {
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["java"] = this bindings["java"] = this
bindings["baseUrl"] = baseUrl bindings["baseUrl"] = baseUrl
bindings["cookie"] = CookieStore bindings["cookie"] = CookieStore
@@ -278,8 +278,7 @@ class AnalyzeUrl(
bindings["book"] = ruleData as? Book bindings["book"] = ruleData as? Book
bindings["source"] = source bindings["source"] = source
bindings["result"] = result bindings["result"] = result
val context = RhinoScriptEngine.getScriptContext(bindings) val scope = RhinoScriptEngine.getRuntimeScope(bindings)
val scope = RhinoScriptEngine.getRuntimeScope(context)
source?.getShareScope()?.let { source?.getShareScope()?.let {
scope.prototype = it scope.prototype = it
} }
@@ -3,7 +3,7 @@ package io.legado.app.model.localBook
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.* import io.legado.app.constant.*
@@ -291,7 +291,7 @@ object LocalBook {
AppConfig.bookImportFileName + "\nJSON.stringify({author:author,name:name})" AppConfig.bookImportFileName + "\nJSON.stringify({author:author,name:name})"
//在脚本中定义如何分解文件名成书名、作者名 //在脚本中定义如何分解文件名成书名、作者名
val jsonStr = RhinoScriptEngine.run { val jsonStr = RhinoScriptEngine.run {
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["src"] = tempFileName bindings["src"] = tempFileName
eval(js, bindings) eval(js, bindings)
}.toString() }.toString()
@@ -1,7 +1,7 @@
package io.legado.app.model.webBook package io.legado.app.model.webBook
import android.text.TextUtils import android.text.TextUtils
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
@@ -127,7 +127,7 @@ object BookChapterList {
Debug.log(book.origin, "◇目录总数:${list.size}") Debug.log(book.origin, "◇目录总数:${list.size}")
coroutineContext.ensureActive() coroutineContext.ensureActive()
val formatJs = tocRule.formatJs val formatJs = tocRule.formatJs
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["gInt"] = 0 bindings["gInt"] = 0
list.forEachIndexed { index, bookChapter -> list.forEachIndexed { index, bookChapter ->
bookChapter.index = index bookChapter.index = index
@@ -1,7 +1,7 @@
package io.legado.app.utils package io.legado.app.utils
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import io.legado.app.exception.RegexTimeoutException import io.legado.app.exception.RegexTimeoutException
import io.legado.app.help.CrashHandler import io.legado.app.help.CrashHandler
@@ -32,7 +32,7 @@ fun CharSequence.replace(regex: Regex, replacement: String, timeout: Long): Stri
while (matcher.find()) { while (matcher.find()) {
if (isJs) { if (isJs) {
val jsResult = RhinoScriptEngine.run { val jsResult = RhinoScriptEngine.run {
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["result"] = matcher.group() bindings["result"] = matcher.group()
eval(replacement1, bindings) eval(replacement1, bindings)
}.toString() }.toString()
+18 -6
View File
@@ -1,6 +1,6 @@
package io.legado.app package io.legado.app
import com.script.SimpleBindings import com.script.ScriptBindings
import com.script.rhino.RhinoScriptEngine import com.script.rhino.RhinoScriptEngine
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
@@ -28,7 +28,7 @@ class JsTest {
@Test @Test
fun testMap() { fun testMap() {
val map = hashMapOf("id" to "3242532321") val map = hashMapOf("id" to "3242532321")
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["result"] = map bindings["result"] = map
@Language("js") @Language("js")
val jsMap = "$=result;id=$.id;id" val jsMap = "$=result;id=$.id;id"
@@ -43,7 +43,7 @@ class JsTest {
@Test @Test
fun testFor() { fun testFor() {
val scope = RhinoScriptEngine.run { val scope = RhinoScriptEngine.run {
val scope = getRuntimeScope(getScriptContext(SimpleBindings())) val scope = getRuntimeScope(ScriptBindings())
eval(printJs, scope) eval(printJs, scope)
scope scope
} }
@@ -86,7 +86,7 @@ class JsTest {
s[2].substr(0,n); s[2].substr(0,n);
""".trimIndent() """.trimIndent()
val x = RhinoScriptEngine.run { val x = RhinoScriptEngine.run {
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["result"] = "筳彩涫第七百一十四章 人头树鮺舦綸" bindings["result"] = "筳彩涫第七百一十四章 人头树鮺舦綸"
eval(js, bindings) eval(js, bindings)
} }
@@ -97,7 +97,7 @@ class JsTest {
@Test @Test
fun chapterText() { fun chapterText() {
val chapter = BookChapter(title = "xxxyyy") val chapter = BookChapter(title = "xxxyyy")
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["chapter"] = chapter bindings["chapter"] = chapter
@Language("js") @Language("js")
val js = "chapter.title" val js = "chapter.title"
@@ -108,7 +108,7 @@ class JsTest {
@Test @Test
fun javaListForEach() { fun javaListForEach() {
val list = arrayListOf(1, 2, 3) val list = arrayListOf(1, 2, 3)
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings["list"] = list bindings["list"] = list
@Language("js") @Language("js")
val js = """ val js = """
@@ -120,4 +120,16 @@ class JsTest {
Assert.assertEquals(result, 6.0) 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")
}
} }
+2 -1
View File
@@ -45,4 +45,5 @@ android.nonTransitiveRClass=true
CronetVersion=123.0.6312.80 CronetVersion=123.0.6312.80
CronetMainVersion=123.0.0.0 CronetMainVersion=123.0.0.0
android.injected.testOnly=false android.injected.testOnly=false
android.nonFinalResIds=true android.nonFinalResIds=true
android.injected.androidTest.leaveApksInstalledAfterRun=true
+5
View File
@@ -37,6 +37,7 @@ protobufJavalite = "4.26.1"
quickChineseTransfer = "0.2.13" quickChineseTransfer = "0.2.13"
room = "2.6.1" room = "2.6.1"
splitties = "3.0.0" splitties = "3.0.0"
rhino = "1.7.15"
activity = "1.8.2" activity = "1.8.2"
kotlinxSerialization = "1.6.3" 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" } 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-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
room-runtime = { module = "androidx.room:room-runtime", 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" } kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinxSerialization" }
liveeventbus = { module = "com.github.michaellee123:LiveEventBus", version.ref = "liveeventbus" } liveeventbus = { module = "com.github.michaellee123:LiveEventBus", version.ref = "liveeventbus" }
material = { module = "com.google.android.material:material", version.ref = "material" } 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" } 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" } zxing-lite = { module = "com.github.jenly1314:zxing-lite", version.ref = "zxingLite" }
+2 -1
View File
@@ -35,7 +35,8 @@ android {
} }
dependencies { 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) implementation(libs.kotlinx.coroutines.core)
@@ -81,6 +81,11 @@ abstract class AbstractScriptEngine(val bindings: Bindings? = null) : ScriptEngi
return this.eval(script, getScriptContext(bindings)) 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) @Throws(ScriptException::class)
override fun eval(reader: Reader): Any? { override fun eval(reader: Reader): Any? {
return this.eval(reader, context) return this.eval(reader, context)
@@ -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()
}
}
}
@@ -45,9 +45,14 @@ interface ScriptEngine {
@Throws(ScriptException::class) @Throws(ScriptException::class)
fun eval(script: String, bindings: Bindings): Any? fun eval(script: String, bindings: Bindings): Any?
@Throws(ScriptException::class)
fun eval(script: String, bindings: ScriptBindings): Any?
@Throws(ScriptException::class) @Throws(ScriptException::class)
fun eval(script: String, context: ScriptContext): Any? fun eval(script: String, context: ScriptContext): Any?
fun getRuntimeScope(bindings: ScriptBindings): Scriptable
fun getRuntimeScope(context: ScriptContext): Scriptable fun getRuntimeScope(context: ScriptContext): Scriptable
fun getScriptContext(bindings: Bindings): ScriptContext fun getScriptContext(bindings: Bindings): ScriptContext
@@ -53,8 +53,8 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable {
private val indexedProps: MutableMap<Any, Any?> private val indexedProps: MutableMap<Any, Any?>
private val implementor: InterfaceImplementor private val implementor: InterfaceImplementor
fun eval(js: String, bindingsConfig: SimpleBindings.() -> Unit = {}): Any? { fun eval(js: String, bindingsConfig: ScriptBindings.() -> Unit = {}): Any? {
val bindings = SimpleBindings() val bindings = ScriptBindings()
bindings.apply(bindingsConfig) bindings.apply(bindingsConfig)
return eval(js, bindings) return eval(js, bindings)
} }
@@ -249,6 +249,16 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable {
return newScope 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) @Throws(ScriptException::class)
override fun compile(script: String): CompiledScript { override fun compile(script: String): CompiledScript {
return this.compile(StringReader(script) as Reader) return this.compile(StringReader(script) as Reader)