今回は実際に拡張機能を作ります。
まずは 前回 Console で動作することを確認したプログラムを、ページ読み込み時に実行される拡張機能にします。
まず、拡張機能のファイルを入れるフォルダを作成してください。基本的に1つの拡張機能に対して1つフォルダを作ります。名前はなんでもOKです。 次に Visual Studio Code (以下 VSCode) を起動して、今作成したフォルダを開いてください (メニュー File > Open Folder)
このフォルダの中に content_script.js という名前のファイルを作成します。 VSCode の左側に表示される Explorer のフォルダ名にカーソルを合わせると New File アイコンが表示されます。 エディタに以下のプログラムを入力して保存してください。
let calendars = document.querySelectorAll(".list-rst__calendar-frame");
for(let i = 0; i < calendars.length; i++){
let e = calendars[i];
e.style.display = 'none';
}
次に、拡張機能についての情報や説明などを決められたフォーマットで書いた マニフェストファイル を作成します。 マニフェストファイルは決められたファイル名 manifest.json で作る必要があります。 先ほどと同じ要領で、以下の内容を manifest.json として作成してください。
{
"name": "Sample Chrome Extension",
"description": "Remove Unnecessary Parts from Tabelog etc.",
"version": "0.1",
"manifest_version": 3,
"content_scripts": [
{
"matches": ["https://tabelog.com/*"],
"js": ["content_script.js"]
}
]
}
マニフェストファイルは json 形式で書きます。設定ファイルとしてよく用いられるファイル形式のひとつです。 json は JavaScript の Object と互換性があるファイル形式で、キーと値のペアを列挙したものです。 キーの文字列を必ず "" (ダブルクォーテーション) で囲む必要があるので注意してください。
各設定項目の説明を少しだけします。これは後で読むことにして先に進んでいただいても結構です。
{...}
はオブジェクトで、[...]
は配列です。マニフェストファイルに指定できる項目は他にもたくさんあります が、実際に利用するものはあまり多くありません。
拡張機能の管理画面を Chrome のメニュー(右上の点々アイコン)から開きます。アドレス欄に直接 chrome://extensions/ と入力して開くこともできます。 初めて設定画面を開いたときは Chrome ストアに公開されていない拡張機能がインストールできるように右上の「デベロッパーモード」スイッチをオンにしてください。
拡張機能のファイルが入ったフォルダを管理画面にドラッグアンドドロップするだけで拡張機能をインストールすることができます。 下図のように設定画面に拡張機能の名前・説明・バージョン番号が表示されていることを確認してください。 管理画面の上部にある「パッケージ化されていない拡張機能を読み込む」からインストールすることも可能です。 (補足:「パッケージ化された拡張機能」とはストアからインストールする完成品のことです。)
インストールした状態で 食べログのページ を開いてみてください。 開いたときからすでにカレンダー部分が削除されているはずです。
管理画面に戻って、上図の右下にあるボタンを押すと拡張機能のオン・オフを切り替えることができます。 オフにした状態で食べログのページを再読み込みするといつも通りカレンダーが表示されるはずです。
拡張機能のプログラムを書き換えたときには設定画面で再読み込みする必要がありますので忘れないように注意してください。 プログラムを書き換えたら、まず拡張機能を再読み込みして、さらに適用対象のウェブページを再読み込みして動作確認をすることになります。
ファイルを配布すれば他の人にも同じようにインストールして使ってもらうことができます。 もっと広く使ってもらうには Chrome ウェブストア に収録してもらうとよいでしょう。 ここでは詳しく説明しませんので、公式ドキュメント を読んで挑戦してみてください。 ウェブストア登録料として 5$ かかります (アプリごとではなく1回だけです)。
ページ読み込み時に指定した CSS を適用する拡張機能もほとんど同じように作ることができます。以下の CSS を mystyle.css という名前で先ほどのフォルダに保存してください。 各ル―ルの最後にある !important は「既に設定されているルールよりもこちらを優先して」という意味です。 拡張機能で CSS ルールを上書きしたいときにはとりあえずつけておくとよいと思います。
.o365cs-base, .o365sx-appName, .o365sx-button {
background-color: #5eb954 !important;
}
続いて Outlook on the Web を表示するときに mystyle.css を適用するように manifest.json の content_scripts の部分を以下のように書き換えましょう。 付け加える部分の前につくカンマを忘れやすいので要注意です。
"content_scripts": [
{
"matches": ["https://tabelog.com/*"],
"js": ["content_script.js"]
},
{
"matches": ["https://outlook.office.com/mail/*"],
"css": ["mystyle.css"]
}
]
拡張機能の再読み込みボタンを押したら、Outlook on the Web を開いてみてください。 ヘッダーの背景色が某伝説のアイドルグループのテーマカラーになったと思います。
今回は一つの拡張機能に複数の機能 (食べログの改変と Outlook on the Web の改変) を入れましたが、1つの拡張機能は1つの機能とすることが推奨されています。 この原則を守らないとストアに収録してもらえません (審査ではねられます) ので、ちゃんとしたものを作るときは手抜きでひとまとめにしないようにしましょう。
ここから先はページ中の文章を書き換えるプログラムの書き方を見ていきます。説明上はひとつひとつ拡張機能にはしませんので、Console で動作確認をする、おもしろいプログラムができたら拡張機能にするなどしてください。
突然ですが、次に示すプログラムは文書中すべての「です。」を「でござるよ。」に置き換える関数を定義して、それを呼び出すものです。 まずは Console 上で試してみてください。これを応用して Twitter を絵文字だらけにしてしまう拡張機能なんか作るとおもしろいかもしれませんね。
function f(n){
let cs = n.childNodes;
for(let i = 0; i < cs.length; i++){
let c = cs[i];
if(c.nodeType == Node.TEXT_NODE){
c.textContent = c.textContent.replace(/です。/g, "でござるよ。");
}
else{
f(c);
}
}
}
f(document.body); // document.body は HTML の文章全体を示す要素
このプログラムを理解するにはいくつかのことを学ぶ必要があります。
.replace(/です。/g, "でござるよ。")
が書き換えを行っている部分です。
書き換えたい文字列を指定している一つ目の引数 /です。/g
には 正規表現 を使っています。
正規表現は文字列中のパターンを表現するもので /パターン/フラグ
という文法で書きます。
正規表現を使うと文字列中であるパターンに当てはまる (= マッチする) 部分について処理するプログラムを書くことができます。
文字列の書き換え以外にはたとえば、ユーザが入力した電話番号がちゃんと電話番号のフォーマットになっているかをチェックするときなどに使います。
みなさんも入力したデータが正しくないと表示されて入力をやり直したことが一度くらいはありますよね?
正規表現について本気で書き始めるととても長くなりますので、よくまとまっている Mozilla JavaScript Guide の 正規表現 を紹介しつつ、 わかりやすくてよく使うところをピックアップして紹介していきます。
まず、フラグには g, i などの文字を書きます。
/ab/i
は ab, Ab, aB, AB のいずれにもマッチします。パターンの方はかなりたくさん書き方がありますので、いくつかの書き方を例で見ていきたいと思います。
/(乃木坂|欅坂|けやきざか|日向坂)46/
/w+/
/ab+c/
, /(ab)+c/
/\d{3}/
Mozilla JavaScript Guide の 正規表現 を読みながら、色々な書き換えを試してみましょう。
"プログラミングおもしろすぎるwwwww".replace(/w+/, "😂")
のような1行プログラムを Console で試すのもお手軽だと思います。
2種類以上の書き換えを行う場合には "ビールもワインも大好きです".replace(/ビール|麦酒/, "🍺").replace(/ワイン/, "🍷")
のように数珠つなぎに書くことができます。
HTML文書は、PCのフォルダの中にさらにフォルダがあるのと同じように、要素の中にさらに要素を持つ入れ子になったデータ構造を取ります。 デベロッパーツールの Elements タブで、エクスプローラ (Mac では Finder) で順番にフォルダを開いていくように、HTMLの要素を開いていくとよく似ていることが実感できると思います。
要素を JavaScript で扱うときには Object として扱うことができます。文書をオブジェクトとして表すこの方法のことは Document Object Model (DOM) と呼ばれています。
ある要素から見て、それを含む要素は1つです。この要素のことを 親要素 と呼びます。要素オブジェクトの parentNode キーにあります。
また、要素は、複数の要素を含むことができます。これらの要素のことを 子要素 と呼びます。要素オブジェクトの childNodes キーにあります(複数形です)。 childNodes は配列のように扱うことができます。
nodeType キーにはその要素の種類を表す値が記録されています。主に扱うのは Node.ELEMENT_NODE
と Node.TEXT_NODE
です。
テキストノードは子要素を含まず、textContent キーに表示する文字列を持ちます。textContent に文字列を代入することで書き換えることができます。
ある関数が、その関数自身を呼び出すことを 再帰呼び出し (recursive call) と呼びます。
数学で出てくる漸化式や帰納法とも関連する考え方です。たとえば以下のような関数を実行したらどのような結果が出るか予想ができますか?
function g(n){
if(n > 2){
return g(n-1) + g(n-2);
}
else{
return 1;
}
}
for(let i = 1; i < 10; i++){ console.log(i + " : " + g(i)); }
これは漸化式 an = an-1 + an-2 で表されるフィボナッチ数列をプログラムにしたもので、関数 g の中で関数 g を呼び出す、再帰呼び出しが含まれています。 再帰呼び出しを含む関数には必ず条件分岐を使って、条件分岐をしない場合を作っておく必要があります。そうしないと無限ループと同様に永遠に終わらないプログラムになってしまいます。 漸化式でも必ず a1 = 1 などの初期値が与えられるのと同じようなことです。
漸化式の一般項を求めるのが必ずしも容易ではないのと同じように、プログラミングでも再帰呼び出しを使って書く方がずっと簡単に書けるプログラムがあります。 先の例に出てくる、すべての HTML 要素を辿りながら処理をするプログラムも再帰呼び出しが便利な場面のひとつです。 ある要素についてする処理をさらにその子要素にも行うように再帰呼び出し、 ただしテキスト要素には子要素がないので再帰呼び出しをしない、というプログラムになります。