Chrome拡張機能の開発

JavaScript

初めてChrome拡張を作成したのですが、概念が分かりにくかったので覚書。
拡張機能としてはフェーズが3種類あります。

ページ内スクリプトに最も近いContentScript、
クリック時にのみ起動し、対象のサイトに直接は介入できないPageAction / BrowserAction、
永続化されて存在するBackgroundPage

Content Script

content_scriptのmanifestファイルを見てみましょう。
細かい項目は省きますが、jsとcssを対象のサイトに挿入できます。

{
    "manifest_version": 2,
    "name": "hoge",
    "version": "0.1.0",
    "description": "",
    "content_scripts": [
        {
            "run_at": "document_end",
            "matches": ["http://*/*", "https://*/*"],
            "js": ["index.js"],
            "css": ["index.css"]
        }
    ]
}

ただ、この言い方は語弊があります。冒頭でも「ページ内スクリプトに最も近い」と書いたとおり、このScriptの立ち位置はもう少し複雑です。
実は対象サイトに元々存在するjsと並列に対象サイトに打ち込まれるわけではありません。

挿入されるjs群は元のサイトとは独立した空間で実行されます。
一方で、jsが扱うdocument(html)は対象のサイトとなります。
htmlを通して両者は結合しています。

どういうことかというと、挿入したjsは対象サイトから独立しているため、対象のサイト内に埋め込まれているjsの変数やメソッドを直接扱ったりはできません。あれ?思ってたのと違う!と思われるかもしれませんが、そうなのです。

しかし、強引な手法で相手先のjs空間と同じ空間にjsを埋め込むことができます。それはscriptタグを相手のhtmlに埋め込むことです。こうすることによって対象サイトのjs空間内に直接jsを埋め込み実行できます。

この辺りがかなりトリッキーな構造になっているため、ちゃんと理解しないと思った動作ができません。

またContentScriptはBrowserが持つAPIのフル機能は使えません。いわゆるサイト内jsと同じレベルでしか扱えないため、後述の方法(BrowserActionやBackgroundPage)を併用して高度な機能を実現します。

Page Action / Browser Action

ContentScriptでは実行できないブラウザのAPIを利用したい時かつViewが必要な場合に使います。
生存期間など細かな違いはあると思いますが、後述のBackgroundPageと実質的に同じで、BackgroundPageにviewを持たせたいときに利用する、と認識しています。

PageActionを設定すると、画面右上のツールバー上に表示されるようになり、クリックすると設定したhtmlを表示できます。

私は使ってないので正確かどうか保証しかねますが、Page Action / Browser Actionともに同じものです。デフォルトでONなのがBrowserActionで、デフォルトでOFFなのがPageActionと思っています。

BackgroundPage / EventPage

APIのフル機能を使えるScriptになります。
BackgroundPageは永続化し、Chromeの起動から終了まで生存し続けてリソースを食うため、EventPageの利用が推奨されています。両者の違いはBackground常駐か、イベントトリガーで必要なときのみリソースを取るかです。

EventPageにすると永続化せずに定期的に終了されるため、データを貯めておくような処理の場合は終了前にChrome内のstorageに書き出す、といった処理が必要になります。
逆に言うとその程度なので、基本的にEventPageを利用します。

{
    "manifest_version": 2,
    "name": "hoge",
    "version": "0.1.0",
    "description": "",
    "content_scripts": [
        {
            "run_at": "document_end",
            "matches": ["http://*/*", "https://*/*"],
            "js": ["index.js"],
            "css": ["index.css"]
        }
    ],
    "background": {
        "scripts": ["background.js"],
        "persistent": false
    },
    "permissions": ["downloads"],
    "icons": {
        "128": "hoge.png"
    }
}

利用する場合はbackgroundにjsを指定し、persistentにfalseを指定します。
persistent: trueの場合はBackgroundPage(永続化)、
persistent: falseの場合がEventPage
になります。

permissionsという項目も増えています。
これは拡張機能に利用させるChromeAPI群を指定します。
tabsだったり、downloadsだったり、使いたい機能を設定します。

background.jsとcontent_script.jsとは別の世界線で実行されているため、直接相互のメソッドを呼んだりはできません。
chrome.runtime系のメソッドにて通信します。
一般的なのはこんな感じです。

background.js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.download_url) {
        chrome.downloads.download({
            url: message.download_url,
            filename: message.filename,
            saveAs: false,
        });
    }
    sendResponse('download called');
});
content_script.js

function download(url, filename) {
    chrome.runtime.sendMessage(
        {
            download_url: url,
            filename: filename,
        },
        function (responseMessage) {
            console.log(responseMessage);

        }
    );
}

content_script側からchrome.runtime.sendMessageでbackground.jsに向けてメッセージを飛ばします。
background.js側ではmessageイベントを購読しておき、目的のイベントが飛んできた場合に発火させます。レスポンスを返したい場合はsendResponseに適当なオブジェクトを詰めて返します。

コメント