はじめに
chrome拡張には大別して2つのパターンがあります。
拡張アイコンをクリックした時に「ポップアップウィンドウが開くもの」と「ポップアップウィンドウは開かないもの」の2つです。
これは単にuiの違いだけではなく、scripting APIとactiveTabを使ったDOM操作のフローが根本的に変わるという特徴があります。
ここを理解せずにコードを書くと「コードは正しいはずなのに、なんでDOMが操作できないんだろう?」といったことになってしまうので、そうならないようにポイントをまとめました。
前提知識
- activeTab — ユーザーが拡張アイコンをクリックした時にだけ、現在のタブへの一時的なアクセス権を付与する権限
- scripting API —
chrome.scripting.executeScript()でページにスクリプトを一時的に注入する仕組み - Service Worker(background.js) — ブラウザのバックグラウンドで動くプロセス。ページのDOMには直接アクセスできない
詳細は次の記事を参照:
Chrome拡張のスクリプト注入と権限設計 — content_scriptsのリスクとscripting APIによる解決策
最初に結論
Chrome拡張のアイコンクリック時の動作は、manifest.jsonでdefault_popupを指定するかどうかで二者択一になる。
action.onClickedイベントが発火する
DOM操作の起点
popup.js
background.js
用途
フォーム表示、情報の確認・編集
ワンクリックで即座に処理実行
この2つは排他的で、popupが設定されているとonClickedイベントは発火しない。
action.onClicked — Fired when an action icon is clicked. This event will not fire if the action has a popup.
参考: chrome.action API — Chrome for Developers
パターン1:popupなし — background.jsで処理する
どういう拡張か
アイコンをクリックしたら、ユーザーとのやりとりなしに即座に処理が走る拡張。例えば「ワンクリックでダークモードに切り替える」「今のページのスクリーンショットを撮る」「ページの背景色を変える」など。
manifest.json
default_popupを指定しない。
{
"manifest_version": 3,
"name": "One Click Extension",
"version": "1.0",
"action": {
"default_title": "クリックで実行"
},
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
}
}
処理の流れ
ユーザーがアイコンをクリック
↓
action.onClicked イベントが発火(background.js)
↓
activeTab の権限が有効になる
↓
background.js から executeScript() でページにスクリプトを注入
↓
ページ内で関数が実行され、結果が background.js に返る
コード例
// background.js
chrome.action.onClicked.addListener(async (tab) => {
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
// ページのコンテキストで実行される
document.body.style.backgroundColor = 'red';
return { title: document.title };
}
});
console.log(results[0].result);
});
Chrome公式のactiveTabチュートリアルもこのパターンで書かれている。
参考: Inject scripts into the active tab — Chrome for Developers
パターン2:popupあり — popup.jsで処理する
どういう拡張か
アイコンをクリックした後にフォームや情報を表示し、ユーザーが確認・操作してから処理を行う拡張。例えば「ブックマークツール」「翻訳ツール」「パスワードマネージャー」など。
manifest.json
default_popupを指定する。
{
"manifest_version": 3,
"name": "Popup Extension",
"version": "1.0",
"action": {
"default_title": "ブックマークする",
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
}
}
処理の流れ
ユーザーがアイコンをクリック
↓
popup.html が開く(action.onClicked は発火しない)
↓
activeTab の権限が有効になる
↓
popup.js から executeScript() でページにスクリプトを注入
↓
ページ内で関数が実行され、結果が popup.js に返る
↓
popup.js が取得した値を popup.html のDOMに表示
コード例
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<h3 id="title"></h3>
<p id="url"></p>
<p id="published_at"></p>
<button id="save">保存</button>
<script src="popup.js"></script>
</body>
</html>
// popup.js
document.addEventListener('DOMContentLoaded', async () => {
// 現在のアクティブタブを取得
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// ページにスクリプトを注入してメタデータを取得
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
return {
title: document.title,
url: window.location.href,
published_at: document.querySelector('meta[property="article:published_time"]')
?.getAttribute('content')
?.slice(0, 10) || ""
};
}
});
const metadata = results[0].result;
// popup.html のDOMに表示
// popup.jsはpopup.htmlと同じレンダラープロセスで動くので、`document.getElementById()` で直接DOMを操作できる。
document.getElementById('title').textContent = metadata.title;
document.getElementById('url').textContent = metadata.url;
document.getElementById('published_at').textContent = metadata.published_at;
});
混乱しやすいポイント
background.jsのonClickedでメタデータを取得してpopupに表示したら?
default_popup を指定すると onClicked イベント自体が発火しないので不可。
popup.jsからexecuteScript()は呼べないのでは?
popup.htmlが開いた時点で activeTab の権限が有効になるので、popup.js から直接 chrome.scripting.executeScript() を呼べる。
popup.htmlは必ず作るものでは?
popupウィンドウが不要な拡張(ワンクリックで即座に処理するタイプ)ではpopup.htmlを作らず、onClicked + background.js で処理する。
どちらを選ぶかの判断基準
popupなし(background.js + onClicked)を選ぶ場合:
- クリック後にユーザーとのやりとりが不要
- 処理が「実行するだけ」で完結する
- 結果の表示がバッジやアイコンの変化で十分
popupあり(popup.js)を選ぶ場合:
- 取得した情報をユーザーに見せたい
- フォームで入力や編集をしてから送信したい
- 複数の操作ボタンを提供したい
参考リンク
- chrome.action API — Chrome for Developers
- The “activeTab” permission — Chrome for Developers
- Inject scripts into the active tab (Tutorial) — Chrome for Developers
- chrome.scripting API — Chrome for Developers
Photo by Pawel Czerwinski on Unsplash





