Home > Latest topics

Latest topics > Split Browser開発のよもやま話(9):ポップアップボタン上へのドラッグ&ドロップの検知

宣伝1。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

宣伝2。Firefox Hacks Rebooted発売中。本書の1/3を使って、再起動不要なアドオンの作り方のテクニックや非同期処理の効率のいい書き方などを解説しています。既刊のFirefox 3 Hacks拡張機能開発チュートリアルと併せてどうぞ。

Firefox Hacks Rebooted ―Mozillaテクノロジ徹底活用テクニック
浅井 智也 池田 譲治 小山田 昌史 五味渕 大賀 下田 洋志 寺田 真 松澤 太郎
オライリージャパン

Split Browser開発のよもやま話(9):ポップアップボタン上へのドラッグ&ドロップの検知 - Feb 02, 2007

Split Browserの作り込みの話のおまけ。このエントリにはドラッグ&ドロップの実装に関する話が含まれているかもしれません。

まず基本の話として、Firefoxで(というかXULで)ドラッグ&ドロップを実装するには、ondraggesutre, ondragover, ondragenter, ondragexit, ondragdropの5つのイベントハンドラと、XPCOMの機能を使う必要がある。このあたりの話はMDCのXULチュートリアルには無いんだけど、古いXULチュートリアルには載ってるので、熟読しとくことをお勧めしたい。

XUL要素をドラッグしようとすると、draggesutreというイベントが発行される。いわゆるAjaxとかだと、ボタンを押下→マウスが動いた、という操作をそれぞれ別のイベントで拾わないといけなかったり、クリック時にマウスがブレただけでドラッグ開始と判断してしまわないように閾値を設定したり、と色々めんどくさい配慮がいるんだけど、XULではdraggestureイベントいっこ拾うだけで済むので話が早い。

ドラッグ中にボタンを放した時、つまりドロップの操作が行われた時には、dragdropというイベントが発生する。これは他のアプリケーションからのドラッグ&ドロップでも発生するので、アプリケーションの垣根を越えてのデータのやりとりもできる。やろうと思えば多分バイナリデータも渡せるんじゃないかな……やったことは無いけど。

あとの3つのイベントはおまけのようなもので、ドラッグ中にポインタが載った要素に対して、今ドラッグ中のデータをドロップできるかどうか(その要素がそのデータのドロップを受け入れられるかどうか)を示す、とかそういった用途で使うことが多い。

データの受け渡しにはXPCOMの機能を使う。詳細は旧チュートリアルの当該項目で解説されてる……ンだけど、ぶっちゃけこんなの真面目に使ったらあかん(ぉぃ)。これをラッピングして簡単に使えるようにしてくれる物として、nsDragAndDropという標準ライブラリがあって、これはFirefoxでも利用できる(っていうかFirefox内部で使われまくり)ので是非活用しましょう。旧チュートリアルのnsDragAndDropの使い方の解説利用例は要チェックですよ。

……というのがドラッグ&ドロップの実装の基本。ここから先は、その応用。

一つ前のエントリで書いたとおり、Split Browserではmousemoveイベントをハンドリングして、ポインタがブラウズ領域の端に来た時にその位置にボタンを表示するようになっている。このボタンをドラッグ&ドロップの際にも表示しておけば、リンクやブックマーク、あるいはローカルのファイルをFirefoxで開く時なんかにも、いちいち「上下左右いずれかに分割→そのブラウズ領域のロケーションバーでURIを入力」なんてめんどくさいことをしなくても、ドラッグしてボタン上でドロップすれば新しい分割ブラウザでそのリンクなりブックマークなりファイルなりを表示する、という操作ができるようになる。

ということで早速やってはみたものの、最初は上手くいかなかった。

まず、mousemoveイベントをトリガーにしていると、ドラッグ&ドロップ中のマウスの移動には反応してくれない。何かをドラッグしている最中というのは、Firefoxではいろんな処理に影響が出る。

  • ドラッグ中にはmousemove, mouseover, mouseoutイベントは発生せず、代わりにdragover, dragenter, dragexitイベントが発生する。
  • タイマー(setTimeout()setInterval())はドラッグ中は停止する。ドラッグ中にセットしたタイマーは、ドラッグ操作が終わった直後から動き始める。

そんなワケなので、改めてdragoverイベントをトリガーにしてボタンをポップアップ表示するようにした。具体的には、以下のようなコードをsubbrowser要素のバインディング定義に加えた。


<field name="contentAreaEdgeDNDObserver"><![CDATA[
({
  mOwner : this,

  onDragOver: function (aEvent, aFlavour, aDragSession)
  {
    if (aFlavour.contentType  == 'text/x-moz-url' ||
      aFlavour.contentType == 'text/unicode' ||
      aFlavour.contentType == 'application/x-moz-file') {
      if (this.mOwner.toggleToolbar) {
        this.mOwner.toggleToolbar(true, true);
        this.mOwner.toggleToolbar(false, true, 0);
      }
      this.mOwner.handleMouseOverEvent(aEvent);
    }
  },

  getSupportedFlavours: function ()
  {
    var flavourSet = new FlavourSet();
    flavourSet.appendFlavour('text/x-moz-url');
    flavourSet.appendFlavour('text/unicode');
    flavourSet.appendFlavour('application/x-moz-file', 'nsIFile');
    return flavourSet;
  }
})
]]></field>

(中略)

<handler event="dragover" phase="capturing"
  action="nsDragAndDrop.dragOver(event, this.contentAreaEdgeDNDObserver);"/>

nsDragAndDrop用のオブザーバは、getSupportedFlavours()メソッドさえ備えていれば、あとは全てのメソッドを備えている必要はない。ここではドラッグ開始もドロップも検知せず、単にドラッグ中にブラウズ領域の端にポインタが来たかどうかを調べるためだけにオブザーバを使うので、このオブザーバにはonDragOver()メソッドしか持たせていない。

イベントハンドラの定義でphase="capturing"という指定があるのがポイント。これはJavaScriptでtarget.addEventListener(type, listener, true)と書くのと同じで、イベントが伝搬される優先順位を逆転させるための物だ。

通常、DOMのイベントモデルでは、発生したイベントは「イベントが発生した要素」からその祖先要素へと順番に伝搬していき、イベントハンドラやイベントリスナは、それらが設定された要素のところまでイベントが昇ってきた段階で初めて動き出す。これをイベントの伝搬とかバブリングとか言うんだけど、XBLでphase="capturing"という指定を使ったり、addEventListener()メソッドの第3引数にtrueを渡したりすると、そのイベントハンドラ(リスナ)には特別なルールが適用されて、イベントが発生した瞬間、バブリングを待たずにイベントを処理できるようになる(イベントのキャプチャリング)。

XULのbrowser要素には標準でdragoverイベントに対応したイベントハンドラが定義されていて、こいつがイベントを捕捉した時点でイベントのバブリングをキャンセルしてしまうため、その祖先要素であるsubbrowser要素ではいつまで待ってもdragoverイベントを拾えない。そのため、キャプチャリングを使って、キャンセルされる前のイベントを強制的に拾うようにする必要がある。というわけ。

ここではドラッグ&ドロップで受け取れるデータの型として、文字列とファイル(実際に渡るのはファイルパスだけ)を指定してある。これはつまり、ドラッグ中のデータがこれらの型である場合にのみオブザーバのonDragOver()メソッドが呼ばれて、ボタンを表示する処理が行われるという事だ。

なお、同様のオブザーバをポップアップ表示されるボタン用にも用意してあって、こっちは基本的にドロップ操作だけを受け付けるようにしてある。

……という風にしてドロップ操作の受け入れを実現したんだけれども、ここで2つの問題が発生してしまった。

  1. Linux上ではボタンへのドロップ操作を検知できなかった。
  2. Drag de Goのような、ドラッグ操作を乗っ取る他の拡張機能が動かなくなった。

順番に、それぞれの解決策を解説しよう。

XULはクロスプラットフォームなのが売りとはいえ、実際には環境ごとに若干、挙動に差異がある。今回の問題もそういった差異の一つに起因していて、Windowsだとdragoverイベントをトリガーにして表示したボタンの上に何かをドロップした時はそのボタンでdragdropイベントが発生するのに、Linuxではボタンの下にあるbrowser要素の方でdragdropイベントが発生してしまう事に、実際に動かしてみて気がついた。

これは、コードとしては特に複雑なことをする必要はなかった。browser要素へのドラッグ&ドロップのオブザーバについて、ドロップ時の処理をオーバーライドして「ドロップ位置がボタンのある位置で、且つ、ボタンが表示されていた場合は、ボタンの上にドロップされたものと認識する」という処理を付け加えてやったところ、意図したとおりに動いてくれるようになった。

面倒だったのはもう一つの方の、他の拡張機能とのコンフリクトだ。

オブザーバのonDragOver()メソッドの呼び出し元となるnsDragAndDropのdragOver()メソッドは、そのオブザーバがデータを受け入れ可能だった場合、問答無用でイベントのバブリングをキャンセルしてしまうという仕様になっている。このせいで、Split Browserのオブザーバがdragoverイベントを拾ってボタンを表示した瞬間にイベントがキャンセルされてしまい、Drag de Goがdragoverイベントを拾えないという事態になってしまっていた。

この仕様は、考えてみればとてもまっとうなものだ。例えばブラウザの中のテキストボックスに文字列をドラッグ&ドロップした時に、ブラウザ側のイベントハンドラに処理が渡ってしまって、ドロップされたテキストがURIでそれが読み込まれたり何かしてしまったら、まあ、普通に困る。だからこその、そういったトラブルを防ぐための仕様なんだろう。

しかし今回はそれとは話が違う。Split Browserは単にボタンを表示するかしないかの判断のためにイベントを捕捉しているだけであって、判断が終わったらイベントは何事もなかったかのようにバブリングを続けて貰いたいんだ。

というわけで今回はnsDragAndDropの使用を諦めて、直接XPCOMコンポーネントの機能を呼び出すようにすることにした。具体的には以下のように、nsDragAndDropのコードから必要な処理だけを抜き出している。


<handler event="dragover" phase="capturing"><![CDATA[
  const DragService = Components
                       .classes['@mozilla.org/widget/dragservice;1']
                       .getService(Components.interfaces.nsIDragService);
  var session = DragService.getCurrentSession();
  if (!session) return;

  if (session.isDataFlavorSupported('text/x-moz-url') ||
      session.isDataFlavorSupported('text/unicode') ||
      session.isDataFlavorSupported('application/x-moz-file'))
      this.handleMouseOverEvent(event);
]]></handler>

……という風に、最初のリリース以後は細かい使い勝手の向上のためにあれこれ手を尽くしている感じ。今後も何か一般的なテクニックとして参考になりそうなネタがあれば、ドキュメント化していきたい。

分類:Mozilla > XUL, , , , , , , 時刻:03:31 | Comments/Trackbacks (0) | Edit

Comments/Trackbacks

TrackBack ping me at


の末尾に2014年1月19日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2007-02-02_splitbrowser-dragdrop.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。

Post a comment

writeback message: Ready to post a comment.

2014年1月19日時点の日本の首相のファミリーネーム(ひらがなで回答)

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき

オススメ

Mozilla Firefox ブラウザ無料ダウンロード