본문 바로가기

프로그래밍/Chromium(CEF4Delphi)

[Chromium] CEF4Delphi - RTTI를 이용한 JavaScript 실행 및 결과 처리

1. 개요

  임베딩된 크롬 웹브라우저에 사용자 정의 자바스크립트(JavaScript)를 강제로 실행 시킬수 있고, 자바스크립트 내에 델파이코드로 작성된 델파이 객체를 직접 호출할 수 있으며, 실행 결과를 어플리케이션에서 받아서 적절한 처리를 할 수 있다. 

2. 주요 기능 정의

  • 임베딩된 웹브라우저의 Context Menu(팝업메뉴)에 사용자 정의 메뉴 항목을 추가한다.
  • 추가된 Context Menu 항목에 해당하는 실행 코드를 작성할 수 있다.
  • 자바스크립트 내에서 호출 가능한 델파이 확장 객체를 정의 하고 웹브라우저에 등록 할 수 있다.
  • 사용자 정의 자바스크립트를 지정된 웹브라우저 프레임에 실행시킬 수 있다.
  • 실행된 자바스크립트의 결과를 받아서 델파이 코드 내에서 처리할 수 있다.

3. 구현하기

가. 델파이 확장 객체 정의

  자바스크립트에서 호출 가능한 확장 객체는 일반 클래스로 정의하되 호출할 함수 및 프로시저는 class 함수 및 프로시저로 작성되어야 한다.

  또한 자바스크립트 내에서 실행 되어야 하기 때문에 Visual Component에 대한 사용은 제한 된다.

  확장객체 예제 에서는 자바스크립트에서 호출한 함수 및 프로시저에서는 "ICefProcessMessage" 메시지 객체를 생성하여 웹브라우저에 파라미터를 포함한 처리 메시지를 보내도록 작성 되었다

  웹브라우저를 통해 전달된 메시지는 TChromium.OnProcessMessageReceived 이벤트 핸들러에서 해당 메시지 정보를 받아서 처리를 할 수 있다.

uses
  {$IFDEF DELPHI16_UP}
  Winapi.Windows,
  {$ELSE}
  Windows,
  {$ENDIF}
  uCEFRenderProcessHandler, uCEFBrowserProcessHandler, uCEFInterfaces, uCEFProcessMessage,
  uCEFv8Context, uCEFTypes, uCEFv8Handler;
const
 MOUSEOVER_MESSAGE_NAME        = 'mouseover';
 CUSTOMNAME_MESSAGE_NAME     = ‘customname’;
type
  TTestExtension = class
    class procedure mouseover(const data: string);
    class procedure sendresulttobrowser(const msgtext, msgname : string);
  end;

…
class procedure TTestExtension.mouseover(const data: string);
var
  msg: ICefProcessMessage;
begin
  TRACE('mouseover'); //Visual Component를 사용하지 않는 일반 함수 호출은 동작
  ShowMessage('TEST'); //Visual Component를 변경하는 함수 호출시 무시됨.
  msg := TCefProcessMessageRef.New(MOUSEOVER_MESSAGE_NAME);
  msg.ArgumentList.SetString(0, data);

  // 웹브라우저에 되돌려 주기 위한 메시지 생성     
  // TChromium.OnProcessMessageReceived 에서 이벤트를 수신
  // TCefv8ContextRef.Current는 현재 자바스크립트가 실행된 프레임에
  // 대한 V8 Context를 리턴함
  TCefv8ContextRef.Current.Browser.MainFrame.SendProcessMessage(PID_BROWSER, msg);
end;

//여기서는 자바스크립트 호출시 메시지 이름을 직접 넣어서 호출할 수 있도록 했다.
class procedure TTestExtension.sendresulttobrowser(const msgtext, msgname : string);
var
  msg: ICefProcessMessage;
begin
  TRACE('sendresulttobrowser');
  msg := TCefProcessMessageRef.New(msgname);
  msg.ArgumentList.SetString(0, msgtext);

  TCefv8ContextRef.Current.Browser.MainFrame.SendProcessMessage(PID_BROWSER, msg);
end;

 

나. 런타임 확장 객체를 웹브라우저에 등록

  런타임 확장 객체를 웹브라우저에 등록하기 위해서는 크롬어플리케이션 초기화시에 "GlobalCEFApp.OnWebKitInitialized" 이벤트 핸들러를 만들어 지정하도록 한다.

//이벤트 핸들러
procedure GlobalCEFApp_OnWebKitInitialized;
begin
{$IFDEF DELPHI14_UP}
  TCefRTTIExtension.Register('myextension', TTestExtension);
{$ENDIF}
end;

procedure CreateGlobalCEFApp;
begin
  GlobalCEFApp                     := TCefApplication.Create;
  GlobalCEFApp.OnWebKitInitialized := GlobalCEFApp_OnWebKitInitialized;//확장객체 이벤트 핸들러 지정
  GlobalCEFApp.DisableFeatures     := 'NetworkService,OutOfBlinkCors';
end;

 

다. 웹브라우저 팝업메뉴(Context Menu) 항목 추가 등록

임베딩된 웹브라우저의 Context Menu항목을 지정하기 위해서는 "TChromium.OnBeforeContextMenu"이벤트 핸들러에서 다음과 같이 사용자 메뉴를 추가하는데, 추가될 각 메뉴 항목에 대한 메뉴ID를 지정해 주어야 한다.

추가할 메뉴ID는 기 정의된 "const MENU_ID_USER_FIRST = 26500;" 로 부터 증가되는 번호로 지정하길 권고 한다.

const
  MINIBROWSER_CONTEXTMENU_SETJSEVENT        = MENU_ID_USER_FIRST + 1;
  MINIBROWSER_CONTEXTMENU_JSVISITDOM        = MENU_ID_USER_FIRST + 2;
  
...
procedure TJSRTTIExtensionFrm.Chromium1BeforeContextMenu(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  const params: ICefContextMenuParams; const model: ICefMenuModel);
begin
  // 사용자가 추가하고 싶은 메뉴항목을 추가 한다.
  model.AddSeparator;
  model.AddItem(MINIBROWSER_CONTEXTMENU_SETJSEVENT,       'Set mouseover event');
  model.AddItem(MINIBROWSER_CONTEXTMENU_JSVISITDOM,       'Visit DOM in JavaScript');
end;

 

라. 추가된 사용자 메뉴(Context Menu) 실행코드 작성

선택된 Context Menu에 대한 실행 코드는 "TChromium.OnContextMenuCommand"에서 commandId 값에 따라 실행 코드를 달리 작성한다.

procedure TJSRTTIExtensionFrm.Chromium1ContextMenuCommand(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  const params: ICefContextMenuParams; commandId: Integer;
  eventFlags: Cardinal; out Result: Boolean);
begin
  Result := False;

  // Context Menu로 추가된 CommandId별 실행 코드를 등록 한다.
  case commandId of
    MINIBROWSER_CONTEXTMENU_SETJSEVENT :
      if (browser <> nil) and (browser.MainFrame <> nil) then
        //자바스크립트 코드를 작성하여 강제로 웹브라우저에게 실행 시켰다.
        browser.MainFrame.ExecuteJavaScript(
          'document.body.addEventListener("mouseover", function(evt){'+
            'function getpath(n){'+
              'var ret = "<" + n.nodeName + ">";'+
              'if (n.parentNode){return getpath(n.parentNode) + ret} else '+
              'return ret'+
            '};'+
            'myextension.mouseover(getpath(evt.target))}'+   //여기서 델파이로 작성된 확장 객체의 프로시저(mouseover)를 호출 했다.
          ')', 'about:blank', 0);

    MINIBROWSER_CONTEXTMENU_JSVISITDOM :
      if (browser <> nil) and (browser.MainFrame <> nil) then
        //자바스크립트 코드를 작성하여 강제로 웹브라우저에게 실행 시켰다.
        browser.MainFrame.ExecuteJavaScript(
          'var testhtml = document.body.innerHTML;' +
          'myextension.sendresulttobrowser(testhtml, ' + quotedstr(CUSTOMNAME_MESSAGE_NAME) + ');',  //여기서 델파이로 작성된 확장 객체의 프로시저(sendresulttobrowser)를 호출 했다.
          'about:blank', 0);
  end;
end;

 

4. 실행 예제

Context Menu "Set mouseover event" 실행 : 마우스가 각 Element위에 있을때 해당 Element의 Tag경로를 상태바에 보여주는 기능
상태바에 현재 마우스 커서가 있는 위치의 Element에 대한 Path 정보를 보여준다
Context Menu "Visit DOM in JavaScript" 실행 : 현재 웹페이지의 Body HTML(innerHTML) 소스를 뷰어를 통해 보여주는 기능
Body Element의 InnerHTML을 보여준다.

 

5. 전체 흐름에 대한 도식