Chrome拡張機能(タブURL取得)

Chrome拡張で「現在のウィンドウの全タブURLをワンクリックでコピー」する最小構成コードです。

manifest.json / background.js / offscreen.html / offscreen.js の4ファイルを同一フォルダに保存し、
chrome://extensions から「パッケージ化されていない拡張機能を読み込む」で導入してください。

フォルダ構成

tab-url-copier/
  manifest.json
  background.js
  offscreen.html
  offscreen.js

1) manifest.json

{
  "manifest_version": 3,
  "name": "Tab URL Copier (1-click, stable)",
  "version": "1.0.1",
  "description": "Copy all tab URLs in the current Chrome window to clipboard with one click (stable via offscreen).",
  "permissions": ["tabs", "offscreen", "clipboardWrite"],
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "action": {
    "default_title": "Copy tab URLs"
  }
}

2) background.js

async function ensureOffscreen() {
  const exists = await chrome.offscreen.hasDocument?.();
  if (exists) return;

  await chrome.offscreen.createDocument({
    url: "offscreen.html",
    reasons: ["CLIPBOARD"],
    justification: "Write tab URLs to clipboard."
  });
}

async function copyTextToClipboard(text) {
  await ensureOffscreen();
  const res = await chrome.runtime.sendMessage({ type: "COPY_TO_CLIPBOARD", text });
  if (!res?.ok) throw new Error(res?.error || "Clipboard copy failed");
}

function uniqKeepOrder(arr) {
  const seen = new Set();
  const out = [];
  for (const x of arr) {
    if (!seen.has(x)) {
      seen.add(x);
      out.push(x);
    }
  }
  return out;
}

async function badge(text) {
  await chrome.action.setBadgeText({ text });
  if (text) setTimeout(() => chrome.action.setBadgeText({ text: "" }), 1200);
}

chrome.action.onClicked.addListener(async () => {
  try {
    const tabs = await chrome.tabs.query({ currentWindow: true });
    const urls = uniqKeepOrder(
      tabs.map(t => t.url).filter(u => typeof u === "string" && u.length > 0)
    );

    await copyTextToClipboard(urls.join("\n"));
    await badge(String(urls.length));
  } catch (e) {
    console.error(e);
    await badge("ERR");
  }
});

3) offscreen.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Offscreen</title>
  </head>
  <body>
    <script src="offscreen.js"></script>
  </body>
</html>

4) offscreen.js

chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
  (async () => {
    if (msg?.type !== "COPY_TO_CLIPBOARD") return;

    try {
      await navigator.clipboard.writeText(msg.text ?? "");
      sendResponse({ ok: true });
    } catch (e) {
      // fallback for restricted environments
      try {
        const ta = document.createElement("textarea");
        ta.value = msg.text ?? "";
        document.body.appendChild(ta);
        ta.select();
        document.execCommand("copy");
        ta.remove();
        sendResponse({ ok: true, fallback: true });
      } catch (e2) {
        sendResponse({ ok: false, error: String(e2) });
      }
    }
  })();

  return true;
});