【敗北記録】SafariでAI対話ログ保存拡張機能を作ろうとして断念した話 〜WebKitの厳格さとPWAの制約に阻まれた開発記〜

Safariのロゴと開発コードのエラー画面、そして絶望してうなだれている人の背中のアイキャッチ。 アプリ・Webサービス
本サイトはプロモーションが含まれています

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審査を通していない拡張機能を動かすには、以下の設定が必要でした:

  1. Safariの「開発」メニューを有効化
  2. 「署名されていない拡張機能を許可」を有効化
  3. 個別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では動きません。

  • permissionsdownloadsを追加しても無反応
  • 特に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時間かけても微妙なのしか作れませんでした。疲れたー。

コメント