Chrome Extension の作り方 (その1: 3つの世界) - Qiita
Chrome Extension の作り方 (その2: Contents Script) - Qiita
Chrome Extension の作り方 (その3: Browser Action / Page Action) - Qiita
Chrome Extension の作り方 (その4: Event Page / Background Page) - Qiita
の連載の最終回!!
ChromeExtensionを作っていると「メッセージパッシング」という言葉を見かけます。
Message Passing - 公式
Chrome Extension の作り方 (その1: 3つの世界) - Qiitaの説明の通り、3つの世界があるのですが、異世界とやり取りしたい状況が出てきます。
今回のサンプルでは content scripts
とbrowser action
間でのメッセージパッシングの例を挙げます。
イメージ図
サンプルアプリ
- メモを残せる
- メモに「現在選択してる文字列」を挿入できる
完成品
フォルダ構成
ChromeMemo
└ content
└ content.js
└ lib
└ jquery-3.4.0.min.js
└ popup
└ popup.css
└ popup.html
└ popup-chrome.js
└ popup-main.js
└ icon_128.png // アイコンはなくても大丈夫
└ manifest.json
※ popup-chrome.js
と popup-main.js
今回は2つのファイルに分けていますが、一つのファイルにまとめても動きます。
画面の操作のためのJS(popup-main.js)と、メッセージパッシング用(popup-chrome.js)のJSに分けてみました。
manifest.json
{
"manifest_version": 2,
"name": "ChromeMemo",
"version": "1.0.0",
"description": "アイコンクリックで、メモを起動します。URLボタンでURL,コピーボタンで選択範囲をメモに入れ込みます。メモを保存するときはSAVEをクリックしてください。",
"icons": {
"128": "icon_128.png"
},
"permissions": ["tabs","storage"],
"browser_action": {
"default_title": "ChromeMemo",
"default_popup": "popup/popup.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content/content.js"]
}
]
}
↓ アイコン画像がない場合はこの部分はなくても大丈夫です。
"icons": {
"128": "icon_128.png"
},
popup.html
Browser Actionとして、右上のアイコンを押したときに表示されるポップアップ(HTML)
ここで popup-main.js
と popup-chrome.js
を読み込んでる。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ChromeMemo</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<textarea id='memo'></textarea>
<input type='button' id='copy' value='選択範囲をコピー'>
<input type='button' id='save' value='メモを保存'>
<script src='../lib/jquery-3.4.0.min.js'></script>
<script src='popup-main.js'></script>
<script src='popup-chrome.js'></script>
</body>
</html>
popup.css
デザインセンスある人はもっとカッコよくしてほしい。
#memo{
width: 400px;
height: 300px;
font-size: 16px;
background-color: #ffffc4;
border: 0;
}
/*ボタンを押したとき*/
input:active {
-webkit-transform: translateY(4px);
transform: translateY(4px);
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.2);
border-bottom: none;
}
popup-main.js
- 保存されてるメモを読み込んでテキストエリアに表示する
- 保存ボタンが押されたら保存する
の処理を書きました。
let memo;
loadData();
//データの読み込み
function loadData(){
chrome.storage.local.get(['chromememo'], function(obj){
memo = obj.chromememo;
//データがない場合の初期化
if(!memo){
memo = [ {text: "", lastUpdate: new Date()} ];
chrome.storage.local.set({chromememo:memo}, function(){});
return;
}
$('#memo').val(memo.text);
});
}
//保存ボタンが押されたとき
$('#save').on('click', function(){
memo = {
text: $('#memo').val(),
lastUpdate: new Date()
};
chrome.storage.local.set({chromememo: memo}, function(){
alert('保存が完了しました');
});
});
chrome.storage - 公式
Chromeが持ってる保存領域(storage)にデータを保存(set)するのと、取り出す(get)することができます。
これ(chrome.storageのAPI)を使いたいので、manifest.json
に下記の記述があります。(tabも後で出てきます)
"permissions": ["tabs","storage"],
popup-chrome.js
今回のポイント1: メッセージを送る!
// 「選択範囲をコピー」が押されたら、現在アクティブなタブへ通信をして、選択範囲の情報を取得
$('#copy').on('click', function(){
chrome.tabs.query( {active:true, currentWindow:true}, function(tabs){
chrome.tabs.sendMessage(tabs[0].id, {message: '選択範囲ちょうだい'}, function(item){
if(!item){
alert('選択範囲が見つかりませんでした');
return;
}
$('#memo').val($('#memo').val() + item);
});
});
});
インデントが深くてパット見て「うっ」ってなりますがもっとうまいやり方あるのかな。
まずは「内側」の chrome.tabs.sendMessage
から見ていきます。
chrome.tabs.sendMessage(tabs[0].id, {message: '選択範囲ちょうだい'}, function(item){
sendMessage
の第一引数に「どのタブの中にあるページにメッセージを送るのか」を指定する必要があります。これをタブのidで指定します。詳しくは chrome.tabs.sendMessage - 公式 参照。
で、そのタブidを取得するために「外側」のこれ↓をしています。
chrome.tabs.query( {active:true, currentWindow:true}, function(tabs){
chrome.tabs.query
の第一引数に {active:true,currentWindow:true}
を与えていますが、第一引数に何を指定できるのかはchrome.tabs - 公式 参照。上記では「現在のウィンドウ(複数のウィンドウが開かれている可能性もあるので)にあるタブで、activeなタブ」という指定になります。
queryの第一引数の条件にマッチするタブが配列になって tabs
で受け取れます。
今回の条件にマッチするタブは一つしか存在しないので、 tabs[0]
で取り出せます。
tabs[0].id
でこのタブのidが取得できる、と。
下記の構造がわかれば
chrome.tabs.sendMessage(/*送信先のタブid*/, /*送るモノ*/, function(/*レスポンス*/){
/*レスポンスを受け取ったあとの処理*/
});
popup-chrome.js
の全体像もわかりますね!
// 「選択範囲をコピー」が押されたら、現在アクティブなタブへ通信をして、選択範囲の情報を取得
$('#copy').on('click', function(){
// 対象のタブのidを取得したい
chrome.tabs.query( {active:true, currentWindow:true}, function(tabs){
// 取得したタブid(tabs[0].id)を利用してsendMessageする
chrome.tabs.sendMessage(tabs[0].id, {message: '選択範囲ちょうだい'}, function(item){
// sendMessageのレスポンスが item で取得できるのでそれを使って処理する
if(!item){
alert('選択範囲が見つかりませんでした');
return;
}
$('#memo').val($('#memo').val() + item);
});
});
});
content.js
今回のポイント2: メッセージを受け取る!レスポンスを返す!
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
let selection;
console.log(request.message); // -> 選択範囲ちょうだい が出力される
// 画面で選択されている部分を文字列で取得する
if(window.getSelection){
selection = window.getSelection().toString();
}else{
selection = '';
}
sendResponse(selection);
});
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
で受け取ります。送られてきたメッセージの中身は request.message
で取り出せます。
今回は {message: '選択範囲ちょうだい'}
は何の役にも立ってないですが、例えば
一つの content.js
に対して、複数のパターンのメッセージが送られてくる場合はmessageの中身によってcontent.js
側で処理を分岐することができますね。(すみません、調査できてないのですが sender
からも判別できそうですね)
処理した結果(今回は selection
)を
sendResponse(selection);
ってやると、メッセージを送信した人にレスポンスを返せます。
メッセージパッシングの関係図
覚え方としては
- 「content scriptsはtab内にあるページに仕込んだスクリプト」だから、
chrome.tabs
で送る! - あとは
chrome.runtime
だ!
おしまい
ChromeExtensionについて5回でまとめてみました。
第1回でも書きましたがこの内容は私が考えたのではなく、@ikeikeda のレクチャをもとにしたものです。スゴイのは彼です。多分そのうち彼もアウトプットしてくれると思いますYO!
Chrome拡張とGASを連携させる例 にも書きましたが、Googleスプレッドシートをデータベース代わりにすれば、小規模ユースのアプリケーションは作れそうですね。