WSHでないjavascriptでSleepする
wshでは、WScript.Sleepすれば良いだけですが、ブラウザーやHTAの場合はそうはいきません。
もちろん、setTimeoutの綱渡りを繰り返せば良いだけですが、functionを分割する必要があります。
ということで、手始めに制約ありまくりのsleepを実装してみた。
そのうち制約を解除して行く、、、かもしれない。
まずはサンプル
wshで電卓を制御するスクリプト。
SendKeys メソッド
var WshShell = WScript.CreateObject("WScript.Shell"); WshShell.Run("calc"); WScript.Sleep(1000); WshShell.AppActivate("電卓"); WScript.Sleep(1000); WshShell.SendKeys ("1{+}"); WScript.Sleep(500); WshShell.SendKeys("2"); WScript.Sleep(500); WshShell.SendKeys("~"); WScript.Sleep(500); WshShell.SendKeys("*3"); WScript.Sleep(500); WshShell.SendKeys("~"); WScript.Sleep(100); WScript.Echo("result:9?")
これを、HTAなりHTML(ActiveXなのでIE限定だけど)にするには、結構難儀でした。
もちろん、ビジーループやping(やTimeout)実行を使って良ければ大したことはありませんが、
まっとうな方法を考えると、setTimeoutかと思います。
そうすると、Sleep前後を関数に分割して、setTimeoutによる関数綱渡りです。
8分割ですか。
これを、オレオレ言語で勝手にsetTimeoutするようにしてみました。
HTAにすると、ActiveXの警告がなくて良いかもね。
注目すべきは、
<script id="sampleFunc" type="text/x-junejun-sleep-support-javascript">
のscriptタグ部分で、上のwsh版とほとんど変わりありません。
<html> <head> <title>電卓制御</title> <script type="text/javascript"> <!-- var JunejunSleepSupportJavascript = function(__name) { this.__name = __name; this.__getName = function(funcName, no, stack) { var ret = funcName + '(' + no + ', ' + stack + ')'; if(no == -1) { ret = 0; while(this.__getName(funcName, ret, stack) in this.__src) ret++; } return ret; } this.__eval = function(__src, __name) { var __param = this.__param[__name]; var ret = eval(__src); this.__param[__name] = __param; return ret; } this.__src = {}; this.__param = {}; this.__reSpecial = /[\r\n;]\s*(sleep|call)\s*\(/m; this.__step = function(funcName, no, stack) { var name = this.__getName(funcName, no, stack); var src = this.__src[name]; var bSet = false; if(!bSet) { if(this.__reSpecial.test(src)) { // 特殊処理の場合は、その前までを実行し、特殊処理する this.__eval(RegExp.leftContext, name); // $` src = RegExp.rightContext; // $' var cond = src.substr(0, this.__getPair(src, "(", ")", 0)); src = src.substr(cond.length + 1); this.__src[name] = src; switch(RegExp.$1) { case "sleep": // sleepの場合は、setTimeout setTimeout(this.__name + '.__step("' + funcName + '", ' + no + ', ' + stack + ')', this.__eval(cond, name)); bSet = true; break; case "call": // callの場合は、stackを加算、実行する this.__eval('this.__run(' + cond + ', "' + funcName + '", ' + no + ', ' + (stack + 1) + ')', name); bSet = true; break; } } else { // 問題なければ、まるまる実行 this.__eval(src, name); } } if(!bSet) { delete this.__src[name]; delete this.__param[name]; if(stack > 0) this.__step(funcName, no, stack - 1); } } this.__getPair = function(org, pre, suf, start) { var preIdx = org.indexOf(pre, start); var sufIdx = org.indexOf(suf, start); while(sufIdx > preIdx && preIdx > 0) { preIdx = org.indexOf(pre, preIdx + 1); sufIdx = org.indexOf(suf, sufIdx + 1); } return sufIdx; } this.__reCommentLine = /\/\/[^\r\n]*[\r\n]/gm; this.__reCommentBlock = /\/\*((?!\*\/)(.|\s))*\*\//gm; this.__run = function(funcName, param, stackName, no, stack) { var ss = document.getElementsByTagName('script'); for(var i = 0; i < ss.length; i++) { if(ss[i].type == "text/x-junejun-sleep-support-javascript" && ss[i].id == funcName) { // 行コメントとブロックコメントを削除する var src = ss[i].text.replace(this.__reCommentLine, ""); src = src.replace(this.__reCommentBlock, ""); // 実行する if(no == -1) no = this.__getName(stackName, no, stack); var name = this.__getName(stackName, no, stack); this.__src[name] = src; this.__param[name] = param; this.__step(stackName, no, stack); break; } } } this.run = function(funcName, param) { this.__run(funcName, param, funcName, -1, 0); } }; //--> </script> <script type="text/javascript"> <!-- /* この版の注意 ・sleepはブロック内では使用できません(if文中やループ中は無理) ・callはブロック内では使用できません ・eval内のvar宣言変数は、sleep等をまたぐことができません */ var __$_ = new JunejunSleepSupportJavascript("__$_"); function main() { alert('start main'); __$_.run('sampleFunc', {}); alert('end main'); } //--> </script> <script id="sampleFunc" type="text/x-junejun-sleep-support-javascript"> __param.wsh = new ActiveXObject("WScript.Shell"); __param.wsh.Run("calc"); sleep(1000); __param.wsh.AppActivate("電卓"); sleep(1000); __param.wsh.SendKeys ("1{+}"); sleep(500); __param.wsh.SendKeys("2"); sleep(500); __param.wsh.SendKeys("~"); sleep(500); __param.wsh.SendKeys("*3"); sleep(500); __param.wsh.SendKeys("~"); sleep(100); alert("result:9?"); </script> </head> <body onload="main();"> </body> </html>
解説
使い方
JunejunSleepSupportJavascriptクラスに全てを詰め込んでます。
このクラスをグローバル変数にnew(引数はグローバル変数名)して使用する想定です。
このクラスの定義部分を外部スクリプトにすれば、結構すっきりするのではないでしょうか。
そして、idが関数名相当で、type="text/x-junejun-sleep-support-javascript"のscript(オレオレ関数)を書きます。
基本、普通のjavascriptとして記述してもらえれば、OKです。
最後に、グローバルにしたオブジェクトのrunメソッドを、第一引数:関数名相当、第二引数:渡すパラメーターで呼び出せば、勝手にsetTimeoutしながらeval実行します。
なお、第二引数は__paramとして参照・更新できます。
オブジェクトの__で始まるものは変更しないでくださいね。
もちろん、runメソッドも。
sleep以外に、callも使えます。
callの引数はrunメソッドと同じで、別のオレオレ関数を呼び出します。
基本思想
なるべくjavascript。
処理速度のために、まとめてevalする。
sleep/call前後で分割して、前をeval、後ろをsetTimeout。
これを、__stepメソッドで実装しています。
this以下が見えてしまうのはやむなしとして、それ以外をなるべく見えないようにするために、__evalメソッドを用意しています。
ローカル変数は、__paramを使う想定。
(通常、runメソッドの第二引数をオブジェクトにして、そこに詰め込むのがお勧め)
制約は、main関数のあるscript内にコメントとして書きましたが、
- sleep/callはブロック内では使用できません(if文中やループ中は無理)
- var宣言変数は、sleep等をまたぐことができません(__paramがオブジェクトならそこに追加するとか)
ブロック内で使えないのがかなり痛いです。
少なくともwhileはサポートしたいところ。