« 2009年03月 | メイン | 2009年05月 »

2009年04月 アーカイブ

2009年04月02日

Twitterのpostを爆発させるGreasemonkey

Twitterで流行し、一時期はその発言を見ない日はなかった『爆発しろ』という言葉ですが、現在においても散見されます。このたびTwitterのPostを爆発させるGreasemonkeyをリリースしました事をここにお知らせします。
当Greasemonkeyをインストールしますと、タイムラインのPostに火薬が充填されます。充填された火薬は画面上では判断することができませんのでご了承下さい。Postをクリックすると火薬へ着火され爆発します。
爆発前
爆発前
爆発後
爆発後
尚、TwitterのHomeでのみ動作します。
爆発が隣のPostに飛び火し、爆発の連鎖は起こさないように最前の注意を払っておりますのでご安心下さい。また、CSS3の機能を利用しているため、現時点ではFirefox3.0では動作しません。FirefoxのBeta版とGreasemonkeyというAdd-onをご利用下さい。リリース時にダウンロードできるFirefoxは3.1Beta3となっております。
一度爆発したPostは火薬を消費しているため爆発をしません。一画面に表示されるPostは20までですので、連続して爆発できるのは標準で20回までとなっておりますが、21回以上の連続爆発をというお客様の声に御応えするためにAutoPagerizeに対応しました。
AutoPagerizeにより新しいページが読み込まれるたびに新しい火薬が充填されます。これにより連続100回以上の爆発も可能となっております。
快適な爆発生活をお楽しみ頂けるよう、是非ともexplotterをご利用下さい。
2009/04/02 追記
一応、爆発はアニメーションしています。処理が重くて破片の移動が間に一回だけはいっているだけになってます。もし、もう少し細かいアニメーションが見たいときは、104行目のこの部分を
)(2,50,positionData)();
下のようにして見てください。
)(5,50,positionData)();
第一引数はアニメーションの枚数、第二引数は時間の間隔になっています。
2009/04/02 追記2
爆発した破片の到達点の相対座標を乱数で決める時に次のような計算にしがちです。
var n=200;
var x=(Math.random()*2-1)*n;
var y=(Math.random()*2-1)*n;
でも、このような方法だと分布が均等になってしまい不自然になります。ではこれならどうでしょうか。
var n=200;
var x=(Math.random()-Math.random())*n;
var y=(Math.random()-Math.random())*n;
分布が中央によりますが分布の形状が正方形になってしまいます。今回は以下のように記述しました。
var distance=Math.random()*200;
var direction=Math.random()*2*Math.PI;
var x=distance*Math.cos(direction);
var y=distance*Math.sin(direction);
飛距離と方向を乱数で定義し、それをX-Y方向にベクトルを分解しています。これで随分とリアルにはなりましたが、飛距離の分布がまだ均等になっています。本来であればX-Y平面に対するZ軸方向の角度によって飛距離は決定すべきです。つまり、分布は飛距離ではなく角度に対して一定あるべきですが、今回はここまでは計算させていませんし、高さを表現する為のサイズ変更も行っていません。さらに突き詰めるのであれば、地面で反射する破片を考慮することも考えられます。どこまで行なうかは色々な条件とのバランスの上で決めるべきではないでしょうか。
2009/04/02 追記3
positionのabsoluteを使用した場合、移動元の表示領域は保持したままにならずにつめられてしまいます。今回の場合は、post表示領域の高さが変化してしまいます。しかし、破片の回転に使用しているtransformは、移動元に表示領域を残したまま表示位置を変更します。破片の移動もtranslateを利用した場合、cssの指定が全てtransform内で収まり非常に好都合なのでこの方法で行なう予定でした。
part.style.MozTransform="rotate("+progress*datum.rotate+"deg) translate("+progress*datum.x+"px,"+progress*datum.y+"px)";
ところがtranslateを指定して位置を移動した場合、親要素からはみだすと表示されなくなってしまいました。このため、祖先要素のoverflowにvisibleを追加したのですがこれでもうまくいきませんでした。仕方がないので、破片のCSSのpositionにrelativeとz-indexに大きめの値を指定し、leftとtopの指定で対応しました。
part.style.left=progress*datum.x+"px";
part.style.top=progress*datum.y+"px";
part.style.MozTransform="rotate("+progress*datum.rotate+"deg)";
この、親からはみだすと消えてしまう現象の詳細はわかりません。transformの仕様かもしれませんし、他のCSSの値が影響している可能性もあります。もしかすると、ブラウザに依存した挙動かもしれません。知らないとはまってしまうかもしれません。ちょっとしたバッドノウハウになりそうですね。

2009年04月03日

gitとgit-svnのコマンド

gitとgit-svnのコマンドの個人的なメモ
すぐ忘れるのでメモ。
git svn clone [svnリポジトリURL]
svnのリポジトリをcloneする

git svn clone [svnリポジトリURL] -s
標準的な構成のsvnのリポジトリをcloneする

git svn clone [svnリポジトリURL] -T trunk -b branches -t tags
branchesやtagsを指定してsvnのリポジトリをcloneする

git branch
branch一覧

git branch -r
リモートのbranch一覧

git checkout [branch名]
branchを切り替える

git checkout -b [gitのbranch名] [リモートのbranch名]
リモートのブランチをgitに読み込む

git svn rebase
svnのリポジトリとローカルリポジトリを同期する

2009年04月04日

Shell CommandをWrapしてappに変換するShell Commandを書いてみた

最近、FirefoxのBeta版を入れた。普段から、Firefoxは開発用のプロファイルを分けて使っているけど、Beta版用のプロファイルも作成した。例えば、Beta版用プロファイルの名前が「Firefox3.1 Beta3」だったとすると、Windowsでは、Firefoxのベータ版のショートカットを作成して「-no-remote -p "Firefox3.1 Beta3"」を追加しておくだけで良い。この方法を知ったのは数日前。TwitterとWassrに複数バージョン立ち上げる方法はないものかと言っていたらたくさんの人からレスを頂いた。名前出しの確認をとっていないのでここでは名前は出さない方向で。「-no-remote」と「-p」は常用してたけど、バージョンが違うと使えないと思い込んでいたけど、プロファイルはバージョンが違っても共用できるとのこと。しかし、MacではいちいちShellを起動して、
/Applications/Firefox\ Beta3.app/Contents/MacOS/firefox-bin -no-remote -p "Firefox3.1 Beta3"
としなくてはいけない。これが作業をする上で非常に面倒だった。GUIから起動したいと思って、TwitterやWassrでなんとかならないものかとまたぼやいていたら、elimさんからAppleScriptやAutomatorでShellを実行する方法があると教えて頂いた。
AppleScriptの例
AppleScriptの例
elimさんのgyazoより
AppleScriptの保存時のオプション
AppleScriptの保存時のオプション
elimさんのgyazoより
この方法、すっかり忘れてた。AppleScriptなんか随分前に使ってたのに...。どれくらい前かというと、AppleScriptが日本語でCodingできるくらい前から。AppleScriptは、WindowsでいうところのWSHみたいなもので、確かWSHよりも前からあったはず。各アプリケーションがAppleScript用に準備したAPIを使って色々な連携を可能とする結構優れた仕組み。AutomatorはAppleScriptのGUI版みたいなもので、プログラミングの知識がなくても、ワークフローをDrag&Dropで組み立てることができる。で、作ってみたは良いけど、起動が遅い。Shell Commandが遅いんじゃなく、AppleScriptやAutomatorの起動が遅すぎる。たかだかShell Commandを一行実行する為だけに、AppleScriptやAutomatorは重厚すぎた。しかも、Firefoxを実行している間はAppleScriptは立ち上がりっぱなしになるので、Dockには2つのアイコンが同時に増えてしまう。TwitterやWassrで気持ち悪いと言っていたら、またelimさんからレスが。
これって、yharaさんが去年言っていたやつだ。Binaryでなくても実行権限さえ持っていればShell Commandでも可能だということを思い出した。サンプルをダウンロードし、修正して作れば簡単なんだけど、勉強がてら自分でゼロから作成してみた。やってて気づいたのは、アイコン作成の部分以外の作成作業はShellとEmacsで簡単に可能だということ。これって、Emacsの作業はRedirectで可能だから全部Shellでも可能だ。アイコンの指定がなくてもappファイルの起動ができた。これはShell Command化ができるなぁと思ったので作ってみた。Shell Commandはほとんど知らないので次のリファレンスを参照しながら作成した。
前置き長くてごめんなさい。実装したのを貼ります。
make-command-wrapper
#!/bin/sh

if [ $# -ne 2 ] ; then
    echo "make-command-wrapper: This makes the application that wraps the shell command."
    echo "usage: make-command-wrapper [new application name] [shell command]"
    exit 1
fi

app_name="$1.app"
shell_command=$2

if [ -e "$app_name" ] ; then
    echo "The file of the same name or the folder already exists."
    exit 1
fi

mkdir $app_name
cd $app_name
mkdir Contents
cd Contents

cat <<__END_OF_MESSAGE__ >>Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>CFBundleExecutable</key>
    <string>core.sh</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleSignature</key>
    <string>????</string>
  </dict>
</plist>
__END_OF_MESSAGE__

mkdir MacOS
cd MacOS

{
  echo "#!/bin/sh"
  echo ""
  echo "$shell_command &"
  echo "exit 0"
} >>core.sh

chmod 755 core.sh

exit 0
知っている人からすると例外処理が甘いだろうなとは思うけど、初めてのShell Command作成にしては上出来かなと。これを「make-command-wrapper」と言う名前で保存して実行権限を指定し、
./make-command-wrapper [新しいアプリケーション名] [実行するShell Command] 
とすると、Shell CommandをWrapしたappファイルが作成できる。これ結構便利かも。と、思ってたけど、当初の目的であるFirefoxのBeta版をプロファイル指定したものをapp化してみて愕然。
./make-command-wrapper firefoxbata3 /Applications/Firefox\\\ Beta3.app/Contents/MacOS/firefox-bin\ -no-remote\ -p\ \"Firefox3.1\ Beta3\"
Escapeが多くて非常にわかりにくい。使い物になるのかな、これ。
感想
古い知識とこの一年間で覚えたことが混じり合って変な気分。そしてTwitterやWassrがなければ無理だったと思う。しかし、ここ数年で開発のスタイルが大きく変わって、スピードが速くなったよなぁ。
関連リンク

2009年04月12日

XMLHttpRequestでファイルをDataSchemeで取得する実験

2009/04/15 追記
このエントリーの後に問題は解決しbinaryを取得することができました。最後のほうにある「その2」を参照してください。
画像等のファイルのデータをBase64化したDataSchemeでなんとか取得できないかと試してみました。結論からいうと失敗です。ただ、もう少し調査するともしかするとできるようになるかもしれません。しかし、自分の現時点の知識では、これが限界かと思います。もしかすると、問題箇所を誰かが解決してくれるのかもしれないと淡い期待を持って公開します。また、提示するcodeは使い方によってはDoS攻撃と思われてしまうような挙動をします。詳細は後述しますが実行時は注意してください。
自分で開発しているようなサイトの場合、自分でJavaScriptからデータを取得する仕組みを実装すればいいのでここでは考えないことにします。サーバ上の静的なファイルのデータ取得をしたいのは、クロスサイトで実行可能なXMLHttpRequestを使えるGreasemonkeyからではないでしょうか。これらの理由から、以下Firefox3上のGreasemonkeyを前提にしています。
そもそもXMLHttpRequestには、responseXMLとresponseTextというテキストを前提としたメソッドしかないためバイナリが取得できません。初期のXMLHttpRequestが実装された当時のIEにはresponseStreamというメソッドがあったらしいのですが、残しておいて欲しかったですね。まあ、セキュリティホールになりそうな気はしますけど。
バイナリをXMLHttpRequest.responseTextを使って取得した場合、文字化けが発生します。ただ文字化けが発生するのではなくUnicodeとして認識できなかった場合、文字化けが発生した場所が「�」に変換されてしまいます。「�」は「REPLACEMENT CHARACTER」はUnicodeで定義されている文字で、文字コードはFFFDとなっています。これは不可逆変換のため元のデータを推測することができません。もちろん、偶然にも文字化けが発生し得ないデータのみでバイナリが構成されていた場合はこの限りではありませんが...。
文字化けを発生させずにデータを取得する方法は本当にないのでしょうか。もしかすると、文字化けがマルチバイトの文字でしか発生しないのではないかと考えました。そうだとすると、強制的に1Byteずつ取得できればバイナリも取得可能なのではないでしょうか。後述しますが、HTTPには1Byteずつデータを取得する方法があります。そこで、次のコードをFirebug上で実行し「REPLACEMENT CHARACTER」が表示されないかを確認してみました。
for(var i=0;i<256;i++){
    var str=String.fromCharCode(i);
    console.log(str,str.charCodeAt(0),str.charCodeAt(0)==i,str.charCodeAt(0)==0xFFFD);
}
表示されない文字がありますが、少なくとも文字コードは温存されており、「REPLACEMENT CHARACTER」に変換されるようなことはありませんでした。これは、実現へ向けて期待が持てます。
データを1Byteずつ取得するにはどのようにすればいいのでしょうか。それは、HTTPヘッダを利用する方法です。Requestヘッダには取得するデータの範囲を指定する「Range」というものが、Responseヘッダには取得するデータのサイズを表す「Content-Length」があります。これらはダウンローダ等のレジューム機能の実装に使われています。取得しようとしているデータが途中で更新される場合がありますが、データの整合性を保つための仕組みも提供されています。今回は、最終変更日時を表す「Last-Modified」というResponseヘッダと、指定された日時以降にデータが更新されていないことを保証するための「If-Unmodified-Since」というRequestヘッダを利用します。整合性のためのHTTPヘッダはこの他にも色々ありますので、詳細を知りたい方はRFCや以下の連載を参照してください。
今回はデータ取得を非同期で行なうため、取得したデータの範囲を表す「Content-Range」というResponseヘッダも使用しています。また、Dataスキームで指定するコンテンツタイプの為に「Content-Type」というヘッダも利用しています。
さて、長々とHTTPヘッダについて書きましたが、どのように指定するのでしょうか。通常のアクセスであれば指定はできない(はず)のですが、XMLHttpRequestの場合は可能です。Greasemonkeyの場合は、以下のように指定できます。
GM_xmlhttpRequest(
    {
        "method":"GET",
        "url":url,
        "onload":callbackForSuccess,
        "onerror":callbackForError,
        "headers":{
            "If-Unmodified-Since":lastModified,
            "Range":"bytes="+from+"-"+To
        }
    }
);
また、Responseヘッダはcallback関数に渡されるGM_xmlhttpRequestのインスタンスのプロパティ「responseHeaders」から文字列として取得できます。
処理は、まずHEADメソッドで「Content-Length」と「Last-Modified」を取得します。そして、文字化けが発生しないように、「Range」を1Byteずつ変更しながら、「Content-Length」回実行しています。つまり、1KBytesのデータを取得するためには1,024回、1MBytesのデータであれば1,048,576回もHTTPのリクエストを発生させます。これが、はじめにDoS攻撃になりかねないと書いた理由です。以上のことを実装したものを最後にはりますので興味のある方はどうぞ。
では、どうして失敗で終わったのでしょうか。それは、Rangeを指定しデータを取得しても「REPLACEMENT CHARACTER」を返してしまうことがあるからです。理由はわかりませんが、文字コードやContent Typeが関係しているのではないかと思っています。Requestヘッダに「Accept-Charset」や「Content-Type」を指定し取得するデータの種類を強制的に指定してやれば可能なのではないかとにらんでいます。しかし、これらの組み合わせは多岐に及びます。この辺りの知識を私は持ち合わせていません。誰かが解決してくれるのではないかと思いながらここで終わることにします。
// ==UserScript==
// @name        getFileByDataScheme
// @namespace   http://www.kanasansoft.com/
// @include     *
// ==/UserScript==

(
    function(){

        var base64=function(original){
            var table=(
                function(s){
                    var len=s.length;
                    var hash={};
                    for(var i=0;i<len;i++){
                        var bit=i.toString(2);
                        var bit="000000".slice(0,-bit.length%6)+bit;
                        hash[bit]=s.charAt(i);
                    }
                    return hash;
                }
            )("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
            var len=original.length;
            var bits8=[];
            if(original instanceof Array){
                for(var i=0;i<len;i++){
                    var bit=original[i].charCodeAt(0).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }else{
                for(var i=0;i<len;i++){
                    var bit=original.charCodeAt(i).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }
            var len=bits8.length;
            var bits6=[];
            for(var i=0;i<len;i+=3){
                var bit=bits8.slice(i,i+3).join("");
                bit+="000000".slice(0,-bit.length%6);
                Array.prototype.push.apply(bits6,bit.match(/.{6}/g));
            }
            var len=bits6.length;
            var base64=[];
            for(var i=0;i<len;i++){
                base64.push(table[bits6[i]]);
            }
            Array.prototype.push.apply(base64,["=","=","=","="].slice(0,-base64.length%4));
            return base64.join("");
        }

        var parseHTTPHeader=function(responseHeader){
            var headers=responseHeader.split("\n");
            var len=headers.length;
            var parsing=[];
            for(var i=0;i<len;i++){
                if(/^$/.test(headers[i])){
                }else if(/^[\x09\x20]/.test(headers[i])){
                    if(parsing.length==0){
                        throw "SyntaxError:HTTPHeader (first line) "+headers[i];
                    }
                    parsing[parsing.length-1]+="\n"+headers[i];
                }else{
                    parsing.push(headers[i]);
                }
            }
            var len=parsing.length;
            var parsed={};
            for(var i=0;i<len;i++){
                var pair=parsing[i].split(": ",2);
                if(pair.length!=2){
                    throw "SyntaxError:HTTPHeader (format) "+parsing[i];
                }
                if(pair[0] in parsed){
                    throw "SyntaxError:HTTPHeader (repetition) "+pair[0];
                }
                parsed[pair[0]]=pair[1];
            }
            return parsed;
        }

        var getDataMethod=function(res){
            if(data.errorFlag){
                return;
            }
            if(res.status!=206){
                data.errorFlag=true;
                throw "HTTPStatusError (The status is not 206.)"+
                    ":status="+res.status+
                    ":statusText="+res.statusText+
                    ":responseHeaders="+res.responseHeaders;
            }
            var headers=parseHTTPHeader(res.responseHeaders);
            var contentRange=headers["Content-Range"];
            if(!contentRange){
                data.errorFlag=true;
                throw "Error:Sorry. The server do not support HTTP header of \"Content-Range\".";
            }
            var range=contentRange.match(/^bytes (\d+)-\d+\/\d+$/)[1];
            data.gottenData[parseInt(range,10)]=res.responseText;
            data.restData=data.restData.filter(
                (function(range){
                     return function(val){return range!=val;}
                 })(range)
            );
            if(data.restData.length==0){
                var contentType=("Content-Type" in headers)?
                    headers["Content-Type"]:
                    "application/octet−stream";
                var dataScheme="data:"+contentType+
                    ";base64,"+base64(data.gottenData);
                data.callback(dataScheme);
            }
        }

        var getDataByRangeHandler=function(range,callback){
            return function(){
                if(data.errorFlag){
                    return;
                }
                GM_xmlhttpRequest(
                    {
                        "method":"GET",
                        "url":data.url,
                        "onload":callback,
                        "onerror":callback,
                        "headers":{
                            "If-Unmodified-Since":data.lastModified,
                            "Range":"bytes="+range+"-"+range
                        }
                    }
                );
            }
        }

        var startGetData=function(){
            var len=data.restData.length;
            for(var i=0;i<len;i++){
                var getDataByRange=
                    getDataByRangeHandler(data.restData[i],getDataMethod);
                setTimeout(getDataByRange,10*i);
            }
        }

        var loadHeaderMethod=function(res){
            var headers=parseHTTPHeader(res.responseHeaders);
            if(res.status!=200){
                throw "RequestError"+
                    ":status="+res.status+
                    ":statusText="+res.statusText+
                    ":responseHeaders="+res.responseHeaders;
            }
            var headers=parseHTTPHeader(res.responseHeaders);
            var lastModified=headers["Last-Modified"];
            if(!lastModified){
                throw "Error:Sorry. The server do not support HTTP header of \"Last-Modified\".";
            }
            var contentLength=headers["Content-Length"];
            if(!contentLength){
                throw "Error:Sorry. The server do not support HTTP header of \"Content-Length\".";
            }
            contentLength=parseInt(contentLength,10);
            var restData=[];
            for(var i=0;i<contentLength;i++){
                restData.push(i);
            }
            data.lastModified=lastModified;
            data.size=contentLength;
            data.restData=restData;
            startGetData();
        }

        var getHeader=function(url){
            data.url=url;
            GM_xmlhttpRequest(
                {
                    "method":"HEAD",
                    "url":data.url,
                    "onload":loadHeaderMethod,
                    "onerror":loadHeaderMethod
                }
            );
        }

        var data={"errorFlag":false,"gottenData":[]};

        var getFileByDataScheme=function(url,callback){
            data.callback=callback;
            getHeader(url);
        }

        if(window.self==window.top){
            var url=prompt("image url","");
            if(url==null||url==""){
                return;
            }
            var handler=function(url,callback){
                return function(){
                    getFileByDataScheme(url,callback);
                }
            }
            var callback=function(dataScheme){
                var elem=document.createElement("img");
                elem.src=dataScheme;
                document.body.appendChild(elem);
            }
            window.addEventListener(
                "load",
                handler(url,callback),
                false
            );
        }

    }
)();
続きます。

2009年04月13日

XMLHttpRequestでファイルをDataSchemeで取得する実験 その2

前回のエントリーの後日談をライブ間たっぷりに書いていくぜ! 誤字脱字の修正を除いて、書きっぱなしの推敲なしで最後まで。
前回からこれまでの流れ
なんだか次のURLを参照すれば良いのではとWassrで聞いた。
function load_binary_resource(url) {
  var req = new XMLHttpRequest();
  req.open('GET', url, false);
  //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
  req.overrideMimeType('text/plain; charset=x-user-defined');
  req.send(null);
  if (req.status != 200) return '';
  return req.responseText;
}
前回のエントリーの最後の方で書いた通り、Content-TypeやAccept-Charsetの指定で解決するのではと考えていたので、次のようにすればいいと思い込んでしまった。
GM_xmlhttpRequest(
    {
        "method":"GET",
        "url":url,
        "onload":callbackForSuccess,
        "onerror":callbackForError,
        "headers":{
            "Content-Type":"text/plain",
            "Accept-Charset":"x-user-defined"
            "If-Unmodified-Since":lastModified,
            "Range":"bytes="+from+"-"+To
        }
    }
);
結果はやっぱり文字化けした。でも、そのあとでblogへのコメントとはてなブックマークのコメントに同じような指摘があった。「あれ?うまくいかなかったのになぁ...」と思いつつもう一回MDCを確認した。あああ、もしかして「overrideMimeType」ってメソッドが重要ってこと?
じゃあ調べてみよう
MDCのサンプルを見ると、「overrideMimeType」はXMLHttpRequestのメソッドとして準備されている。前回はGreasemonkeyで実装しようとしたので、GM_xmlhttpRequestにI/Fが準備されていないか調べてみる。
あった。そのまんま。こんな感じに修正してみた。
GM_xmlhttpRequest(
    {
        "method":"GET",
        "url":data.url,
        "onload":callback,
        "onerror":callback,
        "overrideMimeType":"text/plain; charset=x-user-defined",
        "headers":{
            "If-Unmodified-Since":data.lastModified,
            "Range":"bytes="+range+"-"+range
        }
    }
);
GM_xmlhttpRequest(
    {
        "method":"HEAD",
        "url":data.url,
        "onload":loadHeaderMethod,
        "onerror":loadHeaderMethod,
        "overrideMimeType":"text/plain; charset=x-user-defined"
    }
);
動くかな。どきどき。
ダメだった。何でだろう。調査中。GM_xmlhttpRequestの書き方がまずいのかな。
ここ見ても間違えてなさそうなんだけどなぁ。
22:07
しまったなぁ。ハードル上げ過ぎた。戦略練り直す。ここから更新時間を入れることにする。
22:54
う〜〜〜ん。無理。中断。再開は今日かもしれないし明日かもしれない。ちょっと頭冷やす。
23:43
再開。単純なcodeから原因を突き止めて行くことにする。
この画像で確認
sample
00:01
こうゆうグリモンを書いて確認。
// ==UserScript==
// @name        getFileByDataScheme
// @namespace   http://www.kanasansoft.com/
// @include     *
// ==/UserScript==

(
    function(){

        var base64=function(original){
            var table=(
                function(s){
                    var len=s.length;
                    var hash={};
                    for(var i=0;i<len;i++){
                        var bit=i.toString(2);
                        var bit="000000".slice(0,-bit.length%6)+bit;
                        hash[bit]=s.charAt(i);
                    }
                    return hash;
                }
            )("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
            var len=original.length;
            var bits8=[];
            if(original instanceof Array){
                for(var i=0;i<len;i++){
                    var bit=original[i].charCodeAt(0).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }else{
                for(var i=0;i<len;i++){
                    var bit=original.charCodeAt(i).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }
            var len=bits8.length;
            var bits6=[];
            for(var i=0;i<len;i+=3){
                var bit=bits8.slice(i,i+3).join("");
                bit+="000000".slice(0,-bit.length%6);
                Array.prototype.push.apply(bits6,bit.match(/.{6}/g));
            }
            var len=bits6.length;
            var base64=[];
            for(var i=0;i<len;i++){
                base64.push(table[bits6[i]]);
            }
            Array.prototype.push.apply(base64,["=","=","=","="].slice(0,-base64.length%4));
            return base64.join("");
        }

        var parseHTTPHeader=function(responseHeader){
            var headers=responseHeader.split("\n");
            var len=headers.length;
            var parsing=[];
            for(var i=0;i<len;i++){
                if(/^$/.test(headers[i])){
                }else if(/^[\x09\x20]/.test(headers[i])){
                    if(parsing.length==0){
                        throw "SyntaxError:HTTPHeader (first line) "+headers[i];
                    }
                    parsing[parsing.length-1]+="\n"+headers[i];
                }else{
                    parsing.push(headers[i]);
                }
            }
            var len=parsing.length;
            var parsed={};
            for(var i=0;i<len;i++){
                var pair=parsing[i].split(": ",2);
                if(pair.length!=2){
                    throw "SyntaxError:HTTPHeader (format) "+parsing[i];
                }
                if(pair[0] in parsed){
                    throw "SyntaxError:HTTPHeader (repetition) "+pair[0];
                }
                parsed[pair[0]]=pair[1];
            }
            return parsed;
        }

        var loadDataMethod=function(res){
            var headers=parseHTTPHeader(res.responseHeaders);
            if(res.status!=200){
                throw "RequestError"+
                    ":status="+res.status+
                    ":statusText="+res.statusText+
                    ":responseHeaders="+res.responseHeaders;
            }
            var contentType=("Content-Type" in headers)?
                headers["Content-Type"]:
                "application/octet−stream";
            var dataScheme="data:"+contentType+
                ";base64,"+base64(res.responseText);
            data.callback(dataScheme);
        }

        var getData=function(url){
            data.url=url;
            GM_xmlhttpRequest(
                {
                    "method":"GET",
                    "url":data.url,
                    "onload":loadDataMethod,
                    "onerror":loadDataMethod,
                    "overrideMimeType":"text/plain; charset=x-user-defined"
                }
            );
        }

        var data={};

        var getFileByDataScheme=function(url,callback){
            data.callback=callback;
            getData(url);
        }

        if(window.self==window.top){
            var url=prompt("image url","");
            if(url==null||url==""){
                return;
            }
            var handler=function(url,callback){
                return function(){
                    getFileByDataScheme(url,callback);
                }
            }
            var callback=function(dataScheme){
                var elem=document.createElement("img");
                elem.src=dataScheme;
                document.body.appendChild(elem);
            }
            window.addEventListener(
                "load",
                handler(url,callback),
                false
            );
        }

    }
)();
結果
//グリモンで出力されたDataScheme
data:image/png;base64,94lQTkcNChoKAAAADUlIRFIAAAAQAAAAEAIDAAAAYvedF/fyAAAAAXNSR0IA9673zhz36QAAAAxQTFRFAC1n98z3zPf/AAAA95n3mff/99VVX/f6AAAAAXRSTlMAQPfm99hmAAAAAWJLR0QA94gFHUgAAAAJcEhZcwAACxMAAAsTAQD3mvecGAAAAAd0SU1FB/fYAwwPCTX3s/fi98n33QAAAENJREFUCPfXY2BgamBgYPfgTAAS96oP94D3xHQg95dpHveQ9+B+CRT3434aAffkXg0DSkwNBQr3qveGAgU5QQQT94j3yzQNKPfB9/0CRPeA9/QC940CAFYZDvfC99z3r3b31QAAAABJRU5E965CYPeC

//[http://software.hixie.ch/utilities/cgi/data/data]で変換
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAAAXNSR0IArs4c6QAAAAxQTFRFAC1nzMz%2FAAAAmZn%2F1VVf%2BgAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2AMMDwk1s%2BLJ3QAAAENJREFUCNdjYGBqYGBg4EwAEqoPgMR0IJdpHpDgfgkU434aAeReDQNKTA0FCqqGAgU5QQQTiMs0DSjB%2FQJEgPQCjQIAVhkOwtyvdtUAAAAASUVORK5CYII%3D
似ても似つかない悲惨な結果に...。昨日のグリモンでは似たような結果だったのになんでだろう。画像を変えたからかな。また、変えてみるかなぁ。
00:22
上の画像で「console.log(res.responseText);」してみたら「�」が大量に出力されてた。やっぱりRangeで分割する必要があるのかな。
04/14 04:54
寝オチしてた。続きは明日。
23:23
Twitterで色々教えてもらった。取得した文字列の上位bitを削らなければいけない、Base64変換の変わりにbtoa関数を使えばよい、GlitchMonkeyというグリモンが参照になるかも、とのこと。JavaScirptは文字列をUTF-16で扱うんだった。「text/plain; charset=x-user-defined」で取得した文字列は1byteだけども、client側で認識される時にはUTF-16になるなって2bytesとなってしまうってことだよね。0x00-0xFFの文字列が0x0000-0x00FFとなってしまうので上位bitを削除しないとbinaryとしてはおかしなことになると...。あと、btoa関数、動作がよくわからなかったのでBase64関数を新規に作ったんだけど...。btoaでいいのか...。native関数の方が速いのはもちろんわかっているんだけど、このままでやってみる。もし、正常に動作すれば古いブラウザでも動くはずなので二兎を追える。最後のGlitchMonkeyは画像を操作するuser scriptみたい。必要に応じて追っかけてみる。
04/15 00:01
認識間違ってた。GlitchMonkeyのsourceが無茶苦茶重要。
特にこの部分。
function base64encode(data) {
  return btoa(data.replace(/[\u0100-\uffff]/g, function(c) {
    return String.fromCharCode(c.charCodeAt(0) & 0xff);
  }));
}
短いのにやりたいことに本質がぎっしり詰まってる。
00:12
次のように修正。
変更前
var dataScheme="data:"+contentType+
    ";base64,"+base64(res.responseText);
変更後
var resText=res.responseText.replace(
    /[\u0100-\uffff]/g,
    function(c){
        return String.fromCharCode(c.charCodeAt(0)&0xff);
    }
)
var dataScheme="data:"+contentType+
    ";base64,"+base64(resText);
さて動くかな...。
00:16
うをぉおおおおお〜〜〜〜〜!!! 動いた!!! すげぇ!!! とりあえず動いたグリモンをはる。

// ==UserScript==
// @name        getFileByDataScheme
// @namespace   http://www.kanasansoft.com/
// @include     *
// ==/UserScript==

(
    function(){

        var base64=function(original){
            var table=(
                function(s){
                    var len=s.length;
                    var hash={};
                    for(var i=0;i<len;i++){
                        var bit=i.toString(2);
                        var bit="000000".slice(0,-bit.length%6)+bit;
                        hash[bit]=s.charAt(i);
                    }
                    return hash;
                }
            )("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
            var len=original.length;
            var bits8=[];
            if(original instanceof Array){
                for(var i=0;i<len;i++){
                    var bit=original[i].charCodeAt(0).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }else{
                for(var i=0;i<len;i++){
                    var bit=original.charCodeAt(i).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }
            var len=bits8.length;
            var bits6=[];
            for(var i=0;i<len;i+=3){
                var bit=bits8.slice(i,i+3).join("");
                bit+="000000".slice(0,-bit.length%6);
                Array.prototype.push.apply(bits6,bit.match(/.{6}/g));
            }
            var len=bits6.length;
            var base64=[];
            for(var i=0;i<len;i++){
                base64.push(table[bits6[i]]);
            }
            Array.prototype.push.apply(base64,["=","=","=","="].slice(0,-base64.length%4));
            return base64.join("");
        }

        var parseHTTPHeader=function(responseHeader){
            var headers=responseHeader.split("\n");
            var len=headers.length;
            var parsing=[];
            for(var i=0;i<len;i++){
                if(/^$/.test(headers[i])){
                }else if(/^[\x09\x20]/.test(headers[i])){
                    if(parsing.length==0){
                        throw "SyntaxError:HTTPHeader (first line) "+headers[i];
                    }
                    parsing[parsing.length-1]+="\n"+headers[i];
                }else{
                    parsing.push(headers[i]);
                }
            }
            var len=parsing.length;
            var parsed={};
            for(var i=0;i<len;i++){
                var pair=parsing[i].split(": ",2);
                if(pair.length!=2){
                    throw "SyntaxError:HTTPHeader (format) "+parsing[i];
                }
                if(pair[0] in parsed){
                    throw "SyntaxError:HTTPHeader (repetition) "+pair[0];
                }
                parsed[pair[0]]=pair[1];
            }
            return parsed;
        }

        var loadDataMethod=function(res){
            var headers=parseHTTPHeader(res.responseHeaders);
            if(res.status!=200){
                throw "RequestError"+
                    ":status="+res.status+
                    ":statusText="+res.statusText+
                    ":responseHeaders="+res.responseHeaders;
            }
            var contentType=("Content-Type" in headers)?
                headers["Content-Type"]:
                "application/octet−stream";
            var resText=res.responseText.replace(
                    /[\u0100-\uffff]/g,
                function(c){
                    return String.fromCharCode(c.charCodeAt(0)&0xff);
                }
            )
            var dataScheme="data:"+contentType+
                ";base64,"+base64(resText);
            data.callback(dataScheme);
        }

        var getData=function(url){
            data.url=url;
            GM_xmlhttpRequest(
                {
                    "method":"GET",
                    "url":data.url,
                    "onload":loadDataMethod,
                    "onerror":loadDataMethod,
                    "overrideMimeType":"text/plain; charset=x-user-defined"
                }
            );
        }

        var data={};

        var getFileByDataScheme=function(url,callback){
            data.callback=callback;
            getData(url);
        }

        if(window.self==window.top){
            var url=prompt("image url","");
            if(url==null||url==""){
                return;
            }
            var handler=function(url,callback){
                return function(){
                    getFileByDataScheme(url,callback);
                }
            }
            var callback=function(dataScheme){
                var elem=document.createElement("img");
                elem.src=dataScheme;
                document.body.appendChild(elem);
            }
            window.addEventListener(
                "load",
                handler(url,callback),
                false
            );
        }

    }
)();
続きます。