diff --git a/app/src/main/assets/js/click_a.js b/app/src/main/assets/js/click_a.js index 9f4ddc384..24a08f56b 100644 --- a/app/src/main/assets/js/click_a.js +++ b/app/src/main/assets/js/click_a.js @@ -15,17 +15,16 @@ if (!window.hasOwnProperty('frost_click_a')) { if (element.tagName !== 'A') element = element.parentNode; //Notifications is two layers under if (element.tagName !== 'A') element = element.parentNode; - if (element.tagName === 'A' && element.getAttribute('href') !== '#') { - var url = element.getAttribute('href'); - if (url.includes('photoset_token')) return; - - + if (element.tagName === 'A') { if (!prevented) { + var url = element.getAttribute('href'); console.log('Click Intercept', url); - if (typeof Frost !== 'undefined') Frost.loadUrl(url); + // if frost is injected, check if loading the url through an overlay works + if (typeof Frost !== 'undefined' && Frost.loadUrl(url)) { + e.stopPropagation(); + e.preventDefault(); + } } - e.stopPropagation(); - e.preventDefault(); } } diff --git a/app/src/main/assets/js/click_a.min.js b/app/src/main/assets/js/click_a.min.js index c7870e2fb..2033fd31e 100644 --- a/app/src/main/assets/js/click_a.min.js +++ b/app/src/main/assets/js/click_a.min.js @@ -4,12 +4,10 @@ window.frost_click_a=!0 ;var prevented=!1,_frostAClick=function(e){ var t=e.target||e.srcElement ;if("A"!==t.tagName&&(t=t.parentNode),"A"!==t.tagName&&(t=t.parentNode), -"A"===t.tagName&&"#"!==t.getAttribute("href")){ +"A"===t.tagName&&!prevented){ var n=t.getAttribute("href") -;if(n.includes("photoset_token"))return -;prevented||(console.log("Click Intercept",n), -"undefined"!=typeof Frost&&Frost.loadUrl(n)), -e.stopPropagation(),e.preventDefault() +;console.log("Click Intercept",n),"undefined"!=typeof Frost&&Frost.loadUrl(n)&&(e.stopPropagation(), +e.preventDefault()) } },_frostPreventClick=function(){ console.log("Click prevented"),prevented=!0 diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index ad9340d7c..4094a3ab9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -54,6 +54,11 @@ fun Activity.cookies(): ArrayList { return intent?.extras?.getParcelableArrayList(EXTRA_COOKIES) ?: arrayListOf() } +/** + * Launches the given url in a new overlay (if it already isn't in an overlay) + * Note that most requests may need to first check if the url can be launched as an overlay + * See [requestWebOverlay] to verify the launch + */ fun Context.launchWebOverlay(url: String) { val argUrl = url.formattedFbUrl L.v("Launch received", url) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt index f24a7a511..2abc9b257 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -25,10 +25,14 @@ class FrostJSI(val webView: FrostWebViewCore) { val cookies: ArrayList get() = activity?.cookies() ?: arrayListOf() + /** + * Attempts to load the url in an overlay + * Returns {@code true} if successful, meaning the event is consumed, + * or {@code false} otherwise, meaning the event should be propagated + */ @JavascriptInterface - fun loadUrl(url: String) { - context.launchWebOverlay(url) - } + fun loadUrl(url: String?): Boolean + = if (url == null) false else context.requestWebOverlay(url) @JavascriptInterface fun reloadBaseUrl(animate: Boolean) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt index 1a907f7fe..2dfdda892 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt @@ -3,6 +3,8 @@ package com.pitchedapps.frost.web import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView +import ca.allanwang.kau.kotlin.LazyContext +import ca.allanwang.kau.kotlin.lazyContext import ca.allanwang.kau.utils.use import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs @@ -19,41 +21,37 @@ import java.io.ByteArrayInputStream private val blankResource: WebResourceResponse by lazy { WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray())) } //these hosts will redirect to a blank resource -private val blacklistHost: Set by lazy { - setOf( - "edge-chat.facebook.com" - ) -} +private val blacklistHost: Set = + setOf( + "edge-chat.facebook.com" + ) //these hosts will return null and skip logging -private val whitelistHost: Set by lazy { - setOf( - "static.xx.fbcdn.net", - "m.facebook.com", - "touch.facebook.com" - ) -} +private val whitelistHost: Set = + setOf( + "static.xx.fbcdn.net", + "m.facebook.com", + "touch.facebook.com" + ) //these hosts will skip ad inspection //this list does not have to include anything from the two above -private val adWhitelistHost: Set by lazy { - setOf( - "scontent-sea1-1.xx.fbcdn.net" - ) +private val adWhitelistHost: Set = + setOf( + "scontent-sea1-1.xx.fbcdn.net" + ) + +private val adblock: LazyContext> = lazyContext { + it.assets.open("adblock.txt").bufferedReader().use { it.readLines().toSet() } } -private var adblock: Set? = null - -fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { +fun WebView.shouldFrostInterceptRequest(request: WebResourceRequest): WebResourceResponse? { val httpUrl = HttpUrl.parse(request.url?.toString() ?: return null) ?: return null val host = httpUrl.host() val url = httpUrl.toString() if (blacklistHost.contains(host)) return blankResource if (whitelistHost.contains(host)) return null - if (!adWhitelistHost.contains(host)) { - if (adblock == null) adblock = view.context.assets.open("adblock.txt").bufferedReader().use { it.readLines().toSet() } - if (adblock?.any { url.contains(it) } ?: false) return blankResource - } + if (!adWhitelistHost.contains(host) && adblock(context).any { url.contains(it) }) return blankResource if (!shouldLoadImages && !Prefs.loadMediaOnMeteredNetwork && request.isMedia) return blankResource L.v("Intercept Request", "$host $url") return null diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt new file mode 100644 index 000000000..0a1878b38 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt @@ -0,0 +1,48 @@ +package com.pitchedapps.frost.web + +import android.content.Context +import com.pitchedapps.frost.facebook.formattedFbUrl +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.isFacebookUrl +import com.pitchedapps.frost.utils.launchWebOverlay + +/** + * Created by Allan Wang on 2017-08-15. + * + * Due to the nature of facebook href's, many links + * cannot be resolved on a new window and must instead + * by loaded in the current page + * This helper method will collect all known cases and launch the overlay accordingly + * Returns {@code true} (default) if overlay is launcher, {@code false} otherwise + */ +fun Context.requestWebOverlay(url: String): Boolean { + if (url == "#") return false + /* + * Non facebook urls can be loaded + */ + if (!url.formattedFbUrl.isFacebookUrl) { + launchWebOverlay(url) + L.d("Request web overlay is not a facebook url", url) + return true + } + /* + * Check blacklist + */ + if (overlayBlacklist.any { url.contains(it) }) return false + /* + * Facebook messages have the following cases for the tid query + * mid* or id* for newer threads, which can be launched in new windows + * or a hash for old threads, which must be loaded on old threads + */ + if (url.contains("/messages/read/?tid=")) { + if (!url.contains("?tid=id") && !url.contains("?tid=mid")) return false + } + L.v("Request web overlay passed", url) + launchWebOverlay(url) + return true +} + +/** + * The following components should never be launched in a new overlay + */ +val overlayBlacklist = setOf("messages/?pageNum", "photoset_token") \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt index 2ebf2c0c1..b1466ee88 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -11,7 +11,6 @@ import com.pitchedapps.frost.activities.LoginActivity import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.SelectorActivity import com.pitchedapps.frost.activities.WebOverlayActivity -import com.pitchedapps.frost.facebook.FACEBOOK_COM import com.pitchedapps.frost.facebook.FB_URL_BASE import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbItem @@ -34,7 +33,7 @@ import org.jetbrains.anko.withAlpha open class BaseWebViewClient : WebViewClient() { override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? - = shouldFrostInterceptRequest(view, request) + = view.shouldFrostInterceptRequest(request) } @@ -125,9 +124,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient */ private fun launchRequest(request: WebResourceRequest): Boolean { L.d("Launching Url", request.url?.toString() ?: "null") - if (webCore.context is WebOverlayActivity) return false - webCore.context.launchWebOverlay(request.url.toString()) - return true + return webCore.context !is WebOverlayActivity && webCore.context.requestWebOverlay(request.url.toString()) } private fun launchImage(url: String, text: String? = null): Boolean {