javascriptで動画の明るさを調整できるchrome拡張機能を作ったから、作り方を1から解説してみる

#初めに
[adjusting the video bright](https://chrome.google.com/webstore/detail/kpcjmbjaopdgkfgjbpggiibcgfipjajn/publish-accepted?authuser=1&hl=ja)
Youtubeやニコニコ動画の動画の明るさを調整できるchrome拡張機能です。

地味に便利な拡張機能と思います。

自分なりの解釈で書いているので、間違っているところがあるかもしれないので参考程度に見ておいてください。

##必要なファイル  
- index.html (browser actionで開くときに必要)
- background.js (backgroundで必要)
- content.js (content_scriptで必要)
- manifest.json (chrome拡張機能の名前や説明、使うchrome APIなどを記述するときに使う 必須なファイル)
。

##まとめる
まず、コードを書く前にやるべきことをまとめます。

**1,ポップアップ画面はどのような画面にするか**
明るさを調整するだけなので、シンプルでいいです。

input type="range"で調整できるようにして、それを左に置き,
決定ボタンは右に置くようにします。
決定ボタンを押せば、動画が暗くなるようにします。

![popup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573077/5f101d0a-bd6d-44ad-f84d-190a584e01f0.png)


汚くてすみません....
右上のiconをクリックすればポップアップが表示されて、rangeが左,ボタンが右 という感じです。
最低でもinputタグのrangeタイプとbuttonタグがあれば十分です。あとはお好みのデザインにしても構いません。

**2.必要なjavascriptファイル。メッセージパッシング**

まずyoutubeの画面を暗くするには、cssであれこれやるのですが、それをするためにはDOM操作が必要になります。
ポップアップに使うHtmlファイルの外部jsファイルでやればいいかと思うとできません。

DOM操作を行うためのJsファイルは決まってます。
manifest.jsonにあるcontents_scriptに指定したjsファイルです。

ポップアップのhtmlファイルにcontent_scriptsに指定したjsファイルを外部jsファイルとしてdom操作をしたら、出来るのかというと,これもできません。

ポップアップするhtmlファイルをこの記事では拡張機能側と呼んでおきます。
拡張機能側からdom操作をすることはできません。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573077/9fd0dc47-213f-f470-12cf-4cf3c75ff1d7.png)


じゃあどうすればいいのかというと、拡張機能側からcontent_scriptsのjsファイルに必要なデータを渡して、そのjsファイル(content.js)でdom操作を行うようにすればいいことです。

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573077/b7201830-3f55-d89c-5453-f4db18f985e8.png)


htmlだけじゃcontent_scriptsにデータを送信できないので、外部jsファイルでcontent_scriptsに送信します。
またデータを送信するにはmanifest.jsonのbackgroundに指定されたbackground.jsがchromeAPIを使い、送信していきます。

流れをまとめると

1,拡張機能側で必要なinputの値を取って、決定ボタンを押す

2,外部jsファイル(background.js)がinputの値などのデータをcontent_scriptsに送信

3,content_scriptsはデータを受け取り、dom操作でページに反映する。

このような感じです。

この送信したり受け取ったりすることを**メッセージパッシング(message passing)**と言います。

##コードを書いていく

**1,DOM操作で、動画を暗くするやり方を知る。**
まず,暗くするやり方を身につけましょう。

Youtubeの動画の画面をデベロッパーツールで見ます。すると、videoタグでclass属性はvideo-streamなどが記載されていることが分かります。
では、そこに filter: brigtness(0.5); とデベロッパーツールの下の方にある、cssを入力できる場所に入力してみると画面が暗くなります。

>cssのfilterプロパティとは画像や動画を加工するために使われています。
特に画像に使われます。
MDNにbrightness以外のものもあるので見てください。

つまり、content_scriptsで指定したjsファイル(content.js)で、videoタグのclass属性を取得さえすれば出来るということです。

```javascript
let movie = document.getElementsByClassName("video-stream");
movie[0].style.filter = "brightness(0.5)";
```
まず1行目でvideo-streamクラスを取得します。
2行目なのですが、なぜmovie[0]の[0]を付けたのかということを簡単に言いますと、特定するためです。
id属性は一つしか付与することはできないのに対し、class属性は複数のタグに同じクラス名を付与できます。

movieだけだと、どのタグについてるvideo-streamクラスなのかわかりません。
なので、ここは一番最初のvideo-streamクラスにfilterプロパティをつけたいので、movie[0]としています。  

```
<div class="video-stream">0</div> //movie[0]
<div class="video-stream">1</div> //movie[1]
```
一つ目のタグを取りたいなら[0]をつけ、2つ目のタグをとりたいなら[1]をつけるようにする。
なお、id属性をとるのなら、[0]などについては必要ありません。

**2,manifest.jsonを作る**

まずサンプルをここに載せて置きます。  

```manifest.json
{
   "manifest_version": 2,
   "name": "",
   "description": "",
   "version": "1.0",
   "browser_action": {
     "default_icon" : "",
     "default_title": "",
     "default_popup": ""
   },
   "icons":{
     "16":"16x16....",
     "
   }
   "permissions" : [
   ],
   "content_scripts": [{
       "matches": ["<all_urls>"],
       "js": [""]
   }],
   "background":{
     "scripts": [""],
     "persistent": false
   }
 }
```

###masnifest.jsonの書き方
manifest.jsonにはこういう感じで書いていきます。
まず、**"manifest_version" : 2**とありますが、manifestのバージョンで、現在バージョンが2であるためです。これは**絶対にこのまま書いておきましょう**

**name**とは**拡張機能の名前**です。

**description**とは**拡張機能の説明**です。どのような拡張機能か書きましょう。

**version**とは**拡張機能のバージョン**を指します。なにか変えたりしてアップデートを行った場合、versionも変えましょう。作成した拡張機能をアップデートをして、再びウェブストアにアップロードするときversionを変えてなかったら、エラーガ起こります。

続いてbrowser_actionについて

**default_icon**は**拡張機能のアイコン**です。
38x38 の大きさの画像を作成して、指定してあげてください。

**default_title**は右上にある拡張機能の**アイコンにマウスオーバー(マウスを乗せる)すると出てくる名前**です。

**default_popup**に関しては,**htmlファイル**を指定します。
指定した場合、アイコンをクリックすると、ポップアップが出てきます。
指定しなかった場合,何も出てきません。

**icons**に**指定された画像はウェブストアの時に使います**
16x16 48x48 128x128の大きさを指定してあげてください

**permissions**では**chrome API**を使いたいときに記述します。
permissionsに書かなくても使えるchromeAPIはあります。

**content_scripts**は**dom操作**をするときに使います。
**maches**には**正規表現**で書き、拡張機能を使う対象のサイトを書きます。
**js**では**動作させるスクリプト**を書きます。
content_scriptsで、今見ているタブ(サイト)をDOM操作で色々と書き換えることができます。

**background: バックグランドページとイベントページがあります。**
今はイベントページが推奨されています。
**scripts** で  **バックグランドで動作するjsファイル**を指定します。
persistentはfalseにすると、イベントページとなり何も書かなかったらバックグランドページとなります。
バックグラウンドページは裏側ではずっと動いているのに対して、イベントページでは必要なときに動くので**persistent: falseは書いておきましょう。**

**ポップアップ画面を作る**

次にポップアップ画面をデザインしていきます。
これは好きなようにして構いませんが、最低でも  

```
  <input type="range">
  <button></button>
```

この二つは必要です。

**3,chrome APIをリファレンスを読みながら作っていく。**
基本的に chrome api を読んでいきながら作ってみましょう。

まず,どんなapiを使うのかまとめると

[chrome tabs api](https://developer.chrome.com/extensions/tabs)
[chrome runtime api](https://developer.chrome.com/apps/runtime)

これらを使っていきます。
tabs APIで送信して、runtime APIで受け取るようにします。
なお**tabs API**は**backgroundで動くjavascriptでないと動作しない**ので、ポップアップするHtmlファイルの外部jsにbackground.jsを使います。

つまり
1,htmlのinputタグの値をbackground.jsで取得し、tabsAPIを使って送信

2,content_scriptsのjsで,background.jsが送信したデータをruntime APIで受け取る

3,受け取ったデータを使いDOM操作をやる。

**background.jsを書いていこう!**  

```background.js
let btn = document.getElementById('btn'); //button を取得
let col = document.getElementById('elem'); // input type=range を取得
```
ボタンをクリックしたら、送信したいので  

```background.js
btn.addEventListener('click',function(){
    //ここにapiを記述
}
```
このように記述します。
まずchrome tabs apiに行きます。tabsAPIで使いたいメソッドは、


- query()
- sendMessage()

この二つのメソッドとなります。

まずqueryメソッドについて学んでいこうと思います。

> chrome.tabs.query(object queryInfo, function callback)

このようなコードとなっています。何が何だかわからないと思いますが見ていきましょう。
queryメソッドの引数にはobject queryInfo, とあります。最後にコンマ( , )があるので、第一引数はここまでということが分かります。

まずobjectとありますが、これは型を示しています。
javascriptでオブジェクトを書くときは,  

```sample.js
let obj = {
  "***": ***
}
```
と書くように、このobject queryInfoも{}で囲んであげて、
その中に ○○○:○○○と書いていきます。  

```sample.js
chrome.tabs.query({}, function callback)
↓
chrome.tabs.query({○○○:○○○, ○○○:○○○}, function callback)
```

このような感じになります。この{}の中にjsonと同じように書いていきます。
公式サイトを見ますが、
active pinned audibleとずらっと書いてあります。その左にbooleanと書いてありますが、これが何を示しているのかというと、簡単に言えば設定です。
booleanというとtrueやfalseということなので、
{active: true} また{active: false}このような感じで書けということです。

ここで使うオプションは**active**と**currentWindow**の二つのオプションです。
ちなみにどっちもbooleanです。

**active**はタブがウィンドウでアクティブかどうか。とグーグル翻訳で出ています。
つまり**今このタブを開いているかどうか**です。
trueにしましょう。

**currentWindow**はタブが現在のウィンドウにあるかどうかです。
つまり、**今見ているサイトかどうか**です。これもtrueにしましょう。

この二つのオプションをtrueにすることで、開いているサイトで尚且つ、今見ているサイトの情報を取得することになります。  

```sample.js
chrome.tabs.query({active: true, currentWindow: true }, () => {
})
```
object  queryInfoが第一引数に対して、第二引数はfunction callbackです。
引数の中に関数があるため、第二引数はコールバック関数であることは一目瞭然です。

コールバック関数の引数には,object queryInfoで指定されたタブ(サイト)の情報があり、それをsendMessageメソッドに渡します。  

```sample.js
chrome.tabs.sendMessage(integer tabId, any message, object options, funct
ion responseCallback)
```
第一引数には、見ているサイトのtabIdを書かないといけないので、queryメソッドのコールバック関数にサイトの情報 (tabId)があるので、使います。  

```sample.js
chrome.tabs.query({active: true, currentWindow: true},tab => {
  chrome.tabs.sendMessage(tab[0].id, any message, object options, fu
  nction responseCallback)
})
```
**tab[0].id**で**tabIdを取得**できます。

**第二引数**には**送信したいデータ**を指定します。
公式サイトには送信するメッセージ。このメッセージは、JSONで送信可能なオブジェクトである必要がありますと書いています。
つまりjson形式で書いてくださいということです。

また送信したいデータはinput type="range"の値です。  

```sample.js
let col = document.getElementById('elem'); // input tag

chrome.tabs.query({active: true, currentWindow: true},tab => {
  chrome.tabs.sendMessage(tab[0].id, { dark : Number(col.value)})
})
```
{dark : Number(col.value)}と第二引数に書きました。
Numberメソッドで、引数にあるinput range の値を数値に変えています。

第三引数からに関しては必要ないと思っています。
まず第一、このinput rangeの値さえcontent_scriptsに渡せばいいのですから、一方的なんですよね。

これをまとめたコードがこちらです。  


```background.js

let btn = document.getElementById('btn');
let col = document.getElementById('elem');

btn.addEventListener('click',function(){
 chrome.tabs.query({active: true, currentWindow: true},tab => {
   chrome.tabs.sendMessage(tab[0].id,{ dark : Number(col.value) })
 })  
})
```
これでボタンをクリックすればapiが実行され、content_scriptsに送信されます。

次はcontent.jsで送信されたデータを受け取り、dom操作で反映させます。

**content.jsを書く**

受け取る方法は簡単で, 
**runtime api** の**chrome.runtime.onMessage.addListener()**を使うだけです。

```content.js
chrome.runtime.onMessage.addListener(function callback)
```
引数の中に関数functionがあるのでコールバック関数となります。
コールバック関数の引数には,

第一引数に**message**, 第二引数に**sender**, 第三引数に**sendResponse**があります。  

```content.js
chrome.runtime.onMessage.addListener(
  function(message, sender, sendResponse){
    
  }
)
```

受け取ったデータをDOM操作するというシンプルなことですから、**第一引数のmessageだけで、問題ない**です。
messageには送信したデータが入っています。

あとは、最初に学んだDOM操作を関数の中に書いてあげます。

```content.js
chrome.runtime.onMessage.addListener(
  function(message, sender, sendResponse){
    let movie = document.getElementsByClassName('video-stream');
    movie[0].style.filter = `brightness(${message.dark})`
  }
)
```
`brightness(${message.dark})`はテンプレートリテラルを使ってます。バッククォートで囲んであげてください。

##作った拡張機能を読み込んでみよう
これで拡張機能は作れたので読み込んでみましょう。

1, まずブラウザを開き,右上の3つの点をクリック
2, その他のツールをクリック出てきた、拡張機能をクリック
3, パッケージ化されていない拡張機能を読み込むをクリックし、フォルダを選択
4,完了です。ページに戻り右上に見ると追加されているのが分かります。
エラーの場合は、manifest.jsonにちょっとおかしいところがあります。なおしましょう。

##ウェブストアにアップロードしよう。
実際にアップロードするのは、この記事で作った拡張機能ではなく、オリジナルの拡張機能でお願いします。

1.chromeウェブストアに行く。
2,右上にある歯車マークを押し,デベロッパーダッシュモードを選択
3,1ドル(だいたい100円)を払う。
4,作ったフォルダを圧縮してzipにしてから、アップロード。
5,ダッシュボードに行き、英語で拡張機能の説明を書き、完了したら審査に出す。自分は英語が書けないので、[DeepL](https://www.deepl.com/ja/translator)を使い書きました。

これでアップ完了です。自分はアップしてから五日ほどで審査が終わり、ストアに出せるようになりました。最初は検索しても出てきませんでしたが、2日ほど待つと後ろの方ですが検索に出てくるようになりました。
またアップデートしたものを、ウェブストアに出すとき、審査されますが自分は1日で終わりました。

#終わりに
初めて拡張機能を作ってみましたが、chromeAPIがちょっと難しかったなと思います。
[adjusting the video bright](https://chrome.google.com/webstore/detail/kpcjmbjaopdgkfgjbpggiibcgfipjajn/publish-accepted?authuser=1&hl=ja)という拡張機能をできれば試してほしいと思います。
UIが崩れてるかもしれないので、もし崩れてたら教えてほしいです。
お願いします！