Skip to content

Commit

Permalink
perf/refactor: move reviewer's asset loading from server to WebViewAs…
Browse files Browse the repository at this point in the history
…setLoader
  • Loading branch information
BrayanDSO committed Dec 23, 2023
1 parent fc53bfe commit df99071
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 93 deletions.
14 changes: 7 additions & 7 deletions AnkiDroid/src/main/assets/card_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
<head>
<title>AnkiDroid Flashcard</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="/assets/flashcard.css">
<link rel="stylesheet" type="text/css" href="/assets/mathjax/mathjax.css">
<link rel="stylesheet" type="text/css" href="file:///android_asset/flashcard.css">
<link rel="stylesheet" type="text/css" href="file:///android_asset/mathjax/mathjax.css">
<style>
::style::
</style>
<link rel="stylesheet" type="text/css" href="/assets/reviewer_extras.css">
<link rel="stylesheet" type="text/css" href="/web/reviewer_extras.css">
::script::
<script src="/assets/jquery.min.js"> </script>
<script src="/assets/scripts/card.js" type="text/javascript"> </script>
<script src="/assets/reviewer_extras_bundle.js"> </script>
<script src="/assets/scripts/js-api.js" type="text/javascript"> </script>
<script src="file:///android_asset/jquery.min.js"> </script>
<script src="file:///android_asset/scripts/card.js" type="text/javascript"> </script>
<script src="file:///android_asset/scripts/js-api.js" type="text/javascript"> </script>
<script src="/web/reviewer_extras_bundle.js"> </script>
</head>
<body class="::class::">
<div id="content">
Expand Down
71 changes: 37 additions & 34 deletions AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import com.ichi2.anki.dialogs.tags.TagsDialog
import com.ichi2.anki.dialogs.tags.TagsDialogFactory
import com.ichi2.anki.dialogs.tags.TagsDialogListener
import com.ichi2.anki.model.CardStateFilter
import com.ichi2.anki.pages.AnkiServer.Companion.LOCALHOST
import com.ichi2.anki.pages.CongratsPage
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.receiver.SdCardReceiver
Expand Down Expand Up @@ -129,10 +130,7 @@ abstract class AbstractFlashcardViewer :
private var mTtsInitialized = false
private var mReplayOnTtsInit = false
private var mAnkiDroidJsAPI: AnkiDroidJsAPI? = null
var server: ReviewerServer? = null

/** Can be used to wait until async calls in onCreate() have finished. */
var asyncCreateJob: Job? = null
lateinit var server: ReviewerServer

/**
* Broadcast that informs us when the sd card is about to be unmounted
Expand Down Expand Up @@ -236,7 +234,6 @@ abstract class AbstractFlashcardViewer :
private set
private var mBaseUrl: String? = null
private var mViewerUrl: String? = null
private var mAssetLoader: WebViewAssetLoader? = null
private val mFadeDuration = 300

@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
Expand Down Expand Up @@ -545,10 +542,7 @@ abstract class AbstractFlashcardViewer :

setContentView(getContentViewAttr(fullscreenMode))

asyncCreateJob = launchCatchingTask {
val mediaDir = withCol { media.dir }
server = ReviewerServer(this@AbstractFlashcardViewer, mediaDir).apply { start() }
}
server = ReviewerServer(this@AbstractFlashcardViewer).apply { start() }

// Make ACTION_PROCESS_TEXT for in-app searching possible on > Android 4.0
delegate.isHandleNativeActionModesEnabled = true
Expand Down Expand Up @@ -583,23 +577,7 @@ abstract class AbstractFlashcardViewer :
mSoundPlayer = Sound(baseUrl)
mViewerUrl = baseUrl + "__viewer__.html"
}
mAssetLoader = WebViewAssetLoader.Builder()
.addPathHandler("/") { path: String ->
try {
val file = File(mediaDir, path)
val inputStream = FileInputStream(file)
val mimeType = guessMimeType(path)
val headers = HashMap<String, String>()
headers["Access-Control-Allow-Origin"] = "*"
val response = WebResourceResponse(mimeType, null, inputStream)
response.responseHeaders = headers
return@addPathHandler response
} catch (e: Exception) {
Timber.w(e, "Error trying to open path in asset loader")
}
null
}
.build()

registerExternalStorageListener()
restoreCollectionPreferences(col)
initLayout()
Expand Down Expand Up @@ -674,7 +652,9 @@ abstract class AbstractFlashcardViewer :

override fun onDestroy() {
super.onDestroy()
server?.closeAllConnections()
if (this::server.isInitialized) {
server.closeAllConnections()
}
mTTS.releaseTts(this)
if (mUnmountReceiver != null) {
unregisterReceiver(mUnmountReceiver)
Expand Down Expand Up @@ -1078,8 +1058,28 @@ abstract class AbstractFlashcardViewer :
mTouchLayer!!.layoutParams = touchLayerContainerParams
}

@SuppressLint("SetJavaScriptEnabled") // they request we review carefully because of XSS security, we have
protected open fun createWebView(): WebView {
val mediaDir = CollectionHelper.getMediaDirectory(this).path
val domain = "$LOCALHOST:${server.listeningPort}"
val assetLoader = WebViewAssetLoader.Builder()
.setHttpAllowed(true)
.setDomain(domain)
.addPathHandler("/web/") { path: String ->
val inputStream = this.javaClass.classLoader!!.getResourceAsStream("web/$path")
WebResourceResponse(guessMimeType(path), null, inputStream)
}
.addPathHandler("/") { path: String ->
try {
val file = File(mediaDir, path)
val inputStream = FileInputStream(file)
WebResourceResponse(guessMimeType(path), null, inputStream)
} catch (e: Exception) {
Timber.w(e, "Error trying to open path in asset loader: %s", path)
null
}
}
.build()

val webView: WebView = MyWebView(this).apply {
scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY
with(settings) {
Expand All @@ -1095,7 +1095,7 @@ abstract class AbstractFlashcardViewer :
isFocusableInTouchMode = typeAnswer!!.autoFocus
isScrollbarFadingEnabled = true
setBackgroundColor(Color.argb(1, 0, 0, 0))
webViewClient = CardViewerWebClient(mAssetLoader, this@AbstractFlashcardViewer)
webViewClient = CardViewerWebClient(assetLoader, this@AbstractFlashcardViewer)
}
Timber.d(
"Focusable = %s, Focusable in touch mode = %s",
Expand Down Expand Up @@ -1640,8 +1640,7 @@ abstract class AbstractFlashcardViewer :
Timber.w("fillFlashCard() called with no card content")
return
}
val cardContent = cardContent!!
processCardAction { cardWebView: WebView? -> loadContentIntoCard(cardWebView, cardContent) }
processCardAction { cardWebView: WebView? -> loadContentIntoCard(cardWebView, cardContent!!) }
mGestureDetectorImpl.onFillFlashcard()
if (!displayAnswer) {
updateForNewCard()
Expand All @@ -1650,12 +1649,16 @@ abstract class AbstractFlashcardViewer :

private fun loadContentIntoCard(card: WebView?, content: String) {
launchCatchingTask {
asyncCreateJob?.join()
server?.reviewerHtml = content
if (card != null) {
card.settings.mediaPlaybackRequiresUserGesture = !mCardSoundConfig!!.autoplay
Timber.v("*** set server %s content to %s", server, content)
card.loadUrl(server?.baseUrl() + "reviewer.html")
card.loadDataWithBaseURL(
server.baseUrl(),
content,
"text/html",
null,
null
)
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,10 @@ open class CollectionHelper {
return getCurrentAnkiDroidDirectoryOptionalContext(context.sharedPrefs()) { context }
}

fun getMediaDirectory(context: Context): File {
return File(getCurrentAnkiDroidDirectory(context), "collection.media")
}

/**
* An accessor which makes [Context] optional in the case that [PREF_COLLECTION_PATH] is set
*
Expand Down
54 changes: 2 additions & 52 deletions AnkiDroid/src/main/java/com/ichi2/anki/ReviewerServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,9 @@ package com.ichi2.anki

import anki.frontend.SetSchedulingStatesRequest
import com.ichi2.anki.pages.AnkiServer
import com.ichi2.anki.pages.RangeHeader
import com.ichi2.anki.pages.toInputStream
import com.ichi2.utils.AssetHelper
import timber.log.Timber
import java.io.File
import java.io.FileInputStream

class ReviewerServer(activity: AbstractFlashcardViewer, private val mediaDir: String) : AnkiServer(activity) {
var reviewerHtml: String = ""
class ReviewerServer(activity: AbstractFlashcardViewer) : AnkiServer(activity) {
private val jsApi = activity.javaScriptFunction()

override fun start() {
Expand All @@ -37,51 +31,7 @@ class ReviewerServer(activity: AbstractFlashcardViewer, private val mediaDir: St
override fun serve(session: IHTTPSession): Response {
val uri = session.uri
Timber.d("${session.method} $uri")
if (session.method == Method.GET) {
if (uri == "/reviewer.html") {
return newFixedLengthResponse(reviewerHtml)
}
if (uri.startsWith("/assets/")) {
val mime = getMimeFromUri(uri)
val stream = when (uri) {
"/assets/reviewer_extras_bundle.js" ->
this.javaClass.classLoader!!.getResourceAsStream("web/reviewer_extras_bundle.js")
"/assets/reviewer_extras.css" ->
this.javaClass.classLoader!!.getResourceAsStream("web/reviewer_extras.css")
else ->
this.javaClass.classLoader!!.getResourceAsStream(uri.substring(1))
}
if (stream != null) {
Timber.v("OK: $uri")
return newChunkedResponse(Response.Status.OK, mime, stream)
}
}

// fall back to looking in media folder
val file = File(mediaDir, uri.substring(1))
if (file.exists()) {
val rangeHeader = RangeHeader.from(session, defaultEnd = file.length() - 1)
val mimeType = AssetHelper.guessMimeType(uri)
return if (rangeHeader != null) {
val (start, end) = rangeHeader
newFixedLengthResponse(
Response.Status.PARTIAL_CONTENT,
mimeType,
file.toInputStream(rangeHeader),
rangeHeader.contentLength
).apply {
addHeader("Content-Range", "bytes $start-$end/${file.length()}")
addHeader("Accept-Ranges", "bytes")
}
} else {
val inputStream = FileInputStream(file)
Timber.v("OK: $uri")
newChunkedResponse(Response.Status.OK, mimeType, inputStream)
}
// probably don't need this anymore
// resp.addHeader("Access-Control-Allow-Origin", "*")
}
} else if (session.method == Method.POST) {
if (session.method == Method.POST) {
val inputBytes = getSessionBytes(session)
if (uri.startsWith(ANKI_PREFIX)) {
return buildResponse {
Expand Down

0 comments on commit df99071

Please sign in to comment.