miscs of vimperator plugin

pluginManager.js ( http://d.hatena.ne.jp/teramako/20081207/p1 ) の絡みで id:teramako に教わったんだけどいつの間にか plguin.context[$PLUGIN_ABSOLUTE_PATH] から plugin 内の global 変数が見えるようになってる。で、まぁいつものごとく重箱の隅ツツキ開始。

と思ったんだけど長くなったので先に結論を書く。 plugin を書く場合に気をつけるべきなのは以下の 2 点。

  • 見せたい変数以外は無名関数内に閉じこめるべき
  • 見せる変数も const で定義するとか使う前に check するとかの事前措置はとっておくべき

ついでに現時点では以下のようなひな形を想定しておけば良いと思う。 PLUGIN_INFOconst で定義しといたほうがいいと思ったのでそうした。んだけど二重に読み込むとエラーが出てしまってウマくないのでやっぱ let で。 id:teramako に指摘してもらった。 thanx.

// required
let PLUGIN_INFO = <VimpeartorPlugin>
<name>vimperator plugin template</name>
<description>vimperator plugin template</description>
<description lang="ja">vimperator プラグインのひな形</description>
<version>0.1</version>
<detail><![CDATA[
== プラグインの説明 ==
:samplecommand:
コマンド説明

]]></detail>
</VimperatorPlugin>

/* optional
let visibleVar = 'vimperator template';

// this feature will be implement (still not in 2008/12/09)
function onUnload() {
    // sample
    delete visibleVar;
}
*/

( function () {

// refer: http://wiki.livedoor.jp/shin_yan/d/addUserCommand%5ffor2%2e0pre
commands.addUserCommand(
    ['samplecommand'],
    'sample command',
    function (args) {
        liberator.echo('hello vimperator, bye-bye mouse.');
    },
    {
/* optional
        argCount: 0,
        literal: false,
        options: ['-s'],
        bang: false,
        count: false,
        hereDoc: false,
        complete: function (context, args) {
        },
*/
    }
);

} )();

こっからダラダラ。 JavaScript の変数の振る舞い ( scope とか var/let で宣言した変数は scope chain の先頭の object の property として扱われるとかの細かいこと ) については前提知識ということで。

まず大きな違いとして無名関数でおおわなくても window object の property として扱われることがなくなった ( C++ 風に言うと global な名前空間を汚染しなくなった ) ので無名関数で囲むべき必要性は薄れている。まぁもとから plugin 内で global 変数の定義とかしてなければ ( commands.addUserCommand だけとか hints.addMode だけとか ) その必要もなかったんだけど。

逆に plugins.context を通して plugin 内 global 変数にアクセスしたいときは無名関数の外で宣言しないといけない。以下のようにすると :echo plugins.context['plugin_variable.js'].a:echo plugins.context['plugin_variable.js'].b も結果は 'visible' になる。ただ、変数宣言時に var/let を使わないと window object の property として扱われるのは言語特性上のことなので注意 ( 以下の例では plugins.context['plugin_variable.js'].c は定義されず window.c が定義されてしまう ) 。

// plugin_variable.js
var a = 'visible';
let b = 'visible';
c = 'window property';

( function () {

var a = 'invisible';
let b = 'invisible';

} )();

で、参照だけじゃなくて代入もできてしまうので実は危ない。以下のように plugin を定義してしまうと、

// golem.js
let spel = 'emeth';

commands.addUserCommand(
    ['golem'],
    'confirm living of golem.',
    function () liberator.echo(spel === 'meth' ? 'dead' : 'alive'),
    {}
);

簡単に golem を殺せる。

// result: 'alive'
:golem

// destruct golem
:js plugins.contexts['golem.js'].spel = 'meth';

// result: 'dead'
:golem

この golem を不死身にするには spellconst で宣言するか、全体を無名関数で覆って spel を隠してしまうかの 2 つの方法がある。で、普通の plugin だと global 変数を書き換えられるのはたぶん意図していないと思うので、見せていいものかどうかと変更しても問題ないかどうかをちゃんと考えて書く必要がある。はず。

というわけでここらへんのもろもろを考えると冒頭に挙げたひな形が出てくるかんじ。