bookmarkletで選択文字列を取得することを考える

bookmarkletで、「選択文字列を取得して何かする」という処理はよくあるものですが(検索エンジンに渡したりとか)、そのことについて暫く考えを巡らせています。

※ 以下のJavascriptソースはMozilla系ブラウザで動作させるものとします。

Firefoxで選択文字列を取得する場合は、以下のような方法が用いられます。

var t = window.getSelection().toString();

多くの場合はこれで問題無いのですが、うまく行かない場合もあります。

まず、フレームが使われている場合。フレームの中のページは、大本のページとは別のwindowオブジェクトなので、その中で選択文字列は上記の方法では取得できないことになります。何とかしようとするならば、window.framesにより子windowオブジェクトを取得し、それぞれのwindowオブジェクトについて再帰的に選択文字列を探していくような処理が必要になります。

また、テキスト入力エリア(type=textであるinput要素、textarea要素)内の選択文字列も、上記の方法ではうまく行かなかったりします。この場合はinput要素、textarea要素を取得して、それぞれの要素オブジェクトTextAreaElementについて、選択開始位置(TextAreaElement.selectionStart)、選択終了位置(TextAreaElement.selectionEnd)、入力エリアの値(TextAreaElement.value)を元に選択文字列を抜き出すことになります。

以上を踏まえ、どんな場合でも選択文字列を取得できるよう、以下のようなスクリプトを書いてみました。

(function(){
  var selected=0,url={};
  url.before='http://www.google.com/search?q=';
  url.after='&foo=bar';
  traceFrames(window);
  if(!selected){
    var q=prompt('please input text.');
    if(q){
      openWindow(q);
    }
  }

  function traceFrames(w){
    getSelectedText(w);
    var flen=w.frames.length;
    for(var i=0;i<flen;i++){
      traceFrames(w[i]);
    }
  }

  function getSelectedText(w){
    var t;
    try{
      t=w.getSelection().toString();
    }catch(e){
      return;
    }
    if(t){
      openWindow(t);
    }else{
      var tias=getTextInputAreas(w);
      for(var i=0;i<tias.length;i++){
        var tia=tias[i];
        var s=tia.selectionStart;
        var e=tia.selectionEnd;
        if(s!=e){
          openWindow(tia.value.substr(s,e-s));
        }
      }
    }
  }

  function openWindow(q){
    selected++;
    var u=url.before+q+url.after;
    alert(u);
    /*window.open(u,'_blank');*/
  }

  function getTextInputAreas(w){
    var elms=w.document.getElementsByTagName('*');
    var tias=[];
    for(var i=0,e;e=elms[i];i++){
      var tag=e.tagName.toLowerCase();
      if(tag=='textarea'){
        tias[tias.length]=e;
      }else if(tag=='input'){
        var ty=e.type.toLowerCase();
        if (!ty||ty=='text') {
          tias[tias.length]=e;
        }
      }
    }
    return tias;
  }
})();

連続空白と改行を除去して頭にjavascript:をつければ、bookmarklet用のURLになります。

実行すると、選択文字列をurl.beforeurl.afterで挟んだ文字列をalert表示します(選択文字列が無い場合は、テキスト入力プロンプトが出ます)。コメントの印(/*, */)を外すと、alert表示されたURLのページを開きます。

幾つか注意点があります。

「最後にフォーカスされたフレーム(またはテキスト入力エリア)」を取得するような方法があったらよいなと思ったのですが、自分ではそれを見つけることができませんでした。ともあれ、「選択文字列を取得して何かする」タイプのbookmarkletの雛形として、上記ソースを役立てています。