Home > Latest topics

Latest topics > XUL/MigemoでのSafari風ハイライト処理で、要素の下にあるリンクにクリックイベントを送る

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

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

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

XUL/MigemoでのSafari風ハイライト処理で、要素の下にあるリンクにクリックイベントを送る - May 04, 2008

一つ前のエントリの続き。

「すべて強調表示」の時に強調箇所以外を暗くするというSafari風ハイライト表示は、元はSafariHighlightを取り込ませてもらったものなんだけれども、基本的には、「画面の最全面に半透明の黒いボックスを表示して全体を覆う」「その上にz-indexを調整して強調箇所を浮かび上がらせる」という二つの操作が鍵になっている。で、このうち前者の方の操作のせいで、「暗くなった所にあるリンクをクリックしても、ハイライトが解除されるだけで、リンク先には飛べない」という問題が起こっていた。

まあ、問題というか実装上そうならざるを得ないという感じで、本家のSafariもこういう仕様だったと思うからまあいいじゃんと思わなくもないんだけど、要望はあるようなので対応しなきゃなーと思ってた。で、この度晴れて対応してみた。highlight.jsの最後の方に付け加えたresendClickEventメソッドがそれ。

resendClickEvent : function(aEvent) 
{
  var utils = aEvent.view
    .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
    .getInterface(Components.interfaces.nsIDOMWindowUtils);
  if ('sendMouseEvent' in utils) { // Firefox 3
    var flags = 0;
    const nsIDOMNSEvent = Components.interfaces.nsIDOMNSEvent;
    if (aEvent.altKey) flags |= nsIDOMNSEvent.ALT_MASK;
    if (aEvent.ctrlKey) flags |= nsIDOMNSEvent.CONTROL_MASK;
    if (aEvent.shiftKey) flags |= nsIDOMNSEvent.SHIFT_MASK;
    if (aEvent.metaKey) flags |= nsIDOMNSEvent.META_MASK;
    window.setTimeout(function(aX, aY, aButton) {
      if (ZoomManager.useFullZoom) { // Firefox 3のフルズームへの対応
        aX = aX * ZoomManager.zoom;
        aY = aY * ZoomManager.zoom;
      }
      utils.sendMouseEvent('mousedown', aX, aY, aButton, 1, flags);
      utils.sendMouseEvent('mouseup', aX, aY, aButton, 1, flags);
    }, 0, aEvent.clientX, aEvent.clientY, aEvent.button);
  }
  else { // Firefox 2, emulation
    var args = [
        'click',
        aEvent.bubbles,
        aEvent.cancelable,
        aEvent.view,
        1,
        aEvent.screenX,
        aEvent.screenY,
        aEvent.clientX,
        aEvent.clientY,
        aEvent.ctrlKey,
        aEvent.altKey,
        aEvent.shiftKey,
        aEvent.metaKey,
        aEvent.button
      ];
    window.setTimeout(function(aSelf, aFrame, aX, aY) {
      var node = aSelf.getClickableElementFromPoint(aFrame, aX, aY);
      if (!node) return;
      var event = aFrame.document.createEvent('MouseEvents');
      args.push(node);
      event.initMouseEvent.apply(event, args);
      node.dispatchEvent(event);
      if ('focus' in node) node.focus();
    }, 0, this, aEvent.view, aEvent.screenX, aEvent.screenY);
  }
},

getClickableElementFromPoint : function(aWindow, aScreenX, aScreenY) 
{
  var accNode;
  try {
    var accService = Components.classes['@mozilla.org/accessibilityService;1']
              .getService(Components.interfaces.nsIAccessibilityService);
    var acc = accService.getAccessibleFor(aWindow.document);
    var box = aWindow.document.getBoxObjectFor(aWindow.document.documentElement);
    accNode = acc.getChildAtPoint(aScreenX, aScreenY);
    accNode = accNode.QueryInterface(Components.interfaces.nsIAccessNode).DOMNode;
  }
  catch(e) {
  }

  var filter = function(aNode) {
    switch (aNode.localName.toUpperCase()) {
      case 'A':
        if (aNode.href)
          return NodeFilter.FILTER_ACCEPT;
        break;
      case 'INPUT':
      case 'TEXTAREA':
      case 'BUTTON':
      case 'SELECT':
        return NodeFilter.FILTER_ACCEPT;
        break;
    }
    return NodeFilter.FILTER_SKIP;
  };

  if (accNode &&
    accNode.nodeType == Node.ELEMENT_NODE &&
    filter(accNode) == NodeFilter.FILTER_ACCEPT)
    return accNode;

  var doc = aWindow.document;
  var startNode = accNode || doc;
  var walker = aWindow.document.createTreeWalker(startNode, NodeFilter.SHOW_ELEMENT, filter, false);
  for (var node = walker.firstChild(); node != null; node = walker.nextNode())
  {
    var box = doc.getBoxObjectFor(node);
    var l = box.screenX;
    var t = box.screenY;
    var r = l + box.width;
    var b = t + box.height;
    if (l <= aScreenX && aScreenX <= r && t <= aScreenY && aScreenY <= b)
      return node;
  }
  return null;
},

見ての通り、Firefox 3ではnsIDOMWindowUtilsの新機能を使ってイベントを発行するようにして根本的解決を図り、Firefox 2ではTabScope由来のコードにnsIAccessibleを組み合わせたものを使ってそれっぽいことをしている、という感じです。どっちもタイマーを使って処理を遅らせているのは、画面全体を覆っているスクリーンが消えてからイベントを発行しないと、もう一度スクリーンをクリックしたのと同じ事になってしまうから。

最初、nsIDOMWindowUtilsのsendMouseEventでclickイベントを発行しようとしてうまくいかなかったんだけど、どうやらこれはDOMのイベントをそのまま発行するわけではなく、本当の本当にユーザの操作をエミュレートするという物みたいだ。間を開けずmousedown→mouseupと実行するとクリックしたのと同じ事になる。

強調表示関係ではこのほかに、テキストフィールド内の強調を自動的に解除するようにするための改良もあります。これも長くなったから別エントリで。

分類:Mozilla > 拡張機能 > xulmigemo, , , , , , , , 時刻:07:23 | Comments/Trackbacks (0) | Edit

Comments/Trackbacks

TrackBack ping me at


の末尾に2014年1月19日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2008-05-04_safarihighlight.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 ブラウザ無料ダウンロード