Chrome拡張機能チートシート〈初心者向け〉

chrome拡張機能開発に際してのチートシートです。

##Hello World
ディレクトリ構造は以下。

```
helloworld
┣ manifest.json
┣ popup.html
┗ icon.png
```

```manifest.json
{
  "name": "Hello World",
  "version": "1.0",
  "manifest_version": 3,
  "description": "tamura ha tensai",
  "action": {
    "default_icon": "icon.png",
    "default_title": "Hello World",
    "default_popup": "popup.html"
  }
}
```

"manifest_version"はとりあえず"3"にしておく。

```popup.html
<!DOCTYPE html>
<html>
<body style="min-width:100px">
Hello World!
</body>
</html>
```

開発ファイルの実行方法。
まずchromeのアドレスバーに以下を打ち込む。

```
chrome://extensions/
```

デベロッパーモードをオンにし『パッケージ化されてない拡張機能を読み込む』から、もしくはD&Dでhelloworldフォルダを読み込ませる。
以上の操作で拡張機能が追加されているはず。

##ローカルストレージ
ローカルストレージはブラウザにデータを保存する機能。
以下はメモ帳を作る拡張機能。

manifest.jsonはhelloworldと同じ

```popup.html
<!DOCTYPE html>
<html>
<body style="min-width:250px;min-height:200px">
<textarea id="id_memo" cols="30" rows="10"></textarea><br>
<input type="button" id="id_save" value="Save">
<script src="myscript.js"></script>
</body>
</html>
```

同じディレクトリにmyscript.jsを追加

```myscript.js
// popup.htmlがロードされたときに呼び出し
window.onload = () => {
  // Key = 'Memo' でローカルストレージがロード
  let memo = localStorage['Memo'];
  if (memo == null){
    memo = '';
  }
  document.getElementById('id_memo').value = memo;
}
document.getElementById('id_save').onclick = () => {
  // Key = 'Memo' でローカルストレージがロード
  localStorage['Memo'] = document.getElementById('id_memo').value;
}
```
##自動で特定のページだけで有効になるスクリプト
yahooでは有効になる拡張機能の例。

```manifest.json
{
  "name": "Tokutei Yukou",
  "version": "1.0",
  "manifest_version": 3,
  "description": "tamura saikou",
  "permissions": ["tabs"],
  "background": {
    "service_worker": "event.js"
  },
  "action": {
    "default_icon": "icon.png",
    "default_title": "Tokutei Yukou",
    "default_popup": "popup.html"
  }
}
```

同じ階層にevent.jsを作成。

```event.js
chrome.action.disable();
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status == 'complete'){
    console.log(tab.url);
    if (tab.url.indexOf('yahoo') != -1){
      console.log('enable');
      chrome.action.enable(tabId);
    }
    else{
      console.log('disable');
      chrome.action.disable(tabId);
    }
  }
});
```

これを実行するとyahooでは有効になるが他のページでは有効になる。

その他、manifest.json以下のようにmatchesを設定することで特定のページでのみ動作させることも可能。

```manifest.json
{
  "name": "Tokutei Yukou",
  "version": "1.0",
  "manifest_version": 3,
  "description": "tamura saikou",
  "content_scripts": [
    {
      "matches": ["*://*.wikipedia.org/*"],
      "js": ["matches.js"]
    },
    {
      "matches": ["*://nory-soft.web.app/*"],
      "js": ["matches.js"]
    }
  ]
}
```

##executeScript
executeScriptを使うことで既存のページの読み書きを行うこともできる。

```manifest.josn
{
  "name": "executeScript",
  "version": "1.0",
  "manifest_version": 3,
  "description": "tamura is keito",
  "permissions": ["scripting"],
  "host_permissions": [
    "http://*/*",
    "https://*/*"
  ],
  "action": {
    "default_icon": "icon.png",
    "default_title": "executeScript",
    "default_popup": "popup.html"
  }
}
```


```popup.html
<!DOCTYPE html>
<html>
<body style="min-width:100px">
<input type="button" id="id_Red" value="Red">
<br><br>
<input type="button" id="id_Blue" value="Blue">
<script src="myscript.js"></script>
</body>
</html>
```


```myscript.js
document.getElementById('id_Red').onclick = () => {
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    chrome.scripting.executeScript({
      target: {tabId: tabs[0].id},
      function: setBackGroundColorRed
    });
  });
}
document.getElementById('id_Blue').onclick = () => {
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    chrome.scripting.executeScript({
      target: {tabId: tabs[0].id},
      function: setBackGroundColorBlue
    });
  });
}
function setBackGroundColorRed(){
  document.body.style.backgroundColor = 'red';
}
function setBackGroundColorBlue(){
  document.body.style.backgroundColor = 'blue';
}
```

これを実行すると既に読み込んであるページの背景色を変更し読み直すことができる。
##contextMenus
contextMenusを使って右クリックメニューに拡張機能を登録できる。
以下は選択したテキストを右クリック→メニュー選択でTwitterで検索結果を表示する拡張機能。

```manifest.json
{
  "name": "contextMenus",
  "version": "1.0",
  "manifest_version": 3,
  "description": "tamura baka",
  "permissions": ["contextMenus"],
  "background": {
    "service_worker": "event.js"
  },
  "icons": {
    "19": "icon.png"
  }
}
```


```event.js
chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: 'contextmenus',
    title: 'Search Twitter for \'%s\'',
    type: 'normal',
    contexts: ['selection']
  });
});
chrome.contextMenus.onClicked.addListener((info) => {
  chrome.tabs.create({
    url: 'http://twitter.com/search?q='+encodeURIComponent(info.selectionText)
  });
});
```

##sendMessage
ドメイン間でのデータ通信を行う場合はsendMessageを使う。

```manifest.json
{
  "name": "sendMessage",
  "version": "1.0",
  "manifest_version": 3,
  "description": "tamura unko",
  "permissions": ["scripting"],
  "host_permissions": [
    "https://nory-soft.web.app/*/",
    "https://norys-shop.web.app/*/"
  ],
  "background": {
    "service_worker": "event.js"
  },
  "action": {
    "default_icon": "icon.png",
    "default_title": "sendMessage",
    "default_popup": "popup.html"
  }
}
```


```popup.html
<!DOCTYPE html>
<html>
<body>
<input type="button" id="id_Recv" value="Recv from DomainA"><br><br>
<input type="button" id="id_Send" value="Send to DomainB">
<script src="myscript.js"></script>
</body>
</html>
```


```myscript.js
document.getElementById('id_Recv').onclick = () => {
  chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
    chrome.scripting.executeScript({
      target: {tabId: tabs[0].id},
      function: RecvFromDomainA
    });
  });
}
document.getElementById('id_Send').onclick = () => {
  chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
    chrome.scripting.executeScript({
      target: {tabId: tabs[0].id},
      function: SendToDomainB
    });
  });
}
function RecvFromDomainA(){
  let value = document.getElementById('id_DomainA').value;
  chrome.runtime.sendMessage({method: 'Send', key: 'key', value: value}, () => {
 });
}
function SendToDomainB(){
  chrome.runtime.sendMessage({method: 'Recv', key: 'key'}, (response) => {
    document.getElementById('id_DomainB').value = response.value;
  });
}
```


```event.js
let value;
chrome.runtime.onMessage.addListener((message, sender, sendResponse) =>{
  switch (message.method){
  case 'Send':
    value = message.value;
    break;
  case 'Recv':
    sendResponse({value: value});
    break;
  }
});
```

##ファイルの読み込みとファイルへの保存
Chromeの規定の保存先に任意のファイル名で保存するスクリプト

```manifest.json
{
  "name": "download",
  "version": "1.0",
  "manifest_version": 3,
  "description": "tamura muramura",
  "action": {
      "default_icon": "icon.png",
      "default_title": "downloads",
      "default_popup": "popup.html"
  }
}
```


```popup.html
<!DOCTYPE html>
<html>
<body style="min-width:250px;min-height:200px">
<textarea id="id_memo" cols="30" rows="10"></textarea><br>
<input type="button" id="id_save" value="Save to localStorage"><br>
<input type="button" id="id_savefile" value="Save to File"><br>
<input type="file" id="id_openfile">
<script src="myscript.js"></script>
</body>
</html>
```


```myscript.js
window.onload = () => {
  let memo = localStorage['Memo'];
  if (memo == null){
     memo = '';
  }
  document.getElementById('id_memo').value = memo;
}
document.getElementById('id_save').onclick = () => {
  localStorage['Memo'] = document.getElementById('id_memo').value;
}
document.getElementById('id_savefile').onclick = () => {
  let memo = document.getElementById('id_memo').value;
  let objectURL = window.URL.createObjectURL(new Blob([memo]));
  let a = document.createElement('a');
  a.download = 'download.txt';
  a.href = objectURL;
  a.click();
}
document.getElementById('id_openfile').onchange = function(){
  let file = this.files[0];
  if (file == null) return;
  let reader = new FileReader;
  reader.onload = function(){
    document.getElementById('id_memo').value = reader.result;
  }
  reader.readAsText(file);
}
```