IDispatch, Access .Document of Shell.Explorer (ActiveX)

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Bruttosozialprodukt
Posts: 463
Joined: 24 Jan 2014, 22:28

IDispatch, Access .Document of Shell.Explorer (ActiveX)

30 May 2015, 18:00

I'd like to create a Shell.Explorer ActiveX GUI element, display a website in it and add a javascript to it. This javascript should then tell my AHK script that something happened on certain events.
But I don't understand how the .Document property can be accessed in a useful way. And I also don't know how I can tell the javascript to call a callback function of the AHK script.

This is basically how I'd like it to work:

Code: Select all

Gui, 1: Add, ActiveX, x0 y0 w800 h500 vWB -HScroll, Shell.Explorer
Gui, Show, w800 h500, test

scriptToInject = 
(
    function Notification() {
        this.Notification = function(title, options) {
            WB_NotificationCallback(title,options);
        }
    }
    Notification.Notification("Test notification.")
)

WB.silent := True
WB.Navigate("example.com")
While (WB.busy or WB.readystate != 4)
    Sleep 100
WB.Navigate("javascript:" scriptToInject)


WB_NotificationCallback(text) {
    TrayTip, New Notification, % text
}

GuiClose() {
    ExitApp
}
( Notification.Notification() will actually be called by my website itself. )

I really hope someone can help me out with this.
User avatar
jethrow
Posts: 188
Joined: 30 Sep 2013, 19:52
Location: Iowa

Re: IDispatch, Access .Document of Shell.Explorer (ActiveX)

30 May 2015, 20:02

Here's an extended example using HTMLFile - thought this focuses more on having your AHK script respond to webpage events, rather than javascript responding to events & communicating with AHK:

Code: Select all

WM_KEYDOWN = 0x100
GoSub, GetHTML
 
OnMessage(WM_KEYDOWN, "WM_KeyDown")
Gui Add, ActiveX, w350 h300 x0 y0 vdoc, HTMLFile
doc.write(html)
Gui, Show, w310 h265 Center, HTML Based GUI
doc.all.type.focus
ComObjConnect(doc, "Doc_")
return
 
Doc_OnClick(doc) {
	id := doc.parentWindow.event.srcElement.id
	if (id = "myDocuments")
		Run %A_MyDocuments%
	else if (id = "AHKLogo") {
		doc.body.innerHTML := "<img src='http://ahkscript.org/boards/styles/prosilver/theme/images/"
			.   "site_logo.png' alt=""Don't worry - It'll change back!"" width='90`%' height='20`%'/>"
		SetTimer, ResetHTML, -3000
	}
	else if (id = "bgcolor")
		doc.body.style.background := (doc.all.bgcolor.value)
	else if (id="show" || id="hide") {
		Gui % (id="show"? "+":"-") "Caption"
		Gui Show
	}
}
WM_KEYDOWN(wParam, lParam, nMsg, hWnd) {
	global doc
	static fields := "hWnd,nMsg,wParam,lParam,A_EventInfo,A_GuiX,A_GuiY"
	WinGetClass, ClassName, ahk_id %hWnd%
	if (ClassName = "Internet Explorer_Server") {	
		; http://www.autohotkey.com/community/viewtopic.php?p=562260#p562260
		pipa := ComObjQuery(doc, "{00000117-0000-0000-C000-000000000046}")
		VarSetCapacity(kMsg, 48)
		Loop Parse, fields, `,
			NumPut(%A_LoopField%, kMsg, (A_Index-1)*A_PtrSize)
		; Loop 2 ; only necessary for Shell.Explorer Object
			r := DllCall(NumGet(NumGet(1*pipa)+5*A_PtrSize), "ptr",pipa, "ptr",&kMsg)
		; until wParam != 9 || doc.activeElement != ""
		ObjRelease(pipa)
		SetTimer, KeyPress, -10
		if r = 0 ; S_OK: the message was translated to an accelerator.
			return 0
	}
}
GuiClose:
{
   doc := ""
   Gui, Destroy
   ObjRelease(pipa)
   ExitApp
}
KeyPress: ; allows keypress to update value first
{
   if (doc.ActiveElement.id ~= "type|mirror")
      doc.all.mirror.value := (doc.all.type.value "")
   return
}
ResetHTML:
{
   doc.body.innerHTML := html
   return
}
GetHTML:
{
   html =
   (
      <html>
         <body style='background-color:White;overflow:auto'>
            <center><H3><b>HTML GUI Test</b></H3></center>
            <form>
               Type : <input type='text' name='type' id='type'/></br>
               Mirror : <input onclick="javascript:alert('This is a Mirror!');document.all.type.focus()" type='text' name='mirror' id='mirror'/>
            </form>
            <button type='button' id='myDocuments'>My Documents</button>
            <button type='button' id='AHKLogo'>Show AHK Logo</button>
            </br></br>
            Background Color: <select id='bgcolor'>
               <option value='White'>White</option>
               <option value='PowderBlue'>PowderBlue</option>
               <option value='Red'>Red</option>
               <option value='Yellow'>Yellow</option>
            </select>
            <form>
               <b>Border: </b>
               <input type='radio' name='show/hide' id='show' /><label>Show</label>
               <input type='radio' name='show/hide' id='hide' /><label>Hide</label>
            </form>
         </body>
      </html>
   )
   return
}
http://www.autohotkey.com/board/topic/5 ... ntry346213
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: IDispatch, Access .Document of Shell.Explorer (ActiveX)

30 May 2015, 22:01

WB.Document.parentWindow (and WB.Document.Script, actually) refer to the window object, which is where all global variables live. You can store a function reference (Func("FuncName")) or any other object in the window object and it will be accessible to the page's scripts. You can also call functions defined in the script via the window object, and pass function references or other objects.

If the Notification "class" already exists in the page you're loading, you probably don't need to add any JavaScript; you can just pass it a reference to your function. Otherwise, there are a few methods to add/execute script:
  • window.eval(js) will evaluate js and give you a result, but it seems to fail fairly often if the page is in an older document mode (WB.Document.documentMode). The WebBrowser control operates in IE7 or IE5 mode by default, depending on the !DOCTYPE. You can override it with the FEATURE_BROWSER_EMULATION registry key or putting <meta http-equiv="X-UA-Compatible" content="IE=edge"> in the document.
  • window.execScript(js) generally works but has no return value.
  • Creating a script element dynamically, as you might in JavaScript.
The way that you define Notification and the way that you use it do not match up (the JavaScript you've posted will never work). I suppose that either it would have to be defined like Notification.Notification = function(...) { ... } or called like (new Notification).Notification(...).

Code: Select all

Gui, 1: Add, ActiveX, x0 y0 w300 h500 vWB, Shell.Explorer
Gui, Show, w800 h500, test
 
scriptToInject = 
(
    function Notification() {
        this.Notification = function(title, options) {
            WB_NotificationCallback(title,options);
        }
    }
    (new Notification).Notification("Test notification.")
)
 
WB.Silent := True
WB.Navigate("http://example.com")
while WB.ReadyState != 4
    Sleep 100
WB.Document.parentWindow.WB_NotificationCallback := Func("WB_NotificationCallback")
WB.Document.parentWindow.execScript(scriptToInject)

WB_NotificationCallback(text) {
    TrayTip, New Notification, % text
}
 
GuiClose() {
    ExitApp
}
If execScript and eval don't work, an alternative is LoadJS(WB.Document, scriptToInject):

Code: Select all

LoadJS(document, js) {
    script := document.createElement("script")
    script.type := "text/javascript"
    script.text := js
    document.getElementsByTagName("head").item(0).appendChild(script)
}
The script might be executed asynchronously, so if you need to know when it's complete, you can either have js call a global function which you pre-define, or store a function reference in script.onload.

You can load external JS files by using src instead of text.
jethrow wrote:this focuses more on having your AHK script respond to webpage events
If the idea is for the AutoHotkey script to hook into a custom JavaScript-based API (Notification.Notification), then the ComObjConnect() method you've demonstrated is very unlikely to work.
Bruttosozialprodukt
Posts: 463
Joined: 24 Jan 2014, 22:28

Re: IDispatch, Access .Document of Shell.Explorer (ActiveX)

31 May 2015, 07:12

Thank you so much!
Lexikos solution basically works so far, but I need a way to inject the JavaScript before any of the JavaScript form the actual website is executed. The problem is that the website checks once in the beginning if (window.Notification) and if this fails, it won't try to use my Notification "class".

I already tried to download the pages code, add my JavaScript to it and load it form a local file, but this breaks the website because it heavily relies on its original URL.
Is there maybe a way to change the URL that the website sees without actually navigating to it?
Or maybe I could navigate to the website, change the whole HTML and then "reload" the JavaScript things?

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: filipemb, gsxr1300, mikeyww and 289 guests