唐突にブラウザを拡張させる

ブラウザを拡張するにあたって

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のリクエストを自動でブロックしてくれる。

またの機会に

Native messaging