Chrome拡張機能の実践：データ保存と活用

[以前の記事](https://qiita.com/Daken91/items/5b651fa159d5315154ac)で、Chrome拡張機能自体の作成についてを実践することができたので、続いてもう一歩踏み込んだところとして、`chrome.storage.local/sync.set/get` を用いた「データの保存」「（保存した）データの使用」について実践してみます

# TL;DR
`chrome.storage.local.set` , `chrome.storage.local.get` の実践
実践を通じて自分でわかったこと

## 完成形
[GitHubレポジトリ](https://github.com/Daken91/Daken_PlainProducts/tree/main/profileFormWriter)に配置しました

## 要件定義
「データを保存」「（保存した）データの使用」が必要になるプロダクトを作る必要がある
そのため、以前から面倒だと思っていた点として、たまにやっていた「[アンケート](https://c.myjcom.jp/user/tokyo/?sc_pid=my_newtop_user_title)の個人情報入力」（ブラウザの機能だといまいち）について半自動化する、をついでにやってしまおうと考える
何かしらのページに対する「入力フォームに入力する個人情報の保存」（→options.htmlを使う）、「保存した情報を入力フォームに（半）自動入力」する拡張機能を最終形とする
セキュリティ面が不安である＋自分のPCでしか使わないものであるため、`chrome.storage.sync` （Googleアカウントを用いて異なるデバイス間でデータを同期、別デバイスでも使用できるようになる）でなく、 `chrome.storage.local` （PC自体にデータを保存する、5MBまで）を用いる

## 設計
### manifest.json
だいたい前のものと同じなので使いまわしです
ただし、今回はどのページでも動いてほしいので、 `"content_scripts"` の `"matches"` は `["http://*/*", "https://*/*"]` とかにしておきます
また今回は（設定用に）オプションページが要るので、`"options_page": "options.html"` を設定、また `chrome.storage` を使うので `"permissions": ["storage"]` もつけておきます

### options.html
特に工夫も何もなく、 `input` タグで入力欄を作っていくだけになります

```HTML:入力欄一例
<div class="form">姓（漢字）: <input type="text" class="inputForm narrowForm" id="lastNameJPKanji" value="">　　　名（漢字）: <input type="text" class="inputForm narrowForm" id="firstNameJPKanji" value=""></div>
```

「念のため」を考えて、使うと使わないとに関わらず、名前（フル・姓・名／漢字・ひらがな・カタカナ）、電話番号（固定・携帯／ハイフン無し・ハイフンあり）など、さらに英語表記の場合など色々用意しました
後から考えればハイフンとか空白とかで区切るときは、出力時に `split` とか使えばいいだろ、とか、こんな沢山入力項目いらんだろ（実際、後のoptions.jsやcontents.jsまで含めて、作るの面倒だった）とかありましたが、まあ実践ということでいいか、と流します

挙動（保存・読み出し・クリア）の発火のために、ボタンも作ってだいたい完成です
（配置若干乱れてる箇所がありますが、自分用である＋今回の主題ではないということで目をつぶります）

ボタン押した時の挙動は、options.htmlで動くための挙動ということでoptions.jsに書いていきます

### options.js
ここで、まずデータの保存である `chrome.storage.local.set` の実践になります

```js:dataset一例
function dataSet () {
    chrome.storage.local.set(
  {
    "ProfilesJP": {
        "FullName": {
            "Kanji": document.getElementById("fullNameJPKanji").value,
            "Hiragana": document.getElementById("fullNameJPHira").value,
            "Katakana": document.getElementById("fullNameJPKana").value
        },
        "LastName": {
            "Kanji": document.getElementById("lastNameJPKanji").value,
            "Hiragana": document.getElementById("lastNameJPHira").value,
            "Katakana": document.getElementById("lastNameJPKana").value
        },
...
    });
}
```

基本的にはこんな漢字で、JSON型データ（オブジェクト）の各キーを定め、そのキーに対し `:` で区切って保存するデータ（今回はinputタグの入力フォーム内に書いたものなので、 `getElementById` で入力フォームを指定し `.value` で中身を指定する）を当てはめる形になります
この関数を「Save」ボタンを押した時に働かせたいので、「Save」ボタンに `addEventListener` `click` で `dataSet` が発火するように設定しておきます

続いて、保存したデータの読み出しのために `chrome.storage.local.get` の実践です
ここでは、保存したデータを読み出して各入力フォームに表示する（データの確認のため）挙動を作ることで学んでいきます

```js:savedDataLoad一例
function savedDataLoad (){
    chrome.storage.local.get("ProfilesJP", function (value) {
        document.getElementById("fullNameJPKanji").value = value.ProfilesJP.FullName.Kanji;
        document.getElementById("fullNameJPHira").value = value.ProfilesJP.FullName.Hiragana;
        document.getElementById("fullNameJPKana").value = value.ProfilesJP.FullName.Katakana;

        document.getElementById("lastNameJPKanji").value = value.ProfilesJP.LastName.Kanji;
        document.getElementById("lastNameJPHira").value = value.ProfilesJP.LastName.Hiragana;
        document.getElementById("lastNameJPKana").value = value.ProfilesJP.LastName.Katakana;
...
    });
}
```

データロードを行なう `chrome.storage.local.get` 自体は上記のように、第一引数に読み込みたいデータのキー（上記だと `"ProfilesJP"` ）を設定することになります（多分 `set` した時の一番上のキーのどれか？）
第二引数に `function (value)` （あるいは `(value) =>` とアロー関数でも良さそう）を設定して、この関数の中でデータが使用できることになります
データ自体の読み出しとしては、 `value` （第二引数の関数の引数として設定したもの）からピリオドで区切り、保存したデータのキーを書いていくことでデータを指定できますが、この際 `value` の直後に来るものとして、 `chrome.storage.local.get` 自体の第一引数に指定したキーを入れないとちゃんと指定できません

```js:読み込めなかった例
document.getElementById("fullNameJPKanji").value = value.FullName.Kanji;
```

```js:読み込めた正答例
document.getElementById("fullNameJPKanji").value = value.ProfilesJP.FullName.Kanji;
```

ここで `value` だけで最初のキーの代わりのようになると誤解して、 `value.FullName.Kanji` のように書いたところ「そんなデータないぞ」とエラー出されて少し詰まりました

この関数についても、対応する「Load」ボタンを押した時に働かせたいので、 `addEventListener` `click` で `savedDataLoad` が発火するように設定しておきます

上記のコードについて、一部のみ設定して、単体テストと結合テストの中間のようなことを実行します

![AutoInput_Input.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700474/ab693e96-c20b-bdb5-b795-fc0b4aadc5af.gif)

ちゃんと入力したデータを保存した後、入力内容を消去してから読み出すと、入力内容が復活しているので動作確認完了です
すべてのデータに対して同じように設定し、options.jsを完成させます
（時折id名やキー名を間違える凡ミスをちょくちょくやらかし、名付けのてきとーさを後悔）

### contents.js
ちゃんと挙動ができたということで、今度は適用先のページで動かすためのcontents.jsを用意しておきます
やること自体はoptinos.jsで行なったデータ読み込みと同じなので、先に作成した `savedDataLoad` と同じように設定するだけです
実際のページを確認して入力フォームのclass名やname名（今回の適用先と考えているページのフォームにはidが設定されてなかった）で指定し、そこに `value.~` を設定していきます

このとき、発火については対象ページのみにしておきたいこと、各ページでフォームの形式やclass名などが統一されていないであろうこと（A社のアンケートフォームとB社のアンケートフォームで入力フォームの数や形式が全然違うなんてザラなので）を考えて、URLで入力フォームを判別し、その際に適した入力が行なわれるように、URLの中身で異なる入力用関数が動くようにします（今回は1つだけ）
また、場合によっては「やっぱ手で入力したい」となるであろうことも考えて「**半**自動」で働くように、入力用関数の最初に `window.confirm` で自動入力するかどうかを選べるようにします

実際に挙動を確かめた結果がこのような形
![AutoInput_demonstration.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700474/2aa1b00d-9c65-9bfc-d14f-ed383238ccec.gif)

「自動入力しますか？」にOKと応えることで、入力対象の情報が入っていることが確認できました

## 学び
* `chrome.storage.local.set( )` の中身にJSON型で記述することで、データを保存できる
* `chrome.storage.local.get("キー名", function (value) { });` の中身に `value.キー名` と書くことで、データの読み出しができる
    *  `value.キー名` について、最初のキー名は一番上のキー名（chrome.storage.local.getの第一引数に入れたもの）でなければならない
* `window.confirm` の実践

## 実用
目的の一つとして、対象アンケートフォームへの入力（半）自動化もあったので、早速使用してみたところ、しっかりフォームが埋められ、回答速度がだいぶ早くなりました
ただ、同じ会社のアンケートフォームの中でも、質問内容によってはフォームの数やname名が異なり適切に埋められないことも何度か
完全にするには事前にフォーム数を見るとか、フォームの手前に書いてある記述名とかで判別する必要がありそうです（今回は要件的にそこまでしなくていいのでやらない）



実用に向く拡張機能が作れるようになったことで、自分の作業も効率化できそうになってきました
似たような機能持つChrome拡張はすでにあると思いますが、学習も目的の一つなので車輪の再発明おおいに結構！
自分用に最適化できるようカスタマイズしていけそうです