Chrome Extensionとは
所謂Chrome拡張と呼ばれるブラウザ用の拡張機能のことです。具体的にはChrome ウェブストアで公開されているような拡張機能であり、Chromeユーザーであれば、Last PassやAdblockといったアプリを一度くらいはインストールしてみたことがあるではないでしょうか。これらの拡張機能は基本的にJavascript+HTML+CSSで書かれており、いくつかのExtension固有の技術について学ぶことで、一般的なフロントエンドのウェブアプリケーションで必要とされる技術と同様の技術で開発することができます。
小さなアプリを小さな労力でも作りやすく、公開もデベロッパーとして登録するための5ドル(600円くらい)だけなため、非常に魅力的なChrome Extensionですが、日本語のドキュメントはそこまで多くないため、とりあえず初心者がそれっぽいアプリを作り、公開までを行った軌跡を、Extensionの解説を含めて記事にしたいと思います。
本記事ではタイトルにある通り、このChrome Extensionを使って選択した場所にモザイクをかけられるような拡張機能を作り、~~他人のツイートを晒・・・~~プライバシーに関する情報に対してモザイクをかけたいという問題を解決したいと思います。
また本記事では初心者に向を対象としており、アプリケーションの作成に必要な技術についてChrome Extension固有の技術についても説明します。
またこのアプリはGitHubにてオープンソースでまったり開発を続けており、MosaicExtensionにてアプリのソースをダウンロードすることができます。適宜確認しながらこの記事を読んでいただければ幸いです。使いたいだけの場合は公式ストアから配信しているため、Web Mosaic - Webストアからインストールできますのでよろしければぜひ。
Chrome Extensionの構成
Chrome Extensionは必要なファイルをフォルダにまとめておいてそれを読み込むことでテストすることができます。読み込み方の手順は次の通りです。
この二つでアプリを開いてテストすることができます。
アプリのテストの仕方がわかったところで、Extensionに必要な要素のうち今回のアプリに必要なManifest、Background、Content Scriptの3つについて説明を行います。
Manifest
ManifestファイルはChrome Extensionに必ず一つ必要なファイルです。Google Extensionではここがフロントエンドの開発と唯一違うところであるといってよく、JSON形式で書かれ、そのアプリの名前、説明、バージョンといった基本的な情報やアプリ持つ権限について記載します。唯一違うところとは言っていますが大して難しいことは書く必要がなく。今回のモザイクアプリの具体的な見ればおおよそ何について記述すればよいかが、わかるかと思います。
{
"name": "Web Mosaic",
"description": "ウェブページの選択した部分をモザイクにするためのChrome Extension",
"version": "0.1",
"background": {
"scripts": ["js/background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery-3.3.1.js","js/spoiler/spoiler.js", "js/content.js"]
}
],
"permissions": ["contextMenus", "<all_urls>","activeTab"],
"manifest_version": 2
}
Background、ContentScriptsについては後述するとして、今回のアプリに必要な内容としては
- name : アプリの名前
- description : 説明書き
- version : アプリのバージョン
- manifest_version :とりあえず2って書いておけばok
- permission 必要な権限
とこのくらいなものです。より詳細な説明や書き方で困った場合、次の記事を参照してください。
Chrome 拡張機能のマニフェストファイルの書き方
Background Page
Chrome Extensionはバックグラウンドページを持つことができます。このページはHTMLやjavascriptで書くことが出来、ごく普通のウェブページと同様に作ることができます。アプリがインストールされてからバックグランドに常駐し続けるため、Chrome APIを使ってイベントの発生を待機したり、バックグランドで処理を回しておくことができます。これがChromeがメモリを占領する原因でもあるわけですが; ;
このバックグランドページの特徴としてChrome APIの(おそらく)すべてのAPIにアクセスすることが可能です。Chrome APIは、ダウンロード情報へのアクセス、Chromeのウォールペーパーの変更など、ブラウザの動作に関わる処理を記述することができます。
制限なく自分だけのバックグランドページを作ることができますが、開いているページの関数やDOMにアクセスすることができません。しかし開いているページに処理を実行しろっていうメッセージを送ることができます。
ではバックグランドページの読み込みと確認の仕方について説明します。といってもこれもマニフェストファイルに書くだけです。具体的には上のマニフェストの次の部分に相当します
"background": {
"scripts": ["js/background.js"]
},
ここではjsフォルダの下にあるbackground.js
というJavascriptをバックグラウンドページとして読み込んでいます。もちろんHTMLファイルを読み込むことができます。この読み込んだバックグランドページは拡張機能画面にある「バックグランドページ」からアクセスすることができます。
例えばbackground.jsにconsole.log("Background activate")
とだけ書いていた場合、次のような画面が出てくるはずです。
Content Scripts
次にContent Scriptsです。Content Scriptsはマッチするすべてのウェブサイトにアクセスした場合に読み込まれます。Content Scriptsは開いているページのDOMや関数にアクセスすることができますが、Chrome APIの一部にアクセスすることができません。CSSをいじってウェブページのデザインを変更する、ウェブページの内容を一部を書き換えるといったことはできますが、「右クリックのメニューにボタンを入れておいて押されたら」や「ページをお気に入りしたら」といったブラウザ上の行動をトリガーにして処理を発動させることができないちょっと惜しい子です。
しかしBackgroundとContent Scriptsはメッセージを送りあうことができるため、Chrome APIに関わることをBackground Pageに行わせ、ウェブページ自体に関わる事はContent Scriptに任せるというのがChrome Extensionの基本的な考え方になります。
今回のアプリの仕様で言えば、「コンテキストメニューにボタンを追加して押されたところを検知する」までがBackground pageのお仕事、「Background pageからメッセージを受け取ったら選択されているところにモザイクをかける」がContent Scriptsのお仕事になります。
Content Scriptsに追加する方法は相も変わらずmanifestファイルに追加するだけです。次は3つのjsを読み込んでいます。
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery-3.3.1.js","js/spoiler/spoiler.js", "js/content.js"]
}
],
先ほどのManifestファイルの抜粋ですが"matches": ["<all_urls>"],
だけがBackground pageとちがうところで、これは正規表現にマッチする場合のみContent Scriptsを読み込むのですが<all_urls>
とすることですべてのURLで読み込まれますgoogleでのみ有効にしたい場合であれば"matches": ["https://www.google.co.jp/*"],
となります。
モザイク機能の実装
ここから先が前述の知識を利用してモザイクをかける機能を実装する話になります。まずは基本的な設計について考えておきます。
- Background Page
- コンテキストメニューへの追加、および押されたときにContent Scriptsに対してメッセージを送信する。
- Content Scripts
- メッセージの受信、および受信した場合選択されている領域にモザイクをかける処理を行う。
必要な処理はこのくらいなものなので
Background Pageの実装
バックグランドページの処理は大きく分けて二つに分けられます。
- コンテキストメニューに項目を追加する。
- Content Scriptsに処理を開始するメッセージを送信する。
1についてはChrome 拡張機能 ー コンテキストメニュー(ContextType/ItemType) を参考に実装を行いました。注意しなくてはいけないのがバックグランドページをイベントページにしてはいけないことです。そうするとこのソースは動かなくなってしまいます。一応解決方法は存在しているらしいですが今回はバックグラウンドページで実装を行います(簡単だから)。
2についてはchrome拡張機能 > background.jsからcontentscript.jsにメッセージを送る方法を参考に実装しました(tabs.sendRequestは非推奨になっていますが・・・)。
したがってバックグラウンドページであるbackground.jsは次のようになります。
chrome.contextMenus.create({
"title" : "選択範囲をモザイクで隠す",
"type" : "normal",
"contexts" : ["selection"],
"onclick": function(info) {
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id,{});
});
}
});
chrome.contextMenus.create
ではコンテキストメニューに項目を追加することができます。contexts:["selection"]
とすることで範囲選択した状態での右クリック時にのみ追加されます。
onclick
はクリック後の動作を表し、ここではタブに対してリクエストを送っています。Content Scripts側ではこのリクエストを受信した場合にモザイク処理を行います。
Content Scriptsの実装
リクエストの受信
まずはリクエストの受信です。正直リクエストを受信してしまえばあとはExtensionと関係ないただのjavascriptのDOM操作になるのでそこから先はただのフロント開発になります。とはいっても先ほどのchrome拡張機能 > background.jsからcontentscript.jsにメッセージを送る方法にContent Script側のプログラムも書いてあるので大したことはありません。
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
console.log("Mosaic activate");
// モザイク処理を書く
}
}
);
基本は上のソースのように記述すればよく、//モザイク処理を書く
というところにモザイクにする処理を書けばいいだけです。こうすることで、コンテクストメニューのボタンを押した場合に処理を実行することができます。
モザイク処理
ここから先はExtensionに関係ない領域ですので簡潔に生きたいと思います。
モザイク処理についてフルスクラッチでプログラムを行うと非常に時間がかかります。そこで今回はSPOILER ALERT!を使おうと思います。このプラグインはwebページにアクセスすればわかりますが、画像や文章にモザイクをかけ、クリックで解除してくれます。使い方も非常に単純で、spoilerAlert('spoiler, .spoiler');
とすれば、spoilerタグで囲んだ要素、またはspoilerクラスを持つタグにモザイクがかかります。MITライセンスであるため、このプラグインについて「一度モザイクを解除された場合、再びクリックされてもモザイクがかからないようにする」ように仕様変更します。これによって一度モザイクをかけた領域を内包するようにモザイクをかけた場合、「最初のモザイクを再びアクティブにするクリックと、次にかけたモザイクを消すクリックが同時に起こってしまう」という状況にならないような仕様にします。
プラグインに施した細かい変更はソースを見ていただくとして、これで肝心のモザイクをかける処理を(他人の力で)解決しました。便利なプラグインですね
DOM操作
あとはselectionを使って、DOMをいじってとか適当な感じで囲めばいいのかと思いましたが、二つの問題からうまくいきません。
一つ目の問題はクラス名がバッティングしてしまった場合、spoilerAlert('.spoiler');
を実行したタイミングですべてのspoilerクラスがモザイクになってしまいます。これは一度モザイクをかけて消した領域なども同様であるため、どこかをモザイクをかけるたびにほかのモザイクがすべて復活してしまいます。もう一つの問題は今回は選択範囲を対象にしているため、spanタグで加工だけだとhtmlの規則違反になる。ということです。具体的な例を次に示します。
<li>...<span class="spoiler">....</li>
<li>.......</li>
<li>...</span>....</li>
というような囲い方になってしまう可能性があるという問題です。これはつまり<li>.....<span class="spoiler">....</li>....</span>
という構造になってしまいHTMLの規則的にアウトになってしまいます。ブラウザはそれでもいい感じに表示はしてくれますが、これが原因となってレイアウトが崩れてしまいます。
この問題はランダムなクラス名にするという方法で一つ目の問題は容易に解決することができますが、二つ目の問題についてはやや解決がめんどさそうだと思い、selection周りを調べていましたが、私の陥っている状況をまさに解決している素晴らしいサイトを見つけてまた他人の力で問題を解決しました。八章第三回 Rangeの活用とSelection — JavaScript初級者から中級者になろう — uhyohyo.netに範囲選択した領域を何らかのタグで囲う場合の、非常にわかりやすいサンプルと解説が載っていますのでこちらに解説を投げたいと思います。結果だけ伝えると
<li>...<random-str>....</random-str></li>
<li class="random-str">.......</li>
<li><random-str>...</random-str>....</li>
という形で、いい感じにランダムな文字列のタグとランダムな文字列のクラスを用いて囲めるようになりました(まだレイアウトが崩れることがありますが)。これはspoilerAlertでモザイク処理をすることで、一度消したモザイクが復活することなく、それでいてレイアウトを崩すことなくモザイクをかけることに成功しました。
ひとまず、コーディングはここまでとします。
公開
とりあえず、作ったアプリを使ってみると選択範囲をいい感じにモザイクにできてるみたいです(ただ一気に選択してモザイクをかけるとまだレイアウトがバグってしまう)。
とりあえずストアに公開したいと思います。ストアの公開に必要なものはクレジットカードと600円くらいだけですので大した準備は必要ありません。
まずはChrome Webストアにアクセスして、歯車マークから「デベロッパー ダッシュボード」をクリックします。
するとお金を要求されるので「今すぐこの料金を支払う」からクレジットカードを使って払ってしまいます。
ここまで行ってしまえばあとは簡単です。新しいアイテムを追加するを押せば、あとはフォームをポチポチ埋めていくだけです。
ただし、この時にいくつか注意点があります
- スクリーンショットなどの画像は指定された大きさでないと受け付けてもらえません
- プロジェクトはマニフェストファイルの入っているフォルダをzipで圧縮してアップロードする
この二点を守れば、あとはさほど難しくはなくひたすら紹介文を書いていけば終わるとひとまずドラフト版が完成すると思います。あとは「公開する」を押してしまえば、ストアに並ぶかどうかの審査が行われ合格すれば晴れてストア入りというわけです。審査はマニフェストファイルで要求している権限によっては時間がかかることがあるようですが、それでも5日程度で公開されます。ちなみに今回作ったアプリも問題なくストアに並びました。
今回の記事はここまでとします。皆様もぜひ何かしらのアプリを作ってみてください。
余談
この記事制作を通じて心底面倒だと思った作業にMarkdownのリンク作成があります。
- 「[]()」←この二つを書いておく
- リンク先のタブに飛んでURLをコピーする
- 戻ってペーストする
- 再びリンク先のタブに飛ぶ
- 今度はHTMLを開いてtitleタグを探してコピーする
- 再び戻ってペースト
これだけの作業をこなさねばならず、タブをたくさん開きっぱなしにしてしまう怠惰な私には非常に面倒な作業だと感じました。
正直できの悪いモザイクアプリなんて作ってないで解決すべき問題はこちらではないかと思いましたので右クリックから簡単にマークダウン形式のリンクを作れるChrome Extensionを作っておきましたw
よろしければ記事制作のお役に立ててください。
Github :
Uno-Takashi/Markdown-Link-Converter: マークダウン形式に変換したURLとタイトルをクリップボードにコピーするChrome Extension
ストア :
申請中
またExtensionはアイデア勝負でいろいろなことができると思います。またJSが多少わかればいいので、中高生向けの講習とかにも向いているかもしれません。それはさておき、この記事を読んだエンジニアがChrome Extensionを作ってくれたらうれしく思います。