広告にモザイクを付けるChromeの拡張機能を作ってみた

# 不快な広告に悩まされてませんか？
　最近、ネットを見ていると「不快な広告」を散見します。筆者はちょっとHなアプリの紹介くらいなら許容範囲なのですが、暴力的な内容のモノは本当にNGなんですよね。あと脳破壊を示唆する漫画とか。
　最初こそ「この広告を表示しない」とオプトアウト設定していたのですが、そういう措置をとれない広告もありますし、なんなら似たような別の広告が表示されることはあり得ます。

「AdBlockでも入れるか？　いやでも、それは申し訳ないし……。まあ、我慢しよう」

　そう思っていたのですが、ある時、筆者の堪忍袋の緒が切れる出来事が起きました。それは防虫剤の広告だったと思うのですが、寝ている人の周りにリアルなGが無数に集まっているデザインだったんです。もうね、思わず「うぎゃ！」と叫んでしまいました。なんでこんな広告を世に出してしまったのか理解に苦しみます。

「決めた、AdBlockerを入れよう！　……でもなあ、AdBlockerを入れたらサイト主に検知されるって聞くし……」

　悩んだ末に私は「広告をブロックはせず、モザイクをかけるだけ」という拡張機能を作ることに決めました！

# 拡張機能の作り方

　拡張機能を作るのは簡単です！　まずフォルダを作ります。その中にmanifest.jsonとcontent.jsというファイルを作ります。manifest.jsonに以下のように記述します。

```json
{
  "name": "AdMosaic",
  "version": "1.0.0",
  "manifest_version": 3,
  "description": "Add mosaic to ads",
  "content_scripts": [{
    "matches": ["*://*/*"],
    "js": [
      "content.js"
    ]
  }]
}
```

name：拡張機能の名前
version：バージョン
manifest_version：3にする
description：拡張機能の説明
content_scripts：拡張機能の設定。配列。
matches：拡張機能が有効になるURL。今回は全サイトで有効化するから`*://*/*`としている。
js：プログラムファイル名。今回は`content.js`

あとは`chrome://extensions/`にアクセス、デベロッパー モードをオンにして「パッケージ化されていない拡張機能を読み込む」をクリックしたら追加できます。

　もはや、メモ帳だけでも作れてしまうレベルで簡単ですね！！

# 広告の検出
　広告は画像、もしくはiframeで実装されていることが多いみたいです。(たぶん)
　そこでまずはiframe要素をすべて取得するコードを書きます。

```js
const iframes = document.getElementsByTagName("iframe");
for(const iframe of iframes){
	//広告か判断するコード
}
```

　もちろん、広告以外にモザイクをかけてしまってはいけないので、広告かどうかを検知できるようにします。今回はtitleが存在するなら、それがadvertisementかadかad contentである物にモザイクをかけてみようと思います。

```js
let title = iframe.getAttribute("title");
if(title !== null){
	title = title.toLowerCase(); //すべて小文字にする
	if(title == "advertisement" || title == "ad" || title == "ad content"){
		hide(iframe); //広告にモザイクをかける
	}
}
```

　あとは広告にモザイクをかける「hide関数」を実装するだけです。

# モザイクをかける

　cssでモザイク（正確にはブラー）をかけるには以下のように書きます。

```css
iframe.ad{
    filter:blur(8px);
}
```

　これをjsから設定すればいいわけですね！
　コードは以下の通り。

```js
function hide(ad){
	ad.style.setProperty("opacity", "0.15", "important");
	ad.style.setProperty("filter", "blur(8px)", "important");
}
```

　広告を薄くして（opacityを0.15に設定）、ブラーフィルタをかけます（filterをblur(8px)に設定）。
　なお、ad.style.opacity="0.15"だと消えないことがあるので、このようにimportant属性を付与して確実に半透明処理が行われるようにします。

# 以上機処理を0.1秒ごとに行う

　広告の中には、ページ読み込み完了時点ではまだ読み込まれておらず、時間差で表示されるものがあります。そこで、「広告を検出したらブラーをかける」という処理を一定時間ごとに行う必要があります。ある関数を一定時間ごとに行うには`setInterval`を使います。

```js
function hideAds(){...中略...}
setInterval(hideAds, 100);//100msごとに関数を呼び出す。
```

# 全ソースコード

```js
function hide(ad){
	ad.style.setProperty("opacity", "0.15", "important");
	ad.style.setProperty("filter", "blur(8px)", "important");
}

function hideAds(){
	const imgs = document.getElementsByTagName("img");
	for(const img of imgs){
		if(img.alt == "Advertisement" || img.classList.contains("img_ad")){
			hide(img);
		}
	}
	const iframes = document.getElementsByTagName("iframe");
	for(const iframe of iframes){
		let id = iframe.getAttribute("id");
		if(id !== null){
			id = id.toLowerCase();
			if(id.startsWith("aswift")){
				hide(iframe);
				continue;
			}
		}
		let title = iframe.getAttribute("title");
		if(title !== null){
			title = title.toLowerCase();
			if(title == "advertisement" || title == "ad" || title == "ad content"){
				hide(iframe);
				continue;
			}
		}
		let aria_label = iframe.getAttribute("aria-label");
		if(aria_label !== null){
			aria_label = aria_label.toLowerCase();
			if(aria_label == "advertisement" || aria_label == "ad" || aria_label == "ad content"){
				hide(iframe);
				continue;
			}
		}
	}
}
hideAds();
setInterval(hideAds, 100);
```