maesblog

[Chrome Extensions(機能拡張)開発] localStorageを使って異なるスコープ間でデータのやり取りを行う方法

Chrome Extensions(機能拡張)の開発でちょっとハマった部分です。あるWebサイト上のデータを取得して、それをlocalStorageに保存して、それをChrome Extensions上で取得して利用しようと思ったのですが、localStorageの仕様上別のスコープとなりそれが無理だということがわかりました。調べてみると、Chrome ExtensionsのAPIを使ってbackground.js上でlocalStorageを使うことで解決できることがわかりました。今回は、その辺りのことをまとめておきます。

localStorageとは

まず最初にlocalStorageについて説明します。すでにlocalStoregaについて把握されている方は読み飛ばしてください。

ブラウザは、Web Storageというクライアントサイドのストレージ(記憶装置)を持っています。localStorageは、その「Web Storage」を実装しているブラウザのwindowオブジェクトのプロパティで、Web Storageに対してデータの読み書きなどを行う機能を提供しています

localStorageには以下のような特徴があります。

  • 保存されたデータは永続的。Webアプリケーションがデータを削除するか、ユーザーがブラウザにデータを削除するように指示するまでデータは保存され続けます。
  • ドキュメントの出身ごとにスコープを持つ。下で詳しく述べますが、localStorageは、プロトコル、ホスト名、ポート番号によってスコープが異なります。スコープが異なると、データの取得、上書きができません。
  • 文字列keyと文字列valueにマッピングするkey-value型の連想配列。扱いが簡単です。
  • 大容量。ブラウザによっても異なりますが、保存容量は5MB〜10MBとなります。

localStorageの詳細は以下をご参照ください。

localStorage APIについて

localStorageは、通常のJavaScriptオブジェクトと同じように扱うことができます。プロパティを設定してデータを保存し、プロパティを読み出してデータを取得します。

// key名'foo'に1を保存
localStorage.foo = 1;

// key名'foo'の値を取得
console.log(localStorage.foo); // => 1

上記の方法でもlocalStorageを扱えますが、以下のような公式のAPIも定義されています。

プロパティ

localStorage.length
localStorageオブジェクトに保存されているデータの数(integer)を返します。

メソッド

localStorage.key(n)
引数に数字「n(number)」が指定された時に、localStorageのn番目のkeyの名前を返します。
localStorage.getItem(key)
引数にkeyの名前が指定された時に、そのkeyの値を返します。
localStorage.setItem(key, value)
引数にkeyの名前と値が指定された時に、localStorageにkeyとそのvalue(値)を追加します。すでに存在していた場合は、そのkeyのvalue(値)を更新します。
localStorage.removeItem(key)
引数にkeyの名前が指定された時に、localStorageからそのkeyを削除します。
localStorage.clear()
localStorageのすべてのkeyを削除します。

APIの使用例

以下はlocalStorage APIの使用例となります。

// Key名'foo'にValueとして「1」を保存
localStorage.setItem('foo', 1);
// Key名'bar'にValueとして「2」を保存
localStorage.setItem('bar', 2);

// localStorageに保存したアイテム数を取得
console.log(localStorage.length); // => 2

// localStorageの0番目のkey名を取得
localStorage.key(0); // => 'foo'
// localStorageからkey名'foo'の値を取得
localStorage.getItem('foo') // => 1

// localStorageからkey名'foo'とその値を削除する
localStorage.removeItem('foo'); // => localStorage.lengthは1
// すべてのデータを削除する
localStorage.clear(); // => localStorage.lengthは0

[参考]

ちなみにlocalStorageに保存したデータはDevelopertoolsの「Resources」の部分で確認できます。データの追加、更新、削除などもここで行うことが可能です。

localstorage

Chrome ExtensionsでlocalStorageを使う際の問題点

localStorageの話がだいぶ長くなりましたが、これから本題となります。上記の通り、localStorageの特徴を考慮すると、ちょっとしたWebのアプリケーションを作るのであれば、保存領域として使うのにlocalStorageで十分なケースが多いんじゃないかと思います。特にブラウザ上で動くChrome Extensionsであれば、かなり親和性も高いです。Chrome Extensionsの開発でlocalStorageを使という話はよく聞きます。

ただここで注意が必要です。上記でも述べましたが、localStorageは「ドキュメントの出身によってスコープが異なる」ということです。Chrome Extensions内で完結するものであれば、特に問題はないと思いますが、ブラウザで表示しているWebサイトから取得したデータをlocalStoregeに保存して、Chrome Extensionsでそのデータを取得したり、またその逆でChrome Extensionsで保存したデータをブラウザで表示しているWebサイト上で取得したりすることができません

localStorageのスコープについて、ここで少し詳しく説明しておきます。localStorageはドキュメントの出身(プロトコル、ホスト名、ポート番号)によってスコープが異なります。ドキュメントの出身が異なると、データを取得したり、上書きしたりすることができません。例えば、以下のURLはすべてドキュメントの出身が異なることになります。

  • http://www.example.com
  • https://www.example.com
  • http://hoge.example.com
  • http://www.example.com:8080

つまり、Chrome ExtensionsとWebサイト間でのデータのやり取りがそのままではできない仕様となっています。

データの橋渡し用にbackground.jsを使う

上記のスコープの問題を解決する方法として、Chrome Extensionsに用意されているbackground.jsを使ってChrome ExtensionsとWebサイト間のデータの橋渡しをさせるようにします

background.jsはその名の通り、Chrome Extensionsのバックグラウンドで実行されるスクリプトファイルとなります。

background.jsを使う場合は、manifest.jsonに以下のように記載します。

{
  "manifest_version": 2,
  ...
  "background": {
    "scripts": ["background.js"]
  },
  "browser_action": {
    ...
    "default_popup": "popup.html"
  },
  "content_scripts": [
    {
      "matches": [
        "https://example.com/*"
        ...
      ],
      "js": ["bundle.js"]
    }
  ],
  ...
}
manisest.json

ちなみに、上記のmanifest.jsonの例では、browser_actionとして‘popup.html’、content_scriptsとして‘https://example.com/*’と指定しています。つまりこのpopup.htmlとhttps://example.com/間でlocalStorateのデータのやり取りを行うのにbackground.jsを使うということになります。

肝心のbackground.jsをどのように使うかというと、Webサイト上(content_scripts上)で行いたいlocalStorageへの命令を、すべてこのbackground.jsで行うようにします。localStorageのスコープはバックグラウンドページとして形成されるので、 browser_action(popup.html)からは直接localStorageを扱えるようになります。Webサイト(content_scripts)とbackground.js間のやり取りはChrome Extensionsのchrome.runtime APIを使って行うようにします。

chrome.runtime APIでメッセージのやり取りを実装

chrome.runtime APIはbackgroundページを検索したり、manifestの詳細な情報を返したり、Webアプリやエクステンションのライフサイクルの中でのイベントの購読や応答を行うために使うAPIです。

ここでのchrome.runtime APIの用途は以下のとおりとなります。

Webサイト上(content_scripts上)からメッセージを送信

backgroundページでそのメッセージを受け取りレスポンスを返す

Webサイト上(content_scripts上)でレスポンスを受け取りコールバックを実行

background.jsにおけるchrome.runtime APIの実装

まずbackground.jsから処理を書いていきます。Webサイト上(content_scripts上)から受け取るメッセージごとに実行する処理を登録しておくイメージとなります。Webサイト上(content_scripts上)から送られたメッセージを受け取るには、chrome.runtime.onMessage.addListener(callback)メソッドを使います。メッセージが送られるとcallback関数を実行します。

上記で説明したlocalStorageのAPIのプロパティやメソッドを実行するために、以下のようにそれぞれ名前をつけて登録しておきます。

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  switch (request.method) {
    case 'getLength': // 保存されているデータ数を取得
      sendResponse({data: localStorage.length});
      break;
    case 'getKeyName': // 指定されたn番目のkey名を取得
      sendResponse({data: localStorage.key(request.number)});
      break;
    case 'getItem': // 指定されたkeyの値を取得
      sendResponse({data: JSON.parse(localStorage.getItem(request.key))});
      break;
    case 'setItem': // 指定されたkeyと値を保存(更新)
      sendResponse({data: localStorage.setItem(request.key, request.value)});
      break;
    case 'removeItem': // 指定されたkeyの値を削除
      sendResponse({data: localStorage.removeItem[request.key]});
      break;
    case 'clearAll' // すべてのデータを削除
      sendResponse({data: localStorage.clear()});
      break;
    default:
      console.log('no method');
      break;
  }
});
background.js

chrome.runtime.onMessage.addListener()メソッドの引数’request’に送られたメッセージが格納されます。つまりメッセージには、background.jsで実行すべき処理の名前と、その処理に必要な値を含めておく必要があります。それから引数’sendResponse’で、content_scriptsにデータを返すようになっています。background.jsで処理した値を名前をつけて’sendResponse’にセットするようにします。

content_scriptsにおけるchrome.runtime APIの実装

content_scripts(Webサイト)では、background.jsに登録した処理名とその処理に必要な値をchrome.runtime.sendMessage()メソッドの引数にセットして、localStorageに処理を加えたい箇所で実行するようにします。

// localStorageに保存されているkey名'foo'の値を取得する場合
chrome.runtime.sendMessage({method: 'getItem', key: 'foo'}, function (response) {
  if (response.data) {
    console.log(response.data);
  }
});

// localStorageにkey名'baa'として値2を保存する場合
chrome.runtime.sendMessage({method: 'setItem', key: 'foo', value: 2});

// localStorageのkey名'foo'の値を削除する場合
chrome.runtime.sendMessage({method: 'removeItem', key: 'foo'});
content_scripts(Webサイト)

background.jsでの処理の結果は、chrome.runtime.sendMessage()メソッドのコールバック関数の引数で受け取ることができます。受け取った値をコールバック関数内で処理することになります。

browser_actionからlocalStorageを扱う場合

browser_action(default_popupに指定したhtmlファイル)からは、直接localStorageにアクセスすることができるので、chrome.runtime APIを使う必要はありません。普通にlocalStorage APIを使ってデータの取得、更新、削除を行ってください。

まとめ

以上、特に難しいことをしないのであれば、chrome.runtime.onMessage.addListener()メソッドとchrome.runtime.sendMessage()メソッドを使った処理をそれぞれbackground.jsとcontent_scripts(Webサイト)に書くだけでChrome ExtensionsでlocalStorageを使えるようになります。background.jsというファイルを用意したり、一見複雑になりそうな感じもしますが、実際に実装してみると意外とこんなものかというくらいあっさり実装できてしまうと思います。

上でも述べましたが、Chrome ExtensionsとlocalStorageは親和性がとても高いです。Chrome Extensionsの開発でlocalStorageを使えるようになったら、開発できるプロダクトの幅も広がってくるので、ぜひここは抑えておきたいところだと思います。ぜひ参考にしてもらえらばと思います。

以下の通称サイ本は若干古いですが、localStorageについてもしっかり書かれていて参考になります。最後に紹介しておきます。

JavaScript 第6版
  • 『JavaScript 第6版』
  • 著者: David Flanagan (著), 村上 列 (翻訳)
  • 出版社: オライリージャパン
  • 発売日: 2012年8月10日
  • ISBN: 978-4873115733

関連記事

コメント

  • 必須

コメント