From b93dce4b913c9e0a28b6ea950b4f87e5ec6031b1 Mon Sep 17 00:00:00 2001 From: Brayan Oliveira <69634269+brayandso@users.noreply.github.com> Date: Fri, 22 Dec 2023 18:48:44 -0300 Subject: [PATCH] perf/refactor: move reviewer's asset loading from server to WebViewAssetLoader --- AnkiDroid/src/main/assets/card_template.html | 14 ++-- .../com/ichi2/anki/AbstractFlashcardViewer.kt | 69 ++++++++++--------- .../java/com/ichi2/anki/CollectionHelper.kt | 4 ++ .../java/com/ichi2/anki/ReviewerServer.kt | 54 +-------------- 4 files changed, 50 insertions(+), 91 deletions(-) diff --git a/AnkiDroid/src/main/assets/card_template.html b/AnkiDroid/src/main/assets/card_template.html index ea9fe4613cae..d413a032a6f1 100644 --- a/AnkiDroid/src/main/assets/card_template.html +++ b/AnkiDroid/src/main/assets/card_template.html @@ -3,17 +3,17 @@ AnkiDroid Flashcard - - + + - + ::script:: - - - - + + + +
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt index 1f086c6138b2..0d11d5e059d2 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt @@ -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 @@ -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 @@ -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) @@ -545,8 +542,7 @@ abstract class AbstractFlashcardViewer : setContentView(getContentViewAttr(fullscreenMode)) - val mediaDir = CollectionHelper.getMediaDirectory(this).path - 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 @@ -581,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() - 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() @@ -672,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) @@ -1076,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) { @@ -1093,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", @@ -1638,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() @@ -1648,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 + ) } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.kt index 1e1228c23f3f..97f3e102e862 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.kt @@ -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 * diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ReviewerServer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ReviewerServer.kt index d23ef852c7e7..99796dc937cf 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ReviewerServer.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ReviewerServer.kt @@ -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() { @@ -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 {