axmol/cocos/ui/UIWebView/UIWebViewImpl-win32.cpp

1239 lines
37 KiB
C++

/****************************************************************************
MIT License
Portions copyright (c) 2017 Serge Zaitsev
https://adxe.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
#include "UIWebViewImpl-win32.h"
#include "UIWebView.h"
#include "base/CCDirector.h"
#include "platform/CCFileUtils.h"
#include "platform/CCGLView.h"
#include "base/base64.h"
#include "ui/UIHelper.h"
#define WIN32_LEAN_AND_MEAN
#include <Shlwapi.h>
#include <codecvt>
#include <utility>
#include <stdlib.h>
#include <Windows.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Shlwapi.lib")
// Edge/Chromium headers and libs
#include "WebView2.h"
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
#include "platform/CCPlatformConfig.h"
USING_NS_CC;
using msg_cb_t = std::function<void(const std::string)>;
inline std::string html_from_uri(const std::string& s)
{
if (s.substr(0, 15) == "data:text/html,")
{
return utils::urlDecode(s.substr(15));
}
return "";
}
inline int json_parse_c(const char* s, size_t sz, const char* key, size_t keysz, const char** value, size_t* valuesz)
{
enum
{
JSON_STATE_VALUE,
JSON_STATE_LITERAL,
JSON_STATE_STRING,
JSON_STATE_ESCAPE,
JSON_STATE_UTF8
} state = JSON_STATE_VALUE;
const char* k = NULL;
int index = 1;
int depth = 0;
int utf8_bytes = 0;
if (key == NULL)
{
index = keysz;
keysz = 0;
}
*value = NULL;
*valuesz = 0;
for (; sz > 0; s++, sz--)
{
enum
{
JSON_ACTION_NONE,
JSON_ACTION_START,
JSON_ACTION_END,
JSON_ACTION_START_STRUCT,
JSON_ACTION_END_STRUCT
} action = JSON_ACTION_NONE;
unsigned char c = *s;
switch (state)
{
case JSON_STATE_VALUE:
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ':')
{
continue;
}
else if (c == '"')
{
action = JSON_ACTION_START;
state = JSON_STATE_STRING;
}
else if (c == '{' || c == '[')
{
action = JSON_ACTION_START_STRUCT;
}
else if (c == '}' || c == ']')
{
action = JSON_ACTION_END_STRUCT;
}
else if (c == 't' || c == 'f' || c == 'n' || c == '-' || (c >= '0' && c <= '9'))
{
action = JSON_ACTION_START;
state = JSON_STATE_LITERAL;
}
else
{
return -1;
}
break;
case JSON_STATE_LITERAL:
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ']' || c == '}' || c == ':')
{
state = JSON_STATE_VALUE;
s--;
sz++;
action = JSON_ACTION_END;
}
else if (c < 32 || c > 126)
{
return -1;
} // fallthrough
case JSON_STATE_STRING:
if (c < 32 || (c > 126 && c < 192))
{
return -1;
}
else if (c == '"')
{
action = JSON_ACTION_END;
state = JSON_STATE_VALUE;
}
else if (c == '\\')
{
state = JSON_STATE_ESCAPE;
}
else if (c >= 192 && c < 224)
{
utf8_bytes = 1;
state = JSON_STATE_UTF8;
}
else if (c >= 224 && c < 240)
{
utf8_bytes = 2;
state = JSON_STATE_UTF8;
}
else if (c >= 240 && c < 247)
{
utf8_bytes = 3;
state = JSON_STATE_UTF8;
}
else if (c >= 128 && c < 192)
{
return -1;
}
break;
case JSON_STATE_ESCAPE:
if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || c == 'n' || c == 'r' || c == 't' ||
c == 'u')
{
state = JSON_STATE_STRING;
}
else
{
return -1;
}
break;
case JSON_STATE_UTF8:
if (c < 128 || c > 191)
{
return -1;
}
utf8_bytes--;
if (utf8_bytes == 0)
{
state = JSON_STATE_STRING;
}
break;
default:
return -1;
}
if (action == JSON_ACTION_END_STRUCT)
{
depth--;
}
if (depth == 1)
{
if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT)
{
if (index == 0)
{
*value = s;
}
else if (keysz > 0 && index == 1)
{
k = s;
}
else
{
index--;
}
}
else if (action == JSON_ACTION_END || action == JSON_ACTION_END_STRUCT)
{
if (*value != NULL && index == 0)
{
*valuesz = (size_t)(s + 1 - *value);
return 0;
}
else if (keysz > 0 && k != NULL)
{
if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0)
{
index = 0;
}
else
{
index = 2;
}
k = NULL;
}
}
}
if (action == JSON_ACTION_START_STRUCT)
{
depth++;
}
}
return -1;
}
inline std::string json_escape(std::string s)
{
// TODO: implement
return '"' + s + '"';
}
inline int json_unescape(const char* s, size_t n, char* out)
{
int r = 0;
if (*s++ != '"')
{
return -1;
}
while (n > 2)
{
char c = *s;
if (c == '\\')
{
s++;
n--;
switch (*s)
{
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case '\\':
c = '\\';
break;
case '/':
c = '/';
break;
case '\"':
c = '\"';
break;
default: // TODO: support unicode decoding
return -1;
}
}
if (out != NULL)
{
*out++ = c;
}
s++;
n--;
r++;
}
if (*s != '"')
{
return -1;
}
if (out != NULL)
{
*out = '\0';
}
return r;
}
inline std::string json_parse(const std::string s, const std::string key, const int index)
{
const char* value;
size_t value_sz;
if (key.empty())
{
json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
}
else
{
json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, &value_sz);
}
if (value != nullptr)
{
if (value[0] != '"')
{
return std::string(value, value_sz);
}
int n = json_unescape(value, value_sz, nullptr);
if (n > 0)
{
char* decoded = new char[n + 1];
json_unescape(value, value_sz, decoded);
std::string result(decoded, n);
delete[] decoded;
return result;
}
}
return "";
}
template <class ArgType>
static std::string getUriStringFromArgs(ArgType* args)
{
if (args != nullptr)
{
LPWSTR uri;
args->get_Uri(&uri);
std::wstring ws(uri);
std::string result = std::string(ws.begin(), ws.end());
return result;
}
return {};
}
static std::string GetDataURI(const std::string& data, const std::string& mime_type)
{
char* encodedData;
cocos2d::base64Encode(reinterpret_cast<const unsigned char*>(data.data()), static_cast<unsigned>(data.size()), &encodedData);
return "data:" + mime_type + ";base64," + utils::urlEncode(encodedData);
}
static double GetDeviceScaleFactor()
{
static double scale_factor = 1.0;
static bool initialized = false;
if (!initialized)
{
// This value is safe to cache for the life time of the app since the user
// must logout to change the DPI setting. This value also applies to all
// screens.
# if _WIN32
HDC screen_dc = ::GetDC(NULL);
int dpi_x = GetDeviceCaps(screen_dc, LOGPIXELSX);
scale_factor = static_cast<double>(dpi_x) / 96.0;
::ReleaseDC(NULL, screen_dc);
# endif
initialized = true;
}
return scale_factor;
}
// declarate
class Win32WebControl
{
public:
Win32WebControl();
bool createWebView(
const std::function<bool (const std::string &)> &shouldStartLoading,
const std::function<void (const std::string &)> &didFinishLoading,
const std::function<void (const std::string &)> &didFailLoading,
const std::function<void (const std::string &)> &onJsCallback);
void removeWebView();
void setWebViewRect(const int left, const int top, const int width, const int height);
void setJavascriptInterfaceScheme(const std::string &scheme);
void loadHTMLString(const std::string &html, const std::string &baseURL);
void loadURL(const std::string &url, bool cleanCachedData);
void loadFile(const std::string &filePath);
void stopLoading();
void reload() const;
bool canGoBack() const;
bool canGoForward() const;
void goBack() const;
void goForward() const;
void evaluateJS(const std::string &js);
void setScalesPageToFit(const bool scalesPageToFit);
void setWebViewVisible(const bool visible) const;
void setBounces(bool bounces);
void setOpacityWebView(float opacity);
float getOpacityWebView() const;
void setBackgroundTransparent();
private:
ICoreWebView2* m_webview = nullptr;
ICoreWebView2Controller* m_controller = nullptr;
HWND m_window{};
POINT m_minsz = POINT{0, 0};
POINT m_maxsz = POINT{0, 0};
DWORD m_main_thread = GetCurrentThreadId();
std::string m_jsScheme;
bool _scalesPageToFit{};
std::function<bool (const std::string &)> _shouldStartLoading;
std::function<void (const std::string &)> _didFinishLoading;
std::function<void (const std::string &)> _didFailLoading;
std::function<void (const std::string &)> _onJsCallback;
static bool s_isInitialized;
static void lazyInit();
static LPWSTR to_lpwstr(const std::string s)
{
const int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
auto* ws = new wchar_t[n];
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws, n);
return ws;
}
bool embed(HWND wnd, bool debug, msg_cb_t cb)
{
std::atomic_flag flag = ATOMIC_FLAG_INIT;
flag.test_and_set();
char currentExePath[MAX_PATH];
GetModuleFileNameA(NULL, currentExePath, MAX_PATH);
char* currentExeName = PathFindFileNameA(currentExePath);
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
std::wstring userDataFolder = wideCharConverter.from_bytes(std::getenv("APPDATA"));
std::wstring currentExeNameW = wideCharConverter.from_bytes(currentExeName);
HRESULT res = CreateCoreWebView2EnvironmentWithOptions(
nullptr, (userDataFolder + L"/" + currentExeNameW).c_str(), nullptr,
new webview2_com_handler(wnd, cb,
[&](ICoreWebView2Controller* controller) {
m_controller = controller;
m_controller->get_CoreWebView2(&m_webview);
m_webview->AddRef();
flag.clear();
},
[this](const std::string& url) -> bool {
const auto scheme = url.substr(0, url.find_first_of(':'));
if (scheme == m_jsScheme)
{
if (_onJsCallback)
{
_onJsCallback(url);
}
return true;
}
if (_shouldStartLoading && !url.empty())
{
return _shouldStartLoading(url);
}
return true;
},
[this]() {
LPWSTR uri;
this->m_webview->get_Source(&uri);
std::wstring ws(uri);
const auto result = std::string(ws.begin(), ws.end());
if (_didFinishLoading)
_didFinishLoading(result);
},
[this]() {
LPWSTR uri;
this->m_webview->get_Source(&uri);
std::wstring ws(uri);
const auto result = std::string(ws.begin(), ws.end());
if (_didFailLoading)
_didFailLoading(result);
},
[this](const std::string& url) { loadURL(url, false); }
)
);
if (res != S_OK)
{
CoUninitialize();
return false;
}
MSG msg = {};
while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
return true;
}
void resize(HWND wnd)
{
if (m_controller == nullptr)
{
return;
}
RECT bounds;
GetClientRect(wnd, &bounds);
m_controller->put_Bounds(bounds);
}
void navigate(const std::string url)
{
auto wurl = to_lpwstr(url);
m_webview->Navigate(wurl);
delete[] wurl;
}
void init(const std::string js)
{
LPCWSTR wjs = to_lpwstr(js);
m_webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr);
delete[] wjs;
}
void eval(const std::string js)
{
LPCWSTR wjs = to_lpwstr(js);
m_webview->ExecuteScript(wjs, nullptr);
delete[] wjs;
}
void on_message(const std::string msg)
{
const auto seq = json_parse(msg, "id", 0);
const auto name = json_parse(msg, "method", 0);
const auto args = json_parse(msg, "params", 0);
if (bindings.find(name) == bindings.end())
{
return;
}
const auto fn = bindings[name];
(*fn->first)(seq, args, fn->second);
}
using binding_t = std::function<void(std::string, std::string, void *)>;
using binding_ctx_t = std::pair<binding_t*, void*>;
std::map<std::string, binding_ctx_t*> bindings;
class webview2_com_handler : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
public ICoreWebView2WebMessageReceivedEventHandler,
public ICoreWebView2PermissionRequestedEventHandler,
public ICoreWebView2NavigationStartingEventHandler,
public ICoreWebView2NavigationCompletedEventHandler,
public ICoreWebView2NewWindowRequestedEventHandler,
public ICoreWebView2ContentLoadingEventHandler,
public ICoreWebView2DOMContentLoadedEventHandler
{
using webview2_com_handler_cb_t = std::function<void(ICoreWebView2Controller*)>;
public:
webview2_com_handler(HWND hwnd,
msg_cb_t msgCb,
webview2_com_handler_cb_t cb,
std::function<bool(const std::string&)> navStartingCallback,
std::function<void()> navCompleteCallback,
std::function<void()> navErrorCallback,
std::function<void(const std::string& url)> loadUrlCallback
)
: m_window(hwnd)
, m_msgCb(std::move(msgCb))
, m_cb(std::move(cb))
, m_navStartingCallback(std::move(navStartingCallback))
, m_navCompleteCallback(std::move(navCompleteCallback))
, m_navErrorCallback(std::move(navErrorCallback))
, m_loadUrlCallback(std::move(loadUrlCallback))
{}
ULONG STDMETHODCALLTYPE AddRef() { return 1; }
ULONG STDMETHODCALLTYPE Release() { return 1; }
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppv) { return S_OK; }
// ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment* env)
{
env->CreateCoreWebView2Controller(m_window, this);
return S_OK;
}
// ICoreWebView2CreateCoreWebView2ControllerCompletedHandler
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Controller* controller)
{
controller->AddRef();
ICoreWebView2* webview;
::EventRegistrationToken token;
controller->get_CoreWebView2(&webview);
webview->add_WebMessageReceived(this, &token);
webview->add_PermissionRequested(this, &token);
webview->add_NavigationStarting(this, &token);
webview->add_NavigationCompleted(this, &token);
webview->add_ContentLoading(this, &token);
webview->add_NewWindowRequested(this, &token);
m_cb(controller);
return S_OK;
}
// ICoreWebView2WebMessageReceivedEventHandler
HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args)
{
LPWSTR message;
args->TryGetWebMessageAsString(&message);
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
m_msgCb(wideCharConverter.to_bytes(message));
sender->PostWebMessageAsString(message);
CoTaskMemFree(message);
return S_OK;
}
// ICoreWebView2PermissionRequestedEventHandler
HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2PermissionRequestedEventArgs* args)
{
COREWEBVIEW2_PERMISSION_KIND kind;
args->get_PermissionKind(&kind);
if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ)
{
args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
}
return S_OK;
}
// ICoreWebView2NavigationStartingEventHandler
HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args)
{
if (m_navStartingCallback)
{
const auto uriString = getUriStringFromArgs(args);
if (!m_navStartingCallback(uriString))
{
args->put_Cancel(true);
}
}
return S_OK;
}
// ICoreWebView2NavigationCompletedEventHandler
HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args)
{
BOOL success;
if (SUCCEEDED(args->get_IsSuccess(&success)) && success)
{
if (m_navCompleteCallback)
{
m_navCompleteCallback();
}
}
else
{
// example of how to get status error if required
//COREWEBVIEW2_WEB_ERROR_STATUS status;
//if (SUCCEEDED(args->get_WebErrorStatus(&status)))
//{
//}
if (m_navErrorCallback)
{
m_navErrorCallback();
}
}
return S_OK;
}
// ICoreWebView2ContentLoadingEventHandler
HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2ContentLoadingEventArgs* args)
{
return S_OK;
}
// ICoreWebView2NewWindowRequestedEventHandler
HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2NewWindowRequestedEventArgs* args)
{
args->put_Handled(true);
return S_OK;
}
// ICoreWebView2NewWindowRequestedEventHandler
HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2NewWindowRequestedEventArgs2* args)
{
args->put_Handled(true);
if (m_loadUrlCallback)
{
const auto uriString = getUriStringFromArgs(args);
m_loadUrlCallback(uriString);
}
args->put_Handled(true);
return S_OK;
}
// ICoreWebView2DOMContentLoadedEventHandler
HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2DOMContentLoadedEventArgs* args)
{
return S_OK;
}
private:
HWND m_window;
msg_cb_t m_msgCb;
webview2_com_handler_cb_t m_cb;
std::function<bool(const std::string&)> m_navStartingCallback;
std::function<void()> m_navCompleteCallback;
std::function<void()> m_navErrorCallback;
std::function<void(const std::string& url)> m_loadUrlCallback;
};
};
namespace cocos2d {
namespace ui {
WebViewImpl::WebViewImpl(WebView *webView) : _createSucceeded(false), _systemWebControl(nullptr), _webView(webView)
{
_systemWebControl = new Win32WebControl();
if (_systemWebControl == nullptr)
{
return;
}
_createSucceeded = _systemWebControl->createWebView(
[this](const std::string &url)->bool {
const auto shouldStartLoading = _webView->getOnShouldStartLoading();
if (shouldStartLoading != nullptr)
{
return shouldStartLoading(_webView, url);
}
return true;
},
[this](const std::string &url) {
WebView::ccWebViewCallback didFinishLoading = _webView->getOnDidFinishLoading();
if (didFinishLoading != nullptr)
{
didFinishLoading(_webView, url);
}
},
[this](const std::string &url) {
WebView::ccWebViewCallback didFailLoading = _webView->getOnDidFailLoading();
if (didFailLoading != nullptr)
{
didFailLoading(_webView, url);
}
},
[this](const std::string &url) {
WebView::ccWebViewCallback onJsCallback = _webView->getOnJSCallback();
if (onJsCallback != nullptr)
{
onJsCallback(_webView, url);
}
});
}
WebViewImpl::~WebViewImpl()
{
if (_systemWebControl != nullptr)
{
_systemWebControl->removeWebView();
delete _systemWebControl;
_systemWebControl = nullptr;
}
}
void WebViewImpl::loadData(const Data &data, const std::string &MIMEType, const std::string &encoding, const std::string &baseURL)
{
if (_createSucceeded)
{
const std::string dataString(reinterpret_cast<char*>(data.getBytes()), static_cast<unsigned int>(data.getSize()));
const auto url = GetDataURI(dataString, MIMEType);
_systemWebControl->loadURL(url, false);
}
}
void WebViewImpl::loadHTMLString(const std::string &string, const std::string &baseURL)
{
if (_createSucceeded)
{
if (string.empty())
{
_systemWebControl->loadHTMLString(
"data:text/html," + utils::urlEncode("<html></html>"), baseURL);
return;
}
const auto html = html_from_uri(string);
if (!html.empty())
{
_systemWebControl->loadHTMLString("data:text/html," + utils::urlEncode(html), baseURL);
}
else
{
_systemWebControl->loadHTMLString(string, baseURL);
}
}
}
void WebViewImpl::loadURL(const std::string &url, bool cleanCachedData)
{
if (_createSucceeded)
{
_systemWebControl->loadURL(url, cleanCachedData);
}
}
void WebViewImpl::loadFile(const std::string &fileName)
{
if (_createSucceeded)
{
const auto fullPath = FileUtils::getInstance()->fullPathForFilename(fileName);
_systemWebControl->loadFile(fullPath);
}
}
void WebViewImpl::stopLoading()
{
if (_createSucceeded)
{
_systemWebControl->stopLoading();
}
}
void WebViewImpl::reload()
{
if (_createSucceeded)
{
_systemWebControl->reload();
}
}
bool WebViewImpl::canGoBack()
{
if (_createSucceeded)
{
return _systemWebControl->canGoBack();
}
return false;
}
bool WebViewImpl::canGoForward()
{
if (_createSucceeded)
{
return _systemWebControl->canGoForward();
}
return false;
}
void WebViewImpl::goBack()
{
if (_createSucceeded)
{
_systemWebControl->goBack();
}
}
void WebViewImpl::goForward()
{
if (_createSucceeded)
{
_systemWebControl->goForward();
}
}
void WebViewImpl::setJavascriptInterfaceScheme(const std::string &scheme)
{
if (_createSucceeded)
{
_systemWebControl->setJavascriptInterfaceScheme(scheme);
}
}
void WebViewImpl::evaluateJS(const std::string &js)
{
if (_createSucceeded)
{
_systemWebControl->evaluateJS(js);
}
}
void WebViewImpl::setScalesPageToFit(const bool scalesPageToFit)
{
if (_createSucceeded)
{
_systemWebControl->setScalesPageToFit(scalesPageToFit);
}
}
void WebViewImpl::draw(Renderer *renderer, Mat4 const &transform, uint32_t flags)
{
if (_createSucceeded && (flags & Node::FLAGS_TRANSFORM_DIRTY))
{
const auto uiRect = cocos2d::ui::Helper::convertBoundingBoxToScreen(_webView);
_systemWebControl->setWebViewRect(static_cast<int>(uiRect.origin.x), static_cast<int>(uiRect.origin.y),
static_cast<int>(uiRect.size.width),
static_cast<int>(uiRect.size.height));
}
}
void WebViewImpl::setVisible(bool visible)
{
if (_createSucceeded)
{
_systemWebControl->setWebViewVisible(visible);
}
}
void WebViewImpl::setBounces(bool bounces)
{
_systemWebControl->setBounces(bounces);
}
void WebViewImpl::setOpacityWebView(float opacity)
{
_systemWebControl->setOpacityWebView(opacity);
}
float WebViewImpl::getOpacityWebView() const
{
return _systemWebControl->getOpacityWebView();
}
void WebViewImpl::setBackgroundTransparent()
{
_systemWebControl->setBackgroundTransparent();
}
} // namespace ui
} //namespace cocos2d
//
// Implement Win32WebControl
//
bool Win32WebControl::s_isInitialized = false;
void Win32WebControl::lazyInit()
{
// reset the main windows style so that its drawing does not cover the webview sub window
auto hwnd = cocos2d::Director::getInstance()->getOpenGLView()->getWin32Window();
const auto style = GetWindowLong(hwnd, GWL_STYLE);
SetWindowLong(hwnd, GWL_STYLE, style | WS_CLIPCHILDREN);
CoInitialize(NULL);
}
Win32WebControl::Win32WebControl() :
_shouldStartLoading(nullptr)
, _didFinishLoading(nullptr)
, _didFailLoading(nullptr)
{
if (!s_isInitialized)
{
lazyInit();
}
}
bool Win32WebControl::createWebView(
const std::function<bool (const std::string &)> &shouldStartLoading,
const std::function<void (const std::string &)> &didFinishLoading,
const std::function<void (const std::string &)> &didFailLoading,
const std::function<void (const std::string &)> &onJsCallback)
{
bool ret = false;
do
{
HWND hwnd = cocos2d::Director::getInstance()->getOpenGLView()->getWin32Window();
HINSTANCE hInstance = GetModuleHandle(nullptr);
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpszClassName = L"webview";
wc.hIcon = nullptr;
wc.hIconSm = nullptr;
//wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int {
auto w = (Win32WebControl*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
if (!w)
{
return DefWindowProc(hwnd, msg, wp, lp);
}
switch (msg)
{
case WM_SIZE:
w->resize(hwnd);
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_GETMINMAXINFO:
{
auto lpmmi = (LPMINMAXINFO)lp;
if (w == nullptr)
{
return 0;
}
if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0)
{
lpmmi->ptMaxSize = w->m_maxsz;
lpmmi->ptMaxTrackSize = w->m_maxsz;
}
if (w->m_minsz.x > 0 && w->m_minsz.y > 0)
{
lpmmi->ptMinTrackSize = w->m_minsz;
}
}
break;
default:
return DefWindowProc(hwnd, msg, wp, lp);
}
return 0;
});
RegisterClassEx(&wc);
m_window = CreateWindow(L"webview", L"", WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr,
hInstance, nullptr);
SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
ShowWindow(m_window, SW_SHOW);
UpdateWindow(m_window);
SetFocus(m_window);
auto cb = [this](const std::string msg) { on_message(msg); };
if (!embed(m_window, false, cb))
{
CCLOG("Cannot create edge chromium webview");
ret = false;
break;
}
resize(m_window);
ret = true;
} while (0);
if (!ret)
{
removeWebView();
}
_shouldStartLoading = shouldStartLoading;
_didFinishLoading = didFinishLoading;
_didFailLoading = didFailLoading;
_onJsCallback = onJsCallback;
return ret;
}
void Win32WebControl::removeWebView()
{
m_controller->Close();
m_controller = nullptr;
m_webview = nullptr;
}
void Win32WebControl::setWebViewRect(const int left, const int top, const int width, const int height)
{
if (m_controller == nullptr)
{
return;
}
RECT r;
r.left = left;
r.top = top;
r.right = left + width;
r.bottom = top - height;
AdjustWindowRect(&r, WS_CHILD | WS_VISIBLE, 0);
SetWindowPos(m_window, nullptr, left, top, width, height, SWP_NOZORDER);
m_controller->put_ZoomFactor(_scalesPageToFit ? GetDeviceScaleFactor() : 1.0);
}
void Win32WebControl::setJavascriptInterfaceScheme(const std::string &scheme)
{
m_jsScheme = scheme;
}
void Win32WebControl::loadHTMLString(const std::string &html, const std::string &baseURL)
{
if (!html.empty())
{
const auto whtml = to_lpwstr(html);
m_webview->NavigateToString(whtml);
delete[] whtml;
}
}
void Win32WebControl::loadURL(const std::string &url, bool cleanCachedData)
{
if (cleanCachedData)
{
m_webview->CallDevToolsProtocolMethod(L"Network.clearBrowserCache", L"{}", nullptr);
}
navigate(url);
}
void Win32WebControl::loadFile(const std::string &filePath)
{
auto fullPath = cocos2d::FileUtils::getInstance()->fullPathForFilename(filePath);
if (fullPath.find("file:///") != 0)
{
if (fullPath[0] == '/')
{
fullPath = "file://" + fullPath;
}
else
{
fullPath = "file:///" + fullPath;
}
}
loadURL(fullPath, false);
}
void Win32WebControl::stopLoading()
{
m_webview->Stop();
}
void Win32WebControl::reload() const
{
m_webview->Reload();
}
bool Win32WebControl::canGoBack() const
{
BOOL result;
return SUCCEEDED(m_webview->get_CanGoBack(&result)) && result;
}
bool Win32WebControl::canGoForward() const
{
BOOL result;
return SUCCEEDED(m_webview->get_CanGoForward(&result)) && result;
}
void Win32WebControl::goBack() const
{
m_webview->GoBack();
}
void Win32WebControl::goForward() const
{
m_webview->GoForward();
}
void Win32WebControl::evaluateJS(const std::string &js)
{
eval(js);
}
void Win32WebControl::setScalesPageToFit(const bool scalesPageToFit)
{
_scalesPageToFit = scalesPageToFit;
m_controller->put_ZoomFactor(_scalesPageToFit ? GetDeviceScaleFactor() : 1.0);
}
void Win32WebControl::setWebViewVisible(const bool visible) const
{
ShowWindow(m_window, visible ? SW_SHOW : SW_HIDE);
if (!visible)
{
// When the frame window is minimized set the browser window size to 0x0 to
// reduce resource usage.
SetWindowPos(m_window, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
}
}
void Win32WebControl::setBounces(bool bounces)
{
}
void Win32WebControl::setOpacityWebView(float opacity)
{
}
float Win32WebControl::getOpacityWebView() const
{
return 1.0f;
}
void Win32WebControl::setBackgroundTransparent()
{
ICoreWebView2Controller2* viewController2;
if (SUCCEEDED(m_controller->QueryInterface(__uuidof(ICoreWebView2Controller2), reinterpret_cast<void**>(&viewController2))))
{
COREWEBVIEW2_COLOR color;
viewController2->get_DefaultBackgroundColor(&color);
color.A = 0; // Only supports 0 or 255. Other values not supported
viewController2->put_DefaultBackgroundColor(color);
}
}
#endif // CC_TARGET_PLATFORM == CC_PLATFORM_WIN32