0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Webアプリの延長として見るChrome拡張機能の開発

Last updated at Posted at 2025-12-03

この記事はIPFactory Advent Calender 2025 3日目の記事です。

Manifest V3の内容です。JavaScriptの基礎的な知識があることを前提としています。

対象

  • Webアプリケーションの開発経験が多少あり、ちょっと外部通信したりするChrome拡張機能を作ってみたい人
  • Chrome拡張機能のドキュメントを読んだけど、いまいちわからなかった人

この記事について

公式のチュートリアルや、少し検索すれば、拡張機能の入りの記事などは出てきます。

しかし、開発していく上でもう少し踏み込んだ内容が、広く浅く欲しいと思い、この記事を書きました。

という建前、アウトプットでの理解の向上を目的としているため、誤り等あると思います。

manifest

これがないと始まりません。拡張機能の設定や権限、使用するファイルなどを定義するJSONファイルです。manifest.jsonという名前で拡張機能のルートディレクトリに配置します。

公式ドキュメント manifest

絶対一度は権限周りで怒られると思います。

permissions

拡張機能がアクセスできるAPIやデータを定義します。

公式ドキュメント permissions-list

私の場合は、以下のパーミッションを使いました。必要なものを上記リンクから適宜選択してください。

  • tabs: ブラウザのタブ情報にアクセスする
  • activeTab: ユーザーが現在アクティブにしているタブに一時的にアクセスする
  • scripting: コンテンツスクリプトを動的に注入する
  • storage: 拡張機能のストレージにデータを保存、取得する

host_permissions

外部通信を行う場合、許可するスコープを指定する必要があります。

"host_permissions": ["https://hoge.com/api/*"]

アーキテクチャ

前提として、イベント駆動型のアーキテクチャであることを理解しておく必要があります。拡張機能は常に動作しているわけではなく、ユーザーの操作や特定のイベントが発生したときに初めて動作します。

全体像

何かしらの拡張機能を入れたことがある人ならなんとなくイメージできるかもしれませんが、拡張機能は以下のような構成でできています。

Extension Pages (Popup, Options...)

≒ UI部分

  • ツールバーのアイコンをクリックしたときに出るポップアップなど
  • ユーザーが操作するためのインターフェースを提供するページ
  • タブやポップアップを閉じるとプロセスは終了する

スクリーンショット 2025-12-03 143658.png

consoleはPopupなどの画面を右クリック→検証から

Content Script

≒ フロントエンド

  • 閲覧中のWebページに注入されるJavaScript
  • WebページのDOMにアクセス、操作したり、ページのイベントの監視ができる
  • ページ本体のJavaScriptとは別のコンテキストで動作するため、直接的な干渉はできない

スクリーンショット 2025-12-03 171552.png

consoleはContent Scriptの注入されたページの開発者ツールから

Background (Service Worker)

≒ バックエンド(Lambda的な)

  • ブラウザ自体のイベント(タブの切り替え、ショートカットキーなど)も監視し、必要に応じて処理を実行する
  • DOMを直接操作することはできないが、他の部分とメッセージでやり取りできる
  • 必要な時だけ起動し、使い終わると停止するので、変数などの状態は保持されない(後述)

スクリーンショット 2025-12-03 144023.png

consoleはchrome://extensions/の「ビューの検証」から

メッセージング

これら3つ(Extension Pages, Content Script, Background)は、それぞれ異なるメモリ空間にいます。そのため、メッセージングを使って通信します。

公式ドキュメント runtime
公式ドキュメント tabs

  • chrome.runtime.sendMessage: Backgroundや他の部分にメッセージを送信する
  • chrome.runtime.onMessage.addListener: 受信したメッセージを処理するリスナーを登録する
  • chrome.tabs.sendMessage: 特定のタブにメッセージを送信する

この辺りのAPIが使えれば、最低限のメッセージングは可能だと思います。

popup.js

document.querySelector('#myButton').addEventListener('click', async () => {
    // アクティブなタブを取得
    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

    // コンテンツスクリプトを注入
    await chrome.scripting.executeScript({
        target: { tabId: tab.id },
        files: ['someContentScript.js']
    });

    // コンテンツスクリプトにメッセージを送信
    chrome.tabs.sendMessage(tab.id, { action: 'doSomething' });
});

someContentScript.js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === 'doSomething') {
        // 何か処理

        // 結果をBackgroundに送信
        chrome.runtime.sendMessage({ action: 'doneSomething' });
    }
});

background.js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === 'doneSomething') {
        // 何か処理
    }
});

Service WorkerとしてのBackground

拡張機能のBackgroundはManifest V3からService Workerとして実装されるようになりました。これにより、従来の常駐型に比べて、リソースの効率的な使用が可能になります!

というのは表向きで、先述の通りすぐ死にます。適当にグローバル変数で状態の保持~などができません。

let counter = 0;

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === 'increment') {
        counter++;
        // 30秒アイドル状態が続くとプロセスが終了し、変数がリセットされる
        sendResponse({ counter });
    }
});

永続化

そのため、状態を保持したい場合は、chrome.storage APIを使用してデータを保存、取得する必要があります。

公式ドキュメント storage

key-value形式でのデータを保存、取得ができます。容量はChrome 113以降10MBまでですが、unlimitedStorageのパーミッションで無制限にできます。

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'increment') {
    (async () => {
      const data = await chrome.storage.local.get('counter');
      let currentCounter = data.counter || 0;

      currentCounter++;

      await chrome.storage.local.set({ counter: currentCounter });
      sendResponse({ counter: currentCounter });
    })();

    return true; 
  }
});

chrome.storageは非同期です!

スクリーンショット 2025-12-02 234649.png

ちなみにwindow.localStorageも使えなくはないですが、そもそも推奨されていないのと、Service WorkerはDOMを持たないので使えません。

外部コードの制約

JavaScriptでは、外部のライブラリをCDNなどから動的に読み込むことがよくあります。
しかし、Manifest V3の拡張機能では、いくつかの制約があります。

リモートコードの禁止

Manifest V3では、リモートコードの実行は禁止されています。

公式ドキュメント remote hosted code

そのため、CDNやfetch.then evalなどで外部からコードを動的に読み込むことはできません。

importScripts('https://cdn.hoge.com/hoge.js');

ライブラリがリモートコードを使用している場合は、諦めるか頑張りましょう。

余談

私は、PoCくらいならいけるかなと思って適当にTesseract.jsをCDNからローカルにコピーして使おうとしていました。(ライセンスは確認しましょう)
ところが、どうやってもうまくWeb Workerが動かなかったので、おとなしくnpmからインストールしてビルドすることにしました。

まとめ

この辺りを押さえておけば、ある程度のChrome拡張機能は作れると思います。
私はそこまで使いませんでしたがそこそこモダンなフロントエンドの技術が使えるので、Web開発の味変として触れてみるのも良いかと思います。
便利な拡張機能を作って、QoLを上げましょう!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?