generated from KyuubiRan/EzXHepler-template
-
Notifications
You must be signed in to change notification settings - Fork 40
/
Copy pathWebViewHook.kt
166 lines (152 loc) · 6.77 KB
/
WebViewHook.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package org.matrix.chromext.hook
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.webkit.ConsoleMessage
import android.webkit.WebView
import de.robv.android.xposed.XC_MethodHook.Unhook
import java.lang.ref.WeakReference
import org.json.JSONObject
import org.matrix.chromext.Chrome
import org.matrix.chromext.DEV_FRONT_END
import org.matrix.chromext.DevTools
import org.matrix.chromext.script.ScriptDbManager
import org.matrix.chromext.script.encodeScript
import org.matrix.chromext.script.openEruda
import org.matrix.chromext.script.urlMatch
import org.matrix.chromext.utils.Log
import org.matrix.chromext.utils.ResourceMerge
import org.matrix.chromext.utils.findMethod
import org.matrix.chromext.utils.hookAfter
import org.matrix.chromext.utils.hookBefore
object WebViewHook : BaseHook() {
var ViewClient: Class<*>? = null
var ChromeClient: Class<*>? = null
var webView: WeakReference<WebView>? = null
fun evaluateJavascript(code: String?) {
if (code != null && webView != null) {
webView?.get()?.settings?.javaScriptEnabled = true
webView?.get()?.settings?.domStorageEnabled = true
webView?.get()?.evaluateJavascript(code, null)
}
}
override fun init() {
val ctx = Chrome.getContext()
ResourceMerge.enrich(ctx)
val promptInstallUserScript =
ctx.assets.open("editor.js").bufferedReader().use { it.readText() }
val customizeDevTool = ctx.assets.open("devtools.js").bufferedReader().use { it.readText() }
val cosmeticFilter =
ctx.assets.open("cosmetic-filter.js").bufferedReader().use { it.readText() }
findMethod(ChromeClient!!, true) {
name == "onConsoleMessage" &&
getParameterTypes() contentDeepEquals arrayOf(ConsoleMessage::class.java)
}
// public boolean onConsoleMessage (ConsoleMessage consoleMessage)
.hookAfter {
// This should be the way to communicate with the front-end of ChromeXt
val consoleMessage = it.args[0] as ConsoleMessage
if (consoleMessage.messageLevel() == ConsoleMessage.MessageLevel.TIP) {
val text = consoleMessage.message()
runCatching {
val data = JSONObject(text)
val action = data.getString("action")
val payload = data.getString("payload")
runCatching { evaluateJavascript(ScriptDbManager.on(action, payload)) }
.onFailure { Log.w("Failed with ${action}: ${payload}") }
}
.onFailure { Log.d("Ignore console.debug: " + text) }
} else {
Log.d(
consoleMessage.messageLevel().toString() +
": [${consoleMessage.sourceId()}@${consoleMessage.lineNumber()}] ${consoleMessage.message()}")
}
}
fun onUpdateUrl(url: String, view: WebView) {
val enableJS = view.settings.javaScriptEnabled
val enableDOMStorage = view.settings.domStorageEnabled
webView = WeakReference(view)
evaluateJavascript("globalThis.ChromeXt=console.debug.bind(console);")
if (url.endsWith(".user.js")) {
evaluateJavascript(promptInstallUserScript)
} else if (url.startsWith(DEV_FRONT_END)) {
view.settings.userAgentString = null
evaluateJavascript(customizeDevTool)
} else if (!url.endsWith("/ChromeXt/")) {
val protocol = url.split("://")
if (protocol.size > 1 && arrayOf("https", "http", "file").contains(protocol.first())) {
val origin = protocol.first() + "://" + protocol[1].split("/").first()
if (ScriptDbManager.userAgents.contains(origin)) {
view.settings.userAgentString = ScriptDbManager.userAgents.get(origin)
}
ScriptDbManager.scripts.forEach loop@{
val script = it
script.exclude.forEach {
if (urlMatch(it, url, true)) {
return@loop
}
}
script.match.forEach {
if (urlMatch(it, url, false)) {
val code = encodeScript(script)
if (code != null) {
evaluateJavascript(code)
Log.d("Run script: ${script.code.replace("\\s+".toRegex(), " ")}")
}
return@loop
}
}
}
if (ScriptDbManager.cosmeticFilters.contains(origin)) {
evaluateJavascript(
"globalThis.ChromeXt_filter=`${ScriptDbManager.cosmeticFilters.get(origin)}`;${cosmeticFilter}")
Log.d("Cosmetic filters applied to ${origin}")
}
}
view.settings.javaScriptEnabled = enableJS
view.settings.domStorageEnabled = enableDOMStorage
}
}
var contextMenuHook: Unhook? = null
findMethod(View::class.java) { name == "startActionMode" && getParameterTypes().size == 2 }
// public ActionMode startActionMode (ActionMode.Callback callback,
// int type)
.hookBefore {
if (it.args[1] as Int == ActionMode.TYPE_FLOATING && it.thisObject is WebView) {
val view = it.thisObject as WebView
val isChromeXt = view.getUrl()!!.endsWith("/ChromeXt/")
webView = WeakReference(view)
contextMenuHook?.unhook()
contextMenuHook =
findMethod(it.args[0]::class.java) { name == "onCreateActionMode" }
// public abstract boolean onCreateActionMode (ActionMode mode, Menu menu)
.hookAfter {
val mode = it.args[0] as ActionMode
val menu = it.args[1] as Menu
val erudaMenu = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, "Eruda console")
if (isChromeXt) {
erudaMenu.setTitle("Developer tools")
}
erudaMenu.setOnMenuItemClickListener(
MenuItem.OnMenuItemClickListener {
if (isChromeXt) {
WebView.setWebContentsDebuggingEnabled(true)
DevTools.start()
} else {
evaluateJavascript(openEruda)
}
mode.finish()
true
})
}
}
}
findMethod(WebView::class.java) { name == "loadUrl" }
// public void loadUrl (String url)
.hookAfter { onUpdateUrl(it.args[0] as String, it.thisObject as WebView) }
findMethod(ViewClient!!, true) { name == "onPageStarted" }
// public void onPageStarted (WebView view, String url, Bitmap favicon)
.hookAfter { onUpdateUrl(it.args[1] as String, it.args[0] as WebView) }
}
}