ひさしぶりに、Goolge Chromeの拡張機能をつくることになったので、メモを残しておきます。
どれぐらいひさしぶりなのか調べてみると、僕が最後に拡張機能をつくったのが、2015年12月だったので、かれこれ7年ぶりでした。
その間に、manifest_versionが2から3にあがったようです。
習作の仕様を決める
個人的に思う、Goolge Chromeの拡張機能のイメージといえば、
- URLバーの右にアイコンが表示される
- アイコンをクリックすると何かが起こる
というものです。
僕が普段使っている拡張機能は、
ぐらいですが、すべて、アイコンクリックきっかけで何かが起こります。
なので、今回はアイコンをクリックをトリガーになにかを起こそうと思います。
なにを起こすか考えたのですが、昔作ったブックマークレットkaze.jsを拡張機能としてリメイクしてみようと思いました。
kaze.jsは実行すると、ブラウザが風邪を引く(本文内の「か」と「ぜ」を非表示にする)ブックマークレットです。
実装する
まず、完成したものをアップしておきます。
manifest.json
{ "name": "kaze-js", "version": "1.0", "manifest_version": 3, "permissions": ["activeTab", "scripting"], "background": { "service_worker": "background.js" }, "action": { "default_title": "kaze-js", "default_icon": "icon.png" } }
manifest_versionは3で実装しました。
permissionsの「activeTab」はアクティブなタブへのアクセス、「scripting」はスクリプトやCSSファイル挿入のパーミッションです。
background.js
chrome.action.onClicked.addListener((tab) => { chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['main.js'], }); });
background.jsはものすごく単純で、アイコンをクリックした際、アクティブなタブで実行するJavaScriptファイルを設定しているだけです。
manifest_versionが2のときは、chrome.browserAction.onClicked.addListenerで登録していましたが、3からはchrome.action.onClicked.addListenerを使うようになりました。
main.js
(() => { 'use strict'; function PandemicSpan(elm) { _constructor.call(this, elm); function _constructor() { this.elm = elm; this.queue = [ _zoomIn.bind(this), _zoomOut.bind(this), _zoomIn.bind(this), _zoomOut.bind(this), _explode.bind(this), _vanish.bind(this) ]; this.achooFlag = false; this.index = 0; this.elm.style.cssText += PandemicSpan.CONST.DEFAULT_STYLE; this.elm.addEventListener('webkitTransitionEnd', _handleTransitionEnd.bind(this), false); this.elm.addEventListener('mozTransitionEnd', _handleTransitionEnd.bind(this), false); this.elm.addEventListener('msTransitionEnd', _handleTransitionEnd.bind(this), false); this.elm.addEventListener('oTransitionEnd', _handleTransitionEnd.bind(this), false); this.elm.addEventListener('transitionend', _handleTransitionEnd.bind(this), false); } function _zoomIn() { this._zoomIn(); } function _zoomOut() { this._zoomOut(); } function _explode() { this._explode(); } function _vanish() { this._vanish(); } function _handleTransitionEnd(evt) { this._handleTransitionEnd(evt); } } PandemicSpan.CONST = { DEFAULT_STYLE : [ ';', 'display: inline-block;', '-webkit-transition: all 1s ease;', '-moz-transition: all 1s ease;', '-ms-transition: all 1s ease;', '-o-transition: all 1s ease;', 'transition: all 1s ease;', '-webkit-transform: rotateZ(0deg);', '-moz-transform: rotateZ(0deg);', '-ms-transform: rotateZ(0deg);', '-o-transform: rotateZ(0deg);', 'transform: rotateZ(0deg);', 'font-size: 100%;', 'opacity: 1;' ].join(''), KLASS : 'k-virus' }; PandemicSpan.prototype._handleTransitionEnd = function(evt) { if (!!this.achooFlag) { ++this.index; if (this.index < this.queue.length) { this.queue[this.index](); } } }; PandemicSpan.prototype._zoomIn = function() { this.elm.style['font-size'] = '200%'; }; PandemicSpan.prototype._zoomOut = function() { this.elm.style['font-size'] = '50%'; }; PandemicSpan.prototype._explode = function() { this.elm.style['-webkit-transform'] = 'rotateZ(360deg)'; this.elm.style['-moz-transform'] = 'rotateZ(360deg)'; this.elm.style['-ms-transform'] = 'rotateZ(360deg)'; this.elm.style['-o-transform'] = 'rotateZ(360deg)'; this.elm.style['transform'] = 'rotateZ(360deg)'; this.elm.style['font-size'] = '5000%'; this.elm.style['opacity'] = '0'; }; PandemicSpan.prototype._vanish = function() { this.elm.style.display = 'none'; this.achooFlag = false; this.index = 0; }; PandemicSpan.prototype.achoo = function() { this.achooFlag = true; this.queue[this.index](); }; let index = 0; let sickSpanList = []; [].slice.call(document.querySelectorAll(`.${ PandemicSpan.CONST.KLASS }`)).forEach((dom) => { dom.style.cssText = ''; dom.classList.remove(PandemicSpan.CONST.KLASS); }); examination(document.body); sickSpanList = [].slice.call(document.querySelectorAll(`.${ PandemicSpan.CONST.KLASS }`)).map((sickSpan) => { return new PandemicSpan(sickSpan); }); setTimeout(function catchCold() { if (index < sickSpanList.length) { sickSpanList[index++].achoo(); setTimeout(catchCold, index); } }, 500); function examination(dom) { [].slice.call(dom.childNodes).forEach((child) => { if (child.nodeType === 3) { child.textContent = child.textContent.replace(/(か|ぜ|カ|ゼ|風|邪)/g, '<<span<<$1>>span>>'); } else { examination(child); } }); if (dom.innerHTML) { dom.innerHTML = dom.innerHTML.replace(/<<span<</g, `<span class="${ PandemicSpan.CONST.KLASS }">`); dom.innerHTML = dom.innerHTML.replace(/>>span>>/g, '</span>'); } } })();
main.jsはブックマークレットの処理と同様のものです。
ページに挿入されるので、DOMにアクセスすることもできます。
Class構文を使っていないのは、7年以上前に書いたからです。