Chromeエクステンションを作ろう：ストレージ編


説明で使用した拡張機能は[こちら](https://chrome.google.com/webstore/detail/redmine-lazy-applicant/dofmgcleeckgckfapnhookpocglpbmgp)からインストールできます. 一人でも多くの方に利用いただければ幸いです.
ソースコードは[こちら](https://bitbucket.org/dhun-h/lazy-applicant)から閲覧できます.


## ストレージとは

拡張機能で永続化したい情報を保存する仕組みです.
[Chrome-Javascript-API](https://developer.chrome.com/extensions/api_index)から次の３つのストレージエリアが提供されています.

* sync
* local
* managed

ストレージ系のAPIは、すべてコールバック関数で処理結果を受け取ります.
UIをブロッキングしないための配慮なのでしょうが、使い勝手は良くありません.

```js:保存処理の例
function saveChanges() {
    var theValue = textarea.value;
    chrome.storage.sync.set({'value': theValue}, function() {
        console.log('Settings saved');
    });
}
```

詳細は[公式サイト](https://developer.chrome.com/extensions/storage)を参照してください.

## プラグインで実装したこと
* チケット登録画面からデータ取得と保存
* オプション画面からデータ取得と保存、削除。　ストレージ使用状況の取得。


## マニフェストファイルの設定 (抜粋)

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

詳細は[公式サイト](https://developer.chrome.com/extensions/background_pages#manifest)を参照してください.

### permissions

プラグインが必要とする権限です.
ストレージを利用する場合は`storage `を指定します.


## ストレージエリア

### sync

Chromeにより同期されるストレージエリアです.
Chromeにログインした状態であれば、保存したデータは異なるデバイスにも自動的に同期されます.

とても魅力的な機能ですが、利用制限制限がかなり厳しいためプラグインでは利用していません.

### local

ブラウザ内に閉じたストレージエリアです.
利用制限はありますが、syncほど厳しくはありません.

プラグインでは、localを利用しました.

### managed

ドメイン管理者により設定され、拡張機能からは読取専用となるストレージエリアです.
といってもまったく調べていないため、これ以上の説明はできません.

### 利用制限

syncとlocalには、利用制限が設けられています.
後述するストレージエリアのメソッドを呼び出した際に利用制限に違反した場合は、[runtime.lastError](https://developer.chrome.com/extensions/runtime#property-lastError)にエラー情報が設定されます.

|制限項目|syncのしきい値|localのしきい値|意味合い|
|---|---:|---:|---|
| QUOTA_BYTES | 102,400 | 5,242,880 |拡張機能で保存できるバイト数の上限です|
| QUOTA_BYTES_PER_ITEM | 8,192 | - |１項目で保存できるバイト数の上限です|
| MAX_ITEMS | 512 | - |拡張機能で保存できる項目数の上限です|
| MAX_WRITE_OPERATIONS_PER_HOUR | 1,800 | - |１時間あたりの更新回数の上限です|
| MAX_WRITE_OPERATIONS_PER_MINUTE | 120 | - |１分間あたりの更新回数の上限です|

バイト数は、キーと値をそれぞれ`JSON.stringify`して加算した値になります.
項目数は、トップレベルの項目数になります.

例えば下記のサンプルデータは、次のように計上されます.

* 項目数＝2
* 全体のバイト数＝48
* 項目hogeのバイト数＝20 (`"hoge"`と`"col1":"value"`のバイト数の和)
* 項目fugaのバイト数＝28 (`"fuga"`と`"col1":10,"col2":false`のバイト数の和)

```json:サンプルデータ
{
  hoge: {
    col1: 'value'
  },
  fuga: {
    col1: 10,
    col2: false
  }
}
```


## ストレージエリアのメソッド

プラグインで利用したストレージ系のAPIを説明します.

[公式サイト](https://developer.chrome.com/extensions/storage#type-StorageArea)

### get

ストレージエリアからデータを取得します.

* 第１引数には、取得するデータのキー文字列などを指定します.
	* 省略する ... すべてのデータを取得します.
	* キー文字列 ... 指定したキーに対する項目を取得します.
	* キー文字列の配列 ... 指定したキーに対する項目をすべて取得します.
	* オブジェクト ... 指定したオブジェクトのキーに対する項目を取得します. キーが存在しない場合は指定したオブジェクトの値が初期値になります.
* 第２引数には、取得したデータを処理するコールバック関数を指定します.

```js
var defaults = {};
defaults.hoge = {
    col1: 'no data';
};

chrome.storage.local.get(defaults, function(items) {
    console.log(items.hoge.col1);
});
```

### set

ストレージエリアにデータを永続化します.

* 第１引数には、永続化するデータを指定します.
* 第２引数には、コールバック関数を指定します.

```js
var entity = {};
entity.hoge = {
    col1: 'new data';
};

chrome.storage.local.set(entity, function() {
    console.log('stored');
});
```

### remove

ストレージエリアからデータを削除します.

* 第１引数には、削除するデータのキー文字列などを指定します.
	* キー文字列 ... 指定したキーに対する項目を削除します.
	* キー文字列の配列 ... 指定したキーに対する項目をすべて削除します.
* 第２引数には、コールバック関数を指定します.

```js
var key = 'hoge';

chrome.storage.local.remove(key, function() {
    console.log('removed');
});
```

### getBytesInUse

ストレージエリアへの登録バイト数を取得します.

* 第１引数には、取得するデータのキー文字列などを指定します.
	* 省略する ... すべてのデータの合計バイト数を取得します.
	* キー文字列 ... 指定したキーに対する項目のバイト数を取得します.
	* キー文字列の配列 ... 指定したキーに対する項目の合計バイト数を取得します.
* 第２引数には、取得したデータを処理するコールバック関数を指定します.

```js
var defaults = {};
defaults.hoge = {
    col1: 'no data';
};

chrome.storage.local.get(defaults, function(bytesInUse) {
    console.log(bytesInUse);
});
```


## tips

### ストレージエリアを確認するスクリプト

バックグラウンドページやオプションページを実装済みであれば、次の手順で登録されたデータを確認できます.
コンテントスクリプトの対象Webページからは、chromeにアクセスできないため実行できません.

1. バックグラウンドページやオプションページを表示
2. デベロッパーツールを起動する
3. コンソールで下記のスクリプトを実行する

```js
chrome.storage.local.get(function(items) { console.log(items) })
```

### ストレージエリアを枯渇させるスクリプト(バイト)

コンソールで下記のスクリプトを実行します.
appendDataSizeを適宜調整してください.

```js:overflow-byte.js
storage = chrome.storage.local;
appendKeyPrefix = 'overflow-';
appendDataSize = 5000 * 1024 - 200;
storage.get(function(e) {
    keys = [];
    $.each(e, function(key, value) {
        if (key.startsWith(appendKeyPrefix)) {
            keys.push(key);
        }
    });
    storage.remove(keys, function() {
        entity = {};
        for (i = 0; i < 10000; i++) {
            key = appendKeyPrefix + i;
            value = '0'.repeat(512);
            entity[key] = value;
            if (JSON.stringify(entity).length > appendDataSize) {
                break;
            }
        }

        storage.set(entity, function() {
            storage.get(function(e) {
                console.log(e);
            });
        });
    });
});
```

### ストレージエリアを枯渇させるスクリプト(項目数)

コンソールで下記のスクリプトを実行します.
appendKeyCountを適宜調整してください.

```js:overflow-keys.js
storage = chrome.storage.sync;
appendKeyPrefix = 'overflow-';
appendKeyCount = 500;
storage.get(function(e) {
    keys = [];
    $.each(e, function(key, value) {
        if (key.startsWith(appendKeyPrefix)) {
            keys.push(key);
        }
    });
    storage.remove(keys, function() {
        keys = {};
        for (i = 0; i < appendKeyCount; i++) {
            keys[appendKeyPrefix + i] = {};
        }
        storage.set(keys, function() {
            storage.get(function(e) {
                console.log(e);
            });
        });
    });
});
```

## 説明で紹介したプラグインのソースコード

* [manifest.json](https://bitbucket.org/dhun-h/lazy-applicant/src/f371e1be0e9d66311bf81555e098b71c25886eb7/src/manifest.json?at=master&fileviewer=file-view-default)
* [db-functions.js](https://bitbucket.org/dhun-h/lazy-applicant/src/f371e1be0e9d66311bf81555e098b71c25886eb7/src/js/common/db-functions.js?at=master&fileviewer=file-view-default)
* [overflow-byte.js](https://bitbucket.org/dhun-h/lazy-applicant/src/f371e1be0e9d66311bf81555e098b71c25886eb7/debug/overflow-byte.js?at=master&fileviewer=file-view-default)
* [overflow-keys.js](https://bitbucket.org/dhun-h/lazy-applicant/src/f371e1be0e9d66311bf81555e098b71c25886eb7/debug/overflow-keys.js?at=master&fileviewer=file-view-default)