はじめに
この記事は、JavaScriptの基本的な読み書きができる人が、Chrome拡張機能の仕組みをざっくり理解するためのまとめです。
Chrome拡張の中身はHTML・CSS・JavaScriptで、特別な言語は使いません。ただ、普通のWebアプリとは少し違う独自のルールがあり、それらを知らずに書き始めると、動かない原因が分からず詰まりがちです。
この記事では、実際の拡張機能のコードを例にしながら、最初に押さえておくべき概念だけを短くまとめています。ここに書いてあることが分かれば、あとは公式ドキュメントやサンプルコードを見ながらちょっとずつ作っていけると思います。
基本的な設計思想
複数のパーツがそれぞれ独立した環境で動き、必要な時だけメッセージで連携して処理を行うというのが基本的な設計思想
メッセージパッシング
各ファイルは間で変数を参照や、メソッドの呼び出しなどは、メッセージパッシングAPIを使用する。
(直接の参照・呼び出しはできない)
パーツ(ファイル)毎に使えるAPIが決まっている
例えばContent Scriptでは主にフロントエンド用途のAPIに限定されているため、高度な機能はService Workerに依頼して間接的に行う必要がある。一方でService Workderではサーバー通信などバックグラウンド処理を担当する多くのAPIが使用可能
登場する主なパーツ
(1) UIページ(popup.html/popup.css/popup.js)
これについては特に特記事項なし
(2) Content Scripts
DOM操作などフロントエンド担当
// content.js
// メッセージパッシング
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action !== 'extractMetadata') return
const title = document.title
// ブラウザ標準のAPIでDOMの値を取得
const published_at = document
.querySelector('meta[property="article:published_time"]')
?.getAttribute('content')
?.slice(0, 10)
sendResponse({
title: title,
published_at: published_at
})
})
(3) Service Worker
- バックエンド担当
- イベント駆動
- DOMに直接アクセスできない
- タブ選択のイベントに対してメッセージ送信
// popup.js async function lookupBuzz(url) { try { // メッセージパッシング const response = await chrome.runtime.sendMessage({ action: 'lookupBuzz', url: url }) if (!response) { throw new Error('buzzが取得できません') } return response } catch (error) { throw new Error(`chrome api error: ${error.message}`) } }
- Service Workerで受信し、actionで指定されたfunctionを呼び出す
// background.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// actionにより
if (message.action === 'lookupBuzz') {
fetchBuzz(message.url)
.then((response) => {
sendResponse(response)
})
.catch((error) => {
sendResponse({ error: error.message })
})
return true
}
})
// Service Workerでは高度なAPIの呼び出しが可能
async function fetchBuzz(url) {
const response = await fetch(....)
📖
chrome.runtime.onMessageリファレンス
📖 非同期レスポンスでreturn trueが必要な理由(Message passing)
activeTabについて
ユーザーの明示的な操作をトリガーにして、一時的なアクセス権を得る仕組み
- 拡張機能を起動した瞬間に現在開いているタブへのアクセス権限が付与される
- タブを切り替え(細かく言うと別のドメインに移動)をするか、タブを閉じた瞬間に権限は消失
権限がある間は、スクリプト注入、タブの情報(URL・タイトル等)の取得、ネットワークリクエストのリスニングなどが可能になる
// 現在のtabのプロパティ値を取得する例
const tabId = activeTab.id
// tab切替時にtabIdを取得して処理する例
chrome.tabs.onActivated.addListener(async (activeInfo) => {
const tab = await chrome.tabs.get(activeInfo.tabId)
await updateBuzzIcon(tab)
})
manifest.json
拡張機能の設定ファイル
{
"name": "fjord-buzz-extension",
"version": "1.0",
"manifest_version": 3, //現行のversion
"background": {
"service_worker": "background.js",
"type": "module" // config.jsをimportするのに必須
},
"description": "Google extension for fjord buzz page",
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [
"content.js"
],
// ページの読み込みが完了後にscriptを注入
"run_at": "document_idle"
}
],
"permissions": [
"cookies",
// タブで表示中のソースから情報(urlやtitleなど)を取得する権限
"tabs"
],
"host_permissions": [
"http://localhost:3000/*"
],
// 拡張アイコン
"action": {
"default_icon": {
"16": "icons/buzz_icon16.png"
},
// アイコンにマウスオーバーした時の文字
"default_title": "fjord-buzz-extension",
// アイコンクリックで開くファイルを指定
"default_popup": "popup/popup.html"
}
}
📖
content_scriptsマニフェストキーの詳細
📖chrome.cookiesAPI リファレンス
📖chrome.actionAPI リファレンス
Illustration by Shahid Mehmood on Unsplash

コメントはまだありません。