🧩

Google Chrome拡張機能開発のチュートリアルを実施してみた(任意のタイミングでのスクリプト実行編)

に公開

チュートリアルの概要

前回はGoogle Chromeの拡張機能やウェブストアに関する開発者向けドキュメントを開くと、読了目安時間が表示される拡張機能を作成しました。
https://zenn.dev/judenfly/articles/judenfly-20250810
今回もGoogle公式のチュートリアルに従い、特定のWebページのスタイルを任意のタイミングで変更する拡張機能を作成します。
https://developer.chrome.com/docs/extensions/get-started/tutorial/scripts-activetab?hl=ja
チュートリアルの流れは以下の通りです。

  • Step 1. マニフェストファイルを作成
  • Step 2. アイコンをダウンロード
  • Step 3. Service Workerを作成
  • Step 4. スタイルシートを作成
  • Step 5. 動作を確認

チュートリアル完了後、Google Chromeの拡張機能やウェブストアに関する開発者向けドキュメントを開いて拡張機能のアイコンをクリックすると、以下のようにページがシンプルなスタイルに変更されます。

Step 1. マニフェストファイルを作成

まず、拡張機能のルートディレクトリfocus-modeを作成し、移動します。
次に、focus-modeディレクトリ直下に以下のようなマニフェストファイルmanifest.jsonを作成します。

manifest.json
{
    "manifest_version": 3,
    "name": "Focus Mode",
    "description": "Enable focus mode on Chrome's official Extensions and Chrome Web Store documentation.",
    "version": "1.0",
    "icons": {
        "16": "images/icon-16.png",
        "32": "images/icon-32.png",
        "48": "images/icon-48.png",
        "128": "images/icon-128.png"
    },
    "action": {
        "default_icon": {
            "16": "images/icon-16.png",
            "32": "images/icon-32.png",
            "48": "images/icon-48.png",
            "128": "images/icon-128.png"
        }
    },
    "background": {
        "service_worker": "background.js"
    },
    "permissions": ["scripting", "activeTab"],
    "commands": {
        "_execute_action": {
            "suggested_key": {
                "default": "Ctrl+B",
                "mac": "Command+B"
            }
        }
    }
}
  • manifest_version
  • name
  • description
  • version
  • icons
  • action.default_icon

の6フィールドの説明は前々回の記事前回の記事に記載しているので省略します。

Service Worker

background.service_workerフィールドではService Workerのスクリプトを1つだけ指定できます。
Service Workerとは特定のイベント(拡張機能をインストールする、タブを開くなど)発生時にバックグラウンドで実行されるスクリプトのことです。
コンテンツスクリプトとは異なり、HTML要素にアクセスすることはできません。

権限

permissionsフィールドではGoogle拡張機能APIの実行に必要な権限を宣言します。
scripting権限はchrome.scripting APIを使用するために必要です。
今回はスタイルシートを挿入するchrome.scripting.insertCSSとスタイルシートを削除するchrome.scripting.removeCSSを使用するために権限を宣言します。
activeTab権限はユーザの操作に応じてアクティブなタブで拡張機能を実行するために必要です。
activeTabは限定的な権限のため、拡張機能のインストール時に権限に関する警告が表示されないというメリットがあります。

ショートカットキー

commandsフィールドではショートカットキーを登録できます。
任意のコマンド名を指定できますが、今回は_execute_actionという予約されたコマンド名を使用しています。
_execute_actionフィールドではツールバー上の拡張機能のアイコンをクリックするアクションのショートカットキーを定義できます。
今回はCtrlキー+Bキー(macOS[1]の場合はCommandキー+Bキー)を押下すると、拡張機能のアイコンをクリックしたと見なされます。

Step 2. アイコンをダウンロード

マニフェストファイルに記載した4つの異なるサイズのアイコンを用意します。
まず、アイコン用のディレクトリimagesfocus-modeディレクトリ直下に作成して移動します。
次に、Google ChromeのGitHubリポジトリからアイコンをダウンロードし、マニフェストファイルの定義通りに命名します。

Invoke-WebRequest -Uri "https://raw.githubusercontent.com/GoogleChrome/chrome-extensions-samples/main/functional-samples/tutorial.focus-mode/images/icon-16.png" -OutFile "icon-16.png"
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/GoogleChrome/chrome-extensions-samples/main/functional-samples/tutorial.focus-mode/images/icon-32.png" -OutFile "icon-32.png"
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/GoogleChrome/chrome-extensions-samples/main/functional-samples/tutorial.focus-mode/images/icon-48.png" -OutFile "icon-48.png"
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/GoogleChrome/chrome-extensions-samples/main/functional-samples/tutorial.focus-mode/images/icon-128.png" -OutFile "icon-128.png"

Step 3. Service Workerを作成

マニフェストファイルに記載したService Workerを作成します。
focus-modeディレクトリ直下に以下のようなスクリプトbackground.jsを作成します。

background.js
chrome.runtime.onInstalled.addListener(() => {
    chrome.action.setBadgeText({
        text: "OFF",
    });
});

const extensions = 'https://developer.chrome.com/docs/extensions';
const webstore = 'https://developer.chrome.com/docs/webstore';

chrome.action.onClicked.addListener(async (tab) => {
    if (tab.url.startsWith(extensions) || tab.url.startsWith(webstore)) {
        const prevState = await chrome.action.getBadgeText({ tabId: tab.id });
        const nextState = prevState === 'ON' ? 'OFF' : 'ON';
        await chrome.action.setBadgeText({
            tabId: tab.id,
            text: nextState,
        });

        if (nextState === "ON") {
            await chrome.scripting.insertCSS({
                files: ["focus-mode.css"],
                target: { tabId: tab.id },
        });
        } else if (nextState === "OFF") {
            await chrome.scripting.removeCSS({
                files: ["focus-mode.css"],
                target: { tabId: tab.id },
            });
        }
    }
});
コードの解説
chrome.runtime.onInstalled.addListener(() => {

chrome.runtime.onInstalled.addListener

  • 拡張機能がインストールされた時
  • 拡張機能がアップデートされた時
  • Google Chromeがアップデートされた時

に引数のコールバック関数を実行する関数です。

    chrome.action.setBadgeText({

chrome.action.setBadgeText[2]はツールバー上の拡張機能のアイコンにバッジを表示する関数です。

        text: "OFF",
    });
});

ツールバー上の拡張機能のアイコンの初期状態としてOFFというバッジを表示します。

const extensions = 'https://developer.chrome.com/docs/extensions';

Google Chromeの拡張機能の開発者向けドキュメントのURLの始めの文字列を変数extensionsに格納します。

const webstore = 'https://developer.chrome.com/docs/webstore';

Google Chromeのウェブストアの開発者向けドキュメントのURLの始めの文字列を変数webstoreに格納します。

chrome.action.onClicked.addListener(async (tab) => {

chrome.action.onClicked.addListenerはツールバー上の拡張機能のアイコンがクリックされると、引数のコールバック関数を実行する関数です。
なお、コールバック関数の中で返り値がPromiseとなるAPIを使用するため、asyncを付けてコールバック関数を非同期関数としています。

    if (tab.url.startsWith(extensions) || tab.url.startsWith(webstore)) {

ツールバー上の拡張機能のアイコンをクリックした時にアクティブになっているタブのURLがhttps://developer.chrome.com/docs/webstoreまたはhttps://developer.chrome.com/docs/extensionsから始まるならばという条件を表しています。

        const prevState = await chrome.action.getBadgeText({ tabId: tab.id });

chrome.action.getBadgeTextは特定のタブにおけるバッジを取得する関数です。
今回はアイコンをクリックしたタイミングでアクティブになっているタブにおけるバッジを取得し、変数prevStateに格納します。
なお、chrome.action.getBadgeTextの返り値はPromiseのため、awaitを付けています。

        const nextState = prevState === 'ON' ? 'OFF' : 'ON';

バッジがONならばOFFを、OFFならばONを変数nextStateに格納します。

        await chrome.action.setBadgeText({
            tabId: tab.id,
            text: nextState,
        });

アクティブなタブにおいてバッジをONからOFFに、またはOFFからONに設定します。

        if (nextState === "ON") {

変更後のバッジがONならばという条件を表しています。

            await chrome.scripting.insertCSS({
                files: ["focus-mode.css"],
                target: { tabId: tab.id },
            });

chrome.scripting.insertCSSはCSSファイルを挿入する関数です。
ここではアクティブなタブにfocus-mode.cssを挿入します。

        } else if (nextState === "OFF") {

変更後のバッジがOFFならばという条件を表しています。

            await chrome.scripting.removeCSS({
                files: ["focus-mode.css"],
                target: { tabId: tab.id },
            });
        }
    }
});

chrome.scripting.removeCSSは拡張機能によって挿入されたCSSファイルを削除する関数です。
ここではアクティブなタブからfocus-mode.cssを削除します。

Step 4. スタイルシートを作成

Service Workerに記載したスタイルシートを作成します。
focus-modeディレクトリ直下に以下のようなスタイルシートfocus-mode.cssを作成します。

focus-mode.css
* {
    display: none !important;
}

html,
body,
*:has(article),
article,
article * {
    display: revert !important;
}

[role='navigation'] {
    display: none !important;
}

article {
    margin: auto;
    max-width: 700px;
}
コードの解説
* {
    display: none !important;
}

*ユニバーサルセレクタと呼ばれ、全ての要素を指定します。
display: noneは要素を非表示にします。
ここでは全ての要素を非表示にします。
!importantは宣言の優先順位が高いことを示しています。

html,

前段で全ての要素を非表示にしたので、ここからは必要最低限の要素を再表示します。
html要素を再表示します。

body,

body要素を再表示します。

*:has(article),

子要素にarticleを持つ全ての親要素を再表示します。

article,

article要素を再表示します。

article * {
    display: revert !important;
}

article内の全ての要素を再表示します。
その際、revertを指定することで、ブラウザ既定のスタイルで表示します。
また、!importantと宣言することで、* { display: none !important; }より優先順位が高いことを示します。

[role='navigation'] {
    display: none !important;
}

ナビゲーションは不要なので、ここで再度非表示にしておきます。

article {
    margin: auto;
    max-width: 700px;
}

article要素の左右の余白を自動設定にし、横幅の最大値を700pxとします。

Step 5. 動作を確認

拡張機能が完成したので、Google Chromeに読み込みます。
なお、拡張機能の読み込み方法は前々回の記事に記載しているので省略します。
作成した拡張機能が有効になっている状態で、今回のチュートリアルのページを開きます。

そして、Ctrlキー+Bキーを押下するとバッジがOFFからONに切り替わり、スタイルシートが挿入されてページの表示がシンプルになりました。

今回のチュートリアルはこれにて終了です。

脚注
  1. macOSでは自動的にCtrlキーをCommandキーに変換しますが、今回はmacフィールドを用いて明示的にmacOS用のショートカットキーを定義しています。 ↩︎

  2. ここで登場するchrome.action.setBadgeTextの返り値はPromiseですが、後続の処理がないので、awaitを付けて結果を待つことはしません。 ↩︎

Discussion