ブラウザのバージョンが古いとCSSの入れ子(CSS Nesting)が効かない

CSS Nesting Module として定められた仕様が、主要なブラウザに実装されだしたのが2023年4月ごろ。

https://www.w3.org/TR/2023/WD-css-nesting-1-20230214/

https://caniuse.com/css-nesting

2023年12月頃までは、型(タグ、要素)セレクタの入れ子をするためには先頭に&記号が必要だった。
(上図で 四角囲みの3 が付与されているバージョンが相当する)

parent {
  /* 親のスタイル */

  & child {
    /* 親の子のスタイル */
  }

}

2023年12月頃に仕様が更新され、型セレクタの先頭に記号が不要となった。

parent {
  /* 親のスタイル */

  child {
    /* 親の子のスタイル */
  }

}

このため、2023年12月以降リリースのバージョンのブラウザで CSS Nesting を使ったスタイルを開発していると、 それ以前のブラウザで予期せぬ表示がされてしまう可能性に気づくことが難しい。

比較的最近に導入された機能であるため、なるべく CSS Nesting は使わないように開発したほうが良さそう。1


  1. 今どき、ブラウザを最新版にアップデートするのはごく当たり前だが、エンタープライズではそういうわけにいかなかったりしますし・・・

Chrome で png 画像をクリップボードにコピーすると、格納された画像データのアルファチャネルが欠落するケースがある

タイトルの通り。

Chrome1 上でPNG画像をクリップボードにコピーし、それを navigator.clipboard.read() で読み取って得られる画像データは、元画像のアルファチャネルがすべて255(不透過)のときに アルファチャネルがないPNGデータ として扱われてしまう。

検証

準備

次の2枚の画像を用意する。

画像名 画像 説明
sample.png 全ピクセルのアルファチャネルが255 (不透過)
sample_2.png 文字部分のみアルファチャネルが0 (透過)

検証用のHTMLは次の通り。 クリップボードにPNG画像が格納されていることを前提として、「read」ボタンを押すと以下の2つのデータをhex形式でコンソールログに出力するようにしている。 - PNGシグネチャ - PNG の IHDRヘッダー

詳細は PNG の仕様を参照すること。

www.libpng.org

<!DOCTYPE html>
<html lang="ja">
<head>
    <style>
    </style>
</head>
<body>
    <img src="./sample.png"/>
    <img src="./sample_2.png"/>
    <div>
        <input type="button" id="read" value="read"/>
    </div>
    <script>
        function createSliceTracker(array) {
            let currentIndex = 0; // 現在位置のインデックス

            return {
                slice: (count) => {
                const slice = array.slice(currentIndex, currentIndex + count); // 指定範囲をスライス
                currentIndex += count; // インデックスを更新
                return slice;
                },
                reset: () => {
                currentIndex = 0; // インデックスをリセット
                },
                hasMore: () => {
                return currentIndex < array.length; // 取得可能な要素が残っているか
                }
            };
        }
        function uint8ArrayToHexArray(uint8Array) {
            return Array.from(uint8Array) 
                .map(byte => byte.toString(16).padStart(2, '0')) 
        }
        document.getElementById("read").addEventListener("click", async () => {
            const cb = (await navigator.clipboard.read())[0];
            const imageblob = await cb.getType("image/png");
            const bytedata = new Uint8Array(await imageblob.arrayBuffer());
            const hex = uint8ArrayToHexArray(bytedata);
            const t = createSliceTracker(hex);
            console.log('PNGシグネチャ\t', t.slice(8).join(' '))
            console.log('IHDRチャンク長さ\t', t.slice(4).join(' '))
            console.log('IHDRチャンク名\t', t.slice(4).join(' '), '(ASCIIで"IHDR")')
            console.log('å¹…\t', t.slice(4).join(' '))
            console.log('高さ\t',t.slice(4).join(' '))
            console.log('ビット深度\t',t.slice(1).join(' '))
            console.log('色の種類\t',t.slice(1).join(' '))
            console.log('圧縮方法\t',t.slice(1).join(' '))
            console.log('フィルタ方式\t',t.slice(1).join(' '))
            console.log('インターレース方式\t',t.slice(1).join(' '))
            console.log('CRC\t',t.slice(4).join(' '))
        })
    </script>
</body>
</html>

ブラウザ上では、このように2枚の画像と、クリップボードを読み取る「read」ボタンが表示される。

Chrome の場合

ページ上のsample.png の方をコピーして「read」ボタンを押すと2、コンソールには次の表示が出る。

PNGシグネチャ    89 50 4e 47 0d 0a 1a 0a
IHDRチャンク長さ   00 00 00 0d
IHDRチャンク名  49 48 44 52 (ASCIIで"IHDR")
å¹…  00 00 00 96
高さ   00 00 00 96
ビット深度  08
色の種類     02
圧縮方法     00
フィルタ方式   00
インターレース方式  00
CRC  b3 63 e6 b5

色の種類が 02 であり、ピクセルにアルファチャネルがない R,G,B 形式の画像であることを表している。 しかし、クリップボードを経由せずに直接 sample.png の画像ファイル自体を解析すると、実際には色の種類は06 でありアルファチャネルが含まれていることがわかった。

参考: ブラウザと同様にヘッダを読み込むスクリプトは次の通り。 node.js v23.6.0 で動作確認済み。

const fs = require('fs');

function createSliceTracker(array) {
    let currentIndex = 0; // 現在位置のインデックス

    return {
        slice: (count) => {
        const slice = array.slice(currentIndex, currentIndex + count); // 指定範囲をスライス
        currentIndex += count; // インデックスを更新
        return slice;
        },
        reset: () => {
        currentIndex = 0; // インデックスをリセット
        },
        hasMore: () => {
        return currentIndex < array.length; // 取得可能な要素が残っているか
        }
    };
}
function uint8ArrayToHexArray(uint8Array) {
    return Array.from(uint8Array) 
        .map(byte => byte.toString(16).padStart(2, '0')) 
}
function uint8ArrayToHex(uint8Array) {
    return uint8ArrayToHexArray(uint8Array).join(''); 
}

const buf = fs.readFileSync('./sample.png')
const bytedata = new Uint8Array(buf);
const hex = uint8ArrayToHexArray(bytedata);
const t = createSliceTracker(hex);
console.log('PNGシグネチャ\t', t.slice(8).join(' '))
console.log('IHDRチャンク長さ\t', t.slice(4).join(' '))
console.log('IHDRチャンク名\t', t.slice(4).join(' '), '(ASCIIで"IHDR")')
console.log('å¹…\t', t.slice(4).join(' '))
console.log('高さ\t',t.slice(4).join(' '))
console.log('ビット深度\t',t.slice(1).join(' '))
console.log('色の種類\t',t.slice(1).join(' '))
console.log('圧縮方法\t',t.slice(1).join(' '))
console.log('フィルタ方式\t',t.slice(1).join(' '))
console.log('インターレース方式\t',t.slice(1).join(' '))
console.log('CRC\t',t.slice(4).join(' '))

一方、アルファチャネルが255以外のピクセルを含む sample_2.png をコピーして「read」ボタンを押すと、色の種類は06として読み取られることを確認した。

PNGシグネチャ 89 50 4e 47 0d 0a 1a 0a
IHDRチャンク長さ 00 00 00 0d
IHDRチャンク名 49 48 44 52 (ASCIIで"IHDR")
å¹… 00 00 00 96
高さ 00 00 00 96
ビット深度 08
色の種類 06
圧縮方法 00
フィルタ方式 00
インターレース方式 00
CRC 3c 01 71 e2

Firefox の場合

Firefox3 では、どちらの画像も色の種類は 06 として読み取られた。

端末の画像ビューア等でコピーし、ブラウザで読み取った場合

色の種類は 06 として読み取られた。

検証結果から推測されること

  • Chrome上で画像をコピーした場合
    • 全ピクセルのアルファチャネルが255の場合、クリップボード上のデータはアルファチャネルが無くなる。
    • アルファチャネルが255以外のピクセルを含む場合、クリップボード上のデータにはアルファチャネルが含まれる。(元画像と同じバイナリになる)
  • Firefox上や、端末の画像ビューア等で画像をコピーした場合
    • 元画像と同じバイナリになる

アルファチャネルが255であれば、RGBの3色で扱っても何ら問題がないため、メモリ消費を抑える処理の一環でこのような処理がされているのかもしれない。


  1. 116.0.5845.140(Official Build) (64 ビット)
  2. Chromeの場合、クリップボードにアクセスする許可を求めるダイアログが出るため、「許可する」をクリックする。
  3. 131.0.2 (64 ビット))

HTMLで要素が正方形になるように維持する

HTML において要素が正方形になるように維持する CSS の書き方を整理する。

基本

aspect-ratio: 1 を指定する。 加えて、width または height のどちらかを指定してもよい。

指定していない方向の長さが aspect-ratio で指定した比に基づいて決まる。 例えば width: 200px と指定した場合、height が自動で200pxになろうとする。

幅と高さのどちらも指定しない場合、それぞれデフォルト値が auto になり、次のような値になる。

  • width: auto: 親要素の幅
  • height:auto: 自要素のコンテンツの高さ

max-width: 100%, max-height: 100% を指定すれば、親要素の大きさに応じてwidth、heightが調整される。

例えば width: 200px と指定した場合、 親要素の幅 < 200px においては aspect-ratio: 1 で指定したとおり height = width になるよう自動調整され、 200px <= 親要素の幅 においては height が 200px に固定されることになる。

max-width: 100%, max-height: 100% を指定するときの注意点

アスペクト比を保つために子要素の width または height が調整されるため、 あるタイミングで次のような状態になる可能性がある。

  • 親要素のwidth < 子要素の計算上の width
  • 親要素のheight < 子要素の計算上の height

このとき子要素にwidth, height を指定していても、 max-width,max-height の制約が優先されてアスペクト比は保たれなくなる。[^1]

[^1]max-width の指定を外せばアスペクト比は保たれるが、親要素をはみ出すことになる。

OCIのインスタンスにSSH接続しようとしても応答しない

OCI の VM.Standard.E2.1.Micro インスタンスに ssh 接続しようとしても無応答状態になっていた。

リソースのメトリクスを見ると次の通り。CPUもだがメモリが相当逼迫しているように見える。

再起動して /var/log/messages を見ると dnf が OOM Killer で kill されていることがわかった。

# cat /var/log/messages | grep -E "(dnf)|(oom)" | tail -n 30
Jan  7 12:06:51 instance-20250103-1654 systemd[1]: dnf-makecache.service: Main process exited, code=killed, status=9/KILL
Jan  7 12:06:52 instance-20250103-1654 systemd[1]: dnf-makecache.service: Failed with result 'signal'.
Jan  7 12:06:52 instance-20250103-1654 systemd[1]: Failed to start dnf makecache.
Jan  7 13:27:29 instance-20250103-1654 systemd[1]: Starting dnf makecache...
Jan  7 13:27:31 instance-20250103-1654 dnf[117558]: Failed determining last makecache time.
Jan  7 13:27:31 instance-20250103-1654 dnf[117558]: Ksplice for Oracle Linux 8 (x86_64)              55 kB/s | 3.5 kB     00:00
Jan  7 13:27:31 instance-20250103-1654 dnf[117558]: MySQL 8.0 for Oracle Linux 8 (x86_64)            50 kB/s | 3.5 kB     00:00
Jan  7 13:27:31 instance-20250103-1654 dnf[117558]: MySQL 8.0 Tools Community for Oracle Linux 8 (x 204 kB/s | 3.5 kB     00:00
Jan  7 13:27:31 instance-20250103-1654 dnf[117558]: MySQL 8.0 Connectors Community for Oracle Linux 138 kB/s | 3.5 kB     00:00
Jan  7 13:27:31 instance-20250103-1654 dnf[117558]: Oracle Software for OCI users on Oracle Linux 8 145 kB/s | 3.5 kB     00:00
Jan  7 15:18:29 instance-20250103-1654 kernel: systemd invoked oom-killer: gfp_mask=0x1100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
Jan  7 15:18:29 instance-20250103-1654 kernel: oom_kill_process.cold+0xb/0x10
Jan  7 15:18:29 instance-20250103-1654 kernel: [  pid  ]   uid  tgid total_vm      rss pgtables_bytes swapents oom_score_adj name
Jan  7 15:18:30 instance-20250103-1654 kernel: [ 117558]     0 117558   727344   180091  5648384   431790             0 dnf
Jan  7 15:18:30 instance-20250103-1654 kernel: oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/dnf-makecache.service,task=dnf,pid=117558,uid=0
Jan  7 15:18:30 instance-20250103-1654 kernel: Out of memory: Killed process 117558 (dnf) total-vm:2909376kB, anon-rss:716116kB, file-rss:4248kB, shmem-rss:0kB, UID:0 pgtables:5516kB oom_score_adj:0
Jan  7 15:18:30 instance-20250103-1654 kernel: oom_reaper: reaped process 117558 (dnf), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Jan  7 15:18:31 instance-20250103-1654 systemd[1]: dnf-makecache.service: Main process exited, code=killed, status=9/KILL
Jan  7 15:18:31 instance-20250103-1654 systemd[1]: dnf-makecache.service: Failed with result 'signal'.
Jan  7 15:18:31 instance-20250103-1654 systemd[1]: Failed to start dnf makecache.
Jan  7 16:30:23 instance-20250103-1654 systemd[1]: Starting dnf makecache...
Jan  7 16:30:24 instance-20250103-1654 dnf[121116]: Failed determining last makecache time.
Jan  7 16:30:25 instance-20250103-1654 dnf[121116]: Ksplice for Oracle Linux 8 (x86_64)              54 kB/s | 3.5 kB     00:00
Jan  7 16:30:25 instance-20250103-1654 dnf[121116]: Ksplice for Oracle Linux 8 (x86_64)              21 MB/s |  12 MB     00:00
Jan  7 16:30:31 instance-20250103-1654 dnf[121116]: MySQL 8.0 for Oracle Linux 8 (x86_64)            50 kB/s | 3.5 kB     00:00
Jan  7 16:30:31 instance-20250103-1654 dnf[121116]: MySQL 8.0 Tools Community for Oracle Linux 8 (x 114 kB/s | 3.5 kB     00:00
Jan  7 16:30:31 instance-20250103-1654 dnf[121116]: MySQL 8.0 Connectors Community for Oracle Linux 192 kB/s | 3.5 kB     00:00
Jan  7 16:30:31 instance-20250103-1654 dnf[121116]: Oracle Software for OCI users on Oracle Linux 8 212 kB/s | 3.5 kB     00:00
Jan  7 16:30:37 instance-20250103-1654 dnf[121116]: Oracle Software for OCI users on Oracle Linux 8  32 MB/s | 176 MB     00:05

調べてみると、類件がありそう。

OCIの無料Instanceでyum(dnf)使うとOOMする対策 blog.osakana.net

OCI以外でも、メモリの問題でdnfが原因で無応答になることがある模様。

wp.kaz.bz

実際に dnf makecache を実行すると、ssh接続がハングしてしまった。 明日設定を見直すことにする。

cloudflareでの非HTML(動画、画像)の配信をしすぎると規約違反となりサービス利用停止の恐れがあるらしい

タイトル通り。昔は全体の規約のセクション2.8に記載があったようで、セクション2.8と呼ばれていたようだ。

現在はcdnの規約として記載されている。

https://www.cloudflare.com/ja-jp/service-specific-terms-application-services/#content-delivery-network-terms

機械翻訳ではあるが、重要な箇所を和訳すると以下の通り。

エンタープライズ カスタマーでない限り、Cloudflare は CDN 経由でビデオやその他の大容量ファイルを提供するために使用する必要がある特定の有料サービス (開発者プラットフォーム、画像、ストリームなど) を提供します。お客様が有料サービスなしで CDN を使用してビデオや過度の割合の写真、音声ファイル、その他の大容量ファイルを提供している場合、または使用している疑いがある場合、Cloudflare は CDN へのアクセスや使用を無効化または制限する権利、または CDN を介したお客様の特定のリソースへのエンドユーザーのアクセスを制限する権利を留保します。

cloudflare→cloudfront→S3 でフルSSLで疎通

自宅鯖運用に向けた検証の1つとして、cloudflareを使ったサイトの公開を試した。

サイトはS3に配置した適当な index.html である。1

CloudFlare の初期設定

CloudFlare を一度も使ったことがなかったので、初期設定から実施する。

取得済みのドメインを設定し、ひとまずDNSレコードはすべて空で登録する。 レジストラの権威DNSサーバを CloudFlare のものに変えろ、と指示があるので従う。自分の場合は お名前.com navi のネームサーバ設定から、その他のサービスを使うよう選択し、Cloudflareの権威DNSサーバを設定した。反映に最大24時間程度かかると記載があったが、数分で反映された。反映されると、サービスの状態が「アクティブ」になる。

S3 と CloudFrontの設定

S3 へのコンテンツ配置

まずは、公開するサイトのファイルをS3に用意する。

  • バケットをデフォルト設定で作成する。
  • ルート直下に index.html をアップロードする。

CloudFront ディストリビューションの作成

S3の静的コンテンツをCloudFrontで配信するように設定する。

  • ディストリビューションを新規で作成
  • Origin domain で先に作成したS3バケットを設定する。
  • WAFはサンプルなので有効にしない。
  • デフォルトルートオブジェクトを index.html に設定する。
  • 他の設定はデフォルトで作成する。

この時点では、作成されたディストリビューションのドメイン名(d111111abcdef8.cloudfront.netのような形式のドメイン名)でアクセスすると、Access Denied になるはずである。

CloudFront から S3 にアクセスできるようにポリシーを変更する

オリジンの設定を編集する。

  • オリジンアクセスの設定を Origin access control settings に変更する。
  • Origin access control で「Create new OAC」を選択すると、Origin domain に設定したS3バケットに対応する名前でOACが作成される。
  • 「ポリシーをコピー」でバケットポリシーをコピーし、対象の S3 バケット のバケットポリシーに設定する。
  • 変更を保存する。

この状態で、再度ドメイン名でアクセスすると、index.html の内容が参照できるはずである。

CloudFlare の設定

CNAMEレコードを作成する。

CloudFlareに登録したドメインが example.com、CNAMEに設定したサブドメイン名が foo の場合、foo.example.com へのアクセスがターゲットのドメインにプロキシされることになる。

しかし、この状態で foo.example.com にアクセスすると、CloudFront から 403エラーが返ってきてしまう。どうやら、CloudFrontが作成したドメイン名以外でアクセスするときは、代替ドメイン名(CNAME)を設定する必要がある模様。参考

CloudFront への CNAME 追加

CloudFrontの設定で CNAME を追加しようとすると、次のようなエラーが表示され設定できず。

To add an alternate domain name (CNAME) to a CloudFront distribution, you must attach a trusted certificate that validates your authorization to use the domain name. For more details, see: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-requirements

というわけで、信頼できる認証局で発行された証明書が必要なことが分かった。

なお、CloudFlare側で「オリジン証明書」を発行する事ができるが、CloudFlare の証明書は CloudFront においては信頼できるCAから発行されていない、とエラーが出る。

The certificate that is attached to your distribution was not issued by a trusted Certificate Authority. For more details, see: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-requirements

https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/#cloudflare-origin-ca-root-certificate を参考に、証明書チェーンにCloudFlareのCAルート証明書を設定してもみたが、同様なエラーで利用できなかった。

Reddit の https://www.reddit.com/r/aws/comments/bb8bpg/cloudfront_cannot_use_certificates_in_acm_from/ で議論されている通り、Mozilla Included CA Certificate List にCloudFlareが含まれていないことが根本原因のようである。仕方ないので、ACMを使ってサブドメインに対応する証明書を発行した。2

適切に証明書が発行できていれば、CloudFront の CNAME の設定が成功するはずである。

CloudFlare の SSL/TLS 設定の変更

これにより、CloudFlare⇔オリジン 間の通信もSSL/TLS通信になったため、CloudFlareのSSL/TLS 設定において「フル (厳密)」あるいは 自動 SSL/TLS を設定する。 CloudFlare側のドメインでアクセスして、サイトが正常に閲覧できればOK。


  1. ChatGPTに適当な最低限のサンプルHTMLを出力してと頼んだら、頼んでもいないのにTodo一覧とTodo登録の2ページを出してきた。オーバースペック。笑
  2. CloudFlare にDNS認証用のCNAMEレコードを作成し、DNS認証を成功させておく必要がある。

OCIのネットワーク・セキュリティ・グループとセキュリティルールのざっくりとした違い

以下、コンピュートインスタンスで利用する前提として整理する。

  • ネットワーク・セキュリティ・グループ(NSG)

    • OCIリソースの仮想ファイアウォールとして機能する。
    • コンピュートインスタンスのVNICに1つ以上のNSGを付与できる。
    • AWSにおけるセキュリティグループのようなもの。
    • サブネット設計とセキュリティ要件を分離するために、基本的にはNSGを使用することが推奨されている。(参考:セキュリティ・ルール)
  • セキュリティ・リスト

    • ネットワーキング・サービスが提供する仮想ファイアウォール。
    • セキュリティ・リストが関連付けられているサブネット内のすべてのVNICに適用される。
    • AWSにおけるネットワークACLに似ているが、VNIC に適用されるため多層防御ではないことに注意する必要がある。 *1

デフォルトでインスタンスを作成すると、NSGが未設定で作成され、インスタンスの属するサブネットのセキュリティ・リストでトラフィックが制御される模様。 したがって、基本的にはセキュリティ・リストのイングレスルールは最低限にして、NSGで開放の制御をするのがよさそうである。

*1:NSGとセキュリティ・リストを併用する場合、ルールはUnionしたものになる。NSGやセキュリティ・リストに設定できるルールは「許可」のみのため、NSGでトラフィックを適切に絞っていたとしても、セキュリティ・リストに不適切な設定をしたせいで意図せぬトラフィックが許可されてしまう危険性がある。