Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Dynamic event binding to IE (not the DOM!)

161 views
Skip to first unread message

Csaba Gabor

unread,
Dec 18, 2005, 3:51:27 PM12/18/05
to
It is possible to write a .vbs file wherein, staying strictly in
VBScript, InternetExplorer.Application is brought up and VBScript
procedures within the .vbs are dynamically bound to events in its DOM.
I provide an example of such a keystroke listener below.

What I would like to do is to accomplish the same type of thing with
the InternetExplorer.Application object itself (specifically, I would
like to flag for the DocumentComplete event) WITHIN the
MSScriptControl.ScriptControl object. This means that Set ie =
WScript.CreateObject("InternetExplorer.Application", "ie") and
WScript.ConnectObject, described at
http://msdn.microsoft.com/library/default.asp?URL=/library/welcome/dsmsdn/scripting04092001.htm,
are out.

Motivation. I am writing a PHP server side app. I can create an
instance of IE with PHP script, and in fact I can attach events
listeners to it, too. Very nifty. As long as I run it from the
desktop. However, if the PHP script is invoked as a web server script,
then IE is still created, but PHP has a bug which doesn't allow me to
hook up events. I've reported this (
http://bugs.php.net/bug.php?id=35719 ) and a fix has been denied (Well,
the exact message was: com_event_sink() and com_message_pump() are
intended to be used in
standalone, single-threaded processes.)

I say it's a PHP bug because if this same PHP script invokes a .vbs
script, the VBScript will run just fine, events and all (still within
the server's security constraints, which is probably where the PHP bug
stems from). The problem with this solution is that whereas the
VBScript responds to IE events, there is no event driven mechanism to
communicate this back to the calling PHP program. I could always have
the traditional loop with sleep, but in that case there is no reason to
go out to the .vbs program. The other possibility along these lines is
to have PHP just be a conduit for invoking the .vbs and have the entire
processing take place there. This would accomplish my goal of being
event driven, but it does up the maintainance on the project. PHP is
supposed to be the server, after all, not a front for someone else.
But it is a fallback solution.

Another passing thought was to have PHP capture the event bound
instance of IE that VBScript creates. But this is dead at the outset
because with this being a server side script,
CreateObject("Shell.Application").Windows usage is not permitted. In
short, the new instance of IE is not visible to the rest of the world.

Thus, in an attemt to stay event driven, I thought I'd ask if someone
hasn't been enterprising enough to figure out how to dynamically attach
an event listener to IE without needing to use WScript. PHP is
perfectly happy to stuff VBScript code into the
MSScriptControl.ScriptControl. If it could listen to events there,
then the VBScript code in the ScriptControl can reach out and kick off
PHP code directly (by means of adding PHP class instances to the
ScriptControl). Again, doing this with the DOM is not the problem -
it's doing it with the IE object that's the trick.

Thanks for any ideas,
Csaba Gabor from Vienna


Oh, here's the Dynamic atachment of VBScript event listeners to IE's
DOM (a keystroke listener):

Set ie = createObject("InternetExplorer.Application")
ie.Navigate2("google.com")
ie.Visible = true
Do While (ie.readyState<>4): WScript.Sleep 10: Loop

'Set a event listener dynamically
ie.document.onkeydown = ShowPopupLater("key pressed: ")

On Error Resume Next
Do while ie.visible 'When we force a quit, this errors
if Err Then Exit Do 'And is caught by this line
WScript.Sleep 10
Loop
MsgBox "Finished"

Function ShowPopupLater(theText)
Set oWSh = CreateObject("WScript.Shell")
oWSh.Environment("volatile")("txtOut") = theText
Set ShowPopupLater = GetRef("EvtAlert")
End Function

Sub mypopup(txt)
CreateObject("WScript.Shell").popup txt, 4, "popup", 131120
End Sub

Sub EvtAlert()
'Variables can be passed through the environment or globally
Set oWSh = CreateObject("WScript.Shell")
mypopup oWSh.Environment("volatile")("txtOut") & _
ie.document.parentWindow.event.keyCode
End Sub


Aside: the passing of info via volatiles is pretty poor in this example
and was done for illustrative purposes. In particular, these values
are very exposed and subject to getting overwritten.

Michael Harris (MVP)

unread,
Dec 18, 2005, 9:23:22 PM12/18/05
to
---snip---

> Thus, in an attemt to stay event driven, I thought I'd ask if someone
> hasn't been enterprising enough to figure out how to dynamically
> attach an event listener to IE without needing to use WScript. PHP is
> perfectly happy to stuff VBScript code into the
> MSScriptControl.ScriptControl. If it could listen to events there,
> then the VBScript code in the ScriptControl can reach out and kick off
> PHP code directly (by means of adding PHP class instances to the
> ScriptControl). Again, doing this with the DOM is not the problem -
> it's doing it with the IE object that's the trick.
>
> Thanks for any ideas,
> Csaba Gabor from Vienna

The ScriptX object from www.meadroid.com has methods to connect COM object
event sources to client sinks. It's in the free (no license required)
feature subset. Whether or not it works in your PHP context is up to you to
find out via experimentation ;-)...

--
Michael Harris
Microsoft MVP Scripting


Csaba Gabor

unread,
Dec 20, 2005, 11:00:44 AM12/20/05
to
Michael Harris (MVP) wrote:
> The ScriptX object from www.meadroid.com has methods to connect COM object
> event sources to client sinks. It's in the free (no license required)
> feature subset. Whether or not it works in your PHP context is up to you to
> find out via experimentation ;-)...

thank you, Thank You, THANK YOU. This has led to a whole raft of ideas
which had been bottled up inside me, so I'm going to jot a few down.
First of all, I took a look at the website and downloaded ScriptX v.
6.2, but I have not yet installed it with all the ideas this has
unleashed. I skimmed the docs (they have some very nice coding
examples), and this is exactly the kind of thing I was looking for.

However, there might (in the absence of testing) be a problem. The
idea is that I'm supposed to plunk a specific object of theirs onto the
web page, and that object gets me access to the events pertaining to
the object (described at meadroid.com/scriptx/docs/gendocs.htm - click
Dynamic Event Binding, then NewEventSink). Very nice - just what I
ordered. Only, I want to access a remote website, so of course that
object isn't in the web page. Three ideas: 1. Load my own page with
said object, and then navigate to the other site. The problem is that
I would expect the old object carrying out the linking to be
obliterated. Still, if it wasn't, this would be the best thing. 2.
Navigate to the other site and then inject the object. Problem is that
the DocumentComplete event will be long gone by then. 3. Put the page
in a frame. The problem with this is that they could theoretically do
top.src=... and blow this scheme away. There are some other issues
with this, discussed in the next paragraph.

So, let's assume I can't use the ScriptX object for my purposes.
Still, it turns out that the website in question doesn't muck with top.
Therefore, couldn't I do <iframe onload="myEventCode()"
src="http://foreignDomain.com"></iframe>. Well, there are two issues
with this. 1. Even though http://foreignDomain.com is allowed to have
cookies when it is what is in the address bar, IE is asking me all over
the place if I will allow cookies from this same site when it is framed
(the site loads fine if I say no). Whereas I could say yes or no to
the immediate cookies, not addressing this problem in general will lead
to my program hanging in the future without explanation. Is there some
way to (programatically?) address this cookie business within the
frames? 2. Whereas that onload will fire after the iframe has loaded,
cross domain issues prevent me from getting ahold of the page. Rats.

However, the scriptx approach (IE having a non text, external
component) is still viable. All I have to do is create a small little
OCX and all the OCX will do is create and return a new IE instance, and
hook it up to event handlers (within the OCX). The only point of the
OCX based event handlers is to reach back out to the .vbs file and hook
themselves up to the appropriate event handlers in the .vbs file. Not
sure if I'll be able to programatically detect all the event handlers
in the .vbs file and make it all automated, but prospects seem good
(slurp in the running script file and loop through the .vbs looking for
/^\s*(Sub|Function)\s+(ie[^\s\(]*)(\(|\s).*/ and then trying to do a
GetRef without error on the captured procedure name). Since I don't
see any WScript usage in this approach, I expect this should work just
as well when wrapped into a ScriptComponent (except that I'll have to
adjust the code self induction since there will no longer be a .vbs
file to parse) called from PHP. I'm very happy with this approach -
maybe it will work, too.

This idea flotilla also reminded me of something I'd figured out two
years back and subsequently misplaced. OK, it's not perfect, but it
allows me to bypass a large class of cross domain security bugs in
vbscript. If I'm using a VBScript application (.vbs file) on my
machine to access the DOM of a web page, I expect to get complete
access and not just top level bits and pieces. Specifically, if a web
page has an I/Frame, I want access to that frame. It's my machine -
gimme. Of course if someone could give me a coherent explanation for
why this would be a security issue, I would change my tune, but till
then - I view it as a bug. IE (on my Win 2K Pro) used to have a bug
whereby you could get by this restriction using .Document (with capital
D), but, sadly, that truly was a security problem since any old web
page could do it. Long since patched.

(And in case anyone has made it to here, but doesn't understand the
distinction I'm making, consider: If a domain A could get ahold of the
DOM of domain B, then it (domain A) could simulate the user taking
actions with regards to domain B. All logs/records would show the
action originating from the user's browser/computer. However, when we
use a .vbs Script to gerrymand IE, IE has no particular objection to
our (VBScript) mucking with its DOM at all, and the simulation of
actions reflects the reality that they come from the user's computer.
Why should access to an I/Frame's DOM through the .vbs not be allowed?)

So, we use the DocumentComplete event (or something like it). The
point is that it gives you a pointer to the (frame's) window object.
Now you better save this object away as soon as you get it in the event
handler, but there you go, that's the basis. Actually, you have to be
very careful because there is precious little hard connection between
the frame object and what is inside. In other words, once you've got
the frame's window, how do you establish the linkage to the i/frame
object that contains it? Check the urls? Nope, because you could have
more than 1 of the same, and more importantly, the containing object
will have no clues about any url redirection that may have happened.
If I recollect, the best I found was to do window (or screen?) based
position matching. This worked on the mainstream pages I tested on,
but the approach could also be fooled (e.g. style="display:blank"?). I
would love to hear of a more robust solution. At any rate, I am
exceptionally happy to be reminded of this.

Thanks again Michael,
Csaba Gabor from Vienna
PS. I would be happy to get a response to the issue of cookies when an
IFrame bound site is attempting to set them.


Finally, your reward for wading through all this verbiage is little
demo illustrating proof of concept with regards to iframe DOM access:

Two files, plunk them into the same directory: first, DocComp.htm:
<html>
<head><title>Testing</title></head>
<body>Hi mom
<iframe onload="alert('Iframe has been loaded. '+
'Close browser for final report')"
src="http://ebay.com" style="border:solid 2px red">
</iframe>Hi dad
</body></html>


Run this second file: DocComp.vbs:
'DocumentComplete example with IFrame and onload


Set ie = WScript.CreateObject("InternetExplorer.Application", "ie")

ie.visible = true
out = ""

ie.Navigate2("about:blank")
'Next line must be there when events are present
while (ie.readyState<>4): WScript.Sleep 10: Wend

loc = mid(WScript.ScriptFullName,1, _
len(WScript.ScriptFullName)-3) & "htm"
ie.Navigate2(loc)

On Error Resume Next
Do While ie.visible 'Run until IE is closed
If Err Then Exit Do
WScript.Sleep 10
Loop
popup "Done with program" & vbcrlf & out

Sub ieDocumentComplete (pDisp, url)
out = out & typename(pDisp) & ": " & url & vbcrlf
'Proof of access to the DOM
out = out & mid(pDisp.document.body.innerHTML,1,100) & vbcrlf
out = out & vbcrlf
End Sub

Sub popup (text)
Set oWSH = CreateObject("WScript.Shell")
If typename(text)<>"String" Then _
text="Non string type: " & typename(text)
If Len(text)=0 Then text="Empty string"
oWSH.Popup text, 4, "VBS Popup", 131120
End sub

Csaba Gabor

unread,
Dec 20, 2005, 11:16:11 PM12/20/05
to
Csaba Gabor wrote:
> However, the scriptx approach (IE having a non text, external
> component) is still viable. All I have to do is create a small little
> OCX and all the OCX will do is create and return a new IE instance, and
> hook it up to event handlers (within the OCX). The only point of the
> OCX based event handlers is to reach back out to the .vbs file and hook
> themselves up to the appropriate event handlers in the .vbs file. Not
> sure if I'll be able to programatically detect all the event handlers
> in the .vbs file and make it all automated, but prospects seem good
> (slurp in the running script file and loop through the .vbs looking for
> /^\s*(Sub|Function)\s+(ie[^\s\(]*)(\(|\s).*/ and then trying to do a
> GetRef without error on the captured procedure name). Since I don't
> see any WScript usage in this approach, I expect this should work just
> as well when wrapped into a ScriptComponent (except that I'll have to
> adjust the code self induction since there will no longer be a .vbs
> file to parse) called from PHP. I'm very happy with this approach -
> maybe it will work, too.

I haven't run this through PHP yet, but looks like we have good
fighting chances with VBScript/OCX. If you run the demo, it will bring
up an instance of IE and point it at google.com. As google.com has
finished loading, it will pop up a message box saying so. Clicking OK
closes IE and ends the demo. The important point is that the event
handler we designate is invoked from within the OCX and calls out to
the VBScript file. Here's a proof of concept:

We need one VBScript file, and one .ocx file (which will have two
parts).

Once the .ocx file file detailed lower down has been created, just
double click on this Launcher.vbs (VBScript) file:
Set IEhandler = WScript.CreateObject("IE.EventHandler")
IEhandler.setHandler GetRef("DownloadCompleteHandler")

set IE = IEhandler.newIE
IE.visible = true
IE.Navigate2 "http://www.google.com"

On Error Resume Next
Do While ie.visible = true


If Err Then Exit Do
WScript.Sleep 10
Loop

Popup "Done", null

Sub DownloadCompleteHandler()
If (IE.ReadyState >= 2) Then 'maybe it should be 3?
Popup IE.LocationURL, "Download Complete - " & IE.ReadyState
IE.Quit
End If
End Sub

Public Sub Popup(text, Title)
If (typename(Title)<>"String") Then Title = "VBScript popup"
If (Len(Title)<1) Then Title = "VBScript popup"
CreateObject("WScript.Shell").Popup text, 4, Title, 131120
End Sub
'------------------------ End Launcher.vbs
------------------------------------------


I create the .OCX file with the wonderful, free VB5CCE from Microsoft.
Here are the detailed instructions:
Step 1. Bring up VB5CCE (or some other VB development environment to
create an ActiveX project (OCX))

Step 2. Configure VB5CCE:
a. Under Project \ Properties \ Component \ Version Compatibility
select No Compatibility, then click OK
b. Under Project \ References
select the following projects, then click OK to add
1. Microsoft Internet Controls
(ShDocVw.dll) for shell functions

Step 3. Select the Project1 and change its name to IE
Step 4. Select the UserControl1 and change its name to EventHandler
Insert the following (8 lines of) code:

Dim ieEvents As New clsIeEvents
Public newIE As New SHDocVw.InternetExplorer

Private Sub UserControl_Initialize()
Set ieEvents.ieEvent = newIE
End Sub

Public Sub setHandler(refHandler)
Set ieEvents.downloadCompleteHndlr = refHandler
End Sub


Step 5. Select Project \ Add Class Module
a. Name this class clsIeEvents
b. Add the following (7 lines of) code:

Public WithEvents ieEvent As SHDocVw.InternetExplorer
Public downloadCompleteHndlr As Object 'callback to
DownloadCompleteHandler

Private Sub ieEvent_DownloadComplete()
Dim oCall As Object
Set oCall = downloadCompleteHndlr
oCall
End Sub

Step 6. Select File \ Save Project to save the files you've created.
Keep in mind that VB5CCE does not automatically save files. Also, you
have to look carefully where they are being saved, cause it doesn't
remember directory settings so well (ie make sure you save them all to
the same directory). You can change the name of the files, but I'd
keep the extensions as suggested.

Step 7. Select File \ Make... to create and register your IEevents.ocx

Step 8. Double click on Launcher.vbs to see the demo.


Csaba Gabor from Vienna

Csaba Gabor

unread,
Dec 21, 2005, 7:50:42 AM12/21/05
to
A sad day. For PHP readers, the complete thread is found here:
http://groups.google.com/group/microsoft.public.scripting.vbscript/browse_frm/thread/60438965c65c574b/

I was able to implement the PHP event handler for the
InternetExplorer.Application object by going through the
MSScriptControl.ScriptControl, for which I show the code below. This
code requires the IEevents.ocx which I described in the previous post.
This works fine as long as I use a php.exe, php-win.exe, or even
php-cgi.exe (collectively referred to as CLI) from the command line (on
my Win XP Pro system) to invoke Launcher.php.

Unfortunately, it breaks right away (line 2) when I invoke it as a
result of a web request: IE.EventHandler cannot be created. Thus, the
technique I show is not useful here because, as I have mentioned in my
first post, it is possible to directly connect up
InternetExplorer.Application events to PHP code (using com_event_sink),
when PHP is run as CLI.

Nevertheless, this technique has wide applicability to DOM event
handling without any .ocx requirement.

Sigh,
Csaba Gabor from Vienna

Launcher.php (requires IEevents.ocx for the creation of IE.EventHandler
- see previous post):
<?php
$ieHandler = new COM("IE.EventHandler");
$IE = $ieHandler->newIE;
$IE->Visible = true;
$oClassInstance = new evtHandler; // PHP event handler
$oClassInstance->IE = $IE; // create local var for said handler

// set up the event handler connection
$oScript = new COM("MSScriptControl.ScriptControl");
$oScript->Language = "VBScript";
$oScript->AddObject ("scriptClassInstRef", $oClassInstance);
$oScript->AddObject ("ieHandler", $ieHandler);
// next 4 lines connect the VBScript handler to PHP handler
$oScript->AddCode('Sub EvtWrapper()
scriptClassInstRef.dispatchEvent _
"DownloadCompleteHandler", "Download Complete"
End Sub');
// next line connects the OCX handler to the VBScript handler
$oScript->ExecuteStatement ('ieHandler.setHandler
GetRef("EvtWrapper")');

$IE->Navigate2 ("http://www.google.com");
try { while ($IE->visible) com_message_pump(600); }
catch (Exception $e) { popup ("IE has gone away"); }
popup ("Done with PHP Script");
$webP = !!$_SERVER['REQUEST_METHOD']; // request from the web?
if ($webP)
print "<html><head><title>Events demo</title></head>
<body>IE has loaded its document,
DownloadComplete was detected,
and IE was terminated</body></html>";


class evtHandler {
function DownloadCompleteHandler() {
$aArgs = func_get_args();
if ($this->IE->ReadyState>=2) {
popup ($this->IE->LocationURL,
$aArgs[0] . " - " . $this->IE->ReadyState);
$this->IE->Quit(); } }
public $dispatchEvent;
function dispatchEvent() { call_user_func_array (array(&$this,
"".array_shift($argsRest=func_get_args())), $argsRest); }
}

function popup ($text, $title="PHP popup", $timeout=4, $style=131120) {
$oWSH = new COM("WScript.Shell");
if (is_null($text)) $text = "NULL";
$oWSH->Popup($text, $timeout, $title, $style); }
?>

Michael Harris (MVP)

unread,
Dec 21, 2005, 7:34:15 PM12/21/05
to
> A sad day. ,,,

At least I ponted you to something to fill up all of that free time you seem
to have <VBG>!

0 new messages