こんばんは
タイトル通りなのですが、AndroidのtargetSdkVersionを29(Android10において後方互換モードでない設定)を設定すると、#文字以降が表示されなくなる問題があります。
再現するコード
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
WebView.setWebContentsDebuggingEnabled(true)
val html = """
<html>
<body>
<style>
#red { color:red; }
</style>
<div id="red">ああああ</div>
</body>
</html>
""".trimIndent()
webview.loadData(html, null, null)
}
}
わざわざ掲載するほどではありませんが、レイアウトxmlです
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:id="@+id/webview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
実行結果
本来であればcssのstyleにてidセレクタのredに指定されているので、
赤い「ああああ」が表示されるはずなのですが、表示されません。
Inspectionで中身を見た結果
空のstyleタグのみが残っています。。。 Chromeの検査ツールは不完全なHTMLがあると、勝手に終了タグを補完するので、 \#redの#が解釈できずにそれ以降が存在しないものとして扱われるのだろうと推測しています。なぜなのか
https://issuetracker.google.com/issues/141625552
Googleのissuetrackerでの発言にヒントがありました。
"For all other values of encoding (including null) it is assumed that the data uses ASCII encoding for octets inside the range of safe URL characters and use the standard %xx hex encoding of URLs for octets outside that range. See RFC 3986 for more information. Applications targeting Build.VERSION_CODES.Q or later must either use base64 or encode any # characters in the content as %23, otherwise they will be treated as the end of the content and the remaining text used as a document fragment identifier."
話題のDeepL翻訳
"エンコーディングの他のすべての値(nullを含む)については、データは安全なURL文字の範囲内のオクテットにASCIIエンコーディングを使用し、その範囲外のオクテットにはURLの標準的な%xxの16進数エンコーディングを使用すると仮定されています。詳細はRFC 3986を参照してください。Build.VERSION_CODES.Q 以降をターゲットにしたアプリケーションは、base64 を使用するか、コンテンツ内の # 文字を %23 としてエンコードしなければなりません。
要するに「安全なURL文字の範囲内のオクテットにASCIIエンコーディングを使用し、」の安全なURL文字の範囲に#が引っかかるということでしょうか。
対応方法
発言で述べられている通り、base64化するか、#を%23に置き換えましょう。
Base64化の例
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
WebView.setWebContentsDebuggingEnabled(true)
val html = """
<html>
<body>
<style>
#red { color:red; }
</style>
<div id="red">ああああ</div>
</body>
</html>
""".trimIndent()
val base64version: String = Base64.encodeToString(html.toByteArray(), Base64.DEFAULT)
webview.loadData(base64version, null, "base64")
}
}
※importはandroid.util.Base64のものを使用しています。
#を直接%23に置き換える例
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
WebView.setWebContentsDebuggingEnabled(true)
val html = """
<html>
<body>
<style>
#red { color:red; }
</style>
<div id="red">ああああ</div>
</body>
</html>
""".trimIndent()
val replace = html.replace("#", "%23")
webview.loadData(replace, null, null)
}
}