3
1

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 2023-12-18

はじめに

本記事はラクスアドベントカレンダーの19日目です。
タブを開き過ぎて,見つからず同じページをいくつも開くことが最近続いていたので,タブを検索する拡張機能を作りました.
コード全体はこちらです.
この記事ではChrome拡張を作った際に調べた情報のまとめと作ったタブ検索機能について紹介します.

Chrome拡張の情報源

タブ検索機能のChrome拡張

今回作ったChrome拡張機能を例にして,作成に必要なファイルと手順を紹介していきます.

  • 今回作った拡張機能

下の画像のように,拡張機能を選択するとポップアップが出現し,今開いているタブの一覧が出現します.入力ボックスにタイトルを入力して検索ボタンを押下するとタブを検索できます.
拡張機能チュートリアルのタブマネージャーをベースにして作りました.

image.png

プロジェクト構造

.
├── images
│   ├── icon-128.png
│   ├── icon-16.png
│   ├── icon-32.png
│   └── icon-48.png
├── manifest.json
├── popup.css
├── popup.html
└── popup.js
  • images/
    imagesは画像を保存するディレクトリです.今回の拡張機能で利用した画像が格納されています.

  • manifest.json
    拡張機能の機能や構成を記述するファイルです.

  • popup.html
    popupで表示するページのhtmlを記述するファイルです.

  • popup.css
    popupで表示するページのCSSを記述するファイルです.

  • popup.js
    popupで表示するページのJavaScriptを記述するファイルです.

これらのファイルを編集して拡張機能を実装しました.
次からファイルの中身を説明していきます.

manifest.json

このファイルは以下の通りに記述しました.

{
  "manifest_version": 3,
  "name": "Tab Manager",
  "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_popup": "popup.html"
  },
  "host_permissions": ["https://*/*"]
}
  • manifest_version
    パッケージが必要とするマニフェストファイル形式のバージョンを指定します.
    現状は3でいいようです.
    参考:Manifest Version

  • name
    拡張機能の名前です.この項目の記述は必須です.
    45文字の制限があります.
    参考:Manifest - name

  • icons
    拡張機能またはテーマを表すアイコンを 1 つ以上指定します.
    参考:Manifest - Icons

  • action
    Chromeで拡張機能のツールバーボタンを制御するchrome.action API を使用できるようにします.
    ポップアップのファイルを指定することで,ユーザーがツールバーにある拡張機能のアクションボタンをクリックすると、アクションのポップアップが表示されるようになります.
    default_popupにhtmlファイルを指定します.
    参考:chrome.action

  • host_permissions

拡張機能のAPIの使用を許可するAPIを設定します.
ここで特定のURLのみを指定しておくと許可されていないURLのタブは検索結果に含まれません.
記述はMatch patternsに書いてある形式で行います.
今回はhttpsであればこの拡張機能の使用を許可するようにしています.

参考:Declare permissions
参考:Match patterns

popup.css

popupのCSSを記述するファイルです.
特にこだわりがなかったので,チュートリアルのCSSをそのまま使用しました.
このファイルは以下の通りに記述しました.

body {
  width: 20rem;
}

ul {
  list-style-type: none;
  padding-inline-start: 0;
  margin: 1rem 0;
}

li {
  padding: 0.25rem;
}
li:nth-child(odd) {
  background: #80808030;
}
li:nth-child(even) {
  background: #ffffff;
}

h3,
p {
  margin: 0;
}

popup.html

popupのhtmlを記述するファイルです.
チュートリアルをベースに一部変更しています.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./popup.css" />
  </head>
  <body>
    <template id="li_template">
      <li>
        <a>
          <h3 class="title">Tab Title</h3>
          <p class="pathname">Tab Pathname</p>
        </a>
      </li>
    </template>

    <h1>タブ検索</h1>
    <input type="text" id="search" placeholder="タイトルを入力">
    <button>検索</button>
    <ul></ul>
  </body>
</html>
<script src="./popup.js" type="module"></script>

popup.js

popupで使用するJavaScriptを記述するファイルです.
チュートリアルをベースに一部変更しています.

// 一覧を取得
const tabs = await chrome.tabs.query({
  url: ["*://*/*"],
});
const template = document.getElementById("li_template");
const elements = new Set();
for (const tab of tabs) {
  // コピーして新規のテンプレを用意する
  const element = template.content.firstElementChild.cloneNode(true);
  // タイトルとパスを記述する
  const title = tab.title.split("-")[0].trim();
  const pathname = new URL(tab.url);
  element.querySelector(".title").textContent = title;
  element.querySelector(".pathname").textContent = pathname;
  // リンクを押したときの動作を仕込む
  element.querySelector("a").addEventListener("click", async () => {
    await chrome.tabs.update(tab.id, { active: true });
    await chrome.windows.update(tab.windowId, { focused: true });
  });
  elements.add(element);
}

document.querySelector("ul").append(...elements);
const button = document.querySelector("button");

// 検索
button.addEventListener("click", async () => {
  const searchText = document.getElementById("search").value;

  // 配列
  const url = [];
  for (const tab of tabs) {
    // 部分一致でタイトルを検索,一致したらURLを配列に格納
    if (tab.title.includes(searchText)) {
      url.push(tab.url);
    }
  }
  const searchTabs = await chrome.tabs.query({
    url: url,
  });
  const template = document.getElementById("li_template");
  // ulを空にする
  document.querySelector("ul").innerHTML = "";
  const elements = new Set();
  for (const tab of searchTabs) {
    // コピーして新規のテンプレを用意する
    const element = template.content.firstElementChild.cloneNode(true);
    // タイトルとパスを記述する
    const title = tab.title.split("-")[0].trim();
    const pathname = new URL(tab.url).pathname;
    element.querySelector(".title").textContent = title;
    element.querySelector(".pathname").textContent = pathname;
  
    element.querySelector("a").addEventListener("click", async () => {
      await chrome.tabs.update(tab.id, { active: true });
      await chrome.windows.update(tab.windowId, { focused: true });
    });
    elements.add(element);
  }
  document.querySelector("ul").append(...elements);
});

まず,一覧を取得します.

const tabs = await chrome.tabs.query({
  url: ["*://*/*"],
});

検索するタブのURLを指定します.
指定する形式はMatch patternsを使用します.
また複数指定することも可能です.
その場合は以下の例のように配列の内容を変更します.

const tabs = await chrome.tabs.query({
  url: ["http://*/*","https://*/*"],
});

次にtabを表示させます.
要素はfor文で取り出しています.
for文の中身について一部解説していきます.

const element = template.content.firstElementChild.cloneNode(true);

これはpopup.htmlで記述してあるtemplateをコピーしています.

popup.htmlの一部
    <template id="li_template">
      <li>
        <a>
          <h3 class="title">Tab Title</h3>
          <p class="pathname">Tab Pathname</p>
        </a>
      </li>
    </template>

このテンプレートを書き換えて,タイトルとURLを追加します.

取得したtabは画像のようにtitleとurlを持っています.
image.png

htmlの要素に取得したtitleとurlを追加します.

  // const pathname = new URL(tab.url).pathname;
  const pathname = new URL(tab.url);
  element.querySelector(".title").textContent = title;
  element.querySelector(".pathname").textContent = pathname;

次にタグを押下したときに対象のタブに遷移するようにします.

  element.querySelector("a").addEventListener("click", async () => {
    await chrome.tabs.update(tab.id, { active: true });
    await chrome.windows.update(tab.windowId, { focused: true });
  });

最後に検索機能のメイン部分です.

button.addEventListener("click", async () => {
  const searchText = document.getElementById("search").value;

  // 配列
  const url = [];
  for (const tab of tabs) {
    // 部分一致でタイトルを検索,一致したらURLを配列に格納
    if (tab.title.includes(searchText)) {
      url.push(tab.url);
    }
  }
  const searchTabs = await chrome.tabs.query({
    url: url,
  });
  // 省略
  for (const tab of searchTabs) {
    // 省略
  }
  document.querySelector("ul").append(...elements);
});

queryにはurlを与える必要があるので,取得済みのタブ情報をタイトルで検索してURLを取得します.
urlは複数指定できるので,url配列に一致したURLを格納しておいて,queryをもう一度実行します.

以上の実装でタブ検索機能が実装できました.

拡張機能の読み込み

このページに従ってやっていけば読み込みできます.

  1. タブで chrome://extensionsを検索します.
    image.png

  2. デベロッパーモードを有効化します
    image.png

  3. パッケージ化されていない拡張機能を読み込むを選択します.

  4. フォルダを選択します
    image.png

以上で拡張機能が使用可能になります.

まとめ

今回はChrome拡張機能を作成しました.
githubにサンプルがたくさんあったので,ぜひ興味があるかたは見てみると自分が作りたい機能の参考になると思います.
記事を見ていただきありがとうございました.

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?