#include "webview.h"
#define CINTERFACE
#include <windows.h>
#include <commctrl.h>
#include <exdisp.h>
#include <mshtmhst.h>
#include <mshtml.h>
#include <shobjidl.h>
#include <stdio.h>
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "oleaut32.lib")
#pragma comment(lib, "uuid.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "user32.lib")
#ifndef DPI_AWARENESS_CONTEXT_SYSTEM_AWARE
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
#endif
struct mshtml_webview {
const char *url;
int width;
int height;
int resizable;
int debug;
int frameless;
webview_external_invoke_cb_t external_invoke_cb;
void *userdata;
HWND hwnd;
IOleObject **browser;
BOOL is_fullscreen;
DWORD saved_style;
DWORD saved_ex_style;
RECT saved_rect;
};
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
static BOOL EnableDpiAwareness();
static int DisplayHTMLPage(struct mshtml_webview *wv);
WEBVIEW_API void webview_free(webview_t w) {
free(w);
}
WEBVIEW_API void* webview_get_user_data(webview_t w) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
return wv->userdata;
}
static inline BSTR webview_to_bstr(const char *s) {
DWORD size = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0);
BSTR bs = SysAllocStringLen(0, size);
if (bs == NULL) {
return NULL;
}
MultiByteToWideChar(CP_UTF8, 0, s, -1, bs, size);
return bs;
}
#define WEBVIEW_KEY_FEATURE_BROWSER_EMULATION \
L"Software\\Microsoft\\Internet " \
"Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION"
static int webview_fix_ie_compat_mode() {
HKEY hKey;
DWORD ie_version = 11000;
WCHAR appname[MAX_PATH + 1];
WCHAR *p;
if (GetModuleFileNameW(NULL, appname, MAX_PATH + 1) == 0) {
return -1;
}
for (p = &appname[wcslen(appname) - 1]; p != appname && *p != L'\\'; p--) {
}
p++;
if (RegCreateKeyW(HKEY_CURRENT_USER, WEBVIEW_KEY_FEATURE_BROWSER_EMULATION,
&hKey) != ERROR_SUCCESS) {
return -1;
}
if (RegSetValueExW(hKey, p, 0, REG_DWORD, (BYTE *)&ie_version,
sizeof(ie_version)) != ERROR_SUCCESS) {
RegCloseKey(hKey);
return -1;
}
RegCloseKey(hKey);
return 0;
}
static const TCHAR *classname = "WebView";
WEBVIEW_API webview_t webview_new(
const char* title, const char* url, int width, int height, int resizable, int debug,
int frameless, webview_external_invoke_cb_t external_invoke_cb, void* userdata) {
if (webview_fix_ie_compat_mode() < 0) {
return NULL;
}
HINSTANCE hInstance = GetModuleHandle(NULL);
if (hInstance == NULL) {
return NULL;
}
HRESULT oleInitCode = OleInitialize(NULL);
if (oleInitCode != S_OK && oleInitCode != S_FALSE) {
return NULL;
}
EnableDpiAwareness();
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = wndproc;
wc.lpszClassName = classname;
RegisterClassEx(&wc);
DWORD style = WS_OVERLAPPEDWINDOW;
if (!resizable) {
style &= ~(WS_SIZEBOX);
}
if (frameless) {
style &= ~(WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
}
HDC screen = GetDC(0);
int DPI = GetDeviceCaps(screen, LOGPIXELSX);
ReleaseDC(0, screen);
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = MulDiv(width, DPI, 96);
rect.bottom = MulDiv(height, DPI, 96);
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0);
RECT clientRect;
GetClientRect(GetDesktopWindow(), &clientRect);
int left = (clientRect.right / 2) - ((rect.right - rect.left) / 2);
int top = (clientRect.bottom / 2) - ((rect.bottom - rect.top) / 2);
rect.right = rect.right - rect.left + left;
rect.left = left;
rect.bottom = rect.bottom - rect.top + top;
rect.top = top;
BSTR window_title = webview_to_bstr(title);
struct mshtml_webview* wv = (struct mshtml_webview*)calloc(1, sizeof(*wv));
wv->width = width;
wv->height = height;
wv->url = url;
wv->resizable = resizable;
wv->debug = debug;
wv->frameless = frameless;
wv->external_invoke_cb = external_invoke_cb;
wv->userdata = userdata;
wv->hwnd =
CreateWindowEx(0, classname, window_title, style, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
HWND_DESKTOP, NULL, hInstance, (void *)wv);
SysFreeString(window_title);
if (wv->hwnd == 0) {
webview_free(wv);
OleUninitialize();
return NULL;
}
SetWindowLongPtr(wv->hwnd, GWLP_USERDATA, (LONG_PTR)wv);
if (wv->frameless) {
SetWindowLongPtr(wv->hwnd, GWL_STYLE, style);
}
DisplayHTMLPage(wv);
ShowWindow(wv->hwnd, SW_SHOWDEFAULT);
UpdateWindow(wv->hwnd);
SetFocus(wv->hwnd);
return wv;
}
#define WM_WEBVIEW_DISPATCH (WM_APP + 1)
typedef struct {
IOleInPlaceFrame frame;
HWND window;
} _IOleInPlaceFrameEx;
typedef struct {
IOleInPlaceSite inplace;
_IOleInPlaceFrameEx frame;
} _IOleInPlaceSiteEx;
typedef struct {
IDocHostUIHandler ui;
} _IDocHostUIHandlerEx;
typedef struct {
IInternetSecurityManager mgr;
} _IInternetSecurityManagerEx;
typedef struct {
IServiceProvider provider;
_IInternetSecurityManagerEx mgr;
} _IServiceProviderEx;
typedef struct {
IOleClientSite client;
_IOleInPlaceSiteEx inplace;
_IDocHostUIHandlerEx ui;
IDispatch external;
_IServiceProviderEx provider;
} _IOleClientSiteEx;
typedef DPI_AWARENESS_CONTEXT (WINAPI *FnSetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
typedef BOOL (WINAPI *FnSetProcessDPIAware)();
#ifdef __cplusplus
#define iid_ref(x) &(x)
#define iid_unref(x) *(x)
#else
#define iid_ref(x) (x)
#define iid_unref(x) (x)
#endif
static inline WCHAR *webview_to_utf16(const char *s) {
DWORD size = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0);
WCHAR *ws = (WCHAR *)GlobalAlloc(GMEM_FIXED, sizeof(WCHAR) * size);
if (ws == NULL) {
return NULL;
}
MultiByteToWideChar(CP_UTF8, 0, s, -1, ws, size);
return ws;
}
static inline char *webview_from_utf16(WCHAR *ws) {
int n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
char *s = (char *)GlobalAlloc(GMEM_FIXED, n);
if (s == NULL) {
return NULL;
}
WideCharToMultiByte(CP_UTF8, 0, ws, -1, s, n, NULL, NULL);
return s;
}
static int iid_eq(REFIID a, const IID *b) {
return memcmp((const void *)iid_ref(a), (const void *)b, sizeof(GUID)) == 0;
}
static HRESULT STDMETHODCALLTYPE JS_QueryInterface(IDispatch *This,
REFIID riid,
LPVOID *ppvObj) {
if (iid_eq(riid, &IID_IDispatch)) {
*ppvObj = This;
return S_OK;
}
*ppvObj = 0;
return E_NOINTERFACE;
}
static ULONG STDMETHODCALLTYPE JS_AddRef(IDispatch *This) { return 1; }
static ULONG STDMETHODCALLTYPE JS_Release(IDispatch *This) { return 1; }
static HRESULT STDMETHODCALLTYPE JS_GetTypeInfoCount(IDispatch *This,
UINT *pctinfo) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE JS_GetTypeInfo(IDispatch *This,
UINT iTInfo, LCID lcid,
ITypeInfo **ppTInfo) {
return S_OK;
}
#define WEBVIEW_JS_INVOKE_ID 0x1000
static HRESULT STDMETHODCALLTYPE JS_GetIDsOfNames(IDispatch *This,
REFIID riid,
LPOLESTR *rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId) {
if (cNames != 1) {
return S_FALSE;
}
if (wcscmp(rgszNames[0], L"invoke") == 0) {
rgDispId[0] = WEBVIEW_JS_INVOKE_ID;
return S_OK;
}
return S_FALSE;
}
static HRESULT STDMETHODCALLTYPE
JS_Invoke(IDispatch *This, DISPID dispIdMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult,
EXCEPINFO *pExcepInfo, UINT *puArgErr) {
size_t offset = (size_t) & ((_IOleClientSiteEx *)NULL)->external;
_IOleClientSiteEx *ex = (_IOleClientSiteEx *)((char *)(This)-offset);
struct mshtml_webview *wv = (struct mshtml_webview *)GetWindowLongPtr(
ex->inplace.frame.window, GWLP_USERDATA);
if (pDispParams->cArgs == 1 && pDispParams->rgvarg[0].vt == VT_BSTR) {
BSTR bstr = pDispParams->rgvarg[0].bstrVal;
char *s = webview_from_utf16(bstr);
if (s != NULL) {
if (dispIdMember == WEBVIEW_JS_INVOKE_ID) {
if (wv->external_invoke_cb != NULL) {
wv->external_invoke_cb(wv, s);
}
} else {
GlobalFree(s);
return S_FALSE;
}
GlobalFree(s);
}
}
return S_OK;
}
static IDispatchVtbl ExternalDispatchTable = {
JS_QueryInterface, JS_AddRef, JS_Release, JS_GetTypeInfoCount,
JS_GetTypeInfo, JS_GetIDsOfNames, JS_Invoke};
static BOOL EnableDpiAwareness() {
HMODULE libUser32 = GetModuleHandleW(L"user32.dll");
if (libUser32) {
FnSetThreadDpiAwarenessContext SetThreadDpiAwarenessContext =
(FnSetThreadDpiAwarenessContext) GetProcAddress(libUser32, "SetThreadDpiAwarenessContext");
if(SetThreadDpiAwarenessContext != NULL) {
if (SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE) != NULL) {
return TRUE;
}
}
FnSetProcessDPIAware SetProcessDPIAware =
(FnSetProcessDPIAware) GetProcAddress(libUser32, "SetProcessDPIAware");
if(SetProcessDPIAware != NULL) {
return SetProcessDPIAware();
}
}
return FALSE;
}
static ULONG STDMETHODCALLTYPE Site_AddRef(IOleClientSite *This) {
return 1;
}
static ULONG STDMETHODCALLTYPE Site_Release(IOleClientSite *This) {
return 1;
}
static HRESULT STDMETHODCALLTYPE Site_SaveObject(IOleClientSite *This) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE Site_GetMoniker(IOleClientSite *This,
DWORD dwAssign,
DWORD dwWhichMoniker,
IMoniker **ppmk) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE
Site_GetContainer(IOleClientSite *This, LPOLECONTAINER *ppContainer) {
*ppContainer = 0;
return E_NOINTERFACE;
}
static HRESULT STDMETHODCALLTYPE Site_ShowObject(IOleClientSite *This) {
return NOERROR;
}
static HRESULT STDMETHODCALLTYPE Site_OnShowWindow(IOleClientSite *This,
BOOL fShow) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE
Site_RequestNewObjectLayout(IOleClientSite *This) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE Site_QueryInterface(IOleClientSite *This,
REFIID riid,
void **ppvObject) {
if (iid_eq(riid, &IID_IUnknown) || iid_eq(riid, &IID_IOleClientSite)) {
*ppvObject = &((_IOleClientSiteEx *)This)->client;
} else if (iid_eq(riid, &IID_IOleInPlaceSite)) {
*ppvObject = &((_IOleClientSiteEx *)This)->inplace;
} else if (iid_eq(riid, &IID_IDocHostUIHandler)) {
*ppvObject = &((_IOleClientSiteEx *)This)->ui;
} else if (iid_eq(riid, &IID_IServiceProvider)) {
*ppvObject = &((_IOleClientSiteEx *)This)->provider;
} else {
*ppvObject = 0;
return (E_NOINTERFACE);
}
return S_OK;
}
static HRESULT STDMETHODCALLTYPE InPlace_QueryInterface(
IOleInPlaceSite *This, REFIID riid, LPVOID *ppvObj) {
return (Site_QueryInterface(
(IOleClientSite *)((char *)This - sizeof(IOleClientSite)), riid, ppvObj));
}
static ULONG STDMETHODCALLTYPE InPlace_AddRef(IOleInPlaceSite *This) {
return 1;
}
static ULONG STDMETHODCALLTYPE InPlace_Release(IOleInPlaceSite *This) {
return 1;
}
static HRESULT STDMETHODCALLTYPE InPlace_GetWindow(IOleInPlaceSite *This,
HWND *lphwnd) {
*lphwnd = ((_IOleInPlaceSiteEx *)This)->frame.window;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
InPlace_ContextSensitiveHelp(IOleInPlaceSite *This, BOOL fEnterMode) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE
InPlace_CanInPlaceActivate(IOleInPlaceSite *This) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
InPlace_OnInPlaceActivate(IOleInPlaceSite *This) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
InPlace_OnUIActivate(IOleInPlaceSite *This) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE InPlace_GetWindowContext(
IOleInPlaceSite *This, LPOLEINPLACEFRAME *lplpFrame,
LPOLEINPLACEUIWINDOW *lplpDoc, LPRECT lprcPosRect, LPRECT lprcClipRect,
LPOLEINPLACEFRAMEINFO lpFrameInfo) {
*lplpFrame = (LPOLEINPLACEFRAME) & ((_IOleInPlaceSiteEx *)This)->frame;
*lplpDoc = 0;
lpFrameInfo->fMDIApp = FALSE;
lpFrameInfo->hwndFrame = ((_IOleInPlaceFrameEx *)*lplpFrame)->window;
lpFrameInfo->haccel = 0;
lpFrameInfo->cAccelEntries = 0;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE InPlace_Scroll(IOleInPlaceSite *This,
SIZE scrollExtent) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE
InPlace_OnUIDeactivate(IOleInPlaceSite *This, BOOL fUndoable) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
InPlace_OnInPlaceDeactivate(IOleInPlaceSite *This) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
InPlace_DiscardUndoState(IOleInPlaceSite *This) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE
InPlace_DeactivateAndUndo(IOleInPlaceSite *This) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE
InPlace_OnPosRectChange(IOleInPlaceSite *This, LPCRECT lprcPosRect) {
IOleObject *browserObject;
IOleInPlaceObject *inplace;
browserObject = *((IOleObject **)((char *)This - sizeof(IOleObject *) -
sizeof(IOleClientSite)));
if (!browserObject->lpVtbl->QueryInterface(browserObject,
iid_unref(&IID_IOleInPlaceObject),
(void **)&inplace)) {
inplace->lpVtbl->SetObjectRects(inplace, lprcPosRect, lprcPosRect);
inplace->lpVtbl->Release(inplace);
}
return S_OK;
}
static HRESULT STDMETHODCALLTYPE Frame_QueryInterface(
IOleInPlaceFrame *This, REFIID riid, LPVOID *ppvObj) {
return E_NOTIMPL;
}
static ULONG STDMETHODCALLTYPE Frame_AddRef(IOleInPlaceFrame *This) {
return 1;
}
static ULONG STDMETHODCALLTYPE Frame_Release(IOleInPlaceFrame *This) {
return 1;
}
static HRESULT STDMETHODCALLTYPE Frame_GetWindow(IOleInPlaceFrame *This,
HWND *lphwnd) {
*lphwnd = ((_IOleInPlaceFrameEx *)This)->window;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
Frame_ContextSensitiveHelp(IOleInPlaceFrame *This, BOOL fEnterMode) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE Frame_GetBorder(IOleInPlaceFrame *This,
LPRECT lprectBorder) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE Frame_RequestBorderSpace(
IOleInPlaceFrame *This, LPCBORDERWIDTHS pborderwidths) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE Frame_SetBorderSpace(
IOleInPlaceFrame *This, LPCBORDERWIDTHS pborderwidths) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE Frame_SetActiveObject(
IOleInPlaceFrame *This, IOleInPlaceActiveObject *pActiveObject,
LPCOLESTR pszObjName) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
Frame_InsertMenus(IOleInPlaceFrame *This, HMENU hmenuShared,
LPOLEMENUGROUPWIDTHS lpMenuWidths) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE Frame_SetMenu(IOleInPlaceFrame *This,
HMENU hmenuShared,
HOLEMENU holemenu,
HWND hwndActiveObject) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE Frame_RemoveMenus(IOleInPlaceFrame *This,
HMENU hmenuShared) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE Frame_SetStatusText(IOleInPlaceFrame *This,
LPCOLESTR pszStatusText) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
Frame_EnableModeless(IOleInPlaceFrame *This, BOOL fEnable) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
Frame_TranslateAccelerator(IOleInPlaceFrame *This, LPMSG lpmsg, WORD wID) {
return E_NOTIMPL;
}
static HRESULT STDMETHODCALLTYPE UI_QueryInterface(IDocHostUIHandler *This,
REFIID riid,
LPVOID *ppvObj) {
return (Site_QueryInterface((IOleClientSite *)((char *)This -
sizeof(IOleClientSite) -
sizeof(_IOleInPlaceSiteEx)),
riid, ppvObj));
}
static ULONG STDMETHODCALLTYPE UI_AddRef(IDocHostUIHandler *This) {
return 1;
}
static ULONG STDMETHODCALLTYPE UI_Release(IDocHostUIHandler *This) {
return 1;
}
static HRESULT STDMETHODCALLTYPE UI_ShowContextMenu(
IDocHostUIHandler *This, DWORD dwID, POINT *ppt,
IUnknown *pcmdtReserved, IDispatch *pdispReserved) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
UI_GetHostInfo(IDocHostUIHandler *This, DOCHOSTUIINFO *pInfo) {
pInfo->cbSize = sizeof(DOCHOSTUIINFO);
pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER | DOCHOSTUIFLAG_DPI_AWARE;
pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE UI_ShowUI(
IDocHostUIHandler *This, DWORD dwID,
IOleInPlaceActiveObject *pActiveObject,
IOleCommandTarget *pCommandTarget,
IOleInPlaceFrame *pFrame, IOleInPlaceUIWindow *pDoc) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE UI_HideUI(IDocHostUIHandler *This) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE UI_UpdateUI(IDocHostUIHandler *This) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE UI_EnableModeless(IDocHostUIHandler *This,
BOOL fEnable) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
UI_OnDocWindowActivate(IDocHostUIHandler *This, BOOL fActivate) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
UI_OnFrameWindowActivate(IDocHostUIHandler *This, BOOL fActivate) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
UI_ResizeBorder(IDocHostUIHandler *This, LPCRECT prcBorder,
IOleInPlaceUIWindow *pUIWindow, BOOL fRameWindow) {
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
UI_TranslateAccelerator(IDocHostUIHandler *This, LPMSG lpMsg,
const GUID *pguidCmdGroup, DWORD nCmdID) {
return S_FALSE;
}
static HRESULT STDMETHODCALLTYPE UI_GetOptionKeyPath(
IDocHostUIHandler *This, LPOLESTR *pchKey, DWORD dw) {
return S_FALSE;
}
static HRESULT STDMETHODCALLTYPE UI_GetDropTarget(
IDocHostUIHandler *This, IDropTarget *pDropTarget,
IDropTarget **ppDropTarget) {
return S_FALSE;
}
static HRESULT STDMETHODCALLTYPE UI_GetExternal(
IDocHostUIHandler *This, IDispatch **ppDispatch) {
*ppDispatch = (IDispatch *)(This + 1);
return S_OK;
}
static HRESULT STDMETHODCALLTYPE UI_TranslateUrl(
IDocHostUIHandler *This, DWORD dwTranslate, OLECHAR *pchURLIn,
OLECHAR **ppchURLOut) {
*ppchURLOut = 0;
return S_FALSE;
}
static HRESULT STDMETHODCALLTYPE
UI_FilterDataObject(IDocHostUIHandler *This, IDataObject *pDO,
IDataObject **ppDORet) {
*ppDORet = 0;
return S_FALSE;
}
static const SAFEARRAYBOUND ArrayBound = {1, 0};
static IOleClientSiteVtbl MyIOleClientSiteTable = {
Site_QueryInterface, Site_AddRef, Site_Release,
Site_SaveObject, Site_GetMoniker, Site_GetContainer,
Site_ShowObject, Site_OnShowWindow, Site_RequestNewObjectLayout};
static IOleInPlaceSiteVtbl MyIOleInPlaceSiteTable = {
InPlace_QueryInterface,
InPlace_AddRef,
InPlace_Release,
InPlace_GetWindow,
InPlace_ContextSensitiveHelp,
InPlace_CanInPlaceActivate,
InPlace_OnInPlaceActivate,
InPlace_OnUIActivate,
InPlace_GetWindowContext,
InPlace_Scroll,
InPlace_OnUIDeactivate,
InPlace_OnInPlaceDeactivate,
InPlace_DiscardUndoState,
InPlace_DeactivateAndUndo,
InPlace_OnPosRectChange};
static IOleInPlaceFrameVtbl MyIOleInPlaceFrameTable = {
Frame_QueryInterface,
Frame_AddRef,
Frame_Release,
Frame_GetWindow,
Frame_ContextSensitiveHelp,
Frame_GetBorder,
Frame_RequestBorderSpace,
Frame_SetBorderSpace,
Frame_SetActiveObject,
Frame_InsertMenus,
Frame_SetMenu,
Frame_RemoveMenus,
Frame_SetStatusText,
Frame_EnableModeless,
Frame_TranslateAccelerator};
static IDocHostUIHandlerVtbl MyIDocHostUIHandlerTable = {
UI_QueryInterface,
UI_AddRef,
UI_Release,
UI_ShowContextMenu,
UI_GetHostInfo,
UI_ShowUI,
UI_HideUI,
UI_UpdateUI,
UI_EnableModeless,
UI_OnDocWindowActivate,
UI_OnFrameWindowActivate,
UI_ResizeBorder,
UI_TranslateAccelerator,
UI_GetOptionKeyPath,
UI_GetDropTarget,
UI_GetExternal,
UI_TranslateUrl,
UI_FilterDataObject};
static HRESULT STDMETHODCALLTYPE IS_QueryInterface(IInternetSecurityManager *This, REFIID riid, void **ppvObject) {
return E_NOTIMPL;
}
static ULONG STDMETHODCALLTYPE IS_AddRef(IInternetSecurityManager *This) { return 1; }
static ULONG STDMETHODCALLTYPE IS_Release(IInternetSecurityManager *This) { return 1; }
static HRESULT STDMETHODCALLTYPE IS_SetSecuritySite(IInternetSecurityManager *This, IInternetSecurityMgrSite *pSited) {
return INET_E_DEFAULT_ACTION;
}
static HRESULT STDMETHODCALLTYPE IS_GetSecuritySite(IInternetSecurityManager *This, IInternetSecurityMgrSite **ppSite) {
return INET_E_DEFAULT_ACTION;
}
static HRESULT STDMETHODCALLTYPE IS_MapUrlToZone(IInternetSecurityManager *This, LPCWSTR pwszUrl, DWORD *pdwZone, DWORD dwFlags) {
*pdwZone = URLZONE_LOCAL_MACHINE;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE IS_GetSecurityId(IInternetSecurityManager *This, LPCWSTR pwszUrl, BYTE *pbSecurityId, DWORD *pcbSecurityId, DWORD_PTR dwReserved) {
return INET_E_DEFAULT_ACTION;
}
static HRESULT STDMETHODCALLTYPE IS_ProcessUrlAction(IInternetSecurityManager *This, LPCWSTR pwszUrl, DWORD dwAction, BYTE *pPolicy, DWORD cbPolicy, BYTE *pContext, DWORD cbContext, DWORD dwFlags, DWORD dwReserved) {
return INET_E_DEFAULT_ACTION;
}
static HRESULT STDMETHODCALLTYPE IS_QueryCustomPolicy(IInternetSecurityManager *This, LPCWSTR pwszUrl, REFGUID guidKey, BYTE **ppPolicy, DWORD *pcbPolicy, BYTE *pContext, DWORD cbContext, DWORD dwReserved) {
return INET_E_DEFAULT_ACTION;
}
static HRESULT STDMETHODCALLTYPE IS_SetZoneMapping(IInternetSecurityManager *This, DWORD dwZone, LPCWSTR lpszPattern, DWORD dwFlags) {
return INET_E_DEFAULT_ACTION;
}
static HRESULT STDMETHODCALLTYPE IS_GetZoneMappings(IInternetSecurityManager *This, DWORD dwZone, IEnumString **ppenumString, DWORD dwFlags) {
return INET_E_DEFAULT_ACTION;
}
static IInternetSecurityManagerVtbl MyInternetSecurityManagerTable = {IS_QueryInterface, IS_AddRef, IS_Release, IS_SetSecuritySite, IS_GetSecuritySite, IS_MapUrlToZone, IS_GetSecurityId, IS_ProcessUrlAction, IS_QueryCustomPolicy, IS_SetZoneMapping, IS_GetZoneMappings};
static HRESULT STDMETHODCALLTYPE SP_QueryInterface(IServiceProvider *This, REFIID riid, void **ppvObject) {
return (Site_QueryInterface(
(IOleClientSite *)((char *)This - sizeof(IOleClientSite) - sizeof(_IOleInPlaceSiteEx) - sizeof(_IDocHostUIHandlerEx) - sizeof(IDispatch)), riid, ppvObject));
}
static ULONG STDMETHODCALLTYPE SP_AddRef(IServiceProvider *This) { return 1; }
static ULONG STDMETHODCALLTYPE SP_Release(IServiceProvider *This) { return 1; }
static HRESULT STDMETHODCALLTYPE SP_QueryService(IServiceProvider *This, REFGUID siid, REFIID riid, void **ppvObject) {
if (iid_eq(siid, &IID_IInternetSecurityManager) && iid_eq(riid, &IID_IInternetSecurityManager)) {
*ppvObject = &((_IServiceProviderEx *)This)->mgr;
} else {
*ppvObject = 0;
return (E_NOINTERFACE);
}
return S_OK;
}
static IServiceProviderVtbl MyServiceProviderTable = {SP_QueryInterface, SP_AddRef, SP_Release, SP_QueryService};
static void UnEmbedBrowserObject(webview_t w) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
if (wv->browser != NULL) {
(*wv->browser)->lpVtbl->Close(*wv->browser, OLECLOSE_NOSAVE);
(*wv->browser)->lpVtbl->Release(*wv->browser);
GlobalFree(wv->browser);
wv->browser = NULL;
}
}
static int EmbedBrowserObject(webview_t w) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
RECT rect;
IWebBrowser2 *webBrowser2 = NULL;
LPCLASSFACTORY pClassFactory = NULL;
_IOleClientSiteEx *_iOleClientSiteEx = NULL;
IOleObject **browser = (IOleObject **)GlobalAlloc(
GMEM_FIXED, sizeof(IOleObject *) + sizeof(_IOleClientSiteEx));
if (browser == NULL) {
goto error;
}
wv->browser = browser;
_iOleClientSiteEx = (_IOleClientSiteEx *)(browser + 1);
_iOleClientSiteEx->client.lpVtbl = &MyIOleClientSiteTable;
_iOleClientSiteEx->inplace.inplace.lpVtbl = &MyIOleInPlaceSiteTable;
_iOleClientSiteEx->inplace.frame.frame.lpVtbl = &MyIOleInPlaceFrameTable;
_iOleClientSiteEx->inplace.frame.window = wv->hwnd;
_iOleClientSiteEx->ui.ui.lpVtbl = &MyIDocHostUIHandlerTable;
_iOleClientSiteEx->external.lpVtbl = &ExternalDispatchTable;
_iOleClientSiteEx->provider.provider.lpVtbl = &MyServiceProviderTable;
_iOleClientSiteEx->provider.mgr.mgr.lpVtbl = &MyInternetSecurityManagerTable;
if (CoGetClassObject(iid_unref(&CLSID_WebBrowser),
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, NULL,
iid_unref(&IID_IClassFactory),
(void **)&pClassFactory) != S_OK) {
goto error;
}
if (pClassFactory == NULL) {
goto error;
}
if (pClassFactory->lpVtbl->CreateInstance(pClassFactory, 0,
iid_unref(&IID_IOleObject),
(void **)browser) != S_OK) {
goto error;
}
pClassFactory->lpVtbl->Release(pClassFactory);
if ((*browser)->lpVtbl->SetClientSite(
*browser, (IOleClientSite *)_iOleClientSiteEx) != S_OK) {
goto error;
}
(*browser)->lpVtbl->SetHostNames(*browser, L"My Host Name", 0);
if (OleSetContainedObject((struct IUnknown *)(*browser), TRUE) != S_OK) {
goto error;
}
GetClientRect(wv->hwnd, &rect);
if ((*browser)->lpVtbl->DoVerb((*browser), OLEIVERB_SHOW, NULL,
(IOleClientSite *)_iOleClientSiteEx, -1,
wv->hwnd, &rect) != S_OK) {
goto error;
}
if ((*browser)->lpVtbl->QueryInterface((*browser),
iid_unref(&IID_IWebBrowser2),
(void **)&webBrowser2) != S_OK) {
goto error;
}
webBrowser2->lpVtbl->put_Left(webBrowser2, 0);
webBrowser2->lpVtbl->put_Top(webBrowser2, 0);
webBrowser2->lpVtbl->put_Width(webBrowser2, rect.right);
webBrowser2->lpVtbl->put_Height(webBrowser2, rect.bottom);
webBrowser2->lpVtbl->Release(webBrowser2);
return 0;
error:
UnEmbedBrowserObject(w);
if (pClassFactory != NULL) {
pClassFactory->lpVtbl->Release(pClassFactory);
}
if (browser != NULL) {
GlobalFree(browser);
}
return -1;
}
#define WEBVIEW_DATA_URL_PREFIX "data:text/html,"
static int DisplayHTMLPage(struct mshtml_webview *wv) {
IWebBrowser2 *webBrowser2;
VARIANT myURL;
LPDISPATCH lpDispatch;
IHTMLDocument2 *htmlDoc2;
BSTR bstr;
IOleObject *browserObject;
SAFEARRAY *sfArray;
VARIANT *pVar;
browserObject = *wv->browser;
int isDataURL = 0;
const char *webview_url = wv->url == NULL ? "" : wv->url;
if (!browserObject->lpVtbl->QueryInterface(
browserObject, iid_unref(&IID_IWebBrowser2), (void **)&webBrowser2)) {
LPCSTR webPageName;
isDataURL = (strncmp(webview_url, WEBVIEW_DATA_URL_PREFIX,
strlen(WEBVIEW_DATA_URL_PREFIX)) == 0);
if (isDataURL) {
webPageName = "about:blank";
} else {
webPageName = (LPCSTR)webview_url;
}
VariantInit(&myURL);
myURL.vt = VT_BSTR;
myURL.bstrVal = webview_to_bstr(webPageName);
if (!myURL.bstrVal) {
badalloc:
webBrowser2->lpVtbl->Release(webBrowser2);
return (-6);
}
webBrowser2->lpVtbl->Navigate2(webBrowser2, &myURL, 0, 0, 0, 0);
VariantClear(&myURL);
if (!isDataURL) {
return 0;
}
char *url = (char *)calloc(1, strlen(webview_url) + 1);
char *q = url;
for (const char *p = webview_url + strlen(WEBVIEW_DATA_URL_PREFIX); *q = *p;
p++, q++) {
if (*q == '%' && *(p + 1) && *(p + 2)) {
*q = hex2char(p + 1);
p = p + 2;
}
}
if (webBrowser2->lpVtbl->get_Document(webBrowser2, &lpDispatch) == S_OK) {
if (lpDispatch->lpVtbl->QueryInterface(lpDispatch,
iid_unref(&IID_IHTMLDocument2),
(void **)&htmlDoc2) == S_OK) {
if ((sfArray = SafeArrayCreate(VT_VARIANT, 1,
(SAFEARRAYBOUND *)&ArrayBound))) {
if (!SafeArrayAccessData(sfArray, (void **)&pVar)) {
pVar->vt = VT_BSTR;
bstr = webview_to_bstr(url);
if ((pVar->bstrVal = bstr)) {
htmlDoc2->lpVtbl->write(htmlDoc2, sfArray);
htmlDoc2->lpVtbl->close(htmlDoc2);
}
}
SafeArrayDestroy(sfArray);
}
release:
free(url);
htmlDoc2->lpVtbl->Release(htmlDoc2);
}
lpDispatch->lpVtbl->Release(lpDispatch);
}
webBrowser2->lpVtbl->Release(webBrowser2);
return (0);
}
return (-5);
}
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
struct mshtml_webview *wv = (struct mshtml_webview *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (uMsg) {
case WM_CREATE:
wv = (struct mshtml_webview *)((CREATESTRUCT *)lParam)->lpCreateParams;
wv->hwnd = hwnd;
return EmbedBrowserObject(wv);
case WM_DESTROY:
UnEmbedBrowserObject(wv);
PostQuitMessage(0);
return TRUE;
case WM_SIZE: {
IWebBrowser2 *webBrowser2;
IOleObject *browser = *wv->browser;
if (browser->lpVtbl->QueryInterface(browser, iid_unref(&IID_IWebBrowser2),
(void **)&webBrowser2) == S_OK) {
RECT rect;
GetClientRect(hwnd, &rect);
webBrowser2->lpVtbl->put_Width(webBrowser2, rect.right);
webBrowser2->lpVtbl->put_Height(webBrowser2, rect.bottom);
}
return TRUE;
}
case WM_WEBVIEW_DISPATCH: {
webview_dispatch_fn f = (webview_dispatch_fn)wParam;
void *arg = (void *)lParam;
(*f)(wv, arg);
return TRUE;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
WEBVIEW_API int webview_loop(webview_t w, int blocking) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
MSG msg;
if (blocking) {
if (GetMessage(&msg, 0, 0, 0) < 0) return 0;
} else {
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE) == 0) return 0;
}
switch (msg.message) {
case WM_QUIT:
return -1;
case WM_COMMAND:
case WM_KEYDOWN:
case WM_KEYUP: {
HRESULT r = S_OK;
IWebBrowser2 *webBrowser2;
IOleObject *browser = *wv->browser;
if (browser->lpVtbl->QueryInterface(browser, iid_unref(&IID_IWebBrowser2),
(void **)&webBrowser2) == S_OK) {
IOleInPlaceActiveObject *pIOIPAO;
if (browser->lpVtbl->QueryInterface(
browser, iid_unref(&IID_IOleInPlaceActiveObject),
(void **)&pIOIPAO) == S_OK) {
r = pIOIPAO->lpVtbl->TranslateAccelerator(pIOIPAO, &msg);
pIOIPAO->lpVtbl->Release(pIOIPAO);
}
webBrowser2->lpVtbl->Release(webBrowser2);
}
if (r != S_FALSE) {
break;
}
}
default:
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
WEBVIEW_API int webview_eval(webview_t w, const char *js) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
IWebBrowser2 *webBrowser2;
IHTMLDocument2 *htmlDoc2;
IDispatch *docDispatch;
IDispatch *scriptDispatch;
if ((*wv->browser)
->lpVtbl->QueryInterface((*wv->browser),
iid_unref(&IID_IWebBrowser2),
(void **)&webBrowser2) != S_OK) {
return -1;
}
if (webBrowser2->lpVtbl->get_Document(webBrowser2, &docDispatch) != S_OK) {
return -1;
}
if (docDispatch->lpVtbl->QueryInterface(docDispatch,
iid_unref(&IID_IHTMLDocument2),
(void **)&htmlDoc2) != S_OK) {
return -1;
}
if (htmlDoc2->lpVtbl->get_Script(htmlDoc2, &scriptDispatch) != S_OK) {
return -1;
}
DISPID dispid;
LPOLESTR evalStr = L"eval";
if (scriptDispatch->lpVtbl->GetIDsOfNames(
scriptDispatch, iid_unref(&IID_NULL), &evalStr, 1,
LOCALE_SYSTEM_DEFAULT, &dispid) != S_OK) {
return -1;
}
DISPPARAMS params;
VARIANT arg;
VARIANT result;
EXCEPINFO excepInfo;
UINT nArgErr = (UINT)-1;
params.cArgs = 1;
params.cNamedArgs = 0;
params.rgvarg = &arg;
arg.vt = VT_BSTR;
static const char *prologue = "(function(){";
static const char *epilogue = ";})();";
int n = strlen(prologue) + strlen(epilogue) + strlen(js) + 1;
char *eval = (char *)malloc(n);
if (eval == NULL) {
return -1;
}
snprintf(eval, n, "%s%s%s", prologue, js, epilogue);
arg.bstrVal = webview_to_bstr(eval);
free(eval);
if (arg.bstrVal == NULL) {
return -1;
}
if (scriptDispatch->lpVtbl->Invoke(
scriptDispatch, dispid, iid_unref(&IID_NULL), 0, DISPATCH_METHOD,
¶ms, &result, &excepInfo, &nArgErr) != S_OK) {
SysFreeString(arg.bstrVal);
return -1;
}
SysFreeString(arg.bstrVal);
scriptDispatch->lpVtbl->Release(scriptDispatch);
htmlDoc2->lpVtbl->Release(htmlDoc2);
docDispatch->lpVtbl->Release(docDispatch);
return 0;
}
WEBVIEW_API void webview_dispatch(webview_t w, webview_dispatch_fn fn,
void *arg) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
PostMessageW(wv->hwnd, WM_WEBVIEW_DISPATCH, (WPARAM)fn, (LPARAM)arg);
}
WEBVIEW_API void webview_set_title(webview_t w, const char *title) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
BSTR window_title = webview_to_bstr(title);
SetWindowText(wv->hwnd, window_title);
SysFreeString(window_title);
}
WEBVIEW_API void webview_set_fullscreen(webview_t w, int fullscreen) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
if (wv->is_fullscreen == !!fullscreen) {
return;
}
if (wv->is_fullscreen == 0) {
wv->saved_style = GetWindowLong(wv->hwnd, GWL_STYLE);
wv->saved_ex_style = GetWindowLong(wv->hwnd, GWL_EXSTYLE);
GetWindowRect(wv->hwnd, &wv->saved_rect);
}
wv->is_fullscreen = !!fullscreen;
if (fullscreen) {
MONITORINFO monitor_info;
SetWindowLong(wv->hwnd, GWL_STYLE,
wv->saved_style & ~(WS_CAPTION | WS_THICKFRAME));
SetWindowLong(wv->hwnd, GWL_EXSTYLE,
wv->saved_ex_style &
~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
monitor_info.cbSize = sizeof(monitor_info);
GetMonitorInfo(MonitorFromWindow(wv->hwnd, MONITOR_DEFAULTTONEAREST),
&monitor_info);
RECT r;
r.left = monitor_info.rcMonitor.left;
r.top = monitor_info.rcMonitor.top;
r.right = monitor_info.rcMonitor.right;
r.bottom = monitor_info.rcMonitor.bottom;
SetWindowPos(wv->hwnd, NULL, r.left, r.top, r.right - r.left,
r.bottom - r.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
} else {
SetWindowLong(wv->hwnd, GWL_STYLE, wv->saved_style);
SetWindowLong(wv->hwnd, GWL_EXSTYLE, wv->saved_ex_style);
SetWindowPos(wv->hwnd, NULL, wv->saved_rect.left,
wv->saved_rect.top,
wv->saved_rect.right - wv->saved_rect.left,
wv->saved_rect.bottom - wv->saved_rect.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
}
}
WEBVIEW_API void webview_set_color(webview_t w, uint8_t r, uint8_t g,
uint8_t b, uint8_t a) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
HBRUSH brush = CreateSolidBrush(RGB(r, g, b));
SetClassLongPtr(wv->hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)brush);
}
WEBVIEW_API void webview_exit(webview_t w) {
struct mshtml_webview* wv = (struct mshtml_webview*)w;
DestroyWindow(wv->hwnd);
OleUninitialize();
}
WEBVIEW_API void webview_print_log(const char *s) { OutputDebugString(s); }