はじめに
本記事はNTTテクノクロス Advent Calendar 2025のシリーズ1 18日目の記事です。
こんにちは。NTTテクノクロスの関口です。
今年のアドベントカレンダーの記事でも、業務外での活動(= 遊んでみた 作ってみた、調べてみた)ことについて記事にしていこうと思います。
この記事には試行錯誤の過程が含まれています。
こうするといいよ、などあたたかいコメントをいただけると幸いです。
やってみたこと
AWSの管理者権限は大いなる力すぎるので、やりたいことに応じたIAMを作成してログインするようにしています。
どのアカウントのどのIAMにログインしているんだっけ?が毎回不安になるので、AWSコンソールの画面色を現在ログイン中のアカウント(IAM)に応じて変えるChrome拡張機能を作ってみました。
(最近、AWSアカウントごとにヘッダの色が設定できるようになったようですが、適切なIAM権限が必要であったり、アカウントを使う人全員の色が変わってしまうなどハードルが少し高め...)
ついでに Manifest V2 のサポート完全終了など話題になったChrome拡張機能ってどう動いているの?も調べてみました。
そもそもChrome拡張機能ってなに?
使っている方も多いかとは思いますが、改めて説明する、となるとちょっと難しいなぁ...と思っていたら、開発のスタートガイドにシンプルに説明されていたので引用します。
拡張機能とは
Chrome 拡張機能は、ユーザー インターフェースのカスタマイズ、ブラウザ イベントの監視、ウェブの変更によってブラウジング エクスペリエンスを向上させます。拡張機能の機能の例については、Chrome ウェブストアをご覧ください。
どのように構築されているか
ウェブアプリの作成に使用されるウェブ技術(HTML、CSS、JavaScript)を使用して拡張機能を作成できます。
お客様はどのように対処できますか?
拡張機能は、Web API に加えて、Chrome Extension API にもアクセスして、さまざまなタスクを実行できます。詳細については、開発ガイドをご覧ください。
要するに、HTML、CSS、JavaScriptを用いて自身の手元のブラウザ環境をよくするもの、という理解で良いかと思います。
とりあえずChrome拡張機能を動かしながら仕組みを学んでみる
Chrome拡張機能の構成
Chrome拡張機能の構成は、ルートディレクトリにあるmanifest.jsonに書かれています。
まずは拡張機能が動くことを確認するための拡張機能を作ってみましょう。
全てのサイトにおいて、コンソールにHelloWorldのログを出す拡張機能を作成してみます。
ディレクトリ構成
本サンプルのゴールのディレクトリ構造は以下の通りとなっています。
HelloWorld/
├── manifest.json
└── content.js
思いの外単純 シンプルなファイル構成ですね。
親ディレクトリである、HelloWorld/は適当な名前で大丈夫です。
manifest.json
manifest.jsonは、拡張機能の情報、必要な権限、関連するファイルがどこにあるのか、などが書かれたファイルです。ルートディレクトリに存在している必要があり、ファイル名を変更することができません。
本デモで使うmanifest.jsonの中身も見てみましょう。
必要な情報はこちら(Manifest file format)に詳細の説明が書かれていますが、まずは最低限の情報であるmanifest_version、name、version、あとは本デモで使用するcontent_scriptsを記載していきます。
| キー | 説明 |
|---|---|
manifest_version |
マニフェストファイルのフォーマットを示す。 現時点では 3のみサポートされている。(先述の通りManifest V2が終了したため) |
name |
Chrome拡張機能の名前。 拡張機能一覧などに表示される名前。 |
version |
拡張機能のバージョン番号を示す。 1〜4個のドット区切りの整数(0〜65535)で示す。 自動更新などで使用されます。 詳細はこちら(Manifest - Version)を参照 |
content_scripts |
拡張機能が自動的に実行されるタイミング・内容を示す。 どのURLに、を示す matchesは必須。詳細はこちら(Content scripts)を参照 今回の例では、全てのURLにおいて content.jsを発火させる |
{
"manifest_version": 3,
"name": "HelloWorld",
"version": "1.0.0",
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"content.js"
]
}
]
}
content.js(サンプルで使用)
JavaScriptは分離された世界で実行されるため、ページに含まれるスクリプトや、他の拡張機能のスクリプトにアクセスをすることができません。
そのため、拡張機能内で完結するようなJavaScriptを作成する必要があります。
本デモでは全てのURLでコンソールにHelloWorldを出力するので、下記のようなコードを使います。
簡単 シンプルなコードですね。
console.log("拡張機能からHelloWorld!");
実際に動かしてみる
そろそろ文字ばっかりで疲れてきましたよね。
実際に動かしてみる様子を見ていきましょう。
インストール
Chromeを開き、URL入力欄にchrome://extensions/を入れ、「Enter」を押して拡張機能の管理画面を開きます。
管理画面にあるデベロッパーモードをオンにし、自作の拡張機能を入れられるようにします。

デベロッパーモードをオンにしたら、HelloWorldディレクトリをChromeへドラッグ&ドロップすれば、インストールは完了です。

動作確認
GoogleChromeには Chrome DevTools というツールがついており、コンソールはこちらのツールから見ることができます。
Chrome DevToolsはキーボードのF12キーを押すことで開くことができます。
試しに拡張機能を入れ、コンソールを開いた状態で任意のサイト(例として弊社HP)にアクセスしてみると、コンソールに拡張機能からHelloWorld! と出てきました!

なんとなく仕組みがつかめたので、実際に作ってみる
実際に拡張機能が動くこと確認できたので、いよいよ実現したかった内容を作ってみましょう。
AWSコンソールの画面色を現在ログイン中のアカウント(IAM)に応じて変えるChrome拡張機能を作ってみました。
manifest.jsonの編集
拡張機能が発火するURLの設定
AWSのコンソールを変更するツールなので、そのようにManifestを変更していきます。
まずは、拡張機能を動かしたい画面である、AWSコンソールにアクセスしてみて、URLをいくつかあげてみましょう。
- コンソールの例
https://ap-southeast-2.console.aws.amazon.com/console/home - S3の開始ページの例
https://ap-northeast-1.console.aws.amazon.com/s3/get-started - EC2管理ページの例
https://us-east-1.console.aws.amazon.com/ec2/home
どのページも https://(リージョン名).console.aws.com/(サービス名)/(ページ名)となっていそうです。
今回は、サブドメイン部分のリージョン名、およびURLパス部分は何でも許容するように設定することで、コンソール上のどんな画面でも発火させることができます。
ワイルドカード(*)が使用可能なので、それらの箇所をワイルドカードとしてURLを指定してみると下記のようになります。
https://*.console.aws.amazon.com/*
こちらのURLの情報の反映、および拡張機能の名前と、ついでに説明も追加していきましょう。
nameを「AWS-Color-Change」に変更し、matchesを「https://*.console.aws.amazon.com/*」に変更します。説明であるdescriptionには拡張機能が何をするのかを書きましょう。
{
"manifest_version": 3,
- "name": "HelloWorld",
+ "name": "AWS-Color-Change",
"version": "1.0.0",
+ "description": "AWSのヘッダ色をアカウントに応じて変更します。",
"content_scripts": [
{
"matches": [
- "<all_urls>"
+ "https://*.console.aws.amazon.com/*"
],
"js": [
"content.js"
]
}
]
}
content.jsの編集
先ほど作成したcontent.jsの中身を書き換えてしまいます。
先ほど作成したコードの中身を一度削除します。
- console.log("拡張機能からHelloWorld!");
削除が完了したら、アカウント情報の設定、表示されたアカウント情報を取得、コンテンツ情報を書き換えるコードを記載します。
また、今回はjsでコンテンツが更新されていくWebページであるため、画面描画の変更監視をするコードも記載します。
(アカウント、表示内容の設定にある変数は各自の環境に置き換える必要があります。)
//アカウント、表示内容の設定
const SETTING_ACCOUNT_ID = "1234-5678-9012"; //強調表示するアカウントID(12桁の番号)を指定する
const SETTING_IAM_USER = "admin" //強調表示するIAMユーザ名を指定する
const SETTING_EXPLAIN = "本番(管理者)" //ユーザ名の前につけるフレーズ
//要素が更新された時に呼ばれる、必要情報が読み込まれたかを確認する関数
function checkLoaded() {
const displayedAccountInfo = getDisplayedAccountInfo()
if (displayedAccountInfo) {
//アカウント名が取得できたら監視は停止する
observer.disconnect();
if (SETTING_ACCOUNT_ID == displayedAccountInfo.accountId && SETTING_IAM_USER == displayedAccountInfo.iamUser) {
console.log("設定したアカウントだったので色を変えます。")
changeFontColor("blue", "red");
addTextToAccountInfo(SETTING_IAM_USER,SETTING_EXPLAIN);
}
}
}
//画面表示内容からアカウントIDを取得する関数
function getDisplayedAccountInfo() {
if (document.querySelector("#nav-usernameMenu")) {
let accountId = ""
let iamUser = "";
//ヘッダメニューのアカウントID,ユーザ名を取得
textContent = document.querySelector("#nav-usernameMenu").innerText.split("\n");
if (textContent && textContent.length == 2) {
iamUser = textContent[1];
//ヘッダのアカウントIDは「アカウントID:」や、「Account ID:」が入っているため、切り出す。
const matchResult = textContent[0].match(/\d{4}-\d{4}-\d{4}/);
if (matchResult && matchResult.length > 0) {
accountId = matchResult[0];
return { "accountId": accountId, "iamUser": iamUser }
}
}
}
return null
}
//アカウント情報欄の追記をするアカウント
function addTextToAccountInfo(iamUser, explain) {
if (iamUser && explain) {
const target = document.querySelector("#nav-usernameMenu")
const elements = target.querySelectorAll("span");
const matchedElements = Array.from(elements).filter(el => el.textContent.trim() == iamUser);
if (matchedElements && matchedElements.length == 1) {
matchedElements[0].textContent = explain + " - " + iamUser;
}
}
}
//フォント色を変更するCSSを追加する関数
function changeFontColor(fontColor, bgColor) {
//値がない場合は色変更は実施しない
if (fontColor && bgColor) {
const style = document.createElement("style");
style.textContent = `
#awsc-top-level-nav [aria-controls^='menu--'],#awsc-nav-scallop-icon,#nav-usernameMenu *,#awsc-nav-footer-content * {
color: ${fontColor} !important;
}
#awsc-top-level-nav ,#awsc-nav-footer-content {
background-color: ${bgColor} !important
}
`;
document.head.appendChild(style);
}
}
//監視するオブジェクトの作成。
const observer = new MutationObserver(checkLoaded);
//監視設定。子要素、サブツリーの文字データの変更を監視
observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true });
//監視設定直後は設定された時点での確認が行われないため、初回確認をする
checkLoaded();
インストール(ソースの更新を反映)
一度入れた拡張機能のソースを変更しても、Chromeには反映がされません。
拡張機能のソースコードの編集・保存が終わったら、インストール時の時と同様にchrome://extensions/にアクセスします。
拡張機能詳細画面のより、すでにインストール済みのHelloWorldをがあるため、項目内の再読み込みボタンを押下します。

再読み込みが完了すると、「再読み込みされました。」というポップアップが表示されます。
拡張機能名がAWS-Color-Changeに変わり、説明も追記されました。

動作確認
AWSコンソールに設定値のアカウント(1234-5678-9012)、IAMユーザでログインをしましょう。コンソール画面のヘッダ、フッタが赤くなっていれば成功です。

比較のために何も適用されていない場合と並べてみたのですが、普段の環境と比べて違いがすごくわかりやすいですね。目がチカチカする...
設定機能
先ほどまでの手順でできた拡張機能はハードコーディングされた値をもとに判断をしていました。
情報を変える際に毎回コードを書き直して読み込ませるのは 面倒 大変なので、設定画面を新たに作ってみます。
Chrome拡張機能では、通常のJavaScriptで使える機能だけでなく、Chrome Extension APIというChromeを操作するためのAPIが準備されています。
今回はこちらのAPIのうちchrome.storage APIを使用して、設定値を保持・呼び出しをする機構、その設定を触る画面をそれぞれ作成してみましょう。
manifest.jsonの編集
マニフェストを更新していきます。
今回の変更では、拡張機能をクリックしたときの動作を設定するactionと、Chrome拡張機能APIを用いる際に追加権限が必要な際に使用するpermissionsを追記しましょう。
actionについて
actionは拡張機能アイコンを制御するためのChrome拡張機能APIを使用するためのキーです。
例:ポップアップを表示するを参考に、ポップアップ表示したいHTMLファイルを記述していきます。
{
"manifest_version": 3,
"name": "AWS-Color-Change",
"version": "1.0.0",
"description": "AWSの色変更",
+ "action": {
+ "default_popup": "popup.html"
+ },
+ "permissions": [
+ "storage"
+ ],
"content_scripts": [
{
"matches": [
"https://*.console.aws.amazon.com/*"
],
"js": [
"content.js"
]
}
]
}
popupで使用するファイルについて
ファイル名を記述したら、ポップアップの中身も作っていきましょう。
ポップアップとして作成しますが、中身はWebページと同様に作成ができます。
ポップアップのHTMLで使用するJavaScript、CSSはHTMLファイル上に宣言すればOKです。
popup.htmlの作成、CSSの追加
popup.htmlはガワだけを作成しておき、設定内容の表示などはJavScriptを通じて制御しましょう。
CSSも併せて設定しておきます。
<!DOCTYPE html>
<html>
<head>
<title>アカウント設定</title>
<link rel="stylesheet" href="popup.css">
<meta charset="UTF-8">
</head>
<body>
<h2>アカウント設定</h2>
<button id="add-row-button">列を追加</button><br>
<div id="settings-container">
</div>
<button id="save-button">設定を保存する</button>
<button id="discard-button">変更をキャンセル</button>
<script src="popup.js"></script>
</body>
</html>
body {
/* ポップアップの幅を設定 */
width: 800px;
padding: 10px;
}
.account-id,.iam-user {
/* 入力欄(アカウント情報)の幅を設定 */
width: 10em;
}
.color{
/* カラー設定を見やすくする */
vertical-align: -3px;
margin: 0px;
width: 2em;
border: none;
background: none;
}
.setting-row button{
/* ボタン間隔を調整する */
margin-left: 3px;
margin-right: 3px;
}
popup.jsの作成
popup.jsでは、表示の制御と設定値の保存・読み出しを行います。読み出しにはchrome.storage APIを使用します。
chrome.storage APIについて
storage APIは主にsession local syncと3種類の保存方法が準備されています。
session |
local |
sync |
|
|---|---|---|---|
| タブを閉じても保持される | × | ○ | ○ |
| インストールした端末 だけに保持される |
○ | ○ | × |
| 同じアカウントで同期中の 別の端末に同期される |
× | × | ○ |
設定を保持するためには、sessionは使用することができません。
そのため、同期をした際に競合したデータが生じたときの処理を書くのが面倒だったので 今回はlocalを使用していきます。
設定値をバックアップ/同期したい場合は、chrome.storage.localをchrome.storage.syncに書き換えても問題ありません。
データ読み出し時は非同期処理なので、 Promiseチェーン での記述が必要です。
//ローカルに入れる場合
chrome.storage.local.set({ "accountSettings": "ローカルに値を保存したよ" });
chrome.storage.local.get("accountSettings", (data) => {
console.log(data.accountSettings)
})
//同期をする場合
chrome.storage.sync.set({ "accountSettings": "同期できる場所に保存したよ" });
chrome.storage.sync.get("accountSettings", (data) => {
console.log(data.accountSettings)
})
実際にコンソールで実行してみた様子がこちらです。
保存・読み出しともにできていることがわかります。

設定の保存の方法をもとに、読み出した情報を表示、編集後に保存をするスクリプトを組んでみたものがこちらです。
// DOM要素を取得
const container = document.getElementById("settings-container");
const addButton = document.getElementById("add-row-button");
const saveButton = document.getElementById("save-button");
const discardButton = document.getElementById("discard-button");
// 行を作成・設定値の読み込みをする関数
function createSettingRow(settings = {}) {
//デフォルト値の設定
const DEFAULT_BG_COLOR = "#161D26"
const DEFAULT_TEXT_COLOR = "#ebebf0"
// 新しい行の要素を作成
const row = document.createElement("div");
row.className = "setting-row";
row.innerHTML = `
<input type="text" name="accountId" placeholder="アカウントID" class="account-id" value="${settings.accountId || ''}">
<input type="text" name="iamUser" placeholder="IAMユーザ名" class="iam-user" value="${settings.iamUser || ''}">
<input type="text" name="explain" placeholder="追加メッセージ" class="explain" value="${settings.explain || ''}">
<label for="bgColor">背景色:</label>
<input type="color" name="bgColor" placeholder="背景色" class="color" value="${settings.bgColor || DEFAULT_BG_COLOR}">
<label for="textColor">文字色:</label>
<input type="color" name="textColor" placeholder="文字色" class="color" value="${settings.textColor || DEFAULT_TEXT_COLOR}">
<button class="color-reset-button">色リセット</button><button class="delete-button">削除</button>
`;
// 削除ボタンのイベントリスナーを追加
const deleteButton = row.querySelector(".delete-button");
deleteButton.addEventListener("click", () => {
row.remove();
});
// 色リセットボタンのイベントリスナーを追加
const colorReserButton = row.querySelector(".color-reset-button");
colorReserButton.addEventListener("click", () => {
row.querySelector("[name*=bgColor]").value=DEFAULT_BG_COLOR;
row.querySelector("[name*=textColor]").value = DEFAULT_TEXT_COLOR;
});
return row;
}
//設定を保存する関数
function saveSettings() {
const allRows = container.querySelectorAll(".setting-row");
const settingsArray = [];
allRows.forEach(row => {
const rowData = {};
row.querySelectorAll("input").forEach(input => {
rowData[input.name] = input.value;
});
settingsArray.push(rowData);
});
// ローカルストレージに保存
chrome.storage.local.set({ "accountSettings": settingsArray });
return true
}
// 設定を読み込み、画面に表示する関数
function loadSettings() {
chrome.storage.local.get("accountSettings", (data) => {
const settings = data.accountSettings || [];
//再読み込みした際に追加で描画されないよう、一度描画内容を削除する
container.innerHTML = "";
if (settings.length > 0) {
settings.forEach(setting => {
container.appendChild(createSettingRow(setting));
});
} else {
// 設定がなければ、初期行を1つ追加しておく
container.appendChild(createSettingRow());
}
});
}
//追加ボタンの設定
addButton.addEventListener("click", () => {
container.appendChild(createSettingRow());
});
//保存ボタンの設定
saveButton.addEventListener("click", () => {
if (confirm("保存しますか?")) {
if(saveSettings())alert("保存しました。");
}
});
//変更をキャンセルボタンの設定
discardButton.addEventListener("click", () => {
if (confirm("入力内容を破棄し、最後に保存された状態に戻しますか?")) {
loadSettings();
}
});
//HTMLの描画後に設定値を読み込む設定
document.addEventListener("DOMContentLoaded", loadSettings);
設定画面の確認
先ほどの動作確認時と同様、ソースコードの反映をしたら、拡張機能のアイコンをクリックしてみましょう。
無事にpopupが表示されましたね。
試しに値を入れて「設定を保存する」を押してみましょう。
設定を保存した旨ダイアログが表示されました。


実際に保存されていることを確認してみましょう
適当な値を入れ、ポップアップ以外の箇所を操作すると、ポップアップが消えます。

再度ポップアップを表示させると、適当に入れた値は入っていないことが確認できました。

設定内容を拡張機能本体に読ませてみる
popup.jsで使用したchrome.storageはcontent.jsでも使用することが可能です。
新たな関数を追加してみましょう。
function getSavedAccountSettings(displayedAccountInfo) {
return new Promise((resolve) => {
//入力値の確認
if (displayedAccountInfo.accountId && displayedAccountInfo.iamUser) {
chrome.storage.local.get("accountSettings", (data) => {
const settings = data.accountSettings || [];
//取得済みの配列から表示された値と一致するものを探す
for (const setting of settings) {
if (setting.accountId == displayedAccountInfo.accountId && setting.iamUser == displayedAccountInfo.iamUser) {
console.log("一致");
resolve(setting);
return;
}
}
});
} else {
resolve(null);
}
});
}
また、今まで固定の設定値を用いていたcheckLoaded関数もgetSavedAccountSettingsから設定値を読み出すよう実装を変更します。
function checkLoaded() {
console.log("発火")
const displayedAccountInfo = getDisplayedAccountInfo()
if (displayedAccountInfo) {
//アカウント名が取得できたら監視は停止する
observer.disconnect();
- if (SETTING_ACCOUNT_ID == displayedAccountInfo.accountId && SETTING_IAM_USER == displayedAccountInfo.iamUser) {
- console.log("設定したアカウントだったので色を変えます。")
- changeFontColor("blue", "red");
- addTextToAccountInfo(SETTING_IAM_USER,SETTING_EXPLAIN);
- }
+ getSavedAccountSettings(displayedAccountInfo).then(setting => {
+ if (setting) {
+ console.log("設定したアカウントが見つかったので色を変えます。")
+ changeFontColor(setting.fontColor, setting.bgColor);
+ addTextToAccountInfo(setting.iamUser, setting.explain);
+ }
+ });
}
}
動作確認
先ほどの動作確認時と同様、ソースコードの反映をしたら、設定に別のアカウント情報と背景色を黄色、文字色を黄色にする設定を追加してみます。

1つ目のアカウントにログインをしてみると、無事に設定が反映されていることを確認できました。

また、2つ目のアカウントにログインしてみても別の色に変わっていることが確認できました!!

おわりに
本記事では、Chrome拡張機能を使って、AWSの見え方を設定に応じて変えてみました。
Chrome拡張機能は手元の環境のみに適用することができるので、他の人に影響を与えずに手軽に不便を解消できるのが素晴らしいですね!
最後までお読みいただき、ありがとうございました。
NTTテクノクロス Advent Calendar 2025、明日は @obi-r さんの記事です!ぜひご覧ください!!