Click here to Skip to main content
15,895,084 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I have web browser control and I have acess to the IHTMLDocument interface. I am trying to catch events that occur within a page in my web browser control but am not sure how to get a handle to these events.

What I want to do is:

1) navigate to a website that uses javascript to navigate through its website.

2) capture button presses and the javascript function associated with them

2) post them to another instance of my web page.

I have parts 1 and 3.

Can anyone help with capturing javascript events on the fly?

Thanks in advance
Posted

a quick scoot round the docs would suggest you can get to the scripting engine via the document, using IHTMLDocument::Script[^]

Then there should be something inside the Script Engine[^] that could help you
 
Share this answer
 
Comments
alttaf 29-Sep-11 7:51am    

Code I have used to get my c++ code to fire a javascript function from my web browser control
<pre>


IHTMLDocument* doc;
//block for preparation for receiving the script
DISPID dispid = NULL;
CComVariant vaResult;
// declare the name of the method you want to fire
CComBSTR bstrMember("GoToFirst");
EXCEPINFO excepInfo;
memset(&excepInfo, 0, sizeof excepInfo);
UINT nArgErr = (UINT)0; // initialize to invalid arg
DISPPARAMS dispparams;
memset(&dispparams, 0, sizeof dispparams);

// get document interface
IDispatch *pDisp = m_IE.get_Document();
IDispatch *p =NULL;
ASSERT( pDisp);
pDisp->QueryInterface(IID_IHTMLDocument, (void**)&doc);
hr = doc->get_Script(&p);
p->GetIDsOfNames(IID_NULL, &bstrMember, 1, LOCALE_SYSTEM_DEFAULT, &dispid);

if(hr == S_OK && p != NULL)
{
fires off script event specified in dispid
hr = p->Invoke(dispid,IID_NULL,0,
DISPATCH_METHOD,&dispparams,
&vaResult,&excepInfo,&nArgErr);

}
</pre>

This code block fires off the script provided The name of the method is known. The problem is how can I
A) find out when this function is fired.
B) Capture the event associated with each button click
C) use it if it is my predefined list otherwise do nothing.

I have looked at the MSDN IActiveScriptAuthor::GetEventHandler but there are no examples of what the function expects.

Thanks for your help
barneyman 29-Sep-11 8:03am    
Short answer is I don't know - sorry ...

I'd look at attacking the problem with either the profiler or the debugger interfaces
You need to sink the Web Browswer events using connection points in your code. This [^] may give some pointer to you.
 
Share this answer
 
Its a long way to get them all:
+ first you need a handler (as IDispatch better IDispatchEx)
C++
#include <dispex.h>
class iDummy : public IDispatchEx
{
public: // IUnknown
  virtual HRESULT __stdcall QueryInterface(REFIID riid,void **ppv)
  {
    if(IID_IUnknown   ==riid) return *(IUnknown   **)ppv=this,AddRef();S_OK;
    if(IID_IDispatch  ==riid) return *(IDispatch  **)ppv=this,AddRef();S_OK;
    if(IID_IDispatchEx==riid) return *(IDispatchEx**)ppv=this,AddRef();S_OK;
    return E_NOINTERFACE;
  }
  virtual ULONG __stdcall    AddRef(){ return InterlockedIncrement(&_ref); }
  virtual ULONG __stdcall    Release(){ if(InterlockedDecrement(&_ref)) return _ref; delete thisreturn 0; }

public: // IDispatch
  virtual HRESULT __stdcall GetTypeInfoCount(UINT *pctinfo)
    { return E_NOTIMPL; }
  virtual HRESULT __stdcall GetTypeInfo(UINT iTInfo,LCID lcid,ITypeInfo **ppTInfo)
    { return E_NOTIMPL; }
  virtual HRESULT __stdcall GetIDsOfNames(REFIID riid,LPOLESTR *rgszNames,UINT cNames,LCID lcid,DISPID *rgDispId)
    { unsigned int i; for(i=0;(i<cNames) && (S_OK==GetDispID(rgszNames[i],0,rgDispId+i));i++); return i<cNames?DISP_E_MEMBERNOTFOUND:S_OK; }
  virtual HRESULT __stdcall Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,VARIANT *pVarResult,EXCEPINFO *pExcepInfo,UINT *puArgErr)
    { return InvokeEx(dispIdMember,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,0); }

public: // IDispatchEx
  virtual HRESULT __stdcall GetDispID(BSTR bstrName,DWORD grfdex,DISPID *pid)
    { TRACE(__TEXT("call: %s\r\n"),bstrName); return E_NOTIMPL; }
  virtual HRESULT __stdcall InvokeEx(DISPID id,LCID lcid,WORD wFlags,DISPPARAMS *pdp,VARIANT *pvarRes,EXCEPINFO *pei,IServiceProvider *pspCaller)
    { if(0==id){ Beep(3000,100); }return E_NOTIMPL; }
  virtual HRESULT __stdcall DeleteMemberByName(BSTR bstrName,DWORD grfdex)
    { return E_NOTIMPL; }
  virtual HRESULT __stdcall DeleteMemberByDispID(DISPID id)
    { return E_NOTIMPL; }
  virtual HRESULT __stdcall GetMemberProperties(DISPID id,DWORD grfdexFetch,DWORD *pgrfdex)
    { return E_NOTIMPL; }
  virtual HRESULT __stdcall GetMemberName(DISPID id,BSTR *pbstrName)
    { return E_NOTIMPL; }
  virtual HRESULT __stdcall GetNextDispID(DWORD grfdex,DISPID id,DISPID *pid)
    { return E_NOTIMPL; }
  virtual HRESULT __stdcall GetNameSpaceParent(IUnknown **ppunk)
    { return E_NOTIMPL; }

public:
  iDummy(){ _ref=1; }
private:
  long  _ref;
};

+ next you have to walk all the HTML elements, even if the load is ready (examine the ready state of the web browser control):
C++
  enum { childNodes, length, };

typedef void (*FNWALK)(IDispatch* node);

void WalkNode(IDispatch* node,BSTR* anames,FNWALK fn)
{
  VARIANT    ret1 = { VT_EMPTY, };
  VARIANT    ret2 = { VT_EMPTY, };
  VARIANT    ret3 = { VT_EMPTY, };
  VARIANT    ret4 = { VT_EMPTY, };
  
  fn(node);
  if(S_OK==InvokeName(node,anames[childNodes],ret1))
  {
    if(VT_DISPATCH==ret1.vt)
    {
      if(S_OK==InvokeName(ret1.pdispVal,anames[length],ret2))
      {
        if(VT_I4==ret2.vt)
        {
          wchar_t  num[32];
          ret3.vt = VT_I4;
          for(ret3.lVal=0;ret3.lVal<ret2.lVal;ret3.lVal++)
          {
            _itow_s(ret3.lVal,num,sizeof(num)/sizeof(num[0]),16);
            if(S_OK==InvokeName(ret1.pdispVal,num,ret4))
            {
              if(VT_DISPATCH==ret4.vt)
              {
                WalkNode(ret4.pdispVal,anames,fn);
              }
              VariantClear(&ret4);
            }
          }
        }
        VariantClear(&ret2);
      }
    }
    VariantClear(&ret1);
  }
}

void WalkDocument(IDispatch* doc,FNWALK fn)
{
  BSTR      anames[8];
  
  anames[childNodes] = ::SysAllocString(L"childNodes");
  anames[length] = ::SysAllocString(L"length");
  
  WalkNode(doc,anames,fn);
  for(int i=0;i<=length;i++) SysFreeString(anames[i]);
}

+ some helper functions needed as follows:
C++
HRESULT InvokeName(IDispatch* node,BSTR name,VARIANT& ret)
{
  HRESULT  hr = E_FAIL;
  if(node)
  {
    DISPID      did = 0;
    DISPPARAMS  arg = { 0,0,0,0 };
    if(S_OK==node->GetIDsOfNames(IID_NULL,&name,1,0,&did))
    {
      hr = node->Invoke(did,IID_NULL,0,DISPATCH_PROPERTYGET,&arg,&ret,0,0);
    }
  }
  return hr;
}

HRESULT InvokeWithParams(IDispatch* node,BSTR name,VARIANT* aparam,const unsigned int nparam,VARIANT& ret)
{
  HRESULT  hr = E_FAIL;
  if(node)
  {
    DISPID      did = 0;
    DISPPARAMS  arg = { aparam,0,nparam,0 };
    if(S_OK==node->GetIDsOfNames(IID_NULL,&name,1,0,&did))
    {
      hr = node->Invoke(did,IID_NULL,0,(0<nparam?DISPATCH_PROPERTYPUT:DISPATCH_PROPERTYGET),&arg,&ret,0,0);
    }
  }
  return hr;
}

+ implement the walk function to replace the event handlers you want to replace:
C++
void __fnWalk(IDispatch* node)
{
  BSTR      nodeName = L"nodeName";
  VARIANT    ret1 = { VT_EMPTY, };
  VARIANT    ret2 = { VT_EMPTY, };
  VARIANT    ret3 = { VT_EMPTY, };
  if(S_OK==InvokeName(node,nodeName,ret1))
  {
    if(VT_BSTR==ret1.vt)
    {
      TRACE(__TEXT("name: %s\r\n"),ret1.bstrVal);
      // if(!_wcsicmp(L"INPUT",ret1.bstrVal) || !_wcsicmp(L"BUTTON",ret1.bstrVal))
      {
        if(S_OK==InvokeName(node,L"OnClick",ret2))
        {
          if(VT_DISPATCH==ret2.vt)
          {
            VARIANT        a[8];
            unsigned int  n = 0;
            a[n].pdispVal = new iDummy;
            a[n].vt = VT_DISPATCH;
            ++n;
            if(S_OK==InvokeWithParams(node,L"OnClick",a,n,ret3))
            {
              VariantClear(&ret3);
            }
            while(0<n) VariantClear(a+(--n));
          }
          VariantClear(&ret2);
        }
      }
    }
    VariantClear(&ret1);
  }
  ++__walkdone;
}

+ finally the code you can use all of this:
C++
  IDispatch*  doc = m_web.get_Document();
  if(doc)
  {
    __walkdone = 0;
    WalkDocument(doc,__fnWalk);
    doc->Release();
  }

Functions are tested and work with a simple form.
Good luck.
 
Share this answer
 
Comments
alttaf 30-Sep-11 5:06am    
Wow thanks so much makes alot of sense!!! I will give this a go, I am a bit of a hack and hope kind of guy when it comes to com, so it may take me a while and I may have a few more question. Thanks again!!!!!
alttaf 4-Oct-11 6:38am    
Hi mbue, I have been tracing through the program^ and it seems to be going through each html element and intergorating it, that I kind of understand. However, in the __fnWalk(IDispatch* node) function, how is the event handler changed? In my adapted code I am specifically trying to listen in on a specific script event kept in a seperate javascript file.
mbue 4-Oct-11 7:30am    
The event capturing on that code based on the replacement of the previous event handler by a dummy handler:
example
<a href="#" onclick="javascript:LinkClick();">link with handler</a>
the walk examines a handler LinkClick and replaces that with a new IDispatchEx handler (that beeps). internal html events such as submit i dont know if you can replace them.
To use an external script things would be a little bit difficult.
Therefore you have to load the external script file by using the IActiveScriptParse interface. Examine the function interfaces and replace the appropriate handlers by them from the external script.
This isnt really easy.
Regards.
alttaf 5-Oct-11 7:40am    
Hi Mbue. I managed to get your code working :) I want to change the IDispatchEx function to just do what the button orininally does, I will add extra functionality afterwards. Can you have a look at the code I added and make a comment on how I should proceed. Thanks Alttaf
I have managed to get some of the implementation working. I am getting the name of the javascript function I want to listen to directly from the script file as in my implementation I will most of the time know this and for purposes of getting something working I have hard coded a goToNextSld function.



I get the dispid of the function I want to fire using
C++
// specific predifined function I want to listen for which i will have in array later
            CComBSTR bstrMember1("GoToNextSld");
            pDisp->QueryInterface(IID_IHTMLDocument, (void**)&doc);
            hr = doc->get_Script(&p);

           
           
            p->GetIDsOfNames(IID_NULL, &bstrMember1, 1, LOCALE_SYSTEM_DEFAULT, &dispid); 



I managed to get Mbue code working by adding some more paramaters to the invoke with params method.
C++
HRESULT InvokeWithParams(IDispatch* node,BSTR name,VARIANT* aparam,const unsigned int nparam,VARIANT& ret)
{
   HRESULT  hr = E_FAIL;
   EXCEPINFO excepinfo;
   UINT  puArgErr;
   DISPID      did = 0;
   DISPID      dispidNamed = DISPID_PROPERTYPUT;
   DISPPARAMS  arg = { aparam,0,nparam,0 };
   // These args are necessary otherwise your IDummy IDispatchEx will throw up 
   // error asking for more args
   arg.cArgs =1;
   arg.rgdispidNamedArgs = &dispidNamed;
   arg.cNamedArgs =1;
   arg.rgvarg[0].vt =VT_DISPATCH;
   arg.rgvarg[0].pdispVal = aparam->pdispVal;

   if(node)
   {
      if(S_OK==node->GetIDsOfNames(IID_NULL,&name,1,0,&did))
      {
        
         hr = node->Invoke(did,IID_NULL,LOCALE_SYSTEM_DEFAULT,(0<nparam?dispatch_propertyput:dispatch_propertyget),&arg,&ret,&excepinfo,&puargerr);>
      }
   }
   return hr;
}


Mbue's Code was adapted to only change a specific function but to actually do more than just beep is a problem. How do I implement the call back for the "GoToNextSld" function which i have the dispid for.

In the Idummy.h I tried:
C++
virtual HRESULT __stdcall InvokeEx(DISPID id,LCID lcid,WORD wFlags,DISPPARAMS *pdp,VARIANT *pvarRes,EXCEPINFO *pei,IServiceProvider *pspCaller)
  {  Invoke(id,IID_NULL,0,
                       DISPATCH_METHOD,pdp,
                       0,0,0); return S_OK; }

can I overide this function completely in a IDummy.Cpp file? and Why am I receiving a stack overflow error

I am passing in
C++
void RepWindow::setScriptProperty(IDispatch *p, BSTR bstrMember1, VARIANT ret1, VARIANT ret3)
{
   if(S_OK == InvokeName(p,bstrMember1,ret1))
   {
      if(VT_DISPATCH==ret1.vt)
      {
        // I have the function I want I have modified the IdispatchEx
         VARIANT        a[8];
         unsigned int  n = 0;
         a[n].pdispVal = new IDummy;
         a[n].vt = VT_DISPATCH;
         ++n;
         if(S_OK==InvokeWithParams(p,bstrMember1,a,n,ret3))
         {
            VariantClear(&ret3);
         }
         while(0<n)>      }
      VariantClear(&ret1);
   }

}


I am not doing a walk node and I am running this directly.... can you find see what I am doing wrong? I am not sure the dispid is getting passed in to the IdispatchEx function and I am not sure if I only should be doing this once. I want the current function to do what is doing at the moment. I want to add some extra functionality to it afterwards.

Thanks

Alttaf
 
Share this answer
 
Comments
mbue 5-Oct-11 9:41am    
Ok. To prevent the hooking mechanism to hook the events by your dummy interface, you should identify your own interface:
* create a new CLSID
* return your interface on QueryInterface call, so you know that you got your own implementation.
example:

static const GUID CLSID_ThisIsMyInterface = { 0x85d8c4a1, 0xb644, 0x4cf2, { 0xb7, 0x72, 0xb2, 0x7e, 0xb2, 0x27, 0x1, 0x3d } };

HRESULT iDummy::QueryInterface(REFIID riid,void** ppv)
{
if(CLSID_ThisIsMyInterface==riid) return *(iDummy**)ppv=this,AddRef(),S_OK;
// the other interfaces
}

// hook the old handler
static HRESULT iDummy::HookTheHandler(IDispatch* pOld,IDispatch** ppNew)
{
iDummy* isme;
if(S_OK==pOld->QueryInterface(CLSID_ThisIsMyInterface,(void**)&isme))
{
isme->Release();
return E_FAIL;
}
*ppNew = new iDummy(pOld);
return (*ppNew)?S_OK:E_OUTOFMEMORY;
}

iDummy::iDummy(IDispatch* pOld)
{
oldhandler = 0;
if(S_OK!=pOld->QueryInterface(IID_IDispatchEx,(void**)&oldhandler))
{
oldhandler = 0;
}
}

iDummy::~iDummy()
{
if(oldhandler) oldhandler->Release();
}

HRESULT iDummy::InvokeEx(DISPID id,LCID lcid,WORD wFlags,DISPPARAMS *pdp,VARIANT *pvarRes,EXCEPINFO *pei,IServiceProvider *pspCaller)
{
// forward the old handler
HRESULT hr = oldhandler ? oldhandler->InvokeEx(id,lcid,wFlags,pdp,pvarRes,pei,pspCaller):E_FAIL;
Beep(3000,100); // i am alive
return hr;
}

To call the old impelementation you can store the old IDispatchEx interface (if(S_OK!=handler->QueryInterface(IID_IDispatchEx,(void**)&oldhandler)) oldhandler=0;). Remember you should call Release in your destructor (if(oldhandler) oldhandler->Release();)
in the function InvokeEx of your implementation you can forward the old handler function. handler are always called with DISPID==0 and flags set to DISPATCH_METHOD.
Regards.
alttaf 6-Oct-11 12:26pm    
Hi MBue, Thanks so much for your help I am really struggling but learning at the same time. I am enjoying the challenge!

so when I am running my implementation, how can I check the handler has been assigned to the function. and when does it change back or get released as it were? currently I am trying to get the function that is handled to beep like in your first example but it is not getting caught any good debugging tricks? I have the function Disp Id...
Currently I am able to put and get properties of each of the buttons on my javascript page. I will store the variant arguments of the DISPATCH_PROPERTYGET Idispatch in a separate controller class. My question is how can I implement an event sink in the webroswer that will get the javascript function being called and its parameters? can anyone help?

You can assign javascript properties to the button of your choice by using

C++
hr = p->Invoke(dispidChange, IID_NULL, 0, DISPATCH_PROPERTYPUTREF,&arg,&var,&excepInfo,&nArgErr);
 
Share this answer
 
v2

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900