chrome拡張機能のサイドパネルを使う

前回「フォームを自動入力する拡張機能」の記事を書きまして、
その中で「設定はオプションではなくサイドパネルで行う方式が好み」と書きました。

しかしねぇ、このサイドパネル。
拡張機能内でのデータ連携がなかなかやりにくいものでした。

この記事はそれを踏まえた拡張機能の各画面の解説。
※画面扱いしていいかはアレですが、以下すべて画面と表現します

拡張機能:画面の種類

拡張機能で使える画面は4つあります。
・実際に表示しているタブ
・拡張機能のポップアップ
・拡張機能のオプション画面
・chromeのサイドパネル

これに対して、拡張機能で読み込めるJSは5種
・service_worker
・content_scripts
・オプション画面で読み込めるjs
・side_panelで読み込めるjs
・popupで読み込めるjs
※jsファイルの個数は何個でも大丈夫

それぞれ注釈をつけると

service_worker

・拡張機能の基盤
・拡張機能が追加された時にブラウザに機能を追加する
・タブIDを指定できればページのnodeを取得できる

content_scripts

・ページを開いた時(後?)に裏で実行されるJS
・殆どのブラウザAPI(tabsやstorage)が動作しない
・jsの「document」でページのnodeを取得できる

オプション画面のJS

・オプション画面に設定しているhtmlからscriptタグのsrcで読み込む
・タブのページ情報を取得できるか不明
※タブIDがわかっていればできるかもしれませんが非現実的

side_panelとpopup

・共に設定しているhtmlからscriptタグのsrcで読み込む
・jsの「document」は各htmlのNodeを指す

データの受け渡し

※以降「現在開いているページ」をタブと表現します

個人的に厄介だったのはサイドパネルはタブのhtml情報を取得できないということ。
理解した今なら簡単に言い換えられますが、当初は何故? と思っていたものです。

まず各画面は以下の受け渡しができません
・サイドパネル→タブからのnode取得
・ポップアップ→タブからのnode取得
・タブ→サイドパネルへのnode送信
・タブ→ポップアップへのnode送信

ではどうするかというと
content_scriptsに「この情報をよこせ」と命令します。

つまりサイドパネルから直接データを取得するのではなく
該当タブの裏で実行されているcontent_scriptsがデータを横流ししてくれるということです。

ページ所有者の預かりしならぬところでデータが流れているのだから正に裏取引。

受け渡しコード:要求時

そしてその方法はサイドパネルのリファレンスではなくタブのリファレンスに書いてあります…。
選択したタブのコンテンツ スクリプトにメッセージを渡す

ざっくり書き直すと以下のようになります。

送信側ファイル(サイドパネルかポップアップ)
const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
chrome.tabs.sendMessage(tab.id, {type:"getPageNode"}).then((response) => {
	if(response['node_data'].length === 0){
		return false;
	}
}).catch((error)=>{

});

content_scripts側
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
	if (request.type === "getPageInfo") {
		let page_title = document.title !== null ? document.title : 'タイトルなし';
		let node_data = document.querySelector('a').innerText;
		sendResponse({page_title, node_data});
	}
	return true;
});

これでサイドパネルから現在開いているページの情報を取得できます。
ただしnodeの直接受け渡しはできないのでご注意。

もしサイドパネルでデータの編集などがしたいのであれば、
そのnodeを特定する識別子をサイドパネルに渡し、サイドパネルから変更した内容と識別子を一緒に送信して
content_scripts側で再度その識別子からnodeを特定して編集する必要があります。

それが手間な場合はcontent_scripts側でUIを生成してモーダル表示などさせましょう。

受け渡しコード:送信時

先のコードはあくまでサイドパネルからの要求に「返答する」形でデータを渡します。
つまりcontent_scriptsが主導ではありません。

ではcontent_scriptsのclickイベントなどで発火した操作からサイドパネルにデータを送る場合はどうするか。

この場合は先の送信プログラムの「tabs.sendMessage」を「runtime.sendMessage」に変更します。
逆に受信側は「runtime.onMessage」のままです。

そしてポップアップ、オプション画面、サイドパネル、サービスワーカーのすべてで
受信するときは「runtime.onMessage」を使用します。
つまり「runtime.sendMessage」したものはすべての画面で受信しているということです。
※sendMessageした画面は省かれます

これについてリファレンスには

複数のページが onMessage イベントをリッスンしている場合、
特定のイベントに対して sendResponse() を最初に呼び出したページのみがレスポンスの送信に成功します。
そのイベントに対する他のすべてのレスポンスは無視されます。

と書かれています。

おまけ

拡張機能storeではWebページから画像を抜き出すものが色々公開されています。
これはcontent_scriptsで取得した画像パスをサイドパネルやポップアップに送り、
そこでimgタグなどを使い表示していると思われます。

ではそうした場合、画像のアクセスはどのページから行われている扱いになるのか。
つまりリファラの問題です。

調査結果は「リファラがなくなりました」
またキャッシュは実際のWebページとは別に保持され、
スーパーリロードが使えないため常にキャッシュの影響を受けます。
※閲覧履歴を削除すれば恐らく消せます

以上のことから画像へのアクセスにリファラチェックを行うことで拡張機能を妨害することはできます。
ただしcontent_scriptsでcanvasに貼り付けbase64化もできるためやはりいたちごっこではあります。

最新ブログ一覧