All skills
aktsmm avatar

/chrome-extension-dev

@848fd9b
by yamapanaktsmm/agent-skills26 stars
4

Chrome/ブラウザ拡張機能開発の包括的ガイド。WXTフレームワーク、Manifest V3、Chrome API、テスト手法をカバー。Use when: ブラウザ拡張機能を作成・修正する時。Triggers on 'ブラウザ拡張機能', 'Chrome拡張', 'browser extension', 'WXT', 'content script', 'service worker'.

Use this Skill: https://skilld.dev/gh/aktsmm/agent-skills/chrome-extension-dev

This session only. Nothing lands on disk.

referencespatterns.md

≈2.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

よくあるパターン集

ブラウザ拡張機能開発でよく使う実装パターン。


メッセージングパターン

Content Script ↔ Service Worker

// content.ts - メッセージ送信
const response = await chrome.runtime.sendMessage({
  type: "GET_DATA",
  payload: { key: "value" },
});

// background.ts - メッセージ受信
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === "GET_DATA") {
    // 非同期処理
    fetchData(message.payload).then((data) => {
      sendResponse({ success: true, data });
    });
    return true; // 非同期レスポンスを示す
  }
});

Service Worker → Content Script

// background.ts
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const response = await chrome.tabs.sendMessage(tab.id!, {
  type: "UPDATE_UI",
  data: { theme: "dark" },
});

型安全なメッセージング

// types/messages.ts
type MessageMap = {
  GET_DATA: { request: { key: string }; response: { value: string } };
  SET_DATA: { request: { key: string; value: string }; response: void };
};

type MessageType = keyof MessageMap;

async function sendMessage<T extends MessageType>(
  type: T,
  payload: MessageMap[T]["request"]
): Promise<MessageMap[T]["response"]> {
  return chrome.runtime.sendMessage({ type, payload });
}

// 使用
const result = await sendMessage("GET_DATA", { key: "settings" });

ストレージパターン

型安全なストレージラッパー

// utils/storage.ts
interface StorageSchema {
  settings: {
    theme: "light" | "dark";
    notifications: boolean;
  };
  lastSync: number;
}

export async function getStorage<K extends keyof StorageSchema>(
  key: K
): Promise<StorageSchema[K] | undefined> {
  const result = await chrome.storage.local.get(key);
  return result[key];
}

export async function setStorage<K extends keyof StorageSchema>(
  key: K,
  value: StorageSchema[K]
): Promise<void> {
  await chrome.storage.local.set({ [key]: value });
}

// 使用
const settings = await getStorage("settings");
await setStorage("settings", { theme: "dark", notifications: true });

リアクティブストレージ(React)

// hooks/useStorage.ts
import { useState, useEffect } from "react";

export function useStorage<T>(key: string, defaultValue: T) {
  const [value, setValue] = useState<T>(defaultValue);

  useEffect(() => {
    // 初期値を読み込み
    chrome.storage.local.get(key).then((result) => {
      if (result[key] !== undefined) {
        setValue(result[key]);
      }
    });

    // 変更を監視
    const listener = (
      changes: { [key: string]: chrome.storage.StorageChange },
      areaName: string
    ) => {
      if (areaName === "local" && changes[key]) {
        setValue(changes[key].newValue);
      }
    };

    chrome.storage.onChanged.addListener(listener);
    return () => chrome.storage.onChanged.removeListener(listener);
  }, [key]);

  const updateValue = async (newValue: T) => {
    await chrome.storage.local.set({ [key]: newValue });
    setValue(newValue);
  };

  return [value, updateValue] as const;
}

// 使用
function SettingsComponent() {
  const [theme, setTheme] = useStorage("theme", "light");
  return <button onClick={() => setTheme("dark")}>Dark Mode</button>;
}

Content Script パターン

Shadow DOM でスタイル分離

// content.ts
export default defineContentScript({
  matches: ["<all_urls>"],
  main() {
    // Shadow DOM でホストページのスタイルから分離
    const container = document.createElement("div");
    const shadow = container.attachShadow({ mode: "closed" });

    shadow.innerHTML = `
      <style>
        .ext-panel { /* スタイル */ }
      </style>
      <div class="ext-panel">
        <!-- UI -->
      </div>
    `;

    document.body.appendChild(container);
  },
});

WXT の createShadowRootUi

// content.ts
import { createShadowRootUi } from "wxt/client";

export default defineContentScript({
  matches: ["<all_urls>"],
  cssInjectionMode: "ui",
  async main(ctx) {
    const ui = await createShadowRootUi(ctx, {
      name: "my-extension-ui",
      position: "inline",
      anchor: "body",
      onMount: (container) => {
        const root = createRoot(container);
        root.render(<App />);
        return root;
      },
      onRemove: (root) => {
        root.unmount();
      },
    });
    ui.mount();
  },
});

ページコンテキストでの実行

// ページのグローバル変数にアクセスする必要がある場合
export default defineContentScript({
  matches: ["<all_urls>"],
  world: "MAIN", // ページコンテキストで実行
  main() {
    // window オブジェクトはページと共有
    console.log(window.somePageVariable);
  },
});

ブラウザ自動操作パターン

ref 番号システム

DOM要素に一意の ref 番号を付与し、LLMが確実に要素を特定できるようにする。

// DOM解析でref番号を付与
function assignRefNumbers() {
  const interactiveElements = document.querySelectorAll(
    'button, a, input, select, [role="button"], [role="link"], [role="checkbox"]'
  );

  interactiveElements.forEach((el, i) => {
    el.setAttribute("data-copilot-ref", `e${i}`);
  });

  // 出力形式
  // [e0] button "次へ"
  // [e5] radio "そう思わない"
}

// LLMからの指示を解析
// [ACTION: click, e5]
function executeAction(action: string, ref: string) {
  const element = document.querySelector(`[data-copilot-ref="${ref}"]`);
  if (!element) return;

  switch (action) {
    case "click":
      (element as HTMLElement).click();
      break;
    case "focus":
      (element as HTMLElement).focus();
      break;
  }
}

ボット検出回避

// ループ間に3-5秒のランダム待機
async function humanLikeDelay() {
  const waitTime = 3000 + Math.random() * 2000;
  await new Promise((resolve) => setTimeout(resolve, waitTime));
}

// マウス移動をシミュレート
async function humanLikeClick(element: HTMLElement) {
  // ホバーイベント
  element.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
  await new Promise((resolve) => setTimeout(resolve, 100 + Math.random() * 200));

  // クリック
  element.click();
}

Service Worker パターン

長時間処理の分割

// 30秒制限を回避するため、処理を分割
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === "LONG_TASK") {
    // 即座にレスポンスを返す
    sendResponse({ status: "started" });

    // バックグラウンドで処理を継続
    processInChunks(message.data);

    return false; // 同期レスポンス
  }
});

async function processInChunks(data: any[]) {
  const CHUNK_SIZE = 100;

  for (let i = 0; i < data.length; i += CHUNK_SIZE) {
    const chunk = data.slice(i, i + CHUNK_SIZE);
    await processChunk(chunk);

    // 進捗を保存
    await chrome.storage.session.set({ progress: i + CHUNK_SIZE });
  }

  // 完了通知
  await chrome.runtime.sendMessage({ type: "TASK_COMPLETE" });
}

Keep-Alive パターン

// 定期的にウェイクアップ
chrome.alarms.create("keepAlive", { periodInMinutes: 0.5 });

chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === "keepAlive") {
    // セッションストレージをチェック
    chrome.storage.session.get("pendingTasks").then((result) => {
      if (result.pendingTasks?.length > 0) {
        processPendingTasks(result.pendingTasks);
      }
    });
  }
});

スクリーンショットパターン

現在のタブをキャプチャ

// background.ts
async function captureTab(): Promise<string> {
  const dataUrl = await chrome.tabs.captureVisibleTab(undefined, {
    format: "png",
  });
  return dataUrl;
}

// 使用(権限: activeTab または tabs + host_permissions)
chrome.action.onClicked.addListener(async (tab) => {
  const screenshot = await captureTab();
  // Base64 データURL が返る
});

フルページキャプチャ

// Content Script でスクロールしながらキャプチャ
async function captureFullPage(): Promise<string[]> {
  const screenshots: string[] = [];
  const viewportHeight = window.innerHeight;
  const totalHeight = document.documentElement.scrollHeight;

  for (let y = 0; y < totalHeight; y += viewportHeight) {
    window.scrollTo(0, y);
    await new Promise((resolve) => setTimeout(resolve, 100));

    const screenshot = await chrome.runtime.sendMessage({ type: "CAPTURE" });
    screenshots.push(screenshot);
  }

  return screenshots;
}

外部リソース

Source: SKILL.md on GitHub

2 warnings5mo4 checks · Risk SAFE
  • Gen Agent Trust Hub5mo

    This skill is a comprehensive educational resource for developing Chrome extensions using the WXT framework. It provides guides on Manifest V3, Chrome APIs, implementation patterns, testing strategies, and publishing workflows. No security issues were detected.

  • Socket5mo

    No alerts

  • Snyk5mo

    Risk: MEDIUM · 1 issue

  • Runlayer7mo

    7/7 files flagged

Signed by skilld at 848fd9b. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 17 hours ago.

Activeupdated 3 months ago
argument-hint
作りたい拡張機能、困っている API、対象ファイル
user-invocable
true
metadata
{
  "author": "yamapan (https://github.com/aktsmm)"
}

README badge

README badge for aktsmm/agent-skills/chrome-extension-dev