以下のGreasemonkeyスクリプトを修正した件についての話。

Greasemonkey2.0

Greasemonkey2.0ではFirefoxの変更に合わせて、セキュリティ周りの変更がありました。 それにより、色々なGreasemonkeyがそのままだと動かなくなっています。

動かない原因は大きく分けて2つあります。

  • @grant none がデフォルトになった
  • unsafeWindowの挙動が変わった(Firefox側の変更)

@grant none

に詳しい解説が書いてあります。

簡単にいうと、今までそのまま使えていたGM_*関数は特権関数なので、Greasemonkeyで使う場合は、 事前にスクリプトのメタ情報で

// @grant GM_getValue
// @grant GM_setValue
// @grant GM_getResourceText
// @grant GM_getResourceURL
// @grant GM_openInTab
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest

のように列挙する必要があります。

そのスクリプトで使っているAPIを列挙するのが面倒なので、 既存のスクリプトから、メタ情報のコメントを取得するコマンドラインツールを作りました。

npm install -g greasemonkey_grant_cli
greasemonkey_grant file.user.js

という感じでファイルを指定して実行すると

// @grant GM_addStyle
// @grant GM_xmlhttpRequest

のようなコメント形式で使ってるAPI一覧を得られます。

@grant noneの挙動

@grant none はpage contextで実行されるためGM_*関数は使えません。 ブックマークレットと同じようなものなので、GM_関数が必要ない場合はこれをつかったほうが楽です。

データの読み書き(localstorage)、GM_xmlhttpRequest(クロスドメインはそのままXHRと同じ)、GM_addStyleについてのshimライブラリが Greasemonkey "@grant none" compatibility shim. に用意されいます。

そのためサイト間をまたいだりしないGreasemonkeyは、これをつかって @grant none で動かせるケースが多いでしょう。 (APIを叩く場合は大抵クロスドメイン跨ぐので無理かも)

実例

livedoor Reader で NG word フィルター を実現する Greasemonkey - zaknakの日記 で公開されていた LDRのNGフィルタをするGreasemonkeyはGM_*関数が必要なかったので、 @grant noneで動くように修正しました

unsafeWindowの挙動

もう一つの大きな変更が、unsafeWindow周りの挙動です

unsafeWindow(特権context) -> window(page context) という一方通行なら、今までと同じ書き方で問題ありません。 (Greasemonkeyからpage contextの関数を呼び出すとかは unsafeWindow.hoge()みたいにできる)

unsafeWindow(特権context) -> window(page context)にイベントハンドラを登録して、 window(page context)からそのイベントが発火してメソッドを呼ぶみたいなケースだと問題がおきます。

例えば、LDRをj、kで前後の記事、nで新しいタブで開くGreasemonkeyでは、 以下のようににunsafeWindow経由で、イベントをpage contextに設定していました。

つまり、Greasemonkey内で定義した関数をpage contextから呼ぶようになっていました。

var openAndGoNext = function () {
    var item = w.get_active_item(true);
    if (!item) {
        return;
    }
    // background open
    openNewBackgroundTab(htmlEntityDecode(item.link));
    w.Control.go_next();
};
unsafeWindow.Keybind.add("k", openAndGoNext);

これだと、page contextからopenAndGoNextを呼ぼうとすると Error: Permission denied to access property 'call' のようにパーミッションエラーという感じになって失敗します。

そのため、openAndGoNext をpage contextから呼べるように定義する必要があります。

このケースだとexportFunctionというGreasemonkeyの関数をwindow(page context)に登録するものがあるのでこれを利用すれば、 以下のように修正できます。

function exportGMFunc(fn, name) {
    var fnName = name || fn.name;
    // page contextからfnNameを呼べるようにする特権作成関数
    exportFunction(fn, unsafeWindow, {defineAs: fnName });
    return unsafeWindow[fnName];
}
unsafeWindow.Keybind.add("k", exportGMFunc(openAndGoNext));

他にも変数やオブジェクトを登録する関数などがあり、以下に書いてあります。

この辺はProxy APIでラップしたwindowとかを作れば自動的にいけるんじゃないかと思って、azu/Greasemonkey-unsafeWindow-Proxy というのを書いてたんですが、何かよくわからなくなって放置してます。

実例

ldr_keyhack_jkc+nとLDRFullFeedはこの exportFunction を使って修正してあります。

最近のGreasemonkey

箇条書きで最近のGreasemonkey

  • scriptish/scriptish まだFirefox30対応してない
  • 作者のErik VoldさんはJPM Betaに忙しい様子
  • userscripts.org はほぼ完全に死んでる気がします
  • 代わりに Greasy Fork が動いています。
  • Greasy Forkは活発なのでこちらに移行するといいのでは
  • GitHubにスクリプトを置いて自動的に同期する仕組みなどもある。
  • Greasy Forkは外部スクリプトの読み込みに一部制限がある

最後のやつは外部スクリプトを置ける場所が制限されています(条件から外れた場合はインストールボタンが押せなくなる)

Greasy Fork policy on external scriptsに書いてある CDNやGreasy Forkにおいてあるスクリプトは@requireで読み込んで使うことが出来ます。

簡単にいえば、動的にGreasemonkeyスクリプトの中身が変えられたりしないようにそういう制限を設けているという感じです。

Greasemonkeyは下火ですが、まあまだ簡単に書く場合はAddon SDKより楽なので使う機会は多いです。

JPM Beta が熟してきて、 Addon SDKでもnpmのエコシステムが回ったAddon作成ができるようになるといいですね。 (その場合でもGreasemonkeyは @grant none - 常時実行するブックマークレットみたいなプラットフォームとして残る気はする)

今のJPM Betacfxコマンドの代替的な感じです。(PythonベースじゃなくNode.jsベースになった)

Browserify

Greasemonkeyの@requireでモジュール管理は正直現代的じゃないし難しい気がしてるので、 最近はBrowserifyでビルドしてGreasemonkeyスクリプトを書いています。

ただし、この方法を使った場合はGreasy Forkのポリシーと反してる気がするので、 Greasy Forkでは公開できないかもしれません。

以下で議論してたけど、どうすればいいのかよくわからないのでGitHubに直接置いてます。

Browserifyでビルドする場合は、 git configでローカルのファイルパスを保存してビルドスクリプトを まわせば結構いい感じで開発出来ます。

git config greasemonkey.file /path/to/自分の.user.js
npm run watch # 監視 + ビルド

ファイルサイズが大きくなりやすいですが、管理しやすいし各スクリプトで共通モジュールが簡単にできたりするので、 自分用に書く場合は圧倒的に楽になると思います。

まとめ

  • Greasemonkey 2.0(Firefox30)で色々破壊的な変更が起きた
  • userscripts.org は死んでいる
  • Greasy Forkがかわりに機能してる
    • safeを意識してるのでポリシーは若干厳し目
    • どちらにしてもGitHubにもコードを置いたほうがいいですね
  • Browserifyを使ってGreasemonkeyスクリプトを書く方法もある
  • Addon SDKのエコシステムが強化のためにJPM Betaがでてきた