ChatGPTやClaude、Geminiとの対話は、今や貴重な知的資産です。
非エンジニアながら、私はこれをMarkdown形式で保存するChrome拡張機能を自作し(with AI)、Braveブラウザで快適に使っていました。
そんなある日、Macユーザーとしての本能が囁きました。
「普段使いのSafari、しかもドックのPWAアプリで動けば最強では?」
これが、長く険しい戦いの始まりでした。
結論から言えば、このプロジェクトは断念しました。
この記事は、なぜChromeで動くコードがSafariでは通用しないのか、その技術的な壁の全記録です。(技術的なことは分からないので、大部分をAIに説明してもらっています)
第1の壁:Xcodeと署名の儀式
Safari拡張機能開発は、Chromeのように「フォルダを読み込ませて終わり」ではありません。
1. xcrunコマンドの罠
Chrome拡張機能をSafari用に変換するxcrun safari-web-extension-converterを実行した瞬間、いきなりエラーの洗礼を受けました。
現象:xcode-select: error: invalid developer directory
原因:
Xcodeをインストールしただけでは不十分。Command Line Toolsのパスを手動設定する必要がありました。
解決策:
ターミナルで格闘するより、XcodeのLocations設定画面でパスを指定する方が100倍早い。
2. 署名なしアプリの許可設定
Apple審査を通していない拡張機能を動かすには、以下の設定が必要でした:
- Safariの「開発」メニューを有効化
- 「署名されていない拡張機能を許可」を有効化
- 個別Webサイト設定で「常に許可」を付与
ここまではまだ許容範囲のストレスでした。
第2の壁:見えない文字は「無」である
拡張機能が起動し、いざChatGPTのログを取得しようとした瞬間、不可解な現象が起きました。
「自分の発言は取れるのに、AIの長文回答だけが空っぽになる」
WebKitの厳格すぎる最適化
Chrome(Chromium)は、画面外の要素でもinnerTextでテキストを取得できます。しかし、SafariのWebKitは違いました。
WebKitの仕様:
レンダリングコスト削減のため、画面外や非表示のDOMテキストは計算されません。
結果:
仮想スクロールやCSSで隠れているAIの回答は、プログラム上で「存在しない」扱いになります。
解決策:ゴースト配置(Ghost Positioning)
一瞬だけ画面内に無理やり表示させるハックで対応しました。
// Safari対策:DOMをクローンして画面外の座標に「表示」状態で配置
const clone = block.cloneNode(true);
clone.style.cssText = "position:fixed; top:0; left:-9999px; display:block !important;";
document.body.appendChild(clone);
const text = clone.innerText; // これでSafariが文字を認識
document.body.removeChild(clone);
泥臭いですが、これでテキスト取得に成功しました。(MacのSafari環境で)
第3の壁:PWAという牢獄
テキストは取れました。次はファイルとして保存するだけ…と思いきや、最大の壁「PWA環境の制約」が立ちはだかりました。
1. ダウンロードAPIの不在
Chromeならchrome.downloads.download一発で済む処理が、Safariでは動きません。
permissionsにdownloadsを追加しても無反応- 特にPWAモードではAPI実装が不完全
2. Blob URLの拒絶
<a>タグを生成してBlob URLをクリックさせる方法を試みましたが…
エラー: WebKitBlobResource error 1
意味: Safariは「メモリ上の怪しいデータ」を開かせてくれません。
3. Data URIスキームの限界
最後の手段として、Base64エンコードしたData URI方式を試しました。
結果:
- ダウンロードは成功
- しかしファイル名は強制的に「Unknown」になり、拡張子も消滅(※PC版Safariではファイル名指定が効く場合もあるが、PWA環境ではセキュリティ制限が厳しく完全に無効化された)
理由:
Safariはセキュリティ上、Data URIからのダウンロード時にdownload属性を無視する仕様があります。
最終決戦:共有メニューへの逃避
ダウンロードがダメなら、macOS標準の「共有(Share)」を使えばいい。navigator.share()で共有メニューを呼び出す作戦に出ました。
期待した挙動
共有メニューから「ファイルに保存」を選んで、Markdownファイルとして保存完了。
現実
共有メニューは開きましたが、「ファイルに保存」の選択肢がありませんでした。
あるのは「コピー」や「メモに追加」だけ。(※もちろん「機能拡張を編集」から項目を探しましたが、そこにも「ファイルに保存」は存在しませんでした。完全にOS側から「テキスト」として扱われていたようです)
macOSは、Webアプリから渡されたデータを「ファイル」ではなく「テキストの断片」として認識してしまいます。これをファイルとして扱うには、ネイティブアプリ開発の領域に踏み込む必要がありました。
結論:なぜBraveを選んだのか
最終的に、私は以下の判断に至りました。
MacのSafariで実装できたこと
- 別ウィンドウで開いて手動保存まで実装成功
残った課題
- 保存時に毎回「Unknown.txt」を「Log.md」に手動変更する手間
Braveの選択理由
- 拡張機能を入れるだけで完結
- クリック一発で指定ファイル名で保存
- 余計な手間がゼロ
学んだこと:道具に使われないために
今回の開発で、「Safariで動かすこと」自体が目的化していました。
本来の目的は「AIとの対話をサクッと保存して、知的生産性を上げること」のはずです。
Mac用ブラウザの使い分け
Safari
- Webサイト閲覧
- バッテリー効率重視の作業
Brave / Chrome
- 拡張機能の活用
- 開発・カスタマイズ作業
この使い分けこそが、Macユーザーにとっての最適解です。
おわりに
Safariでの開発は失敗に終わりましたが、WebKitのレンダリング仕様やPWAのサンドボックス構造への理解は深まりました。
この「敗北の記録」が、同じ道を通ろうとするエンジニアの道標になれば幸いです。
はー、Chromeの拡張機能は3時間くらいでいいのが作れたのに、それをSafariの拡張機能に移植しようと思ったら5時間かけても微妙なのしか作れませんでした。疲れたー。

コメント