唐突にブラウザを拡張させる
ブラウザを拡張するにあたって
- この文書はchromeで実験したもので他のブラウザは考慮していない
- chromeの拡張に対する仕様が変化していて公式ドキュメントが役に立たない(主にUIボタンの部分)
- どういったコンテキストがあるのかの理解
- そのコンテキストがどのAPIを使えるのかの理解
manifest.json
{

	"manifest_version": 2,

	"name": "content-script",

	"version": "0.1",

	"background": {

		"scripts": ["background.js"],

		"petsistent": false

	},

	"content_scripts": [

		{

			"matches": ["*://*example1.localhost/*"],

			"js": ["./example1.js"]

		},

		{

			"matches": ["*://*example2.localhost/*"],

			"js": ["./example2.js"]

		}

	],

	"permissions": [

		"tabs",

		"webRequest",

		"webRequestBlocking",

		"<all_urls>"

	]

}
ざざっとキーバリューで要素が並んでいるがpermissionsについて書いておく。permissionsのall_urlsはbackgroundやcontent_scriptsを実行するページを指定するためのものではなく、記載のあるAPIを使うための制限をurlベースで行うものである。上記であればどのURLでもbackground.js内で記載したAPIが使えるということになる。
タブの変化を検知する
chrome.tabs.onUpdated.addListener((id, info, tab )=> console.log(info));



/*

この順に発生する

1. {status: "loading"}

2. {favIconUrl: "url......"}

3. {status: "complete"}

*/
background.js内でtabs APIを使うことでタブが持つ情報を検知できる。
ただし、backgroundからDOMへアクセスできないため、content scriptを使うかchrome.tabs.executeScript()で指定タブに対してjsを実行するかになる。
以下はcontent scriptのrun_atを使ってそれぞれのタイミングでdocumentがどうなっているかのテストである。
// content-script.js

"use strict";

console.log(document.body);



/*

result

document_start => head、body共に空

document_end & document_idle => いずれも要素はjs実行後のもの。違いがわからん。

*/
メッセージング
メッセージングは「one-time requests」と「long-lived connections」の2種類が存在する。
one-time requestsの場合
content scriptを起点にbackgroundからレスポンスをとるサンプル。
// background.js

chrome.runtime.onMessage.addListener((message, sender, response) => {

	response({message: "thanks!"});

});



// content-script.js

chrome.runtime.sendMessage({message: "presents!"}, response => {

	console.log(response.message);

});
次にbackgroundを起点にしてcontent scriptにリクエストを送ってレスポンスをとるサンプル。下記であればdomが完成したあとのメッセージだけを拾うことになる。
// background.js

chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {

	if(changeInfo.status == "complete"){

		chrome.tabs.sendMessage(tabId, {message: "present"}, response =>console.log(response.message));

	}

});



// content-script.js

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => sendResponse({message: "goodbye"}));


background起点のリクエストだとcontent scriptのrun atが大きく関わってくるのでchangeInfo.statusできっちり見張っておく必要がある。こうなってくるとmanifest.jsonで指定するcontent scriptコンテキストよりもchrome.tabs.executeScript()でbackgroundからjsコードを挿入してやるほうがフロー的に安全なのかもしれない。
2020年7月14日時点でリクエストに対するレスポンス機能は廃止予定。(https://github.com/mozilla/webextension-polyfill/issues/16#issuecomment-296693219)
long-lived connectionsの場合
// background.js

chrome.runtime.onConnect.addListener(port => {

	port.onMessage.addListener(message => {

		switch(message.question){

			case 1:

				port.postMessage({answer: 2});

				break;

			case 3:

				port.postMessage({answer: 4});

				break;

		}

	});

});



// content-script.js

let port = chrome.runtime.connect({name: "cry"});

port.onMessage.addListener(message => {

	switch (message.answer){

		case 2:

			port.postMessage({question: 3});

			break;

		case 4:

			break;

	}

});

port.postMessage({question: 1});
runtime.connectでportを開いてやり、そのport内でmesageをやりとりする。runtime.connectのnameを変えて複数作ってruntime.onConnect内でハンドリングして分岐させることも可能。
one-time requestsと同様にbackground側でtabs.connectでportを開くこともできる。
ロードで発生するリクエストを検知する
chrome.webRequest.onBeforeRequest.addListener(listener, filter, info);
background.js内でwebRequest API、webRequestBlockingを使うことで画像ファイル、jsファイル、xmlhttprequestなどのリクエストを検知できる。filterでurls、typesを指定すればそれなりにそのurlのリクエストを自動でブロックしてくれる。