In AHK, the following code can be used to inject a AHK function into Internet Explorer's JavaScript engine:
#Persistent
html =
(
<html>
<head>
<script>
document.setVar = function`(name,val`){
document[name]=val;
}
</script>
</head>
<body>
<h1>Hello!</h1>
</body>
</html>
)
ie := ComObjCreate("InternetExplorer.Application")
ie.navigate("about:blank")
sleep, 2000
msgbox, %html%
ie.document.writeln(html)
ie.visible := true
ie.document.setVar("someFunc",Func("someFunc"))
someFunc(){
msgbox, "hello"
}
After injecting the function, JavaScript can call document.someFunc()
, which will result in JavaScript calling the AHK function, and ultimately running a message box.
I want to port this code to Ruby. So far I have this:
require 'win32ole'
ie = WIN32OLE.new('InternetExplorer.Application')
ie.navigate("about:blank")
sleep(0.1) until !ie.busy
html = <<Heredoc
<html>
<head>
<script>
document.setVar = function(name,val){
document[name]=val;
}
</script>
</head>
<body>
<h1>Hello!</h1>
</body>
</html>
Heredoc
ie.document.writeln(html)
ie.visible = true
Now that we are here we should be able to inject the Ruby method, however currently I have no idea how to implement this. Every time I try something, the JavaScript engine freezes. Some of the things I have tried:
ie.document.setVar("someFunc",method(:someFunc))
#----------------------------------
ie.document.setVar("someFunc",->{puts "hello"})
#----------------------------------
class someClass
def someFunc
puts "hello"
end
end
ie.document.setVar("someClass",someClass})
#----------------------------------
closure = Class.new(Fiddle::Closure) {
def call
puts "hello world"
end
}.new(Fiddle::TYPE_INT,[])
someFunc = Fiddle::Function.new(closure, [], Fiddle::TYPE_INT)
#Both:
doc.setVar("someFunc",closure)
#and
doc.setVar("someFunc",someFunc)
#----------------------------------
None of the above methods work. In the end they all end up freezing the JavaScript engine... Does anyone have any idea how I can pass an actual reference to the Ruby function to JavaScript?
After a week of scouring the internet and finding nothing I thought "What if we are naive and pass an object with method_missing
defined?"
30 minutes later, and I can call Ruby from JavaScript.
class MyFunc
# Called when no arguments are passed to JavaScript function
def call
#Execute any ruby code here!!
#You can also return values back to JavaScript!!
return 1
end
# Called when arguments are passed to JavaScript function
def value(*args)
if args.length == 0
# This will be called if the function is called without parenthesis in JS
# e.g. console.log(document.someFunc)
return nil
else
#This is called with the parsed arguments. Note: Functions passed in from JS are of type WIN32OLE. Theoretically this should be callable, but better would be to make a JS function which can call other JS functions
#Execute any ruby code here!!
puts "#{args.inspect}"
#Can also return values here as well
return 1
end
end
end
ie.document.setVar("myFunc",MyFunc.new})
You can also initialise and access instance variables:
class MyClass
def initialize
@hello = "world"
end
end
ie.document.setVar("myClass",MyClass.new})
#IN IE: document.myClass["hello"] //=> "world"
Note:
Some things can go drastically wrong and even cause ruby crashes. Some examples of things that don't work:
document.myObj
. JavaScript will interpret the function as an Object , perhaps as one would expect.document.myObj["hello"]
.document.myObj["hello"]=1
.Some things also make little sense at all, for example I made the following loop:
Given a class:
class MyClass
def call
puts "Call"
end
def method_missing(m,*args,&block)
puts "#{m}(#{args.inspect})"
end
end
ie.document.setVar("obj",MyClass.new)
and JavaScript:
for(var i=0;i<24;i++){
document.obj[chars[i]]()
}
This will execute every function of obj named with an alphabetical character. And indeed, it does do this for the most part, however sometimes it does not. Sometimes it will call the main call
method, and in the case of document.obj.j()
it will do nothing at all...
Full log:
a([])
b([])
c([])
d([])
e([])
f([])
Hello world
h([])
i([])
k([])
Hello world
m([])
n([])
o([])
q([])
s([])
t([])
Hello world
v([])
w([])
x([])
y([])
Hello world
EDIT
I've written a GIST which makes this generally much easier to achieve. E.G. To pass the File
object to IE you can do the following:
ie.document.setVar("RubyFile",WIN32OLE::getDispatch(File))