Using eval and global JavaScript variables via InternetExplorer.Application

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Fade
Posts: 20
Joined: 03 Dec 2015, 16:49

Using eval and global JavaScript variables via InternetExplorer.Application

21 Apr 2018, 11:49

This is all using AHK v1.1.28.02 and IE11.

I'm trying to use InternetExplorer.Application to execute JavaScript and read the value of a global variable (property of window), like so:

Code: Select all

ie := ComObjCreate("InternetExplorer.Application")
ie.Navigate("about:blank")
while (ie.ReadyState != 4)
	Sleep 10

ie.document.parentWindow.eval("var foo = 'testing';")
MsgBox % ie.document.parentWindow.foo
This results in 0x80020006 (DISP_E_UNKNOWNNAME) for eval. I tried using execScript instead, which seems to still work despite being "no longer supported", but then foo itself gives DISP_E_UNKNOWNNAME. Additionally, I want the return value from the evaluated expression, which execScript does not provide.

I did some searching and eventually wound up on this SO question, in which the edit to the accepted answer mentions that this should work, but ultimately fails for out-of-process calls. The solution given is to use IDispatchEx::GetDispID to resolve the member names. This question also comes to the same conclusion. These made me curious as to what AHK does internally, so I did a quick debugging of the source and found that IDispatchEx::GetDispID is actually used but only when setting a new property (the corresponding code was even introduced specifically to facilitate this for JavaScript/IE).

So, what is the best way to approach this in AHK? As far as I can tell, the only thing I can do is use ComObjQuery for window's IDispatchEx, allocate a BSTR for the name, call GetDispID, and then manually invoke it. This kind of defeats the idea of using the simplest possible solution above and it's instead easier to just use execScript or inject a script tag and communicate with AHK using a hidden div and ie.document.getElementById.

If manually invoking is the only way to go, what would be the ramifications of changing AHK to try IDispatchEx after getting DISP_E_UNKNOWNNAME for not just IT_SET, but IT_GET and IT_CALL as well? (Sorry if this is a dumb question; I've just started learning about COM.)
Fade
Posts: 20
Joined: 03 Dec 2015, 16:49

Re: Using eval and global JavaScript variables via InternetExplorer.Application

24 Apr 2018, 23:05

I've managed to come up with a "manual invoke" strategy that I'm fairly satisfied with. My main goal was to create something that could be used exactly like a typical AHK COM object, so the simplicity of working with ie.document.parentWindow as in the first post would be preserved. I figured this could be achieved using an AHK class with metafunctions that use IDispatchEx, but was worried that it would require reimplementing COM functions baked in to AHK itself. Fortunately, this wasn't the case and I was able to get AHK's COM functionality to do a lot of work and just focus on using IDispatchEx. Here's the class:

Code: Select all

class JSWrapper {
	static IID_IDispatchEx := "{A6EF9860-C720-11d0-9337-00A0C90DCAA9}"
	_self := {}

	__New(obj) {
		this._self.obj := obj
		if !(inf := ComObjQuery(obj, JSWrapper.IID_IDispatchEx)) {
			throw Exception("JSWrapper.__New: Failed to get IDispatchEx interface")
		}
		this._self.inf := inf
		this._self.getDispIDAddr := NumGet(NumGet(inf+0), 7 * A_PtrSize)
		this._self.invokeExAddr  := NumGet(NumGet(inf+0), 8 * A_PtrSize)
	}

	__Delete() {
		ObjRelease(this._self.inf)
	}

	__Set(key, value) {
		if (key != "_self")
			return this._self.obj[key] := value
	}

	__Get(key) {
		if (key != "_self") {
			ceEnabled := ComObjError(false)
			value := this._self.obj[key]
			if (A_LastError & 0xffffffff = 0x80020006) { ; DISP_E_UNKNOWNNAME
				dispId := 0
				bname := DllCall("OleAut32\SysAllocString", "Str", key, "Ptr")
				if !bname
					throw Exception("JSWrapper.__Get(" key "): SysAllocString failed")
				hr := DllCall(this._self.getDispIDAddr, "Ptr", this._self.inf, "Ptr", bname, "UInt", 0x8, "Int*", dispId, "UInt")
				DllCall("OleAut32\SysFreeString", "Ptr", bname)
				if hr
					throw Exception("JSWrapper.__Get(" key "): GetDispID HRESULT " Format("{:#x}", hr & 0xffffffff))

				VarSetCapacity(noparams, 24, 0)
				VarSetCapacity(result, 24, 0)
				hr := DllCall(this._self.invokeExAddr, "Ptr", this._self.inf, "Int", dispId, "UInt", 0x400, "UShort", 0x2, "Ptr", &noparams, "Ptr", &result, "Ptr", 0, "Ptr", 0)
				if hr
					throw Exception("JSWrapper.__Get(" key "): InvokeEx HRESULT " Format("{:#x}", hr & 0xffffffff))

				; "Casting" to VT_BYREF and dereferencing with [] makes a copy of the result variant
				; using VariantCopyInd and then converts that to a corresponding AHK COM wrapper
				; (see ComObject in script_com.h). This does the same thing that a typical
				; ComObject::Invoke + IT_GET does with the result variant of IDispatch::Invoke,
				; except that, unlike Invoke, we give a copy of result to VariantToToken so we have
				; to clear result.
				value := ComObject(0x4000 | NumGet(result, 0, "Short"), &result+8, 0)[]
				hr := DllCall("OleAut32\VariantClear", "Ptr", &result)
				if hr
					throw Exception("JSWrapper.__Get(" key "): VariantClear HRESULT " Format("{:#x}", hr & 0xffffffff))
			}
			else if A_LastError {
				throw Exception("JSWrapper.__Get(" key "): built-in-get HRESULT " Format("{:#x}", A_LastError & 0xffffffff))
			}
			ComObjError(ceEnabled)
			return value
		}
	}

	__Call(name, params*) {
		f := this[name]
		return %f%(params*)
	}
}
And here's an example using it:

Code: Select all

hmOleAut32 := DllCall("LoadLibrary", "Str", "OleAut32.dll", "Ptr")
ie := ComObjCreate("InternetExplorer.Application")
OnExit("Cleanup")

ie.Navigate("about:blank")
;ie.Visible := true
while (ie.ReadyState != 4)
	Sleep 10

window := new JSWrapper(ie.document.parentWindow)
window["fromAhk"] := "this string came from AHK"
js =
(
	alert("In JS eval: fromAhk = " + fromAhk);
	var fromJs = "this string came from JS";
	function test() {
		alert("This is JS function test()");
	}
)
window.eval(js)
MsgBox % "In AHK: window.fromJs = """ window.fromJs """"
window.test()

Cleanup() {
	global
	ie.Quit()
	DllCall("FreeLibrary", "Ptr", hmOleAut32)
}
I'm still curious whether AHK could be changed to support this by trying IDispatchEx for more than just setting properties in ComObject::Invoke whenever GetIDsOfNames returns DISP_E_UNKNOWNNAME. Reversing the logic to check for IS_INVOKE_CALL && TokenIsEmptyString(*aParam[0]) first and having the else block unconditionally query for IDispatchEx worked for my simple example, so I'm hoping someone can shed some light on the possible problems with this approach.
User avatar
Xeo786
Posts: 759
Joined: 09 Nov 2015, 02:43
Location: Karachi, Pakistan

Re: Using eval and global JavaScript variables via InternetExplorer.Application

26 Apr 2018, 03:27

I do not know JAVA that much but I was able to run your JS through ameyrick's ieCOM https://autohotkey.com/boards/viewtopic.php?f=6&t=19300
it worked for me.. and window.fromJs thing is beyond me :HeHe:

Code: Select all

ie := ComObjCreate("InternetExplorer.Application")
ie.Navigate("about:blank")
ie.Visible := true
wait(ie)

js =
(
	var fromJs = "this string came from JS";	
	function test() {
		alert("This is JS function test()");
	}
)

jsappend(js)
js("test();")
js("alert( fromJs );")
return

wait(x=""){
	global
	if x
		wb := x
	DocWait(wb)
	return
}
 
DocWait(x:=""){
	global
	if x 
		wb := x
	try while wb.readyState != 4 or wb.busy
           Sleep, 10
	Sleep, 100
	setvars(wb)
	return
}
 
setvars(x){
	global
	if x
		wb := x
	document := wb.document
	d:= wb.document
	w:= wb.document.parentwindow
	return
}

jsapp(code){
	jsappend(code)
}
 
jsappend(code){
	global
	if !code
		return
	s := d.createElement("script")
	s.type := "text/javascript"
	try {
	  s.appendChild(d.createTextNode(code))
	  d.body.appendChild(s)
	} catch e {
	  s.text := code
	  d.body.appendChild(s)
	}
}
 
js(js:=""){
	global
	if !js
		return
	try w.execScript(js)
	catch {
		DocWait()
		try w.execScript(js)
	}
	return
}
"When there is no gravity, there is absolute vacuum and light travel with no time" -Game changer theory

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot], Spawnova, Thorlian and 262 guests