'2020/04/27'에 해당되는 글 1건

  1. 2020.04.27 CEF# JavaScript Binding(JSB) 337
Windows2020. 4. 27. 16:20

CEF javascript 연동

.NET에서는 다음과 같이 자바스크립트 메서드를 호출한다.

browser.ExecuteScriptAsync("document.body.style.background = 'red';");

browser.ExecuteJavaScriptAsync("(function(){ document.getElementsByName('q')[0].value = 'CefSharp Was Here!'; document.getElementsByName('btnK')[0].click(); })();");

여러개의 프래임으로 구성된 경우 임의 프레임의 스크립트를 다음과 같이 호출 한다.

browser.GetBrowser().GetFrame("SubFrame").ExecuteJavaScriptAsync("document.body.style.background = 'red';");


그럼 언제 자바스크립트를 호출할 수 있는가?

자바스크립트는 V8Context에서만 호출할 수 있다.
IRenderProcessMessageHandler의 OnContextCreated와 OnContextReleased 가 자바스크립트가 호출될 수 있는 환경의 범위를 제공한다. 각 프레임별로 호출되며 frame.IsMain으로 메인프레임여부를 판가름할 수 있다.

OnFrameLoadStart에서 DOM에 접근할 수 있으며 로딩이 완료되기 던에 DOM의 스크립트를 실행할 수 있다.

browser.RenderProcessMessageHandler = new RenderProcessMessageHandler();

public class RenderProcessMessageHandler : IRenderProcessMessageHandler {

  // Wait for the underlying JavaScript Context to be created. This is only called for the main frame.
  // If the page has no JavaScript, no context will be created.
  void IRenderProcessMessageHandler.OnContextCreated(IWebBrowser browserControl, IBrowser browser, IFrame frame)
  {
    const string script = "document.addEventListener('DOMContentLoaded', function(){ alert('DomLoaded'); });";

    frame.ExecuteJavaScriptAsync(script);
  }
}

//Wait for the page to finish loading (all resources will have been loaded, rendering is likely still happening)
browser.LoadingStateChanged += (sender, args) =>
{
  //Wait for the Page to finish loading
  if (args.IsLoading == false)
  {
    browser.ExecuteJavaScriptAsync("alert('All Resources Have Loaded');");
  }
}

//Wait for the MainFrame to finish loading
browser.FrameLoadEnd += (sender, args) =>
{
  //Wait for the MainFrame to finish loading
  if(args.Frame.IsMain)
  {
    args.Frame.ExecuteJavaScriptAsync("alert('MainFrame finished loading');");
  }
};

* 스크립트는 프레임레벨에서 수행되고 모든 페이지는 한개 이상의 프레임으로 구성된다.
* IWebBrowser.ExecuteScriptAsync 확장메서드는 하위호환성을 위해 남겨 있으며 main frame에서 js를 수행하는 숏컷처럼 사용가능하다.
* frame이 자바스크립트를 포함하지 않으면 V8Context가 생성되지 않는다.
* frame이 로드된 후 context가 없는 경우 IFrame.ExecuteJavaScriptAsync를 호출하여 V8Context을 생성할 수 있다.
* OnFrameLoadStart가 호출될때 DOM 로딩이 완료되지 않는다.
* IRenderProcessMessageHandler.OnContextCreated/OnContextReleased는 메인프레임에서만 호출된다.

결과를 리턴하는 자바스크립트 메서드의 호출은 ?

//An extension method that evaluates JavaScript against the main frame.
Task response = await browser.EvaluateScriptAsync(script);
//Evaluate javascript directly against a frame
Task response = await frame.EvaluateScriptAsync(script);

자바스크립트는 비동기로 수행되기 때문에 에러메세지, 결과, 성공플래그등을 포함하는 Task를 반환한다. 
자바스크립트가 수행될때 기본적으로 알아야 할 사항은 다음과 같다.

* 언제 호출하는게 가능한지에 대해 알아야 하며 위에서 설명하였다.
* 프레임레벨에서 스크립트가 수행되고 모든 페이지는 하나이상의 프레임으로 구성된다.
* 스크립트는 렌더프로세스내에서 수행되고 성능상의 이유로 IPC를 통해 전달되고 데이터만 반환한다.
* 프리미티브 타입인 int, double, date, bool과 string이 제공된다.
* 결과로 객체가 제공되고 IDictionary<string, object>형태로 접근을 더 용이하게하기 위해 dynamic 키워드가 제공된다.
* 프리미티브타입이나 앞서 얘기한 객체가 IList

형태로 배열이 결과로 제공될 수 있다.
* HTMLCollection같은 배열과 유사한 객체의 경우 Array.from으로 배열로 반환하여 사용될수 없다.
* 반환되는 객체 그래프의 복잡성이 제한이 있다. 자바스크립트 JSON.toStringify()로 자바스크립트 객체를 JSON문자열로 변환하고 다시 .NET객체로 디코디할수 있다(참조: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)

// Get Document Height
var task = frame.EvaluateScriptAsync("(function() { var body = document.body, html = document.documentElement; return  Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); })();");

//Continue execution on the UI Thread
task.ContinueWith(t =>
{
    if (!t.IsFaulted)
    {
        var response = t.Result;
        EvaluateJavaScriptResult = response.Success ? (response.Result ?? "null") : response.Message;
    }
}, TaskScheduler.FromCurrentSynchronizationContext());

//As stated above, it's best to return only the data you require, these examples demo taking a `HTMLCollection` and returning and returning a simplified representation.

//Get all the span elements and create an array that contains their innerText
Array.from(document.getElementsByTagName('span')).map(x => ( x.innerText));
//Get all the a tags and create an array that contains a list of objects 
//Second param is the mapping function
Array.from(document.getElementsByTagName('a'), x => ({ innerText : x.innerText, href : x.href }));


.NET 클래스를 자바스크립트에 바인딩하는 방법!!

JSB(JavaScript Binding)sms JavaScript와 .NET간의 통신을 가능하게 한다. 동기, 비동기방식 모두 지원한다. Sync버전은 더이상 개발되지 않고 있다.

Async JavaScript Binding

1. 바인딩할 클래스 정의

public class BoundObject {
public int Add(int a, int b) {
return a+b;
}
}

2. JavaScriptObjectRepository로 클래스인스턴스 등록

첫번째 방법
//For async object registration (equivalent to the old RegisterAsyncJsObject)
browser.JavascriptObjectRepository.Register("boundAsync", new BoundObject(), true, options);

두번째 방법
browser.JavascriptObjectRepository.ResolveObject += (sender, e) => {
var repo = e.ObjectRepository;
if (e.ObjectName == "boundAsync")
{
BindingOptions bindingOptions = null; //Binding options is an optional param, defaults to null
bindingOptions = BindingOptions.DefaultBinder //Use the default binder to serialize values into complex objects, CamelCaseJavascriptNames = true is the default
bindingOptions = new BindingOptions { CamelCaseJavascriptNames = false, Binder = new MyCustomBinder() }); //No camelcase of names and specify a default binder
repo.Register("boundAsync", new BoundObject(), isAsync: true, options: bindingOptions);
}
};

객체가 JavaScript로 바인딩 된 경우 .Net에 알림을 받으려면 ObjectBoundInJavascript 이벤트 또는 ObjectsBoundInJavascript 이벤트를 구독 할 수 있다 (두 이벤트 모두 명백히 유사 함).

browser.JavascriptObjectRepository.ObjectBoundInJavascript += (sender, e) => {
var name = e.ObjectName;

Debug.WriteLine($"Object {e.ObjectName} was bound successfully.");
};    

3. 스크립트에서 CefSharp.BindObjectAsync 호출

<script type="text/javascript">

(async function() {

  await CefSharp.BindObjectAsync("boundAsync");

  // 

  boundAsync.add(16, 5).then(function (actualResult) {

    const expectedResult = 21;

    assert.equal(expectedResult, actualResult, "Add 16 + 5 resulte in " + expectedResult);

  });

})();

</script>

 

CefSharp.BindObjectAsync가 호출되면 JavascriptObjectRepository가 주어진 이름으로 인스턴스가 등록되어 있는지 확인한다. 만일 등록이 안되어 있다면 ResolveObject이벤트가 발생한다. 파라메터 없이 CefSharp.BindObjectAsync가 호출되면 등록된 경우 바운드가 되고 등록이 안되었으면 ObjectName을 All로 설정하여 ResolveObject가 모두 호출된다.

example) https://github.com/cefsharp/CefSharp.MinimalExample/tree/demo/javascriptbinding

 

 

Posted by 삼스