2010. 11. 3. 13:52

CHtmlView의 C++에서 생성한 JScript를 Inject하여 실행하기

CHtmlView(CDHtmlDialog, CHtmlDialog)등에서 HTML을 로드하여 실행하게 됩니다.
혹시 이런 생각을 해보신적 있는지요?

기존 HTML 소스에 동적으로 JScript를 추가하고, 그 함수를 호출받고 싶다...
단, 추가할 JScript 소스는 C++에서 명시적으로 정의한다.
==>
즉, 동일한 URL에 대해 기존의 일반 웹 브라우저와 조금 다른 웹(즉, 뭐.. 화면 상단에 강제적인 버튼 추과와 그 처리등등...)을 표현하고 싶다.
즉, C++에서 DocumentComplete Timing때, C++에서 정의한 JScript 함수를 넣는다. Body OnLoad()에 그 함수를 대체해서 넣는다. 즉, 기존 Html이
function OnLoad()
{
 alert('hello');
}
였다면,
function OnLoad()
{
 alert('hello'); // <- 기존 내용
 inject();
}
function inject()
{
 alert('injected');
}
와 같이 수정하면, inject()가 실행될 것입니다.
즉, C++의 DocumentComplete에서,
InjectScript(..., "function OnLoad(){alert('hello');inject();}function inject(){alert('injected');}", ...)
를 호출하는 것입니다.

이를 위한 소스 코드를 아래와 같이 공유합니다.
사용은 CHtmlView의 OnDocumentComplete나 DocumentComplete 때 아래 함수를 호출하면 됩니다.
(view plain을 누르시면 코드 확인이 쉽습니다.)
HRESULT InjectJScript(IN CHtmlView* pcWnd, IN LPCTSTR lpszJScript)
{
	if ((NULL == pcWnd) || (NULL == lpszJScript))
	{
		return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
	}

	return InjectJScript(pcWnd->GetHtmlDocument(), lpszJScript);
}

// HTML에 JScript를 이식시킨다.
HRESULT InjectJScript(IN LPDISPATCH pDispatch, IN LPCTSTR lpszJScript)
{
	HRESULT						hr					= S_OK;
	IHTMLDocument2*				pIHtmlDocument2		= NULL;
	BSTR						bstrScript			= {0,};
	BSTR						bstrElementType		= {0,};
	BSTR						bstrInsertWhere		= {0,};
	IHTMLElement*				pIHtmlElement		= NULL;
	IHTMLElement2*				pIHtmlElement2		= NULL;
	IHTMLElement*				pIHtmlElementScript	= NULL;
	IHTMLScriptElement*			pIHtmlScript		= NULL;

	if ((NULL == pDispatch) || (NULL == lpszJScript))
	{
		hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
		goto FINAL;
	}

	bstrScript		= ::SysAllocString(T2COLE(lpszJScript));
	bstrElementType = ::SysAllocString(T2COLE(TEXT("script")));
	bstrInsertWhere	= ::SysAllocString(T2COLE(TEXT("afterBegin")));

	hr = pDispatch->QueryInterface(IID_IHTMLDocument2, (VOID**)&pIHtmlDocument2);
	if (FAILED(hr))
	{
		pIHtmlDocument2 = NULL;
		ASSERT(FALSE);
		goto FINAL;
	}

	hr = pIHtmlDocument2->get_body(&pIHtmlElement);
	if (FAILED(hr))
	{
		pIHtmlElement = NULL;
		ASSERT(FALSE);
		goto FINAL;
	}

	hr = pIHtmlElement->QueryInterface(IID_IHTMLElement2, (VOID**)&pIHtmlElement2);
	if (FAILED(hr))
	{
		pIHtmlElement2 = NULL;
		ASSERT(FALSE);
		goto FINAL;
	}

 	hr = pIHtmlDocument2->createElement(bstrElementType, &pIHtmlElementScript);
	if (FAILED(hr))
	{
		pIHtmlElementScript = NULL;
		ASSERT(FALSE);
		goto FINAL;
	}

	hr = pIHtmlElementScript->QueryInterface(IID_IHTMLScriptElement, (VOID**)&pIHtmlScript);
	if (FAILED(hr))
	{
		pIHtmlScript = NULL;
		ASSERT(FALSE);
		goto FINAL;
	}

	hr = pIHtmlScript->put_defer(VARIANT_TRUE);
	if (FAILED(hr))
	{
		ASSERT(FALSE);
		goto FINAL;
	}

	hr = pIHtmlScript->put_text(bstrScript);
	if (FAILED(hr))
	{
		ASSERT(FALSE);
		goto FINAL;
	}

	hr = pIHtmlElement2->insertAdjacentElement(bstrInsertWhere, pIHtmlElementScript, NULL);
	if (FAILED(hr))
	{
		ASSERT(FALSE);
		goto FINAL;
	}

	// 여기까지 왔다면, 성공~
	hr = S_OK;

FINAL:

	if (NULL != pIHtmlScript)
	{
		pIHtmlScript->Release();
		pIHtmlScript = NULL;
	}

	if (NULL != pIHtmlElementScript)
	{
		pIHtmlElementScript->Release();
		pIHtmlElementScript = NULL;
	}

	if (NULL != pIHtmlElement2)
	{
		pIHtmlElement2->Release();
		pIHtmlElement2 = NULL;
	}

	if (NULL != pIHtmlElement)
	{
		pIHtmlElement->Release();
		pIHtmlElement = NULL;
	}

	if (NULL != pIHtmlDocument2)
	{
		pIHtmlDocument2->Release();
		pIHtmlDocument2 = NULL;
	}

	::SysFreeString(bstrScript);
	::SysFreeString(bstrElementType);
	::SysFreeString(bstrInsertWhere);

	return hr;
}

ps)
위 코드의 중심은 insertAdjacentElement인데, 좀더 연구를 하면, 기존의 함수를 그대로 두고, OnLoad시 기존의 함수는 그대로 실행되고, 그때 연속하여 Inject 함수를 실행할 수 있게금 할 수 도 있어 보입니다. 이건 노력 여부에 달린 문제네요... 물론, 단순하게 JScript 대신 Tag Element로 insertAdjacentElement하여 해결될 수 있습니다.
하지만, 일반 Tag Element를 insertAdjacentElement하는 예는 많은데, 순수하게 JScript만 되는 경우는 없어서 한번 만들어본 내용입니다.