ページの上にスマホ枠を重ねて、デスクトップとモバイルを同時に確認するChrome拡張を作った
Mobile View という Chrome 拡張を作っています。開いているページの上に、実際のスマホ/タブレットの枠を重ねて表示するツールです。狙いはシンプルで、デスクトップのサイトと、そのモバイル(レスポンシブ)レイアウトを 1 画面で同時に見られること。ウィンドウを縮めたり DevTools を開いたりする必要はありません。
中身は「iframe を貼るだけ」より少し面白かったので、課題の整理と、実装・設計の判断について書きます。
なぜ既存のツールでは物足りなかったか
レスポンシブを組んでいると、「デスクトップでの見え方」と「スマホでの見え方」を行き来し続けることになります。ツールはありますが、それぞれに「でも…」があります。
- DevTools(デバイスモード) — 定番ですが、通常の表示を置き換えてしまいます。デスクトップかモバイルか、どちらかを切り替えて見る形で、しかもウィンドウが狭くなり、入る・出るの操作が必要なモードです。
- オンラインのシミュレーターや専用アプリ(Responsively、Sizzy、BrowserStack など)は複数のビューポートを同時に見せてくれますが、サイトをそのツール/タブの中で開く必要があり、有料のことも多く、「いま見ているブラウジングの上に重ねる」体験ではありません。
- リサイザー系の拡張は、たいていウィンドウサイズを変えるか、新しいタブ/グリッドでサイトを開くだけです。
欲しかったのはもっと単純なもの:いま開いているページから離れずに、デスクトップとモバイルを同時に、リアルタイムで見ること。ワンクリックで、開いているサイトの上に実機風の枠が現れ、その中に同じページが入り、デスクトップは隣に残る。DevTools も、ウィンドウの縮小も、別アプリも不要。そうして Mobile View ができました。
他サイトを枠の中に表示する(X-Frame-Options を外す)
ページは Shadow DOM のオーバーレイ内の <iframe> でレンダリングします。問題は、多くのサイトが X-Frame-Options: deny や Content-Security-Policy: frame-ancestors を返すため、iframe に埋め込めないこと。
解決策は declarativeNetRequest。セッションルールでこれらのレスポンスヘッダーを外し、モバイルの User-Agent を差し込みます。重要なポイント:
- ルールはアクティブタブの
sub_frameのみに適用し、特定のtabIdに紐づけます。ページの top-level ドキュメントには触れず、弱めるのはプレビュー用の iframe だけ。 - ルールはセッションスコープ(ブラウザ再起動で消える)で、オーバーレイを閉じたら外します。そうしないと MV3 では、CSP が弱まったままのタブを残しやすいです。
{
id: ruleId,
action: { type: "modifyHeaders", responseHeaders: [
{ header: "x-frame-options", operation: "remove" },
{ header: "content-security-policy", operation: "remove" },
]},
condition: { resourceTypes: ["sub_frame"], tabIds: [tabId] },
}
画面録画:getDisplayMedia から tabCapture へ
最初は録画を getDisplayMedia で実装していました。ですが、システムの「共有する対象を選択」ダイアログと「タブを共有しています」のバーが出て、そのバーのせいでビューポートが狭くなり、フレーム内のレイアウトが崩れていました。
そこで chrome.tabCapture に移行。プロンプトもバーもありません。MV3 の落とし穴として、getUserMedia / MediaRecorder は service worker から呼べないため、キャプチャと録画は offscreen ドキュメントに置いています。流れは content → background → offscreen。
エクスポートはちゃんとしたフォーマットに作り直しました。マスターは WebM で録り、ダウンロード時に WebCodecs で MP4(H.264) または WebM(VP9) に、画質を選んで再エンコードします。muxer は遅延読み込み。さらにプレビュー内のミニエディタで、先頭・末尾のトリミングとエクスポート速度の変更ができます。
「カスタムサイズ」モード
枠を PNG ではなくコード(CSS)で描くモードを追加しました。任意の解像度・画面サイズを指定でき、インターフェースの種類(OS なし / iOS / 旧 iOS / Android)、本体色を選べて、回転にも対応します(カメラやボタンも回転に追従)。デバイス一覧にない非標準のサイズでレイアウトを確認したいときに便利です。
safe-area —— 正直な制約
開発者は、ノッチやホームインジケーター用の余白に env(safe-area-inset-*) を使います。ですが通常の iframe ではこの値は常に 0で、しかも CSS / JS から差し替えられません——値を出すのはブラウザのエンジンだけだからです。非ゼロの safe-area を実際に設定するには CDP(chrome.debugger の Emulation.setSafeAreaInsetsOverride)が必要で、これは「拡張機能がブラウザをデバッグしています」のバーを出します。
そのため、これは別の**「アドバンスド」モード**に分ける予定です。正確なエミュレーションが必要な人だけが使えるオプションにして、普段の軽いプレビューを重くしないように。
これからと、みなさんへの質問
次は CDP を使って本物の safe-area を出す「アドバンスド」モードを作りたいです。コミュニティへの質問:みなさんは env(safe-area-inset-*) をどうテストしていますか? 実機で確認、DevTools のエミュレーション、それとも もっと良い方法がありますか?
リンクとフィードバック
- Chrome ウェブストア:https://chromewebstore.google.com/detail/mobile-view-%E2%80%94-mobile-simu/hocbjiaeeijekejepphjihbpogikmofh
- 公式サイト:https://mobileview.app
- ソース/連絡先:プロフィールに
実際に使ってみて、不具合や「ここが惜しい」と感じた点があれば、ぜひコメントで教えてください。再現の助けになるので、可能であればスクリーンショットや短い動画、OS・ブラウザのバージョンも添えていただけると本当に助かります。いただいた声をもとに直していきます。日本語のコメント、お気軽にどうぞ。
