Help us understand the problem. What is going on with this article?

Chrome Extension の作り方 (最終話: メッセージパッシング)

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 scriptsbrowser action 間でのメッセージパッシングの例を挙げます。

イメージ図

demoimage.png

サンプルアプリ

  • メモを残せる
  • メモに「現在選択してる文字列」を挿入できる

memodemo.gif

完成品

フォルダ構成

フォルダ構成
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.jspopup-main.js
今回は2つのファイルに分けていますが、一つのファイルにまとめても動きます。
画面の操作のためのJS(popup-main.js)と、メッセージパッシング用(popup-chrome.js)のJSに分けてみました。

manifest.json

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/contents.js"]
    }
  ]
}

↓ アイコン画像がない場合はこの部分はなくても大丈夫です。

  "icons": {
    "128": "icon_128.png"
  },

popup.html

Browser Actionとして、右上のアイコンを押したときに表示されるポップアップ(HTML)
ここで popup-main.jspopup-chrome.js を読み込んでる。

popup.html
<!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

デザインセンスある人はもっとカッコよくしてほしい。

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

  • 保存されてるメモを読み込んでテキストエリアに表示する
  • 保存ボタンが押されたら保存する

の処理を書きました。

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も後で出てきます)

manifest.json
"permissions": ["tabs","storage"],

popup-chrome.js

今回のポイント1: メッセージを送る!

popup-chrome.js
// 「選択範囲をコピー」が押されたら、現在アクティブなタブへ通信をして、選択範囲の情報を取得
$('#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 から見ていきます。

popup-chrome.js
    chrome.tabs.sendMessage(tabs[0].id, {message: '選択範囲ちょうだい'}, function(item){

sendMessage の第一引数に「どのタブの中にあるページにメッセージを送るのか」を指定する必要があります。これをタブのidで指定します。詳しくは chrome.tabs.sendMessage - 公式 参照。

で、そのタブidを取得するために「外側」のこれ↓をしています。

popup-chrome.js
  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が取得できる、と。

下記の構造がわかれば

popup-chrome.js
    chrome.tabs.sendMessage(/*送信先のタブid*/, /*送るモノ*/, function(/*レスポンス*/){
      /*レスポンスを受け取ったあとの処理*/
    });

popup-chrome.jsの全体像もわかりますね!

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: メッセージを受け取る!レスポンスを返す!

content.js
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 だ!

構造.png

おしまい

ChromeExtensionについて5回でまとめてみました。

第1回でも書きましたがこの内容は私が考えたのではなく、@ikeikeda のレクチャをもとにしたものです。スゴイのは彼です。多分そのうち彼もアウトプットしてくれると思いますYO!

Chrome拡張とGASを連携させる例 にも書きましたが、Googleスプレッドシートをデータベース代わりにすれば、小規模ユースのアプリケーションは作れそうですね。

sakaimo
みなさんの記事に助けられております。私も何かお役に立てるよう頑張ります。。。
https://note.mu/sakaimo
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした