To get access to detail
of CustomEvent
in Delphi code you need to:
- obtain reference
IDOMEvent
in your event listener,
- get reference to
IDOMCustomEvent
from it,
- use late binding to navigate through
detail
property.
You obviously failed at step #1. You were on a good way to implement event listener via IDispatch
and pass it as the second parameter of IEventTarget.addEventListener
. At this point you expected to receive some parameters as per the documentation when an event is dispatched to the listener:
listener
[in]
Type: IDispatch
The event handler function to associate with the event. Be aware that the event handler function itself requires two parameters - the first is the event target (that is, the object on which the event handler function is being invoked) and the second is the IDOMEvent
object.
After registering the event listener and raising the event in HTML you discovered that you didn't receive any values in parameter Params
in method Invoke
:

You're not the first one facing this issue and searching for the root cause yields only few results:
Based upon that your listener needs to implement IDispatchEx, which the documentation doesn't mention. You just need to implement its InvokeEx
method and ignore the rest. Here's sample implementation (I dared to rename the class to TWebBrowserEventListener
to better express its purpose):
uses
System.SysUtils, Winapi.Windows, Winapi.ActiveX, MSHTML;
type
THandleEvent = procedure(const Target: IDispatch; const DOMEvent: IDOMEvent) of object;
TWebBrowserEventListener = class(TInterfacedObject, IDispatchEx)
private
FOnHandleEvent: THandleEvent;
{ IDispatch }
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
{ IDispatchEx }
function GetDispID(const bstrName: TBSTR; const grfdex: DWORD;
out id: TDispID): HResult; stdcall;
function InvokeEx(const id: TDispID; const lcid: LCID; const wflags:
WORD; const pdp: PDispParams; out varRes: OleVariant; out pei:
TExcepInfo; const pspCaller: PServiceProvider): HResult; stdcall;
function DeleteMemberByName(const bstr: TBSTR;
const grfdex: DWORD): HResult; stdcall;
function DeleteMemberByDispID(const id: TDispID): HResult; stdcall;
function GetMemberProperties(const id: TDispID; const grfdexFetch:
DWORD; out grfdex: DWORD): HResult; stdcall;
function GetMemberName(const id: TDispID; out bstrName: TBSTR):
HResult; stdcall;
function GetNextDispID(const grfdex: DWORD; const id: TDispID;
out nid: TDispID): HResult; stdcall;
function GetNameSpaceParent(out unk: IUnknown): HResult; stdcall;
protected
procedure HandleEvent(const Target: IDispatch; const DOMEvent: IDOMEvent); virtual;
public
constructor Create(AOnHandleEvent: THandleEvent);
end;
constructor TWebBrowserEventListener.Create(AOnHandleEvent: THandleEvent);
begin
inherited Create;
FOnHandleEvent := AOnHandleEvent;
end;
function TWebBrowserEventListener.DeleteMemberByDispID(const id: TDispID): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.DeleteMemberByName(const bstr: TBSTR;
const grfdex: DWORD): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.GetDispID(const bstrName: TBSTR; const grfdex: DWORD;
out id: TDispID): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.GetMemberName(const id: TDispID;
out bstrName: TBSTR): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.GetMemberProperties(const id: TDispID;
const grfdexFetch: DWORD; out grfdex: DWORD): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.GetNameSpaceParent(out unk: IInterface): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.GetNextDispID(const grfdex: DWORD; const id: TDispID;
out nid: TDispID): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.GetTypeInfo(Index, LocaleID: Integer;
out TypeInfo): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.GetTypeInfoCount(out Count: Integer): HResult;
begin
Result := E_NOTIMPL;
end;
procedure TWebBrowserEventListener.HandleEvent(const Target: IDispatch;
const DOMEvent: IDOMEvent);
begin
if Assigned(FOnHandleEvent) then
FOnHandleEvent(Target, DOMEvent);
end;
function TWebBrowserEventListener.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
begin
Result := E_NOTIMPL;
end;
function TWebBrowserEventListener.InvokeEx(const id: TDispID; const lcid: LCID;
const wflags: WORD; const pdp: PDispParams; out varRes: OleVariant;
out pei: TExcepInfo; const pspCaller: PServiceProvider): HResult;
var
DOMEvent: IDOMEvent;
begin
if (id = DISPID_VALUE) and (pdp^.cArgs = 2) and (pdp^.rgvarg^[0].vt = varDispatch) and
(pdp^.rgvarg^[1].vt = varDispatch) and Supports(IDispatch(pdp^.rgvarg^[1].dispVal), IDOMEvent, DOMEvent) then
begin
HandleEvent(IDispatch(pdp^.rgvarg^[0].dispVal), DOMEvent);
Result := S_OK;
end
else
Result := E_NOTIMPL;
end;
To test that I loaded this HTML into the web browser control:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=edge">
<script>
/* Internet Explorer doesn't support CustomEvent() constructor. Polyfill for IE9+ from
https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent */
(function () {
if ( typeof window.CustomEvent === "function" ) return false;
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: null };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
window.CustomEvent = CustomEvent;
})();
function triggerPmweTest() {
var event = new CustomEvent('pmweTest', { detail: { dataPackage: document.getElementById('input-text').value } });
document.dispatchEvent(event);
}
</script>
</head>
<body>
<input id="input-text" type="text" value="hello Matt" />
<input id="input-checkbox" type="checkbox" />
<button id="button" onclick="triggerPmweTest()">Click</button>
</body>
</html>
And this is how I registered the listener:
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; const URL: OleVariant);
var
Target : IEventTarget;
Listener: IDispatchEx;
begin
Target := WebBrowser1.Document as IEventTarget;
Listener := TWebBrowserEventListener.Create(WebBrowserEvent);
Target.addEventListener('change', Listener, True);
Target.addEventListener('pmweTest', Listener, True);
end;
procedure TForm1.WebBrowserEvent(const Target: IDispatch;
const DOMEvent: IDOMEvent);
var
EventInfo: string;
DOMCustomEvent: IDOMCustomEvent;
begin
EventInfo := 'Type: ' + DOMEvent.type_ + #13#10'SrcElement: ';
if Assigned(DOMEvent.srcElement) then
begin
EventInfo := EventInfo + DOMEvent.srcElement.tagName;
if DOMEvent.srcElement.id <> '' then
EventInfo := EventInfo + '#' + DOMEvent.srcElement.id;
end
else
EventInfo := EventInfo + '#document';
if (DOMEvent.type_ = 'pmweTest') and Supports(DOMEvent, IDOMCustomEvent, DOMCustomEvent) then
EventInfo := EventInfo + #13#10'detail.dataPackage: ' + VarToStr(DOMCustomEvent.detail.dataPackage);
ShowMessage(EventInfo);
end;
The code above listens for change
events on <input>
elements as well as custom pmweTest
event that is triggered by clicking the button. The same listener is used for both types.
When you change the value of text field and focus out of the field then it displays:
Type: change
SrcElement: INPUT#input-text
When you click the checkbox:
Type: change
SrcElement: INPUT#input-checkbox
When you click the button:
Type: pmweTest
SrcElement: #document
detail.dataPackage: hello Matt