MFC WebBrowser 컨트롤 스크립트 오류, FEATURE_BROWSER_EMULATION으로 잡는 법
TL;DR — MFC 앱에 박힌 WebBrowser 컨트롤이 옛 IE7 엔진으로 동작해 스크립트 오류가 터지는 원인을, FEATURE_BROWSER_EMULATION 레지스트리로 IE11까지 강제하는 방법으로 정리했습니다.
문제 상황: "스크립트 오류" 팝업이 끊이지 않는다
C++/MFC로 만든 데스크톱 애플리케이션 안에 CHtmlView나 WebBrowser ActiveX 컨트롤을 박아 HTML 화면을 띄우는 일은 여전히 현장에서 흔합니다. 사내 ERP, 결제 모듈, 리포트 뷰어처럼 "윈도우 네이티브 앱 + 웹 화면" 구조가 필요할 때 가장 손쉬운 선택지였기 때문입니다.
그런데 막상 최신 자바스크립트가 들어간 페이지를 띄우면 다음과 같은 증상이 줄줄이 나옵니다.
- "이 페이지의 스크립트에서 오류가 발생했습니다" 팝업이 반복 노출
Promise,fetch,let/const, 화살표 함수 같은 ES6 문법이 그냥 죽음- CSS 플렉스박스/그리드가 깨지거나 레이아웃이 무너짐
- 같은 페이지를 크롬·엣지로 열면 멀쩡한데 앱 안에서만 깨짐
코드를 아무리 들여다봐도 자바스크립트 자체에는 문제가 없습니다. 원인은 코드가 아니라 컨트롤이 사용하는 렌더링 엔진의 버전에 있습니다.
원인: WebBrowser 컨트롤은 기본적으로 IE7 모드로 동작한다
WebBrowser 컨트롤(그리고 MFC의 CHtmlView)은 내부적으로 시스템에 설치된 Internet Explorer의 렌더링 엔진(MSHTML)을 그대로 빌려 씁니다. 문제는 여기서 발생합니다.
윈도우에 IE11이 깔려 있어도, 애플리케이션이 호스팅하는 WebBrowser 컨트롤은 별다른 설정이 없으면 IE7 호환 모드(quirks mode)로 떨어집니다. 마이크로소프트가 하위 호환성을 깨지 않으려고 일부러 보수적인 기본값을 둔 것입니다.
즉, 운영체제는 2020년대인데 앱 속 브라우저 엔진만 2006년 수준으로 동작하는 셈입니다. 그러니 ES6는 물론이고 표준 DOM API 상당수가 동작하지 않고, 멀쩡한 최신 페이지가 "스크립트 오류"를 토해냅니다.
이 동작은 FEATURE_BROWSER_EMULATION이라는 Feature Control 레지스트리 키로 제어됩니다. 여기에 실행 파일 이름을 등록하고 원하는 IE 버전 값을 넣어 주면, 그 프로세스가 띄우는 WebBrowser 컨트롤이 지정한 버전으로 렌더링합니다.
해결 단계: 레지스트리에 에뮬레이션 버전 등록하기
핵심은 "내 실행 파일을 특정 IE 버전으로 동작하도록 못 박는 것"입니다. 시스템 비트(32/64)에 따라 키 경로가 다르다는 점만 주의하면 됩니다.
1단계 — 시스템 비트에 맞는 레지스트리 경로 찾기
64비트 윈도우에서 32비트로 빌드된 앱(대부분의 레거시 MFC 앱이 여기 해당)은 Wow6432Node 경로를 써야 합니다.
; 32비트 OS, 또는 64비트 앱
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION
; 64비트 OS에서 32비트 앱
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION
팁: 32비트 앱인데 헷갈린다면 둘 다 등록해도 무방합니다. 또한 관리자 권한이 없는 환경에서는
HKEY_LOCAL_MACHINE(HKLM) 대신HKEY_CURRENT_USER(HKCU)의 같은 하위 경로에 써도 동일하게 적용됩니다. 사용자 권한만으로 배포해야 하는 일반 사내 PC에서는 오히려 HKCU가 안전합니다.
2단계 — 실행 파일 이름으로 DWORD 값 생성
FEATURE_BROWSER_EMULATION 키 아래에 DWORD(32비트) 값을 만들고, 이름은 확장자를 포함한 실행 파일 이름으로 지정합니다.
예를 들어 앱 실행 파일이 ReportViewer.exe라면 값 이름도 ReportViewer.exe가 됩니다.
3단계 — 원하는 IE 버전 값 입력 (10진수)
값 데이터는 반드시 10진수 기준으로 넣습니다. 레지스트리 편집기에서 입력할 때 "10진수" 라디오 버튼을 선택하세요.
| 입력 값(10진수) | 렌더링 모드 |
|---|---|
| 11001 | IE11 표준 모드 (권장) |
| 11000 | IE11 (페이지에 !DOCTYPE 있으면 표준, 없으면 IE7) |
| 10001 | IE10 표준 모드 |
| 9999 | IE9 표준 모드 |
| 8888 | IE8 표준 모드 |
| 7000 | IE7 (기본값) |
대부분은 11001을 넣으면 됩니다. 페이지에 <!DOCTYPE html> 선언만 제대로 있다면 IE11 표준 엔진으로 렌더링되어 ES6 일부와 표준 DOM이 살아납니다.
4단계 — 저장 후 앱 재실행
레지스트리는 프로세스 시작 시점에 한 번 읽힙니다. 따라서 값을 바꿨다면 앱을 완전히 종료했다가 다시 켜야 적용됩니다.
코드로 자동화하기: 첫 실행 시 스스로 등록
수동 레지스트리 편집은 배포·유지보수에 취약합니다. 실무에서는 앱이 시작될 때 자기 실행 파일 이름을 자동으로 등록하게 만드는 편이 훨씬 안정적입니다. 아래는 Win32 레지스트리 API를 쓴 예시입니다(변수명·구조는 직접 새로 작성).
#include <Windows.h>
#include <string>
// 현재 실행 파일의 파일명(예: ReportViewer.exe)만 추출
static std::wstring GetSelfExeName()
{
wchar_t fullPath[MAX_PATH] = { 0 };
GetModuleFileNameW(nullptr, fullPath, MAX_PATH);
std::wstring path = fullPath;
size_t pos = path.find_last_of(L"\\/");
return (pos == std::wstring::npos) ? path : path.substr(pos + 1);
}
// WebBrowser 컨트롤을 IE11 표준 모드로 강제 등록
// HKCU에 쓰므로 관리자 권한 없이도 동작한다.
bool ForceBrowserEmulation(DWORD ieMode = 11001)
{
const wchar_t* subKey =
L"Software\\Microsoft\\Internet Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION";
HKEY hKey = nullptr;
LONG rc = RegCreateKeyExW(
HKEY_CURRENT_USER, subKey, 0, nullptr,
REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, &hKey, nullptr);
if (rc != ERROR_SUCCESS) return false;
const std::wstring exeName = GetSelfExeName();
rc = RegSetValueExW(
hKey, exeName.c_str(), 0, REG_DWORD,
reinterpret_cast<const BYTE*>(&ieMode), sizeof(ieMode));
RegCloseKey(hKey);
return (rc == ERROR_SUCCESS);
}
이 함수를 CWinApp::InitInstance() 초기에 한 번 호출해 두면, 사용자가 별도 설정 없이도 항상 IE11 표준 모드로 페이지를 보게 됩니다.
BOOL CMyApp::InitInstance()
{
// 메인 윈도우와 WebBrowser 컨트롤 생성보다 먼저 호출하는 것이 안전
ForceBrowserEmulation(11001);
CWinAppEx::InitInstance();
// ... 이하 기존 초기화 코드 ...
return TRUE;
}
중요: 레지스트리 값은 프로세스 시작 시 읽힌다고 했습니다. 따라서
InitInstance에서 값을 쓰더라도 이미 같은 프로세스에서 만들어진 WebBrowser 컨트롤에는 소급 적용되지 않을 수 있습니다. 가장 확실한 방법은 "처음 실행 시 값이 없으면 등록 → 재실행 안내" 또는 인스톨러 단계에서 등록하는 것입니다. 단, 대다수 환경에서는 컨트롤 생성 전에만 써 두면 정상 동작합니다.
흔한 실수와 엣지 케이스
- 16진수로 잘못 입력:
11001을 16진수 칸에 넣으면 전혀 다른 값(약 69,633)이 됩니다. 반드시 10진수로 입력하세요. - 64비트 OS + 32비트 앱인데 Wow6432Node를 빼먹음: 가장 흔한 "왜 안 되지?" 케이스입니다. 32비트 프로세스가 HKLM에 쓰면 윈도우가 자동으로 Wow6432Node로 리다이렉트하지만, regedit으로 손수 만질 땐 경로를 직접 확인해야 합니다.
- DOCTYPE 누락: 값을 11000으로 넣었는데 페이지에
<!DOCTYPE html>이 없으면 여전히 IE7로 떨어집니다. 11001은 DOCTYPE과 무관하게 표준 모드를 강제하므로 더 안전합니다. - IE11이 최대 한계: 이 방식은 어디까지나 시스템에 설치된 IE 엔진을 쓰는 것이라, 크롬·엣지(Chromium) 수준의 모던 자바스크립트는 지원하지 않습니다.
fetch나async/await까지 필요하면 polyfill을 넣거나, 근본적으로는 WebView2(Edge Chromium 기반)로 전환을 검토해야 합니다. - HKLM 쓰기 권한 부족: 일반 사용자 계정에서 HKLM 쓰기가 막히면 조용히 실패합니다. 위 예제처럼 HKCU에 쓰면 권한 문제를 피할 수 있습니다.
요약
- MFC WebBrowser 컨트롤은 기본적으로 IE7 모드라 최신 페이지에서 스크립트 오류가 난다.
FEATURE_BROWSER_EMULATION레지스트리에 실행 파일명 = 11001(IE11 표준) DWORD를 등록하면 해결된다.- 64비트 OS의 32비트 앱은 Wow6432Node 경로, 권한이 없으면 HKCU를 쓴다.
- 수동 편집보다 앱 시작 시 자동 등록 코드가 운영에 안전하다.
- 진짜 모던 웹이 필요하면 장기적으로는 WebView2 전환이 정답이다.
AI에게 물어볼 때 (프롬프트 팁)
이런 레거시 환경 문제는 맥락을 정확히 주면 AI가 훨씬 정밀한 답을 줍니다. ChatGPT나 Claude에 질문할 때 아래처럼 환경·증상·시도를 한 번에 명시하세요.
나는 Visual Studio 2019, MFC(32비트 빌드) 앱에서 CHtmlView로 사내 웹페이지를 띄운다.
페이지의 ES6(let/const, 화살표 함수)에서 "스크립트 오류"가 발생한다.
운영체제는 64비트 Windows 11이고, IE11이 설치돼 있다.
FEATURE_BROWSER_EMULATION을 코드로 자동 등록하려고 하는데,
HKCU/HKLM/Wow6432Node 중 어디에 써야 하는지와 값 11001 vs 11000 차이를
표로 정리하고, InitInstance에 넣을 예제 코드까지 줘.
FEATURE_BROWSER_EMULATION을 11001로 설정했는데도 fetch와 Promise가 동작하지 않는다.
이 한계가 IE11 엔진 자체의 제약인지 확인하고,
(1) polyfill로 우회하는 방법과 (2) WebView2로 전환하는 방법의
장단점·마이그레이션 난이도를 비교해줘. 우리는 점진적 교체를 원한다.
핵심은 "OS 비트 / 앱 비트 / 컨트롤 종류 / 실패한 시도" 네 가지를 빠짐없이 적는 것입니다. 이렇게 조건을 구조화해 질문하면 AI가 일반론 대신 내 환경에 맞는 답을 냅니다. 더 정교한 기술 프롬프트 작성법은 Prompt Architect의 프롬프트 분석기와 가이드에서 점검해 보세요.