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
|
package com.pitchedapps.frost.injectors
import android.webkit.WebView
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.web.FrostWebViewClient
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.subjects.SingleSubject
import org.apache.commons.text.StringEscapeUtils
import java.util.*
class JsBuilder {
private val css = StringBuilder()
private val js = StringBuilder()
private var tag: String? = null
fun css(css: String): JsBuilder {
this.css.append(StringEscapeUtils.escapeEcmaScript(css))
return this
}
fun js(content: String): JsBuilder {
this.js.append(content)
return this
}
fun single(tag: String): JsBuilder {
this.tag = "_frost_${tag.toLowerCase(Locale.CANADA)}"
return this
}
fun build() = JsInjector(toString())
override fun toString(): String {
val tag = this.tag
val builder = StringBuilder().apply {
append("!function(){")
if (css.isNotBlank()) {
val cssMin = css.replace(Regex("\\s*\n\\s*"), "")
append("var a=document.createElement('style');")
append("a.innerHTML='$cssMin';")
if (tag != null) append("a.id='$tag';")
append("document.head.appendChild(a);")
}
if (js.isNotBlank())
append(js)
}
var content = builder.append("}()").toString()
if (tag != null) content = singleInjector(tag, content)
return content
}
private fun singleInjector(tag: String, content: String) = StringBuilder().apply {
append("if (!window.hasOwnProperty(\"$tag\")) {")
append("console.log(\"Registering $tag\");")
append("window.$tag = true;")
append(content)
append("}")
}.toString()
}
/**
* Contract for all injectors to allow it to interact properly with a webview
*/
interface InjectorContract {
fun inject(webView: WebView) = inject(webView, null)
fun inject(webView: WebView, callback: (() -> Unit)?)
/**
* Toggle the injector (usually through Prefs
* If false, will fallback to an empty action
*/
fun maybe(enable: Boolean): InjectorContract = if (enable) this else JsActions.EMPTY
}
/**
* Helper method to inject multiple functions simultaneously with a single callback
*/
fun WebView.jsInject(vararg injectors: InjectorContract, callback: ((Int) -> Unit)? = null) {
val validInjectors = injectors.filter { it != JsActions.EMPTY }
if (validInjectors.isEmpty()) {
callback?.invoke(0)
return
}
L.d { "Injecting ${validInjectors.size} items" }
if (callback == null) {
validInjectors.forEach { it.inject(this) }
return
}
val observables = Array(validInjectors.size, { SingleSubject.create<Unit>() })
Single.zip<Unit, Int>(observables.asList(), { it.size })
.observeOn(AndroidSchedulers.mainThread())
.subscribe { res, _ ->
callback(res)
}
(0 until validInjectors.size).forEach { i ->
validInjectors[i].inject(this, {
observables[i].onSuccess(Unit)
})
}
}
fun FrostWebViewClient.jsInject(vararg injectors: InjectorContract,
callback: ((Int) -> Unit)? = null) = web.jsInject(*injectors, callback = callback)
/**
* Wrapper class to convert a function into an injector
*/
class JsInjector(val function: String) : InjectorContract {
override fun inject(webView: WebView, callback: (() -> Unit)?) {
webView.evaluateJavascript(function, { callback?.invoke() })
}
}
|