Chrome拡張機能の基本をざっくり解説

はじめに

この記事は、JavaScriptの基本的な読み書きができる人が、Chrome拡張機能の仕組みをざっくり理解するためのまとめです。
Chrome拡張の中身はHTML・CSS・JavaScriptで、特別な言語は使いません。ただ、普通のWebアプリとは少し違う独自のルールがあり、それらを知らずに書き始めると、動かない原因が分からず詰まりがちです。
この記事では、実際の拡張機能のコードを例にしながら、最初に押さえておくべき概念だけを短くまとめています。ここに書いてあることが分かれば、あとは公式ドキュメントやサンプルコードを見ながらちょっとずつ作っていけると思います。

基本的な設計思想

複数のパーツがそれぞれ独立した環境で動き、必要な時だけメッセージで連携して処理を行うというのが基本的な設計思想

📖 Chrome拡張機能の全体像

メッセージパッシング

各ファイルは間で変数を参照や、メソッドの呼び出しなどは、メッセージパッシングAPIを使用する。
(直接の参照・呼び出しはできない)

📖 Message passing

パーツ(ファイル)毎に使えるAPIが決まっている

例えばContent Scriptでは主にフロントエンド用途のAPIに限定されているため、高度な機能はService Workerに依頼して間接的に行う必要がある。一方でService Workderではサーバー通信などバックグラウンド処理を担当する多くのAPIが使用可能

📖 Content Scriptsが直接使えるAPIの一覧

登場する主なパーツ

(1) UIページ(popup.html/popup.css/popup.js)

これについては特に特記事項なし

(2) Content Scripts

DOM操作などフロントエンド担当

📖 Content Scripts

// 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に直接アクセスできない

📖 Extension Service Worker の基本
📖 Service Worker のイベント処理

  1. タブ選択のイベントに対してメッセージ送信
    // 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}`)
      }
    }
    

📖 chrome.runtime.sendMessage() リファレンス

  1. 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について

ユーザーの明示的な操作をトリガーにして、一時的なアクセス権を得る仕組み

  1. 拡張機能を起動した瞬間に現在開いているタブへのアクセス権限が付与される
  2. タブを切り替え(細かく言うと別のドメインに移動)をするか、タブを閉じた瞬間に権限は消失

権限がある間は、スクリプト注入、タブの情報(URL・タイトル等)の取得、ネットワークリクエストのリスニングなどが可能になる 

📖 activeTab パーミッション

// 現在のtabのプロパティ値を取得する例
const tabId = activeTab.id


// tab切替時にtabIdを取得して処理する例
chrome.tabs.onActivated.addListener(async (activeInfo) => {
  const tab = await chrome.tabs.get(activeInfo.tabId)
  await updateBuzzIcon(tab)
})

📖 chrome.tabs API リファレンス


manifest.json

拡張機能の設定ファイル

📖 Manifest file format)
📖 Declare Permissions

{
  "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.cookies API リファレンス
📖 chrome.action API リファレンス

Illustration by Shahid Mehmood on Unsplash

1
End of the article

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