From c3023b0da95059636870b4ed8539dd8734566ba7 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 20 Jun 2023 20:28:29 -0700 Subject: [PATCH] Add most injectors --- app-compose/build.gradle | 4 + .../kotlin/com/pitchedapps/frost/FrostApp.kt | 12 ++ .../com/pitchedapps/frost/StartActivity.kt | 20 ++-- .../frost/web/FrostWebUiOptions.kt | 66 +++++++++++ .../frost/web/state/FrostMiddleware.kt | 18 ++- .../com/pitchedapps/frost/webview/FrostWeb.kt | 6 +- .../frost/webview/FrostWebComposer.kt | 4 +- .../frost/webview/FrostWebViewClients.kt | 10 +- .../webview/injection/FrostJsInjectors.kt | 66 +++++++++++ .../frost/webview/injection/JsInjector.kt | 108 ++++++++++++++++++ .../webview/injection/assets/CssActions.kt | 35 ++++++ .../webview/injection/assets/CssHider.kt | 63 ++++++++++ .../webview/injection/assets/JsActions.kt | 55 +++++++++ .../webview/injection/assets/JsAssets.kt | 77 +++++++++++++ 14 files changed, 527 insertions(+), 17 deletions(-) create mode 100644 app-compose/src/main/kotlin/com/pitchedapps/frost/web/FrostWebUiOptions.kt create mode 100644 app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/FrostJsInjectors.kt create mode 100644 app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/JsInjector.kt create mode 100644 app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/CssActions.kt create mode 100644 app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/CssHider.kt create mode 100644 app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/JsActions.kt create mode 100644 app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/JsAssets.kt diff --git a/app-compose/build.gradle b/app-compose/build.gradle index 3011b341b..28129fe3b 100644 --- a/app-compose/build.gradle +++ b/app-compose/build.gradle @@ -206,6 +206,10 @@ dependencies { implementation("com.google.protobuf:protobuf-kotlin-lite:3.23.3") implementation("app.cash.sqldelight:android-driver:2.0.0-rc01") + + // https://mvnrepository.com/artifact/org.apache.commons/commons-text + implementation("org.apache.commons:commons-text:1.10.0") + } protobuf { diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index f1dde26f6..9a1c4ebcd 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -21,15 +21,20 @@ import android.app.Application import android.os.Bundle import com.google.common.flogger.FluentLogger import com.pitchedapps.frost.hilt.FrostComponents +import com.pitchedapps.frost.webview.injection.FrostJsInjectors +import com.pitchedapps.frost.webview.injection.assets.JsAssets import dagger.hilt.android.HiltAndroidApp import javax.inject.Inject import javax.inject.Provider +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch /** Frost Application. */ @HiltAndroidApp class FrostApp : Application() { @Inject lateinit var componentsProvider: Provider + @Inject lateinit var frostJsInjectors: Provider override fun onCreate() { super.onCreate() @@ -57,6 +62,13 @@ class FrostApp : Application() { }, ) } + + MainScope().launch { setup() } + } + + private suspend fun setup() { + JsAssets.load(this) + frostJsInjectors.get().load() } companion object { diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 0e2098b1b..eb46ba46c 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -18,7 +18,7 @@ package com.pitchedapps.frost import android.content.Intent import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity +import androidx.activity.ComponentActivity import androidx.lifecycle.lifecycleScope import com.google.common.flogger.FluentLogger import com.pitchedapps.frost.components.FrostDataStore @@ -46,7 +46,7 @@ import kotlinx.coroutines.withContext * and will launch another activity without history after doing initialization work. */ @AndroidEntryPoint -class StartActivity : AppCompatActivity() { +class StartActivity : ComponentActivity() { @Inject lateinit var frostDb: FrostDb @@ -65,14 +65,14 @@ class StartActivity : AppCompatActivity() { // TODO load real tabs store.dispatch(TabListAction.SetHomeTabs(data = listOf(FbItem.Feed, FbItem.Menu))) // Test something scrollable - store.dispatch( - TabAction( - tabId = HomeTabSessionState.homeTabId(0), - TabAction.ContentAction.UpdateUrlAction( - "https://github.com/AllanWang/Frost-for-Facebook" - ), - ), - ) + // store.dispatch( + // TabAction( + // tabId = HomeTabSessionState.homeTabId(0), + // TabAction.ContentAction.UpdateUrlAction( + // "https://github.com/AllanWang/Frost-for-Facebook" + // ), + // ), + // ) store.dispatch( TabAction( tabId = HomeTabSessionState.homeTabId(1), diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/web/FrostWebUiOptions.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/web/FrostWebUiOptions.kt new file mode 100644 index 000000000..2b71635fc --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/web/FrostWebUiOptions.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.web + +import com.pitchedapps.frost.components.FrostDataStore +import java.util.concurrent.atomic.AtomicBoolean +import javax.inject.Singleton + +/** Snapshot of UI options based on user preferences */ +interface FrostWebUiOptions { + + val theme: Theme + + enum class Theme { + Original, + Light, + Dark, + Amoled, + Glass // Custom + } +} + +/** + * Singleton to provide snapshots of [FrostWebUiOptions]. + * + * This is a mutable class, and does not provide change listeners. We will update activities + * manually when needed. + */ +@Singleton +class FrostWebUiSnapshot(private val dataStore: FrostDataStore) { + + @Volatile + var options: FrostWebUiOptions = defaultOptions() + private set + + private val stale = AtomicBoolean(true) + + private fun defaultOptions(): FrostWebUiOptions = + object : FrostWebUiOptions { + override val theme: FrostWebUiOptions.Theme = FrostWebUiOptions.Theme.Original + } + + /** Fetch new snapshot and update other singletons */ + suspend fun reload() { + if (!stale.getAndSet(false)) return + // todo load + } + + fun markAsStale() { + stale.set(true) + } +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/web/state/FrostMiddleware.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/web/state/FrostMiddleware.kt index 66840c7fb..73287f866 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/web/state/FrostMiddleware.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/web/state/FrostMiddleware.kt @@ -28,10 +28,26 @@ class FrostLoggerMiddleware : FrostWebMiddleware { next: (FrostWebAction) -> Unit, action: FrostWebAction ) { - logger.atInfo().log("FrostWebAction: %s - %s", action::class.simpleName, action) + if (logInfo(action)) { + logger.atInfo().log("FrostWebAction: %s", action) + } else { + logger.atFine().log("FrostWebAction: %s", action) + } next(action) } + private fun logInfo(action: FrostWebAction): Boolean { + when (action) { + is TabAction -> + when (action.action) { + is TabAction.ContentAction.UpdateProgressAction -> return false + else -> {} + } + else -> {} + } + return true + } + companion object { private val logger = FluentLogger.forEnclosingClass() } diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWeb.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWeb.kt index 0d7d0a40f..dc1232108 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWeb.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWeb.kt @@ -20,6 +20,7 @@ import android.webkit.WebViewClient import com.pitchedapps.frost.ext.WebTargetId import com.pitchedapps.frost.web.FrostWebHelper import com.pitchedapps.frost.web.state.FrostWebStore +import com.pitchedapps.frost.webview.injection.FrostJsInjectors import dagger.BindsInstance import dagger.Module import dagger.Provides @@ -62,8 +63,9 @@ internal object FrostWebModule { fun client( @FrostWeb tabId: WebTargetId, store: FrostWebStore, - webHelper: FrostWebHelper - ): WebViewClient = FrostWebViewClient(tabId, store, webHelper) + webHelper: FrostWebHelper, + frostJsInjectors: FrostJsInjectors, + ): WebViewClient = FrostWebViewClient(tabId, store, webHelper, frostJsInjectors) } /** diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWebComposer.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWebComposer.kt index a33375806..9ca3860df 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWebComposer.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWebComposer.kt @@ -20,6 +20,7 @@ import com.pitchedapps.frost.compose.webview.FrostWebCompose import com.pitchedapps.frost.ext.WebTargetId import com.pitchedapps.frost.web.FrostWebHelper import com.pitchedapps.frost.web.state.FrostWebStore +import com.pitchedapps.frost.webview.injection.FrostJsInjectors import javax.inject.Inject class FrostWebComposer @@ -27,10 +28,11 @@ class FrostWebComposer internal constructor( private val store: FrostWebStore, private val webHelper: FrostWebHelper, + private val frostJsInjectors: FrostJsInjectors, ) { fun create(tabId: WebTargetId): FrostWebCompose { - val client = FrostWebViewClient(tabId, store, webHelper) + val client = FrostWebViewClient(tabId, store, webHelper, frostJsInjectors) val chromeClient = FrostChromeClient(tabId, store) return FrostWebCompose(tabId, store, client, chromeClient) } diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWebViewClients.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWebViewClients.kt index a100319fb..a9bb3bc88 100644 --- a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWebViewClients.kt +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/FrostWebViewClients.kt @@ -32,6 +32,7 @@ import com.pitchedapps.frost.web.state.TabAction import com.pitchedapps.frost.web.state.TabAction.ContentAction.UpdateNavigationAction import com.pitchedapps.frost.web.state.TabAction.ContentAction.UpdateProgressAction import com.pitchedapps.frost.web.state.TabAction.ContentAction.UpdateTitleAction +import com.pitchedapps.frost.webview.injection.FrostJsInjectors import java.io.ByteArrayInputStream /** @@ -63,7 +64,8 @@ abstract class BaseWebViewClient : WebViewClient() { class FrostWebViewClient( private val tabId: WebTargetId, private val store: FrostWebStore, - override val webHelper: FrostWebHelper + override val webHelper: FrostWebHelper, + private val frostJsInjectors: FrostJsInjectors, ) : BaseWebViewClient() { private fun FrostWebStore.dispatch(action: TabAction.Action) { @@ -140,8 +142,10 @@ class FrostWebViewClient( // ) // } - // override fun onPageCommitVisible(view: WebView, url: String?) { - // super.onPageCommitVisible(view, url) + override fun onPageCommitVisible(view: WebView, url: String?) { + super.onPageCommitVisible(view, url) + frostJsInjectors.injectOnPageCommitVisible(view, url) + } // injectBackgroundColor() // when { // url.isFacebookUrl -> { diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/FrostJsInjectors.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/FrostJsInjectors.kt new file mode 100644 index 000000000..77cb6c59e --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/FrostJsInjectors.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.webview.injection + +import android.content.Context +import android.webkit.WebView +import com.google.common.flogger.FluentLogger +import com.pitchedapps.frost.webview.injection.assets.JsActions +import dagger.hilt.android.qualifiers.ApplicationContext +import java.io.BufferedReader +import java.io.FileNotFoundException +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@Singleton +class FrostJsInjectors +@Inject +internal constructor( + @ApplicationContext private val context: Context, +) { + + @Volatile private var theme: JsInjector = JsInjector.EMPTY + + fun injectOnPageCommitVisible(view: WebView, url: String?) { + logger.atInfo().log("inject page commit visible %b", theme != JsInjector.EMPTY) + theme.inject(view) + } + + fun getTheme(): JsInjector { + return try { + val content = + context.assets + .open("frostcore/css/facebook/themes/material_glass.css") + .bufferedReader() + .use(BufferedReader::readText) + JsBuilder().css(content).build() + } catch (e: FileNotFoundException) { + logger.atSevere().withCause(e).log("CssAssets file not found") + JsActions.EMPTY + } + } + + suspend fun load() { + withContext(Dispatchers.IO) { theme = getTheme() } + } + + companion object { + private val logger = FluentLogger.forEnclosingClass() + } +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/JsInjector.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/JsInjector.kt new file mode 100644 index 000000000..28211e26d --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/JsInjector.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2023 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.webview.injection + +import android.webkit.WebView +import org.apache.commons.text.StringEscapeUtils + +interface JsInjector { + + fun inject(webView: WebView) + + companion object { + + val EMPTY: JsInjector = EmptyJsInjector + + operator fun invoke(content: String): JsInjector = + object : JsInjector { + override fun inject(webView: WebView) { + webView.evaluateJavascript(content, null) + } + } + } +} + +private object EmptyJsInjector : JsInjector { + override fun inject(webView: WebView) { + // Noop + } +} + +data class OneShotJsInjector(val tag: String, val injector: JsInjector) : JsInjector { + override fun inject(webView: WebView) { + // TODO + } +} + +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 = tag // TODO TagObfuscator.obfuscateTag(tag) + 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) = + """ + if (!window.hasOwnProperty("$tag") { + console.log("Registering $tag"); + window.$tag = true; + $content + } + """ + .trimIndent() +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/CssActions.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/CssActions.kt new file mode 100644 index 000000000..448737e98 --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/CssActions.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.webview.injection.assets + +import android.webkit.WebView +import com.pitchedapps.frost.webview.injection.JsBuilder +import com.pitchedapps.frost.webview.injection.JsInjector + +/** Small misc inline css assets */ +enum class CssActions(private val content: String) : JsInjector { + FullSizeImage( + "div._4prr[style*=\"max-width\"][style*=\"max-height\"]{max-width:none !important;max-height:none !important}", + ); + + private val injector: JsInjector = + JsBuilder().css(content).single("css-small-assets-$name").build() + + override fun inject(webView: WebView) { + injector.inject(webView) + } +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/CssHider.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/CssHider.kt new file mode 100644 index 000000000..b191faea6 --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/CssHider.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2018 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.webview.injection.assets + +import android.webkit.WebView +import com.pitchedapps.frost.webview.injection.JsBuilder +import com.pitchedapps.frost.webview.injection.JsInjector + +/** + * Created by Allan Wang on 2017-05-31. + * + * List of elements to hide + */ +enum class CssHider(private vararg val items: String) : JsInjector { + CORE("[data-sigil=m_login_upsell]", "[role=progressbar]"), + HEADER( + "#header:not(.mFuturePageHeader):not(.titled)", + "#mJewelNav", + "[data-sigil=MTopBlueBarHeader]", + "#header-notices", + "[data-sigil*=m-promo-jewel-header]", + ), + ADS("article[data-xt*=sponsor]", "article[data-store*=sponsor]", "article[data-ft*=sponsor]"), + PEOPLE_YOU_MAY_KNOW("article._d2r"), + SUGGESTED_GROUPS("article[data-ft*=\"ei\":]"), + + // Is it really this simple? + SUGGESTED_POSTS("article[data-store*=recommendation]", "article[data-ft*=recommendation]"), + COMPOSER("#MComposer"), + MESSENGER("._s15", "[data-testid=info_panel]", "js_i"), + NON_RECENT("article:not([data-store*=actor_name])"), + STORIES( + "#MStoriesTray", + // Sub element with just the tray; title is not a part of this + "[data-testid=story_tray]", + ), + POST_ACTIONS("footer [data-sigil=\"ufi-inline-actions\"]"), + POST_REACTIONS("footer [data-sigil=\"reactions-bling-bar\"]"); + + private val injector: JsInjector = + JsBuilder() + .css("${items.joinToString(separator = ",")}{display:none !important}") + .single("css-hider-$name") + .build() + + override fun inject(webView: WebView) { + injector.inject(webView) + } +} diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/JsActions.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/JsActions.kt new file mode 100644 index 000000000..4bcde321c --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/JsActions.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.webview.injection.assets + +import android.webkit.WebView +import com.pitchedapps.frost.facebook.FB_URL_BASE +import com.pitchedapps.frost.webview.injection.JsInjector + +/** + * Created by Allan Wang on 2017-05-31. + * + * Collection of short js functions that are embedded directly + */ +enum class JsActions(body: String) : JsInjector { + /** + * Redirects to login activity if create account is found see + * [com.pitchedapps.frost.web.FrostJSI.loadLogin] + */ + LOGIN_CHECK("document.getElementById('signup-button')&&Frost.loadLogin();"), + BASE_HREF("""document.write("");"""), + FETCH_BODY( + """setTimeout(function(){var e=document.querySelector("main");e||(e=document.querySelector("body")),Frost.handleHtml(e.outerHTML)},1e2);""", + ), + RETURN_BODY("return(document.getElementsByTagName('html')[0].innerHTML);"), + CREATE_POST(clickBySelector("#MComposer [onclick]")), + // CREATE_MSG(clickBySelector("a[rel=dialog]")), + /** Used as a pseudoinjector for maybe functions */ + EMPTY(""); + + val function = "(function(){$body})();" + + private val injector: JsInjector = JsInjector(function) + + override fun inject(webView: WebView) { + injector.inject(webView) + } +} + +@Suppress("NOTHING_TO_INLINE") +private inline fun clickBySelector(selector: String): String = + """document.querySelector("$selector").click()""" diff --git a/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/JsAssets.kt b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/JsAssets.kt new file mode 100644 index 000000000..1515e4ab8 --- /dev/null +++ b/app-compose/src/main/kotlin/com/pitchedapps/frost/webview/injection/assets/JsAssets.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2018 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.webview.injection.assets + +import android.content.Context +import android.webkit.WebView +import androidx.annotation.VisibleForTesting +import com.google.common.flogger.FluentLogger +import com.pitchedapps.frost.webview.injection.JsBuilder +import com.pitchedapps.frost.webview.injection.JsInjector +import java.io.BufferedReader +import java.io.FileNotFoundException +import java.util.Locale +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Created by Allan Wang on 2017-05-31. Mapping of the available assets The enum name must match the + * js file name + */ +enum class JsAssets(private val singleLoad: Boolean = true) : JsInjector { + CLICK_A, + CONTEXT_A, + MEDIA, + HEADER_BADGES, + TEXTAREA_LISTENER, + NOTIF_MSG, + DOCUMENT_WATCHER, + HORIZONTAL_SCROLLING, + AUTO_RESIZE_TEXTAREA(singleLoad = false), + SCROLL_STOP, + ; + + @VisibleForTesting internal val file = "${name.lowercase(Locale.CANADA)}.js" + + private fun injectorBlocking(context: Context): JsInjector { + return try { + val content = + context.assets.open("frostcore/js/$file").bufferedReader().use(BufferedReader::readText) + JsBuilder().js(content).run { if (singleLoad) single(name) else this }.build() + } catch (e: FileNotFoundException) { + logger.atWarning().withCause(e).log("JsAssets file not found") + JsInjector.EMPTY + } + } + + private var injector: JsInjector = JsInjector.EMPTY + + override fun inject(webView: WebView) { + injector.inject(webView) + } + + private suspend fun load(context: Context) { + withContext(Dispatchers.IO) { injector = injectorBlocking(context) } + } + + companion object { + private val logger = FluentLogger.forEnclosingClass() + + suspend fun load(context: Context) = + withContext(Dispatchers.IO) { JsAssets.values().forEach { it.load(context) } } + } +}