1
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?

Chrome拡張機能の作り方

Last updated at Posted at 2025-12-21

この記事はteam411 Advent Calendar 2025の21日目の記事です

昨日はmetomananaさんの見やすいスライドの作り方でした

はじめに

team411でhogehogeをしているまくらです。今年411で何かやったかな……という気持ちになったので個人開発でそこそこ触れたChromeの拡張機能を書き始めから実際にWebStoreに公開するまでを書こうと思います。案外そこら辺を網羅してる記事が少なかったのもあります。

拡張機能とは

Chromeの拡張機能は、ブラウザに新たな機能を追加し、Web体験を自分好みにカスタマイズできるプログラムです。広告ブロックや翻訳、パスワード管理など、用途は多岐にわたります。Chromeウェブストアから手軽に導入でき、Web閲覧の効率や利便性を劇的に向上させることが可能です。HTMLなどのWeb技術で構築されており、世界中の開発者が便利なツールを日々公開しています。

実際になにか作ってみる

概略を説明するより実例を見た方が早いですよね、少し前にこんな拡張機能がバズっていました。

Googleの検索結果のAI要約をおみくじに変えるというものです。実装がGitHubで公開されていて、読んでみるとかなりシンプルな実装だったのでこれを改変してAI要約とAIタブを消そうと思います。

どういうファイル構成で拡張機能はできているか?

リポジトリにとんでファイル構成を見てみましょう
image.png
README.mdはただの説明ファイルなのでcontent.jsmanifest.jsonで構成されていることになります。manifest.jsonは拡張機能に必須のファイルでこれをChromeは参照して動かしています。

manifest.json

manifest.json
{
    "manifest_version": 3,
    "name": "おみくじ Google 改変拡張",
    "version": "1.0",
    "description": "Google検索の AI要約を非表示にして、おみくじの結果を表示します。",
    "permissions": ["scripting"],
    "host_permissions": ["https://www.google.com/*"],
    "content_scripts": [
      {
        "matches": ["https://www.google.com/search*"],
        "js": ["content.js"],
        "run_at": "document_idle"
      }
    ]
  }

"manifest_version"は現在3以外認められていません、2では拡張機能に与えられる権限が大きすぎて危険だったとのことです。この余波で広告ブロッカー系の拡張機能が打撃を受けていました。
重要なのは"permissions""host_permissions"などの必要とする権限回りです。

  • "scripting"は拡張機能がブラウザ上でJavaScriptやCSSを動的に実行(注入)する権限を持つことを宣言しています
  • "host_permissions"は 拡張機能がアクセスを許可されるWebサイトの範囲を指定しています。ここでは、Googleのドメイン内でのみデータの読み取りや変更が許されます
  • "content_scripts" 特定のページが開かれた際に、自動で実行されるスクリプトのルールです
    • matches: Googleの「検索結果ページ」でのみ動作するよう限定しています
    • js: 実行するプログラム本体(content.js)を指定しています
    • run_at: "document_idle"はページの読み込みがほぼ完了し、ブラウザが落ち着いたタイミングで実行することで、表示速度への影響を抑えています

このように拡張機能を定義する上で必要な事項を記入する所がmanifest.jsonです。勿論これらが項目のすべてではありません、拡張機能のアイコンをクリックすると表示されるポップアップ画面を設定するpopup、バックグラウンドでの動作を設定するbackgroundなど色々あります。

content.js

次にcontent.jsを見てみます。これがこの拡張機能の本体です。

content.js
const fortunes = [
    "大吉:今日はなんでもうまくいく!",
    "中吉:ほどほどに良い日。焦らず進めよう。",
    "小吉:ちょっと良いことがあるかも。",
    "吉:思わぬ出会いに注意。",
    "末吉:小さな幸せを見逃さないで。",
    "凶:無理せず休むのが吉。",
    "大凶:今日は慎重に行動しよう…。"
  ];
  
  function drawFortune() {
    const idx = Math.floor(Math.random() * fortunes.length);
    return fortunes[idx];
  }

  function createFortuneElement() {
    const newDiv = document.createElement('div');
    newDiv.textContent = "🔮 今日のおみくじ 🔮\n" + drawFortune();
    newDiv.style.cssText = `
      background: #fff3cd;
      border: 1px solid #ffeeba;
      padding: 12px;
      margin: 16px 0;
      font-size: 16px;
      font-weight: bold;
      color: #856404;
      border-radius: 8px;
    `;
    return newDiv;
  }
  
  function checkAndReplaceElement() {
    setTimeout(() => {
      const target = document.querySelector('.YzCcne');
      if (target) {
        target.style.display = 'none';
  
        const fortuneElement = createFortuneElement();
        target.parentNode.insertBefore(fortuneElement, target);
  
        window.isFortuneDisplayed = true;
      }
    },10);
  }
  
  // ページが読み込まれたら実行
  if (!window.isFortuneDisplayed) {
    checkAndReplaceElement();
  }

基本的には読んでの通りMath.random()で「おみくじ」をしてそれを親要素が"YzCcne"であるAIの概要にinsert、元の要素はtarget.style.display = 'none'で非表示にしています。
window.isFortuneDisplayedcheckAndReplaceElement()で初めて宣言されています、なのでページ読み込み時はundefinedとなりif (!window.isFortuneDisplayed)がTrueになって実行という寸法です。

ちょっと改変してみる

自分は今回タブと要約を消せばいいだけなのでこのように記述しました。

manifest.json
{
    "manifest_version": 3,
    "name": "AI-Free Search",
    "version": "1.0",
    "description": "Google検索の AI要約とAIモードタブを非表示にします。",
    "content_scripts": [
      {
        "matches": ["https://www.google.com/search*"],
        "js": ["content.js"],
        "run_at": "document_idle"
      }
    ],
    "icons": {
    "16": "icons/icon16.png",
    "32": "icons/icon32.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

scriptingはchrome.scripting APIを使う場合に必要ですが、content.jscontent_scriptsで自動注入されるため、この権限は使っていません。削除しました。
"host_permissions""matches"で自動的に宣言されるので削除しました。
iconsは拡張機能にアイコンが欲しかったので追加しました。

content.js
  function checkAndReplaceElement() {
    setTimeout(() => {
      const targetAbstract = document.querySelector('.YzCcne');
      const targetTab = document.querySelector('.olrp5b');
      if (targetAbstract || targetTab) {
        if(targetAbstract) targetAbstract.style.display = 'none';
        if(targetTab) targetTab.style.display = 'none';
  
        window.isAIFreeDisplayed = true;
      }
    },10);
  }
  
  // ページが読み込まれたら実行
  if (!window.isAIFreeDisplayed) {
    checkAndReplaceElement();
  }

AIモードタブの親要素は'olrp5b'だったので同様にdisplay = 'none'に、おみくじ部分は削除しました。
これくらいなら三項演算子使えば一行で書けそうですね。

実際に公開してみる

拡張機能は作ったので公開していきます。
これ読んで

1.Chrome ウェブストアのデベロッパー登録をする

ChromeWebStoreDeveloperDashBoardでChrome ウェブストアのデベロッパー登録をします。初回に5ドルを支払うことでずっと使えるようになります。Appleが1年で1万くらい取ることを考えれば良心的だと思います。
image.png
項目に同意して支払うと登録が完了します。

2.拡張機能をアップロードする

右上の + 新しいアイテムをクリックして拡張機能をZIPにしたファイルをアップロードしてください
Group 4.jpg
そうするとアップロードに必要な項目を埋めるページに飛ぶので埋めてください、今回はこんな感じです。必要なpermissionが増えるほど各項目が増えてめんどいです。
image.png
あと最低1枚説明画像が必要です。画像の大きさに制限があるものが多いのでFigmaとか使ってなんとかしてください。
image.png

項目を全部埋めると 「審査のため送信が」 押せるようになります。審査には体感2~4日かかります。気長に待ちましょう。
注意ですが、必要な権限以外も含めて申請してしまうと(将来実装予定の権限も)Rejectされてしまうので気を付けましょう

 
 
image.png
こんな風に突発で拡張機能を作ると記事の公開日に間に合いません、なんならこの記事も締め切りに間に合ってません

[追記:25/12/28]
拡張機能が公開されておりました。よかったら使ってください

 
以上です! よいお年を~~

1
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
1
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?