2007-05-01

Greasemonkey で、object 要素を経由して外部 HTML 文書を取得する

Greasemonkey づいている連休です。サーバからデータ受信して処理したいのですが、GM_xmlhttpRequest だと 5KB くらいしか取得できないっぽいです。しかもサーバが Range ヘッダを無視してくれるという状況です。



検索したのですが見つからなかった(ちゃんと調べてない奴ほど、こういうことを言う)ので、強引なスクリプトを書いてみました。



  1. データを取得する側の文書を receiver、取得される HTML 文書を sender とします。


  2. receiver 上のスクリプトが  GM_setValue('req',true) に指定する


  3. receiver に object 要素を埋め込み、sender の URL を表示させる。


  4. sender 上のスクリプトは GM_setValue('req',false) であることを確認したら、req を false にして、文書の一部を GM_setValue('buffer', ...) する


  5. receiver 上のスクリプトが req が false になっていることを確認したら、GM_getValue('buffer') する


  6. 2~5 を繰り返す。


以下がサンプル。httpget と httpsend がコアの関数です。Unicode のエスケープには、sawatの日記 - Unicodeエスケープ を使っています。長くてすみません。もっと、いいやり方ないかなぁ。



// ==UserScript==
// @name           voexample
// @namespace      http://localhost/
// @description    Emulating HTTP GET via object element
// @include        http://example.com/receiver.htm
// @include        http://example.com/sender.htm
// ==/UserScript==

(function (){

  var receiverurl = 'http://example.com/receiver.htm';
  var senderurl = 'http://example.com/sender.htm';

  function httpget(url, callback) {
    function receive(callback) {
      var data = "";
      
      function closure () {
        function unescapeUnicode(str) {
          return str.replace(/\\u([a-fA-F0-9]{4})/g, function(m0, m1) {
            return String.fromCharCode(parseInt(m1, 16));
          });
        }
       
        if (GM_getValue('req')==false) {
          var part = GM_getValue('buffer');
          if (part==false) {
            document.body.removeChild(document.getElementById('__externaldoc'));
            callback(unescapeUnicode(data));
          } else {
            data += part;
            GM_setValue('req', true);
            startClosure();
          }
        } else {
          startClosure();
        }
      }
      
      function startClosure(ms) {
        setTimeout(closure, ms ? ms : 10);
      }
      
      startClosure(0);
    }
   
    function loadDocumentInObject(url) {
      var o = document.createElement('object');
      o.setAttribute('id', '__externaldoc');
      o.data = url;
      document.body.appendChild(o);
    }
   
    GM_setValue('req', true);
    loadDocumentInObject(senderurl);
    receive(callback);
  }

  function httpsend() {
    function escapeUnicode(str) {
      return str.replace(/[^ -~]|\\/g, function(m0) {
        var code = m0.charCodeAt(0);
        return '\\u' + ((code < 0x10)? '000' :
                        (code < 0x100)? '00' :
                        (code < 0x1000)? '0' : '') + code.toString(16);
      });
    }
   
    var i = 0;
    var data = escapeUnicode(document.body.innerHTML);
    var step = 4098;
    var closure = function() {
      if (GM_getValue('rew')==false) {
        setTimeout(closure, 10);
      } else {
        if (i<data.length) {
          GM_setValue('buffer', data.substring(i, i+step));
          GM_setValue('req', false);
          i += step;
          setTimeout(closure, 10);
        } else {
          GM_setValue('buffer', false);
          GM_setValue('req', false);
        }
      }
    }
    setTimeout(closure, 10);
  }
 
  function callback(text) {
    var div = document.createElement('div');
    div.innerHTML = text.toUpperCase();
    document.body.appendChild(div);
  }
 
  if (document.location == receiverurl) {
    httpget(senderurl, callback);
  } else if (document.location == senderurl) {
    httpsend();
  }
 
})();