본문 바로가기

프로그래밍/Chromium(CEF4Delphi)

[Chromium] CEF4Delphi - DOM탐색을 위한 메시지처리 개념잡기

1. 개요

Chromium에는 임베딩된 TWebBrowser 컴포넌트처럼 웹브라우저를 직접 핸들링하는 기능을 제공하지 않습니다. 따라서 VCL Application에서 Embedding시킨 Chromium 웹브라우저에 일을 시키려면(예를 들어 웹페이지 HTML을 얻는 등등..) Chromium의 메시징 처리 매커니즘을 이해할 필요가 있습니다.

2. Chromium 메시지 처리 개념도

3. Chromium 기본 메시지 처리 매커니즘

 

3.1 Chromium 어플리케이션 초기화 및 메시지 처리 핸들러 지정

Chromium을 Embedding하는 VCL 어플리케이션에는 최초 1회 다음과 같은 Chromium이 동작하는 어플리케이션이라고 초기화를 해 주어야 합니다.

GlobalCEFApp  := TCefApplication.Create;

그리고 GlobalCEFApp.OnProcessMessageReceived 메시지 핸들러에서 Chromium에 전달되는 모든 이벤트를 처리 합니다. 이 이벤트 핸들러에는 TChromium 컴포넌트별로 수신된 메시지를 각각 처리할 수 있기 때문에 메시지를 수신받은 브라우저등 메타 정보를 인식할 수 있습니다. 이 이벤트 핸들러의 함수 원형은 다음과 같습니다.

 

procedure GlobalCEFApp_OnProcessMessageReceived(const browser       : ICefBrowser;   //메시지를 전달받은 브라우저(TChromium.browser)
                                                const frame         : ICefFrame;     //메시지를 전달받은 프레임
                                                      sourceProcess : TCefProcessId; //프로세스ID
                                                const message       : ICefProcessMessage; //전달받은 메시지
                                                var   aHandled      : boolean);      //처리여부 결정
                                                

  메시지 처리 핸들러를 다음과 같이 CEFApplication의 이벤트 핸들러로 등록해 주어야 합니다.

  //어플리케이션마다 한번만 초기화 해주어야 함
  GlobalCEFApp                          := TCefApplication.Create;
  GlobalCEFApp.RemoteDebuggingPort      := 9000;
  GlobalCEFApp.OnProcessMessageReceived := GlobalCEFApp_OnProcessMessageReceived; //메시지처리 핸들러
  GlobalCEFApp.DisableFeatures          := 'NetworkService,OutOfBlinkCors';
  GlobalCEFApp.LogFile                  := 'debug.log'; //Chromium내부에서 남긴 오류를 저장할 파일
  GlobalCEFApp.LogSeverity              := LOGSEVERITY_ERROR;//LOGSEVERITY_INFO; //저장할 오류의 종류
  

 

3.2 Chromium에 처리할 메시지 생성/전송

Chromium에서 사용되는 메시지는 TCefProcessMessageRef 클래스를 사용하여 생성후 전송합니다.

이때 TChromium에 전달할 대상은 PID_RENDERER 이어야 합니다.

    //메시지 생성
    TempMsg := TCefProcessMessageRef.New('HTML얻기');
    //메시지 전송
    Chromium1.SendProcessMessage(PID_RENDERER, TempMsg);
    

 

3.3 Chromium Application 메시지 핸들러에서 VISITDom 객체 생성 및 실행

앞서 등록한 Chromium Application 메시지 핸들러에서 message.name에 따른 각 용도별 VisitDom 객체를 생성하고 VisitDom을 호출함으로 해서 원하는 일을 웹브라우저에 시킬 수 있게 됩니다.

이때 VisitDom객체 생성시 동작할 Callback 함수를 함께 전달해 주어야 하는데 VisitDom객체 내부적으로 visit 함수가 실행 될 때 지정된 콜백함수가 실행되기 때문입니다.

VisitDom객체는 유닛 "uCEFDomVisitor.pas"에 선언된 TCefDomVisitorOwn 클래스를 상속받아 작성된 클래스를 사용할 수 있으며 이미 기정의된 두개의 TCefDomVisitorOwn 객체를 사용할 수도 있습니다.

 

  TCefFastDomVisitor = class(TCefDomVisitorOwn)
    protected
      FProc    : TCefDomVisitorProc;

      procedure visit(const document: ICefDomDocument); override;

    public
      constructor Create(const proc: TCefDomVisitorProc); reintroduce; virtual;
  end;

  TCefFastDomVisitor2 = class(TCefDomVisitorOwn)
    protected
      FProc    : TCefDomVisitorProc2;
      FBrowser : ICefBrowser;
      FFrame   : ICefFrame;

      procedure visit(const document: ICefDomDocument); override;

    public
      constructor Create(const browser: ICefBrowser; const frame: ICefFrame; const proc: TCefDomVisitorProc2); reintroduce; virtual;
      destructor  Destroy; override;
  end;
  

 

콜백함수 타입인 "TCefDomVisitorProc" 또는 "TCefDomVisitorProc2"는 "uCEFInterfaces.pas"유닛에 다음과 같이 정의되어 있는데 이 함수 타입을 별도로 정의하여 사용할 수도 있습니다.

  unit uCEFInterfaces;
  ...
  TCefDomVisitorProc                   = {$IFDEF DELPHI12_UP}reference to{$ENDIF} procedure(const document: ICefDomDocument);
  TCefDomVisitorProc2                  = {$IFDEF DELPHI12_UP}reference to{$ENDIF} procedure(const browser : ICefBrowser; const frame: ICefFrame; const document: ICefDomDocument);
  

 

어플리케이션 이벤트 핸들러에서 메시지명에 따라 다음과 같이 VisitDOM을 호출할 수 있습니다

// HTML을 얻는 Visitor 콜백함수
procedure DOMVisitor_GETHTML(const browser: ICefBrowser; const frame: ICefFrame; const document: ICefDomDocument);
var
  msg: ICefProcessMessage;
begin
  // 이 메시지는 TChromium.OnProcessMessageReceived 이벤트 핸들러에서 받을 수 있다.
  msg := TCefProcessMessageRef.New(DOMVISITOR_MSGNAME_FULL);
  msg.ArgumentList.SetString(0, document.Body.AsMarkup);
  frame.SendProcessMessage(PID_BROWSER, msg); //처리 결과를 웹브라우저(PID_BROWSER)로 보낸다.
end;

// TEXT를 얻는 Visitor 콜백함수
procedure DOMVisitor_GETTEXT(const browser: ICefBrowser; const frame: ICefFrame; const document: ICefDomDocument);
var
  msg: ICefProcessMessage;
begin
  // 이 메시지는 TChromium.OnProcessMessageReceived 이벤트 핸들러에서 받을 수 있다.
  msg := TCefProcessMessageRef.New(DOMVISITOR_MSGNAME_FULL);
  msg.ArgumentList.SetString(0, document.Body.AsText);
  frame.SendProcessMessage(PID_BROWSER, msg); //처리 결과를 웹브라우저(PID_BROWSER)로 보낸다.
end;

procedure GlobalCEFApp_OnProcessMessageReceived(const browser       : ICefBrowser;   //메시지를 전달받은 브라우저(TChromium.browser)
                                                const frame         : ICefFrame;     //메시지를 전달받은 프레임
                                                      sourceProcess : TCefProcessId; //프로세스ID
                                                const message       : ICefProcessMessage; //전달받은 메시지
                                                var   aHandled      : boolean);      //처리여부 결정
begin
  aHandled := False;

  if (browser <> nil) then
    begin
      if (message.name = 'HTML얻기') then
        begin
          TempFrame := browser.MainFrame;

          if (TempFrame <> nil) then
            begin
              TempVisitor := TCefFastDomVisitor2.Create(browser, TempFrame, DOMVisitor_GETHTML);
              TempFrame.VisitDom(TempVisitor);
            end;
          aHandled := True;
        end
       else
        if (message.name = 'TEXT얻기') then
          begin
            TempFrame := browser.MainFrame;
            if (TempFrame <> nil) then
              begin
                TempVisitor := TCefFastDomVisitor2.Create(browser, TempFrame, DOMVisitor_GETTEXT);
                TempFrame.VisitDom(TempVisitor);
              end;
            aHandled := True;
          end
         else
         ....

 

3.4 VisitDOM 처리 결과 받기

VisitDOM콜백 함수에 의해 실행된 결과 메시지를 보냈다면 TChromium.OnProcessMessageReceived 핸들러에서 메시지를 받을 수 있습니다.

procedure TDOMVisitorFrm.Chromium1ProcessMessageReceived(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame; sourceProcess: TCefProcessId;
  const message: ICefProcessMessage; out Result: Boolean);
begin
  Result := False;

  if (message = nil) or (message.ArgumentList = nil) then exit;

  if (message.Name = DOMVISITOR_MSGNAME_GETHTML) then
  begin
      ShowStatusText('DOM Visitor result text : ' + message.ArgumentList.GetString(0));
      Result := True;
  end
  else
  if (message.Name = DOMVISITOR_MSGNAME_GETTEXT) then
  begin
      ShowStatusText('DOM Visitor result text : ' + message.ArgumentList.GetString(0));
      Result := True;
  end
  ....
end;