Memonitor Event Web Browser

March 21, 2006 at 3:04 pm | Posted in Code Samples | Leave a comment

by: Zamrony P Juhara

Di artikel kali ini saya akan membahas mengenai cara mengetahui event-event yang terjadi pada web browser. Artikel ini saya tulis untuk menjawab pertanyaan Johan Max di milis Delphindo mengenai bagaimana mendeteksi link pada suatu dokumen HTML diklik dan bagaimana mendeteksi URL yang dituju. Tentunya dengan menggunakan tool favorite kita semua..Delphi.

Tutorial ini hanya mengasumsikan browser web adalah IE, untuk browser lain, cara ini mungkin tidak bekerja.

Source code bisa didownload di sini

Unit yang diperlukan

Import dulu ActiveX MSHTML (Ac. Jika pada sistem anda sudah terdapat file MSHTML.pas atau MSHTML_TLB.pas (cari di direktori Imports) berarti anda sudah siap mengikuti artikel ini.

Langkah-Langkah Memonitor Event Web Browser

  • Buat Implementasi Event Sink
  • Buka Dokumen HTML hingga Lengkap
  • Ambil Elemen-elemen HTML yang ingin dimonitor
  • Koneksikan Event Sink ke Elemen-elemen tersebut

Sampai pada step terakhir maka, aplikasi kita sudah siap memproses event web browser. Kalo kita sudah tidak membutuhkan memonitor event, langkah terakhir adalah

  • Disconnect event Sink

Ok, kita bahas satu-satu.

Membuat Implementasi Event Sink

Event Sink harus diturunkan paling tidak dari interface IDispatch, karena fungsi Invoke() akan dipanggil tiap kali event terjadi.

Berikut ini adalah deklarasi IDispatch (unit system.pas)

  IDispatch = interface(IUnknown)
    ['{00020400-0000-0000-C000-000000000046}']
    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;
  end;

Keempat fungsi diatas harus kita buat implementasinya. IDispatch adalah turunan IUnknown, sehingga selain keempat fungsi diatas, kita perlu juga mengimplementasikan metode IUnknown. Agar tidak perlu mengimplementasi IUnknown kita bisa menurunkan kelas implementasi kita dari TInterfacedObject.

Perhatikan bahwa, selain Invoke(), fungsi lainnya kurang berguna untuk tujuan kita, sehingga bisa kita set tidak terimplementasi, berikut ini contoh kodenya.

unit ueventsink;

interface
uses classes,windows,sysutils,mshtml;

type
  IEventSink=interface(IDispatch)
  ['{AC8E45D3-DABB-4DC0-AD94-D53FA67DD78A}']
    procedure SetOnClick(const Value: THTMLElementOnClick);
    function  GetOnClick: THTMLElementOnClick;
    procedure SetWebBrowser(const Value: IWebBrowser2);
    function  GetWebBrowser: IWebBrowser2;

    property OnClick:THTMLElementOnClick read GetOnClick 
                            write SetOnClick;
    property WebBrowser:IWebBrowser2 read GetWebBrowser 
                           write SetWebBrowser;
  end;

  TEventSink=class(TInterfacedObject,IDispatch,IEventSink)
  public

    procedure SetOnClick(const Value: THTMLElementOnClick);
    function  GetOnClick: THTMLElementOnClick;
    procedure SetWebBrowser(const Value: IWebBrowser2);
    function  GetWebBrowser: IWebBrowser2;

    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;

  end;
implementation

{ TEventSink }

function TEventSink.GetIDsOfNames(const IID: TGUID; Names: Pointer;
  NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
  result:=E_NOTIMPL;
end;

function TEventSink.GetTypeInfo(Index, 
                                LocaleID: Integer;
                            out TypeInfo): HResult;
begin
  result:=E_NOTIMPL;
end;

function TEventSink.GetTypeInfoCount(out Count: Integer): HResult;
begin
  result:=E_NOTIMPL;
end;

function TEventSink.Invoke(DispID: Integer; const IID: TGUID;
     LocaleID: Integer;
     Flags: Word; var Params; 
     VarResult, ExcepInfo,
    ArgErr: Pointer): HResult;
begin

end;

end.

Kita hanya akan fokus ke membuat implementasi Invoke(). Ok sekarang kita tambahkan sebuah property event pada TEventSink


type
THTMLElementOnClick=procedure (Sender:TObject;
Element:IHTMLElement;
var cancel:boolean) of
object
;

Tambahkan dibagian published code berikut

property OnClick:THTMLElementOnClick;

Tekan Shift+Ctrl+C untuk melengkapi kelas class completion

Berikut ini adalah implementasi Invoke().

function TEventSink.Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params;
  VarResult, ExcepInfo,
  ArgErr: Pointer): HResult;
var cancel:boolean;
begin
  case dispID of
    -600:begin
           if Assigned(FOnClick) then
           begin
             cancel:=false;
             FOnClick(self,nil,cancel);
           end;
         end;
  end;
  result:=S_OK;
end;

Implementasi Kode Koneksi.

Untuk mengkoneksi ke elemen HTML kita perlu mendapatkan connection Point ke elemen tersebut caranya sebagai berikut:

function ConnectEvSink(elem:IHTMLElement;eventGUID:TGUID;
                      eventSink:IUnknown):integer;
var cpc:IConnectionPointContainer;
    cp:IConnectionPoint;
begin
  result:=0;
  elem.QueryInterface(IConnectionPointContainer,cpc);
  if cpcnil then
  begin
    cpc.FindConnectionPoint(eventGUID,cp);
    if cpnil then
      cp.Advise(eventSink,result);
  end;
end;

elem adalah elemen HTML yang akan dimonitor event-nya. EventGUID adalah GUID event yang akan dimonitor. Contoh untuk memonitor Elemen anchor maka eventGUID adalah DIID_HTMLAnchorEvents. EventSink adalah implementasi event sink yang kita buat.

Fungsi diatas akan mengembalikan ID yang nantinya dapat kita pergunakan untuk melakukan disconnect event sink.

Disconnect Event Sink

Caranya hampir sama dengan connect tetapi, yang kita gunakan adalah metode UnAdvice().

procedure DisconnectEvSink(elem:IHTMLElement;eventGUID:TGUID;
                      const ID:integer);
var cpc:IConnectionPointContainer;
    cp:IConnectionPoint;
begin
  elem.QueryInterface(IConnectionPointContainer,cpc);
  if cpcnil then
  begin
    cpc.FindConnectionPoint(eventGUID,cp);
    if cpnil then
      cp.UnAdvise(id);
  end;
end;

Implementasi Aplikasi Utama

Kode berikut adalah kode yang akan selalu dipanggil ketika sebuah link diklik

procedure TForm1.AnchorClick(Sender:TObject;
                     Element:IHTMLElement;
                     var cancel:boolean);
begin
  if element<>nil then
  begin
  ShowMessage('Link diklik.');
end;

Konstruktor melakukan inisialisasi event sink.

constructor TForm1.Create(AOWner: TComponent);
begin
  inherited;
  eventSink:=TEventSink.Create;
  eventSink.WebBrowser:=WebBrowser1;
  eventSink.OnClick:=AnchorClick;
end;

Perhatikan, karena elemen-elemen baru dapat diakses setelah semua dokumen telah selesai didownload, maka kita harus memproses elemen-elemen di DocumentComplete browser.

procedure TForm1.WebBrowser1DocumentComplete(Sender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
var doc:IHTMLDocument;
    doc2:IHTMLDocument2;
    i:integer;

    anchors:IHTMLElementCollection;
    anchor:IHTMLElement;
begin
  if WebBrowser1.Document<>nil then
  begin
    WebBrowser1.Document.QueryInterface(IHTMLDocument,doc);
    if doc<>nil then
    begin
      doc.QueryInterface(IHTMLDocument2,doc2);
      if doc2<>nil then
      begin
        anchors:=GetAllAnchors(doc2);
        //proses tiap link yang ada
        for i:=0 to anchors.length-1 do
        begin
          anchor:=anchors.item(i,0) as IHTMLElement;
          ConnectEvSink(anchor,DIID_HTMLAnchorEvents,eventSink);
        end;
      end;
    end;
  end;
end;

Fungsi GetAllAnchors(), dideklarasikan di unit uhtml_utility.pas, mengambil semua link yang ada pada dokumen HTML. untuk Tiap-tiap elemen, kita daftarkan evnt sink kita sehingga kita akan diberitahu melalui Invoke() ketika terjadi event pada elemen-elemen ini.

Mendapatkan Elemen Yang Menghasilkan Event

Kode Invoke() diatas, tidak mampu memberitahukan elemen apa yang menghasilkan sebuah event. Lalu bagaimana?

Interface IHTMLWindow2 memiliki property event yang mewakili event yang sedang digenerate saat ini. Dari property ini lah kita dapat mengetahui elemen apa yang menghasilkan event, melalui property src_Element milik event.
Selain itu event memiliki sebuah property bernama returnValue yang dapat kita pergunakan untuk menjalankan/membatalkan aksi default sebuah elemen.

Lalu bagaimana mendapatkan interface IHTMLWindow2? Jawabannya adalah melalui IHTMLDocument2.parentWindow.

Berikut ini adalah Invoke() yang telah diperbarui:

function TEventSink.Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params;
  VarResult, ExcepInfo,
  ArgErr: Pointer): HResult;
var cancel:boolean;
begin
  case dispID of
    -600:begin
           if Assigned(FOnClick) then
           begin
             cancel:=false;
             elemen:=nil;
             if FWebBrowser<>nil and
                FWebBrowser.Document<>nil then
             begin
                doc:=FWebBrowser.Document as IHTMLDocument2;
                window:=doc.parentWindow;
               if (window<>nil)  and
                (window.event<>nil) then
                elemen:=window.event.src_Element;
             end;
             FOnClick(self,nil,cancel);
             if (window<>nil)  and
              (window.event<>nil) then
              window.event.returnValue:=cancel;
           end;
         end;
  end;
  result:=S_OK;
end;

Kita ubah AnchorClick sehingga tiap kali link diklik, URL-nya ditampilkan juga.

procedure TForm1.AnchorClick(Sender:TObject;
                     Element:IHTMLElement;
                     var cancel:boolean);
var anchor:IHTMLAnchorElement;
    url:wideString;
begin
  if element<>nil then
  begin
    anchor:=element as IHTMLAnchorElement;
    url:='URL='+anchor.href;
  end else
    url:='';
  ShowMessage('Link diklik.'+url);
  //batalkan aksi defaultnya
  cancel:=true;
end;

Ok itu saja. Source code bisa didownload di sini

Leave a Comment »

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.
Entries and comments feeds.