404を許さないChrome拡張機能

調べもの中にリンク切れページや、削除済みページに出会いすぎてイライラしてたのでchrome拡張機能の作り方の勉強のついでに404のページに対抗する拡張機能を作ってみました。

github: https://github.com/mugi111/page-not-found-detector

# 作り方
まずはmanifestファイルを作成していきます。
出来上がりは↓のようになります。

```json:manifest.json
{
    "manifest_version": 2,
    "name": "404 DETECTOR",
    "description": "",
    "version": "1.0.0",
    "content_scripts": [{
        "matches": ["http://*/*", "https://*/*" ],
        "js": ["script.js"],
        "css": ["style.css"]
    }],
    "background": {
        "persistent": true,
        "scripts": ["background.js"]
    },
    "browser_action": {
        "default_title": "404 detector",
        "default_popup": "popup.html"
    },
    "permissions": [
        "tabs",
        "webNavigation",
        "background",
        "http://*/*",
        "https://*/*"
    ]
}
```
下がそれぞれの項目の詳細です。

|プロパティ名||概要|
|:-----------------|:------------------|:---|
|manifest_version||マニフェストのバージョン|
|name||拡張機能の名前|
|description||拡張機能の説明|
|version||拡張機能のバージョン|
|content_script|matches|スクリプトの読み込みを行うページの指定|
||js|読み込まれるjavascript|
||css|読み込まれるcss|
|background|persistent|永続化の有無|
||scripts|バックグラウンドで読み込まれるスクリプト|
|browser_action|default_title|バーに表示されるときの名前|
||default_popup|バーのアイコンをクリックしたときに開くhtml|
|permissions||拡張機能に与える権限|


## 当初の予定
最初はgoogleの検索結果一覧のリンクに対して存在しないリンクに対してマークをつけようとしていました。

こんな感じで`content_script`で指定した`script.js`から、リンクのリストを`chrome.runtime.sendMessage`を使用して`background.js`に送信していました。
`background.js`のほうで`chrome.runtime.onMessage.addListener`で設定しておくことで`chrome.runtime.sendMessage`を拾うことができるようになります。

ページの存在の確認については、`XMLHttpRequest`でレスポンスのステータスコードを見ることで確認しようとしてました。

```javascript:script.js
var atags = document.getElementsByTagName("a");
var hrefs = [];
for (let index = 0; index < atags.length; index++) {
  href = atags.item(index).href;
  chrome.runtime.sendMessage({href});
  hrefs.push(atags.item(index).href);
};

console.log("hello", hrefs);
```

```javascript:background.js
const ping = (url) => {
  var xhr = new XMLHttpRequest();

  xhr.onreadystatechange = () =>{};

  xhr.open("GET", url, true);
  try {
    xhr.send(null);
  } catch (err) {
    console.log(err);
  }
}

chrome.runtime.onMessage.addListener(
  async function(request, sender, callback) {
    console.log(request);
    ping(request.href);
  }
);
```

## 結果
ここで何が起こったかというと、、、
見慣れたあいつが出てきました。
<img width="347" alt="google robot.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256522/5fe45fe2-ef26-3966-d184-3be7d2958a6b.png">
「お使いのコンピュータ ネットワークから通常と異なるトラフィックが検出されました。このページは、リクエストがロボットではなく実際のユーザーによって送信されたことを確かめるものです。」
だそうです。。

## 引っかかったところ
最初`script.js`からリクエストを送信していたところ、
CORB(Cross Origin Read Block)ではじかれまくりました。
<img width="396" alt="corb.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256522/7be8df6d-bc98-7f2c-0e65-0bbea76ba65c.png">

### 回避策
`background.js`からリクエストを送ることで回避できました。


## 別の案
ここでそこら辺のリンクに無差別にリクエストを投げるのはやめようと思い、飛ぶ先のリンクだけに絞りました。
`chrome.webNavigation.onCommitted.addListener`で`webNavigation`の`onCommitted`を拾うようにしました。
[webNavigation参考ページ](https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/API/webNavigation)

ページの存在の確認は当初の案と同じ方法をとりました。
ステータスコードが404だった場合は`tabId`を指定して`goBack`メソッドを呼び出すことでブラウザバックさせています。

urlは`urlMacthes`でhttpかhttps（一般的なページ..?）のみを拾うようにしました。これは、`chrome://settings`や`chrome://extensions`のような特に拾わなくていいようなページを除外したかったためです。

```javascript:script.js
const ping = (url) => {
  var xhr = new XMLHttpRequest();

  xhr.onreadystatechange = () =>{};

  xhr.open("GET", url, false);
  try {
    xhr.onload = function () {}
    xhr.send();
    return xhr.status;
  } catch (err) {
    console.error(err);
  }
}

chrome.webNavigation.onCommitted.addListener((details) => {
  if (details.frameId !== 0){
    return;
  }
  const status = ping(details.url);
  console.log(status);
  if(status === 404) {
    chrome.tabs.goBack(details.tabId);
  }
}, {url: [{urlMatches: "http://*/*"}, {urlMatches: "https://*/*"}]});
```

![notfound.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256522/3fe6c947-a61c-66b7-21da-c773ae3ad52c.gif)
動作はこんな感じです。
存在しないページに飛ぼうとすると強制的にブラウザバックされます。

これでキーボードを壊す心配もありません。
<img width=200 alt="computer_keyboard_yatsuatari_man.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256522/a12e73a8-56f0-931a-665c-bf4a83ab3b99.png" />