WebSurfer's Home

Filter by APML

Xperia 10 V のブラウザで日本語が太字にならない

by WebSurfer 30. September 2025 16:25

今さらながらの話ですが、自分が使っている Xperia 10 V の Chrome および Edge で自分のブログを見ると、日本語は css で font-weight: bold; を指定しても太字にならないということに気が付きました。そのあたりについて少し調べたので備忘録として書いておきます。

2025/12/22 追記: ブラウザがアップデートされたのか日本語も太字で表示されるようになってました。記事の下の方に画像を貼っておきます。

日本語に font-weight: bold が効かない

上の画像の本文の部分の HTML ソースは以下のとおりですが、日本語の文字には font-weight: bold; も font-weight: 600; も効いていないのが分かるでしょうか?

<p style="font-weight: bold;">
  Alice was beginning to get very tired of sitting by her sister 
  on the bank, and of having nothing to do
  アリスは、姉のそばの土手に座っているのに、何もすることがないのに、
  だんだん飽きてきました。
</p>

<div style="font-weight: 600;">
  I'm heavy 私は重いです<br />
  <span style="font-weight: lighter;">I'm lighter 私は軽い</span>
</div>

理由は Android 14 以前の OS のシステムフォントには日本語の太字がないからということで、Web デザイナなら知ってて当たり前のことらしいです。(汗)

もちろん Windows 10 PC のブラウザでは問題ありません。また、Android 15 ではフォントが改善され、日本語も太字で表示できるようになったそうです (参考: Android 15で日本語フォントが大幅改善)。家電量販店に置いてあった最新の Xperia 10, Pixcel 8 で試してみましたが、日本語も太字になることは確認できました。

自分の Xperia 10 V のオリジナルの OS は Android 13 でそれを Android 15 にアップデート済みですが、フォントのアップデートには対応してないようで、上の画像の通り日本語は font-weight: bold; で太字になりません。

「何を今さら、自分が書いたブログなんだから見たらわかっただろうに」と言われそうですね。でも、数日前までは普段自分のスマホで使っている Edge では font-weight: bold; で日本語も太字で表示されていたのです。なので、気が付きませんでした。

想像ですが、以前 Edge で日本語も太字で表示されていた時は文字を重ねて擬似的に太く見せるなどの手段が取られていた。Android 15 でフォントが改善されて日本語の太字も対応できるようになった。文字を重ねて擬似的に太く見せるなどの手段は必要なくなった。なので数日前に行われた Edge の自動アップデートで疑似的な手段は止めた。しかしながら、Xperia 10 V では太字フォントが無いのでダメ・・・ということではなかろうかと思っています。

違うかなぁ・・・ でも、数日前は font-weight: bold; で日本語も太字になっていたのは間違いないし、上記は当たらずとも遠からずだと思うのですが。

なお、自分の Xperia 10 V の Edge で見ても font-weight: bold; で日本語が太字になっているサイトがありましたが、たぶん、Web フォントで対応しているのだろうと思います。


2025/12/22 追記

いつの間にかブラウザがアップデートされたのか、CSS の適用のされ方が PC の Egde, Chrome とほぼ同じになり、font-weight の指定で日本語も太字で表示されるようになってました。

日本語にも font-weight: bold が有効になった

スマートフォンは Android 15; SO-52D Build/68.2.A.3.73、ブラウザは Edge 143.0.3650.88、Chrome 143.0.7499.146 です。上の画像は Edge のものですが Chrome も同じです。

Tags: , , , , , ,

その他

Task.Status が Canceled に変わるタイミング

by WebSurfer 21. August 2025 12:41

Microsoft のドキュメント「タスクの一覧を取り消す」のサンプルコードを試して気づいた話ですが、CancellationTokenSource.Cancel メソッドを実行した際、CancellationToken を受け取る非同期メソッドの Task.Status が Canceled に変わるタイミングが .NET Framework 4.8.1 と .NET 8.0 / 9.0 では異なるということがあったので、備忘録として書いておきます。

サンプルコードはこの記事の下の方に載せておきます。ターゲットフレームワークを .NET 8.0 または .NET 9.0 とした場合と、.NET Framework 4.8.1 とした場合のサンプルコードの動きの違いは、ダウンロードの途中で Enter キーを押してキャンセルをかけた時、後者では Main メソッドの中の try - catch ブロックが実行されないことです。

その原因は、タスク cancelTask の中の s_cts.Cancel(); が実行された時、CancellationToken を受け取る非同期メソッドの Task.Status が Canceled に変わるタイミングが異なるからです。

.NET Framework 4.8.1 の場合、Cancel メソッドの実行で即 Task.Status が WaitingForActivation から Canceled に変わります。

一方、.NET 8.0 / 9.0 の場合、Cancel メソッドの実行では Task.Status は WaitingForActivation のまま変わりません。Task を await して初めて Status が Canceled に変わります。

以下にサンプルコードを検証した際の詳細を書いておきます。

.NET Framework 4.8.1

下の画像は、ターゲットフレームワークを .NET Framework 4.8.1 としたプロジェクトを Visual Studio 2022 でデバッグ実行した時のものです。

.NET Framework 4.8.1 の場合

ダウンロード途中で(すなわち、SumPageSizesAsync メソッド内の foreach ループが回っているとき)Enter キーを押した結果、タスク cancelTask の中の s_cts.Cancel(); が実行され、ProcessUrlAsync メソッド内の非同期メソッドの実行がキャンセルされ、Task.WhenAny メソッドでその引数に設定されたタスク cancelTask と sumPageSizesTask の内 sumPageSizesTask が先に完了と判断され、await による待機を抜け、次の行の if 文に制御が移ったところです。

その結果、if 文の条件 finishedTask == cancelTask は false となるので try - catch ブロックは実行されません。その際に気を付けなければならないのは、await sumPageSizesTask; は実行されないので OperationCanceledException はスローされないということです。

なので、下に載せたサンプルコードのように、OperationCanceledException を catch して何らかの処置を行いたい場合、そのためのコードを追加する必要があります。

ただし、ダウンロードが完了してから(すなわち SumPageSizesAsync メソッド内の foreach ループを抜けて CancellationToken を引数に取る非同期メソッドがすべて完了してから)タスク cancelTask の中の s_cts.Cancel(); が実行された場合は様子が違ってきます。タスク cancelTask の方が先に完了したと判断され、try - catch ブロックは実行されます。(実際にそれを試すには���foreach ループの後に 1 行 await Task.Delay(3000); を追加して、そこで止まっているときに Enter キーを押してみてください)

.NET 8.0 / 9.0

ターゲットフレームワークを .NET 8.0 または 9.0 としたプロジェクトで上と同様にデバッグを行うと以下の画像のようになります。

.NET 8.0 の場合

ダウンロード途中で Enter キーを押すとタスク cancelTask 内の s_cts.Cancel(); が実行されるのですが、その時点では ProcessUrlAsync メソッド内の非同期メソッドの実行がキャンセルされないのか、タスク sumPageSizesTask の Status は WaitingForActivation になっています。一方、タスク cancelTask の Status は RanToCompletion となり、Task.WhenAny メソッドでその引数に設定されたタスク cancelTask が先に完了したと判断され、await による待機を抜け、次の行の if 文に制御が移っています。

この場合、if 分の条件 finishedTask == cancelTask は true となるので try - catch ブロックが実行されます。try 句の中の await sumPageSizesTask; により OperationCanceledException がスローされ、catch 句の中のコードが実行されます。

タスク sumPageSizesTask の Status が WaitingForActivation から Canceled に変わるのは try 句の中の await sumPageSizesTask; で await による待機が終わった時点になります。

以下の画像を見てください。上の画像の if 文でブレークポイントで止まったところからステップ実行して、await sumPageSizesTask; でスローされた OperationCanceledException を catch したところです。sumPageSizesTask の Status が Canceled に変わっています。

.NET 8.0 の場合

try 句の中の await sumPageSizesTask; の行で例外がスローされるので、その下の Console.WriteLine("Download task completed before cancel request was processed."); はスキップされます。このメッセージは「Enter キーが押されたがキャンセルが間に合わずダウンロードが完了してしまった」という意味なのですが、手動で Enter キーを押してこのメッセージを表示するのは無理があるので注意してください。

このメッセージが表示されることを試すには、SumPageSizesAsync メソッド内の foreach ループの後に 1 行 await Task.Delay(3000); を追加して、そこで止まっているときに Enter キーを押してみてください。その時点では CancellationToken を渡された非同期メソッドの実行は終わっているので、タスク cancelTask の中の s_cts.Cancel(); の実行では OperationCanceledException 例外はスローされません。結果、await sumPageSizesTask; の下の行の Console.WriteLine("Download task completed before cancel request was processed."); が実行されます。


.NET Framework 4.8.1 の場合の s_cts.Cancel(); の実行で CancellationToken を渡された非同期メソッドの実行が即キャンセルされるという動きの方が自分としては納得できるのですが、何らかの理由で .NET 8.0(もっと前からかも)で変更したのでしょうか?

そのあたりの説明がある Microsoft のドキュメントは見つけられませんでしたが、とにかく違いがあるということは覚えておいた方が良さそうと思って、備忘録として残しておくことにした次第です。


以下に、上に述べた検証に用いた Microsoft ドキュメントのサンプルコードを載せておきます。

using System.Diagnostics;

class Program
{
    static readonly CancellationTokenSource s_cts = new CancellationTokenSource();

    static readonly HttpClient s_client = new HttpClient
    {
        MaxResponseContentBufferSize = 1_000_000
    };

    static readonly IEnumerable<string> s_urlList = new string[]
    {
            "https://learn.microsoft.com",
            "https://learn.microsoft.com/aspnet/core",
            "https://learn.microsoft.com/azure",
            "https://learn.microsoft.com/azure/devops",
            "https://learn.microsoft.com/dotnet",
            "https://learn.microsoft.com/dynamics365",
            "https://learn.microsoft.com/education",
            "https://learn.microsoft.com/enterprise-mobility-security",
            "https://learn.microsoft.com/gaming",
            "https://learn.microsoft.com/graph",
            "https://learn.microsoft.com/microsoft-365",
            "https://learn.microsoft.com/office",
            "https://learn.microsoft.com/powershell",
            "https://learn.microsoft.com/sql",
            "https://learn.microsoft.com/surface",
            "https://learn.microsoft.com/system-center",
            "https://learn.microsoft.com/visualstudio",
            "https://learn.microsoft.com/windows",
            // "https://learn.microsoft.com/maui" 404 エラーになるのでコメントアウト
    };

    static async Task Main()
    {
        Console.WriteLine("Application started.");
        Console.WriteLine("Press the ENTER key to cancel...\n");

        Task cancelTask = Task.Run(() =>
        {
            while (Console.ReadKey().Key != ConsoleKey.Enter)
            {
                Console.WriteLine("Press the ENTER key to cancel...");
            }

            Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
            s_cts.Cancel();
        });

        Task sumPageSizesTask = SumPageSizesAsync();

        Task finishedTask = await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });
        if (finishedTask == cancelTask)
        {
            // wait for the cancellation to take place:
            try
            {
                await sumPageSizesTask;
                Console.WriteLine("Download task completed before cancel request was processed.");
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("Download task has been cancelled.");
            }
        }

        Console.WriteLine("Application ending.");
    }

    static async Task SumPageSizesAsync()
    {
        var stopwatch = Stopwatch.StartNew();

        int total = 0;
        foreach (string url in s_urlList)
        {
            int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
            total += contentLength;
        }

        stopwatch.Stop();

        Console.WriteLine($"\nTotal bytes returned:  {total:#,#}");
        Console.WriteLine($"Elapsed time:          {stopwatch.Elapsed}\n");
    }

    static async Task<int> ProcessUrlAsync(string url, 
                                                 HttpClient client, 
                                                 CancellationToken token)
    {
        HttpResponseMessage response = await client.GetAsync(url, token);

        // .NET Framework 4.8.1 の ReadAsByteArrayAsync には
        // CancellationToken を引数に取るオーバーロードはない
        // ので下の引数 token は削除すること
        byte[] content = await response.Content.ReadAsByteArrayAsync(token);

        Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

        return content.Length;
    }
}

Tags: , , , ,

.NET Framework

Minimal API で JWT からクレーム情報を取得

by WebSurfer 12. July 2025 13:07

ASP.NET Core Minimal API アプリで、クライアントから送信されてきた JWT からユーザー名やトークンの有効期限などの Payload 情報(ASP.NET ではそれらがクレーム情報になる)を取得する方法を書きます。

JWT (デコード結果)

先の記事「Minimal API で JWT を使った認証」に書いた JWT による認証を実装した ASP.NET Core Minimal API アプリでは、普通に Controller を使用した Web API と同様に、上の画像の JWT の Payload 部分は ASP.NET Core で言うクレームのコレクションとして取り扱われます。

先の記事に従って JWT の発行機能を実装すれば、上の画像通り JWT の Payload に exp (Expiration Time), iss (Issuer), aud (Audience) はデフォルトで含まれます。

任意のクレーム情報を追加することも可能です。例えば Admin ロールとユーザーの id を追加する場合は、先の記事に書いた JWT を生成するヘルパメソッド BuildToken で JwtSecurityToken コンストラクタのパラメータ claims に追加する情報を設定します。具体例は以下の通りです。

// JWT を生成するヘルパメソッド
private static string BuildToken(IConfiguration config, string id)
{
    var key = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(config["Jwt:Key"]!));

    var creds = new SigningCredentials(
        key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        issuer: config["Jwt:Issuer"],
        audience: config["Jwt:Issuer"],
        
        // Admin ロールと id を Claims に追加
        claims: [
            new Claim(ClaimTypes.Role, "Admin"),
            new Claim("UserId", id)
        ],
        
        notBefore: null,
        expires: DateTime.Now.AddMinutes(30),
        signingCredentials: creds);

    return new JwtSecurityTokenHandler().WriteToken(token);
}

上の BuildToken メソッドで生成した JWT をデコードしたものがこの記事の一番上の画像で、Payload に Admin ロールと UserId が追加されています。

エンドポイントにおいて JWT からクレーム情報を取得するのは先の記事「JWT からクレーム情報を取得」に書いた方法と同様にして可能です。

ただし、先の記事のアプリでは ControllerBase.User プロパティから ClaimsPrincipal オブジェクトを取得していましたが、Minimal API は Controller を使わないので、そこのところのみ異なります。

Minimal API では、Microsoft のドキュメント「Minimal API クイック リファレンス」の「特殊な型」のセクションに書いてありますように、エンドポイントに設定したデリゲートの引数に ClaimsPrincipal を含めることで取得します。具体例は以下の通りです。

// 認証が必要なエンドポイント
app.MapGet("/todoitems/auth", [Authorize(Roles = "Admin")] 
                    async (TodoDb db, ClaimsPrincipal user) =>
{
    // JWT の Payload の情報は以下のようにして取得できる
    string? role = user.FindFirst(ClaimTypes.Role)?.Value;
    string? userId = user.FindFirst("UserId")?.Value;
    string? exp = user.FindFirst("exp")?.Value;  // UNIX 時間
    if (exp != null)
    {
        var ticks = long.Parse(exp) * 1000L * 1000L * 10L +
                    DateTime.UnixEpoch.Ticks;
        var expDateTime = new DateTime(ticks, DateTimeKind.Utc);
        var dateTimeUtcNow = DateTime.UtcNow;
        bool isBeforeExp = expDateTime > dateTimeUtcNow;
    }

    return Results.Ok(await db.Todos.ToListAsync());
});

クライアントから JWT が送信されてくると、その Payload の情報から ClaimsPrincipal オブジェクトが生成され、上のコードのデリゲートの引数 ClaimsPrincipal user に渡されます。それから FindFirst(String) メソッドを使ってJWT の Payload の情報を取得できます。

下の画像は Visual Studio 2022 のデバッガを使って上のコードのローカル変数の値を表示したものです。JWT はこの記事の一番上の画像のものです。JWT の Payload の role, userId, exp の情報が正しく取得できていることが分かりますでしょうか?

クレーム情報の取得

Tags: , , , , ,

Web API

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  January 2026  >>
MoTuWeThFrSaSu
2930311234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar