Chrome extension の Content scripts と Service workers

web

Content scripts

Content scripts はページのコンテキストで動くスクリプトで主に DOM 操作を行うのに用いられる。 Manifest の content_scripts フィールドの matches に一致したページで css や js が挿入される。 挿入タイミングは css が DOM の構築前で、 js は run_at によって指定できる。 デフォルトの document_idle では window.onload 以後に挿入されることが保証されている。

$ cat manifest.json
{
  "manifest_version": 3,
  "name": "test-extension",
  "version": "1.0",
  "content_scripts": [
     {
       "matches": ["https://*.sambaiz.net/*"],
       "css": ["content.css"],
       "js": ["content.js"]
     }
   ]
}

$ cat content.css
time {
    color: #ba4dab !important;
}

$ cat content.js
document.querySelectorAll('time').forEach((t) => {
    t.innerText = `+++ ${t.innerText} +++`;
});

chrome.runtime.onMessage.addListener((message) => {
    console.log(`content_script: ${JSON.stringify(message)}`);
})

デフォルトで ページや他の拡張機能とは分離された環境で動くが、 DOM は共有される。 他とのやり取りのために chrome.runtime.connect()して postMessage()したり、 chrome.runtime.sendMessage() API によってメッセージを送受信できるようになっているが、その他の API はほとんど呼べない。

ブラウザのwindow間の値渡し - sambaiz-net

Service workers

Service workers は必要なときにロードされ、 chrome.runtime.onInstalled()chrome.action.onClicked() といった様々なイベントのハンドリングを行う。 Manifest V2 では background_script がこの役割を担っていたが、V3 で廃止された。 なお、V3 ではリクエスト先の設定が host_permissions に分離された。

Chrome ExtensionsとChrome Apps - sambaiz-net

直接 DOM 操作を行うことはできず、chrome.scripting.executeScript() でスクリプトを挿入したり、 Content scripts にメッセージを送って行うなどする必要がある。

$ cat manifest.json 
{
  "manifest_version": 3,
  ...
  "background": {
    "service_worker": "service-worker.js"
  },
  "action": {
    "default_title": "Add @@@"
  },
  "permissions": [
    "scripting",
    "activeTab"
  ],
  "host_permissions": [
    "https://example.com/*"
  ]
}

$ cat service-worker.js
chrome.action.onClicked.addListener((tab) => {
    chrome.scripting.executeScript({
      target: {tabId: tab.id},
      func: (c) => {
        document.querySelectorAll('time').forEach((t) => {
            t.innerText = `${c} ${t.innerText} ${c}`;
        });
      },
      args: ['@@@'] 
    });
    
    chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
        chrome.tabs.sendMessage(tabs[0].id, {
            aaaa: tabs
        })
    });
});

ちなみに、action に default_popup を指定すると onClicked が呼ばれなくなる