JXAでChromeの新規Windowに複数のタブを開く

# 背景

## 目的

Chromeの新規ウィンドウを開いて、そのウィンドウ中に指定した複数のページを開く、という処理をJavascriptで書きます。

## 動機

Chromeでは起動時に指定した複数のページを開くように設定できますが、できれば任意のタイミングでできるようにしたいと思っています。定番のページを開きたいのはなにも起動時だけではありません。そしてその処理を汎用的にプログラミング言語で記述できればなお嬉しいです。

# AppleScript

Mac OSではGUIアプリケーションの自動化を可能にする仕組みがあります。それはAppleScriptです。様々なGUIアプリケーションでAppleScriptから操作できるAPIを用意しています。私はこれまで、AppleScriptを使って下記のように「複数ページを開く」処理を記述していました。

```applescript
set pages to {"http://www.yahoo.co.jp/", "https://google.co.jp/"}

tell application "Google Chrome"
	repeat with page in pages
		open location page
		delay 0.1
	end repeat
	activate
end tell
```

しかしこのAppleScriptは実行環境が限られ、文法も独特で他の環境での応用が効かない、実行環境も使いづらいとなかなかのクセ者です。例えば上記の配列ですが、下記のように改行して記述することができません。この言語を愛している人の気持ちが理解できません。

```applescript
# このように書くことはできない
set pages to {
    "https://www.yahoo.co.jp/",
    "http://www.google.co.jp/"
}
```

# JXA (JavaScript for Automation)

AppleScriptの言語特性が普及率の低さにつながっているとAppleが判断したのかどうかはわかりませんが、Mac OS X Yosemiteから、Javascriptで[Apple event](https://ja.wikipedia.org/wiki/Apple_event)を操作できる機能が追加され、Javascriptでアプリケーションを操作できるようになりました。Javascriptなら誰でも文法を知っていますし、これから普及に繋がるのではないでしょうか？

それでは、上記のAppleScriptのコードをJavascriptで書きなおしてみましょう。

# コード

完成したコードは下記のとおりです。適当なテキストエディタで作成してください。

```js:open-pages.js
//usr/bin/osascript -l JavaScript $0 $@; exit

var pages = [
    "http://www.yahoo.co.jp/",
    "http://www.google.co.jp/"
];

var app = Application('Google Chrome'); // アプリケーションを取得する
var win = app.Window().make(); // 新規ウィンドウを開く

pages.forEach(function(page, i) {
    if (i === 0) {
        // 新規ウィンドウには空のタブがあるという前提
        win.tabs()[0].url = page; // 指定したURLを開く
    }
    else {
        var tab = app.Tab({url:page}); // Tabオブジェクトを作成する
        win.tabs.push(tab); // ウィンドウに配置する
    }

    if (i !== pages.length - 1) {
        delay(0.1); // ちょっと待つ
    }
});

app.activate(); // ウィンドウを最前面に移動する
```

このスクリプトは下記のとおりにコマンドラインで実行することができます。

```console:Terminal
$ osascript -l JavaScript ./open-pages.js
```

ファイルに実行権限を付けておけば、通常のコマンドと同様に実行することもできます。

```console:Terminal
$ ./open-pages.js
```

下記のようにコンパイルしておけば、アイコンをダブルクリックして起動することもできます。デスクトップに置いておくと便利でしょう。

```console:Terminal
$ osacompile -l JavaScript -x -o ~/Desktop/open-pages.app ./open-pages.js
```

# 補足

## 新規Windowを開く／新規タブを開く

調べてもなかなかわからなかったのは新規ウィンドウの開き方です。

```js
Application('Google Chrome').Window().make();
```

一方で新規タブの開き方は、

```js
var tab = Application('Google Chrome').Tab({url:page});
Application('Google Chrome').windows()[0].tabs.push(tab);
```

読みやすさのために一度変数に格納していますが、1行で書くこともできます。ウィンドウも何番目でも問題ないです。そういうことではなくて、ずいぶん書き方が違うなぁ、ということです。

ちなみに、新規ウィンドウの作成を新規タブと同様に記述すると下記のようになるでしょうか。

```js
var win = Application('Google Chrome').Window();
Application('Google Chrome').windows.push(win);
```

このプログラムを実行すると、とりあえず新規ウィンドウが立ち上がります。しかし、下記のようなエラーを吐いて新規ウィンドウを開いた時点でコケます。

> test.js:64:86: execution error: Error on line 3: Error: インデックスが正しくありません。 (11)

しかし、それでも諦めずに繰り返し実行すると、3つ目のウィンドウからは意図したとおりに実行されます。理由がよくわかりません。

## `delay()` がある

Javascriptを書き始めた頃は、 `wait()` とか `sleep()` とかどうやってやるんや？と思ったものですが、なんとJXAには `delay()` という組み込み関数があります。引数は秒数（s）です。小数でも有効です。

## Shebang

あまり見かけない書き方だとは思いますが、shebangは下記のように書きます。

```js
//usr/bin/osascript -l JavaScript $0 $@; exit
```

最初が `//` になっているのは、Javascriptのコメントがこの形式のためです。その後はスクリプトの実行エンジンへのPATHが続き、前述のJavascriptオプション、そして実行されているスクリプトのPATH、すべての引数、という構成になっています。Golangもコメントが `//` なので、コードをインタープリターとして実行する際には同じ書き方（オプションは要らないが）をします。

# 参考

- [鳶嶋工房 / AppleScript / JavaScript for Automation (JXA)](http://tonbi.jp/AppleScript/JavaScript/)
- [Introduction to JavaScript for Automation Release Notes](https://developer.apple.com/library/mac/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/Introduction.html)
- [New Automation Features in OS X Yosemite](https://macosxautomation.com/yosemite/index.html)
- [Home · dtinth/JXA-Cookbook Wiki](https://github.com/dtinth/JXA-Cookbook/wiki)