みかづきブログ・カスタム

基本的にはちょちょいのほいです。

Goolge Chromeの拡張機能の習作をつくる 💻

ひさしぶりに、Goolge Chromeの拡張機能をつくることになったので、メモを残しておきます。
どれぐらいひさしぶりなのか調べてみると、僕が最後に拡張機能をつくったのが、2015年12月だったので、かれこれ7年ぶりでした。
その間に、manifest_versionが2から3にあがったようです。

developer.chrome.com

開発方法を調べる

公式のドキュメントに目を通すのが早いと思います。

developer.chrome.com

今回はそこまで、難しい拡張機能をつくるつもりはなかったので、公式のサンプルコードに目を通せば、だいたいOKでした。

github.com

習作の仕様を決める

個人的に思う、Goolge Chromeの拡張機能のイメージといえば、

  1. URLバーの右にアイコンが表示される
  2. アイコンをクリックすると何かが起こる

というものです。

僕が普段使っている拡張機能は、

  1. Chrome Remote Desktop
  2. Google Analytics Debugger
  3. WebXR API Emulator

ぐらいですが、すべて、アイコンクリックきっかけで何かが起こります。

なので、今回はアイコンをクリックをトリガーになにかを起こそうと思います。

なにを起こすか考えたのですが、昔作ったブックマークレットkaze.jsを拡張機能としてリメイクしてみようと思いました。

kaze.kimizuka.fm

kaze.jsは実行すると、ブラウザが風邪を引く(本文内の「か」と「ぜ」を非表示にする)ブックマークレットです。



実装する

まず、完成したものをアップしておきます。

github.com

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ファイル挿入のパーミッションです。

developer.chrome.com
developer.chrome.com

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を使うようになりました。

developer.chrome.com

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(/&lt;&lt;span&lt;&lt;/g, `<span class="${ PandemicSpan.CONST.KLASS }">`);
      dom.innerHTML = dom.innerHTML.replace(/&gt;&gt;span&gt;&gt;/g, '</span>');
    }
  }
})();

main.jsはブックマークレットの処理と同様のものです。
ページに挿入されるので、DOMにアクセスすることもできます。
Class構文を使っていないのは、7年以上前に書いたからです。


DMEO

アイコンをクリックすると、風邪を引きます。
ブックマークレットを移植してみましたが、全然手間じゃないですし、拡張機能の方が使いやすいので、このブックマークレットも移植しようと思います。

blog.kimizuka.org


追記

移植してみました。

blog.kimizuka.org