特定のサイトでのみ動作するChrome拡張を作る場合、セオリーというとmanifest.jsonのcontent_scripts内でmatches
を使って指定できるが、Chrome拡張でコンテキストメニュー(右クリックメニュー)を作成するためにはbackgroundから指定するサービスワーカーで実装する。
ただしbackground
を使う場合は、調べた限りではサイト指定の仕組みが現状ないため、サイトごとのon/offを自前で実装する必要がある。たぶん。
この記事ではサイト(URL)ごとにon/offを切り替える具体的な実装についてのメモ。
- コンテキストメニューの実装
- インストール時 (chrome.runtime.onInstalled)
- ページ遷移時 (chrome.tabs.onUpdated)
- タブ切替時 (chrome.tabs.onActivated)
- ウインドウ切替 (chrome.windows.onFocusChanged)
- 作成・削除の多重処理対策
- 実装例
- Appendix
- 参考
コンテキストメニューの実装
コンテキストメニューを作成するにはchrome.contextMenus
を使用する。
インストール時 (chrome.runtime.onInstalled)
タブやウインドウ切替の度に作成・削除を行うのでここは無くても良いかもしれないが、基本部分なのでとりあえず。
サイトごとの制御が不要であれば、ここだけで良い。
chrome.runtime.onInstalled.addListener(function(details) { chrome.contextMenus.create({ id: "item1", title: 'hello extension', contexts: ["all"] }); });
ページ遷移時 (chrome.tabs.onUpdated)
同一のタブ内でサイトAからサイトBへ遷移したような場合のイベントリスナーを設定できる。
これを使うと「処理対象にしたいサイトに遷移した場合にメニューを有効化」と「対象外に遷移した場合にメニューを無効化」する。
開いているページのURLはコールバック関数の第3引数にタブ情報があるのでそこから参照する。
chrome.tabs.onUpdated.addListener(function(id,info,tab){ if(tab.active){ if (tab.url.match(target)) { chrome.contextMenus.create(createProperties); } else { chrome.contextMenus.remove("item1"); } } });
タブ切替時 (chrome.tabs.onActivated)
既に開いているタブA・タブB・…を切り替えた場合のイベントリスナーを設定する。
これで「処理対象にしたいサイトを開いてるタブをアクティブにした場合にメニューを有効化」と「対象外のサイトを開いてるタブをアクティブにした場合にメニューを無効化」する。
開いているページのURLはコールバック関数の引数を元に、タブの情報を取得するchrome.tabs.get()
を使用して取り出す。
chrome.tabs.onActivated.addListener(function(info){ chrome.tabs.get(info.tabId,function(tab){ if (tab.url.match(target)) { chrome.contextMenus.create(createProperties); } else { chrome.contextMenus.remove("item1"); } }); });
注意点として、同一ウインドウ内のタブ切替に有効。別ウインドウへの切替時はこのリスナーは反応しない。
ウインドウ切替 (chrome.windows.onFocusChanged)
前述のタブ切替でなく、ウインドウを切り替えたときのイベントリスナーを設定する。
これで「処理対象にしたいサイトを開いているウインドウがアクティブになった場合にメニューを有効化」と「対象外のサイトを開いているウインドウがアクティブになった場合にメニューを無効化」する。
リスナーに登録する関数内でタブ情報をchrome.tabs.query
で取得する。
chrome.windows.onFocusChanged.addListener(function(info){ // ウインドウ切替によるアクティブページの変化 chrome.tabs.query({'active': true, 'currentWindow': true}, tabs => { if (tabs[0].url.match(target)) { chrome.contextMenus.create(createProperties); } else { chrome.contextMenus.remove("item1"); } }); });
作成・削除の多重処理対策
上記のコードのままだと、タブやウインドウ切替時に多重処理が発生するとエラーになる。
遷移 | エラー |
---|---|
処理対象のサイト -> 処理対象のサイト | Unchecked runtime.lastError: Cannot create item with duplicate id <ID名> |
対象外のサイト -> 対象外のサイト | Unchecked runtime.lastError: Cannot find menu item with id <ID名> |
APIリファレンスを見た限り「既にcreate済み/remove済みだったら何もしない」は難しそうだったため、エラーを無視する実装にした。
エラーを無視するには以下の通り。
if (tab.url.match(target)) { chrome.contextMenus.create(createProperties, () => chrome.runtime.lastError); } else { chrome.contextMenus.remove("item1", () => chrome.runtime.lastError); }
実装例
コード全体は以下。
この機能を使って作成したChrome拡張は、「Azure Portalを開いているときに、右クリックメニューでリソースIDをクリップボーをへコピーする」というもので、以下で公開中。
(余談) 機能的にはツールバーの拡張アイコン押下で実装できそうだった(これだとcontent_scripts
のURL指定で処理対象サイトを制限できる)が、「取得した情報をクリップボードに書き込む」が(Documentにフォーカスしていないと動作しない仕様に対する処理が)難しくて断念。
Appendix
create/removeでなくupdateは?
コンテキストメニューの処理にはupdateメソッドもあり、パラメタにenabledやvisibleがあるので、これで切り替えもできる。
と思うんだけど、指定の方法がおかしいのかupdate
のキーになるid
をうまくセットできなくて未検証。
Deprecatedになったリスナー登録
Manifest ver2のときにあった以下のイベントリスナーはver3で無くなった。
拡張アイコンでポップアップを特定サイトでのみ表示する
サンプルコードが"Emulating pageActions with declarativeContent"に載っている。