MozReplをRubyからシームレスに使う
MozReplでいろいろ遊んでいます。なかなか難しいですが、だんだん面白くなっています。MozReplを経由してちょうどWin32OLEみたいにRubyのオブジェクトとして、Javascriptのオブジェクトを扱えるようなクラスJSobjectを作ってみました。
こんな感じでまるでRubyを使っているかのようにJavascriptのオブジェクトが扱えます。
# サンプル # Helloのダイアログを出す $window.alert("Hello") # DOMを操作してみる puts $document.childNodes.item(0) # 自分の見ているURLの表示(高速インタフェース研究会より) puts $content.location.href
ソースコードを下に晒しますが、とても汚いつくりです。特に、Rubyで参照したオブジェクトはGCされずにどんどんJavascriptの配列に溜まっていきます。なんとか、GCしたいのですが、結構面倒な話です。ちょうど分散GCと同じ問題が発生します。
他にも、色々問題があると思います。でも、RubyでMozillaをいじって*遊ぶには*には便利じゃないかなと思います。
変更履歴 2008/1/20 いくつかのバグの修正(FireFoxに渡すJavascriptの括弧が閉じていないとか、セミコロンがないとか) inspectメソッドを追加、これでpが使えるようになった
require 'net/telnet' # Javascriptの表現形式への変換 class Object def to_js self end end class String def to_js '"' + self + '"' end end # Mozillaとの通信等の縁の下 module JSCommon PROMPT = /(^repl> )|(^\.\.\.\.> )/ # @@prompt = /^repl>/ @@telnet = Net::Telnet.new({ "Host" => "localhost", "Port" => 4242, "Prompt" => PROMPT, "Telnetmode" => false}) @@telnet.waitfor(PROMPT) private def js_exec(com) res = nil @@telnet.cmd(com) {|c| res = c.gsub(PROMPT, "") if $DEBUG p com p res end if /^\s*[0-9]+\s*$/ =~ res then res = res.to_i end } res end def make_js_args(args) args.inject([]) {|res, arg| res.push arg.to_js }.join(',') end end # Javascriptのオブジェクトのwapper。実体は、__objtという配列のインデックス class JSObject include JSCommon # この部分はdelgate.rbから引用しています。 preserved = [:__id__, :object_id, :__send__, :invoke_method, :respond_to?, :send] instance_methods.each do |m| next if preserved.include?(m) undef_method m end def initialize(id) @id = id end def method_missing(name, *args) if /([^=]*)=/ =~ name.to_s then js_exec("__objt[#{@id}].#{$1} = #{args[0].to_js};") args[0] else rs = js_exec("__objt.push(__objt[#{@id}].#{name}(#{make_js_args(args)}));") if !rs.is_a?(Integer) then rs = js_exec("__objt.push(__objt[#{@id}].#{name});") end if rs.is_a?(Integer) then JSObject.new(rs - 1) else rs end end end def to_s js_exec("__objt[#{@id}]") end def inspect js_exec("repl.inspect(__objt[#{@id}]);") end def to_js "__objt[#{@id}]" end end # 特に定義していなくても使えるJavascriptの変数等の宣言 class JSTopLevel extend JSCommon @@jstoplevel = nil def self.refvar(name) if @@jstoplevel == nil then js_exec("var __objt = new Array();") js_exec("__objt.push(window);") js_exec("__objt.push(document);") js_exec("__objt.push(content);") js_exec("__objt.push(repl);") @@jstoplevel = Hash.new(nil) @@jstoplevel['window'] = JSObject.new(0) @@jstoplevel['document'] = JSObject.new(1) @@jstoplevel['content'] = JSObject.new(2) repl = JSObject.new(3) @@jstoplevel['repl'] = repl end @@jstoplevel[name] end end # お好みに応じて定義してください。 $window = JSTopLevel.refvar('window') $document = JSTopLevel.refvar('document') $content = JSTopLevel.refvar('content') $repl = JSTopLevel.refvar('repl') if __FILE__ == $0 then # サンプル # Helloのダイアログを出す #$window.alert("Hello") # DOMを操作してみる #puts $document.childNodes.item(0) # 自分の見ているURLの表示(高速インタフェース研究会より) #puts $content.location.href #p $content.document end