Web Music Hackathon #5

Web Music Hackathon #5に参加してきました.

「Twitterを楽器にする」というコンセプトで, MMLを書き込んで投稿すると音楽が再生されるChrome Extensionを作成しました.

過去にツイートされたMMLも演奏可能にし, ビジュアライゼーション機能や, WebSocketによるセッション機能もつけました.


GitHubリポジトリ : Music Tweet

そして…なぜかわからないけど表彰されました〜

Chrome extensions を実装する 3 ~ Page Action / Content Scripts の実装 ~

Implement Page Action

Browser Action の特徴としては,

  • 特定のページで有効になって利用することができる

といった特徴ですが, ポップアップメニューがないことを除けばできることは Browser Action とそれほど変わりません.

manifest.json

Page Action の manifest.json は以下のような感じになります. このうち, 実装の主体となるのが background で指定されている background.js です (ファイル名は background.js でなくても OK です).

manifest.json

{
  "name": "Chrome extensions の名前",
  "version": "Chrome extensions のバージョン",
  "manifest_version": 2,
  "description": "Chrome extensions の 概要",
  "background": {
    "scripts": ["background.js"]
  },
  "page_action": {
    "default_icon": {
      "19": "ツールバーに表示されるアイコンの画像パスで 19 x 19 のサイズは用意しておく",
    },
    "default_title": "アイコンをホバーしたときのツールチップに表示されるテキスト"
  }
}

background.js

// Chrome extensions の Page Action の API
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
    if (tab.url.includes('https://twitter.com/')) {
        // Twitter のページなら有効になる
        chrome.pageAction.show(tabId);
    }
});

// アイコンをクリックした時のイベント処理
chrome.pageAction.onClicked.addListener(() => {
    chrome.tabs.executeScript(null, {
        'code': `document.body.style.backgroundColor='#ff0000';`
    });
});

特定のページで表示させるという処理は, chrome.tabs.onUpdated.addListener メソッドで URL を判定することで可能です. また, Browser Action で利用した chrome.tabs.executeScript メソッドは, Page Action でも利用することが可能です.

Implement Content Scripts

Content Scripts の特徴としては,

  • 特定のページで自動実行される
  • Browser Action や Page Action と組み合わせて利用することができる

といった特徴があります. 特定のページに対して (閉じた) 処理が多い場合は Content Scripts を利用することになるでしょう.

manifest.json

Content Scripts の manifest.json は以下のような感じになります. このうち, 実装の主体となるのが content_script で指定されている content.js です (ファイル名は content.js でなくても OK です).

manifest.json

{
  "name": "Chrome extensions の名前",
  "version": "Chrome extensions のバージョン",
  "manifest_version": 2,
  "description": "Chrome extensions の 概要",
  "content_scripts": [
    {
      "matches": [
        "https://twitter.com/*"
      ],
      "css": ["content.css"],
      "js": ["content.js"]
    }
  ]
}

matches で 特定のページの URL を指定します (この例では, Twitter のページ). また, Content Scripts では, 特定のページに対して適用する CSS を指定することも可能です.

Content Scripts の JavaScript は通常の Web アプリケーションを実装するのとまったく同じです. 例えば, 以下の Content Scripts では, HTMLCanvasElement を生成して, body 直下に追加しています.

content.js

const canvas = document.createElement('canvas');

canvas.width  = window.innerWidth;
canvas.height = window.innerHeight;

document.body.appendChild(canvas);

content.css

@charset "UTF-8";

body > canvas {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.6);
}

また, content.js で利用している変数 canvas には, borwser_action の スクリプトからアクセスすることが可能です.

popup.js

// HTML に 'toggle-visualization' という id 属性を持つ <input type="checkbox" /> があるとします
document.getElementById('toggle-visualization').addEventListener('click', event => {
    chrome.tabs.executeScript(null, {
        'code': `
            // checkbox の状態に応じて, 表示 / 非表示を切り替え
            if (${event.currentTarget.checked}) {
                canvas.removeAttribute('hidden');
            } else {
                canvas.setAttribute('hidden', '');
            }
        `
    });
}, false);

Chrome extensions を実装する 2 ~ Browser Action の実装 ~

Implement Browser Action

Browser Action の特徴としては,

  • ツールバーに常に表示されて利用することができる
  • ポップアップメニュー (アイコンをクリックしたときに表示されるメニュー) を利用することができる

といった特徴から, 最もオーソドックスな Chrome extensions と言えます.

manifest.json

Browser Action の manifest.json は以下のような感じになります. このうち, 実装の主体となるのが default_popup で指定されている popup.html です (ファイル名は popup.html でなくても OK です). この HTML ファイルは通常の Web アプリケーションを実装するのと同様に記述することができます. したがって, CSS / JavaScript ファイルも読み込むことが可能です. また, JavaScript では, Chrome extensions のみで利用可能な API を使うことでより実用的な Chrome extensions を実装することができます.

manifest.json

{
  "name": "Chrome extensions の名前",
  "version": "Chrome extensions のバージョン",
  "manifest_version": 2,
  "description": "Chrome extensions の 概要",
  "browser_action": {
    "default_icon": {
      "19": "ツールバーに表示されるアイコンの画像パスで 19 x 19 のサイズは用意しておく",
    },
    "default_title": "アイコンをホバーしたときのツールチップに表示されるテキスト",
    "default_popup": "popup.html"
  }
}

この記事では, 単純にポップアップメニューに表示されるボタンをクリックしたら, 表示しているページの背景色を変更するという Browser Action タイプの Chrome extensions を実装していきましょう.

popup.html / popup.css

ポップアップメニューのための HTML と CSS を実装します. … が 特に説明することはなく, 通常の Web アプリケーションを実装するのとまったく同じです.

popup.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Browser Action | Chrome extensions</title>
    <link rel="stylesheet" href="stylesheets/popup.css" type="text/css" media="all" />
</head>

<body>
    <ul>
         <li><button type="button" id="button-red">#ff0000</button></li>
         <li><button type="button" id="button-green">#00ff00</button></li>
         <li><button type="button" id="button-blue">#0000ff</button></li>
    </ul>
    <script type="text/javascript" src="javascripts/popup.js"></script>
</body>

popup.css

@charset "UTF-8";

ul {
    list-style: none;
}

ul > li {
    display: block;
}

popup.js

ポップアップメニュー内の DOM を操作したり, イベントリスナーを設定したりするのは, 通常の Web アプリケーションを実装するのとまったく同じです. しかし, 現在開いているページの DOM を操作したり, イベントリスナーを設定したりするのは, Chrome extensions が実装している API を利用する必要があります. また, そのために mainifest.json に以下の記述を追加します.

manifest.json

{
  "name": "Chrome extensions の名前",
  "version": "Chrome extensions のバージョン",
  "manifest_version": 2,
  "description": "Chrome extensions の 概要",
  "permissions": [
    "tabs", "https://*/*"
  ],
  "browser_action": {
    "default_icon": {
      "19": "ツールバーに表示されるアイコンの画像パスで 19 x 19 のサイズは用意しておく",
    },
    "default_title": "アイコンをホバーしたときのツールチップに表示されるテキスト",
    "default_popup": "popup.html"
  }
}

permissions を追加することで現在開いているページ (タブ) に対して何かをすることが可能になります. 今回は, https のページのみ背景色を変えることが可能な設定にしています.

次に, ポップアップメニューのイベントリスナーのみを実装します. これは, 通常の Web アプリケーションを実装するのとまったく同じです.

popup.js

document.getElementById('button-red').addEventListener('click', event => {
    // 現在開いているページ (タブ) の背景色を #ff0000 に変更する処理
}, false);

document.getElementById('button-green').addEventListener('click', event => {
    // 現在開いているページ (タブ) の背景色を #00ff00 に変更する処理
}, false);

document.getElementById('button-blue').addEventListener('click', event => {
    // 現在開いているページ (タブ) の背景色を #0000ff に変更する処理
}, false);

現在開いているページに対してスクリプトを実行するには, chrome.tabs.executeScript メソッドを実行します. 以下のように利用します.

popup.js

document.getElementById('button-red').addEventListener('click', event => {
    chrome.tabs.executeScript(null, {
        'code': `document.body.style.backgroundColor = '${event.currentTarget.textContent}';`
    });
}, false);

document.getElementById('button-green').addEventListener('click', event => {
    chrome.tabs.executeScript(null, {
        'code': `document.body.style.backgroundColor = '${event.currentTarget.textContent}';`
    });
}, false);

document.getElementById('button-blue').addEventListener('click', event => {
    chrome.tabs.executeScript(null, {
        'code': `document.body.style.backgroundColor = '${event.currentTarget.textContent}';`
    });
}, false);

code というキーに, 実行するスクリプトを文字列で指定します. またこのスクリプトでは, 例のようにポップアップメニューのスクリプトの変数の値を渡すことも可能です. さらに, 後ほど紹介する Content Scripts のグローバル変数にアクセスすることも可能です.

Chrome extensions を実装する 1

Overview

Chrome extensions とは, HTML / CSS / JavaScript という Web の標準技術を利用して実装可能な Google Chrome の機能を拡張するアプリケーションです.

通常の Web アプリケーションとは異なり, Google Chrome だけを対象とするので, 新しい HTMLタグや CSS プロパティ, JavaScript API を他のブラウザの実装を気にすることなく, (Chrome に実装されていれば) 利用することができます.

公式ドキュメント

Chrome extensions には, いくつかの種類に分類されます

Browser Action
ツールバーに常に表示されて利用することができる
Page Action
特定のページで有効になって利用することができる
Content Scripts
特定のページで自動実行される
Context Menu
右クリックメニュー

manifest.json

Chrome extensions には, 必ず必要なファイルがあり, それがこの manifest.json です. manifest.json には, 実装する Chrome extensions のバージョンや表示するアイコン, また先ほど述べた Chrome extensions の種類などを指定します.

のちほど紹介する, 自作の Chrome extensions である Music Tweet の manifest.json の例です.

{
  "name": "Music Tweet",
  "version": "1.0.0",
  "manifest_version": 2,
  "description": "Chrome extension for Twitter",
  "permissions": [
    "tabs", "https://twitter.com/*"
  ],
  "content_scripts": [
    {
      "matches": [
        "https://twitter.com/*"
      ],
      "css": ["stylesheets/content.css"],
      "js": ["lib/xsound.min.js", "javascripts/content.js"]
    }
  ],
  "browser_action": {
    "default_icon": {
      "19": "images/icon-19.png",
      "38": "images/icon-38.png"
    },
    "default_title": "Music Tweet",
    "default_popup": "popup.html"
  },
  "icons" : {
    "128": "images/icon-128.png",
    "256": "images/icon-256.png"
  }
}

とりあえずはざっとながめてもらえれば OK ですが, 1 つ重要なのが manifest_version で, これは現状, 必ず 2 を指定する必要があります.

Install

実装した Chrome extensions を Chrome にインストールするには以下の手順をふみます.

1. More Tools > Extensions

More Tools > Extensions
More Tools > Extensions

2. Check Developer mode

Check Developer mode
Check Developer mode

3. Load unpacked extension

Load unpacked extension
Load unpacked extension

4. Select directory

Select directory
Select directory

5. Installed

拡張機能のリストに追加されていればインストール完了です.

Installed
Installed

gulpでES2015 Modulesを利用可能にする

まずは, 必要なモジュールのインストール

$ npm install --save-dev browserify babelify vinyl-source-stream

gulpfile

var gulp       = require('gulp');
var browserify = require('browserify');
var babelify   = require('babelify');
var source     = require('vinyl-source-stream');

gulp.task('compile', function() {
    return browserify('src/main.js', {debug: true})
               .transform(babelify)
               .bundle()
               .on('error', function(error) {
                   console.error(error.message);
               })
               .pipe(source('bundle.js'))
               .pipe(gulp.dest('build/'));
});

試してみます.

src/main.js

import Greeter from './Greeter.js';

const greeter = new Greeter('Hello World');

console.log(greeter.greet());  // Hello World

src/Greeter.js

class Greeter {
    constructor(message) {
        this.message = message;
    }

    greet() {
        return this.message;
    }
}

export default Greeter;
$ node build/bundle.js
Hello World

以上です

gulpでeslint できる環境をつくる

eslintでリンティングできる環境を作成します.

1. gulp-eslintのインストール

$ npm install --save-dev gulp-eslint babel-eslint

2. .eslintrcの作成

{
  "parser": "babel-eslint",
  "env": {
    "node": true,
    "es6": true,
    "browser": true
  },
  "extends": [
    "eslint:recommended"
  ]
}

3. gulpfileにタスクを追加

var gulp   = require('gulp');
var eslint = require('gulp-eslint');

gulp.task('lint', function() {
    return gulp.src('src/*.js')
               .pipe(eslint())
               .pipe(eslint.format())
               .pipe(eslint.failAfterError());
});

gulp + Babel で ES2015を利用できる環境をつくる

1. gulpとgulp-babel, babel-preset-es2015をインストールします

$ npm install --save-dev gulp gulp-babel babel-core babel-preset-es2015

2. gulpfile.jsの作成

var gulp   = require('gulp');
var babel = require('gulp-babel');

gulp.task('compile', function() {
    gulp.src('src/*.js')
        .pipe(babel())
        .pipe(gulp.dest('build/'));
});

3. プロジェクトのルートに.babelrcを作成

{
  "presets": ["es2015"]
}

以上で, ES2015を利用できる環境ができました.
試してみましょう.

src/test.js

  let greet = () => 'Hello World !!';

console.log(greet());

これをコンパイルします.

$ gulp compile

build/test.js

'use strict';

var greet = function greet() {
  return 'Hello World !!';
};