Merge branch 'webview_script_message' of https://github.com/TcT2k/wxWidgets
Add WebView script message and user scripts. See https://github.com/wxWidgets/wxWidgets/pull/2237
This commit is contained in:
@@ -49,6 +49,7 @@ wxDEFINE_EVENT( wxEVT_WEBVIEW_ERROR, wxWebViewEvent );
|
||||
wxDEFINE_EVENT( wxEVT_WEBVIEW_NEWWINDOW, wxWebViewEvent );
|
||||
wxDEFINE_EVENT( wxEVT_WEBVIEW_TITLE_CHANGED, wxWebViewEvent );
|
||||
wxDEFINE_EVENT( wxEVT_WEBVIEW_FULLSCREEN_CHANGED, wxWebViewEvent);
|
||||
wxDEFINE_EVENT( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, wxWebViewEvent);
|
||||
|
||||
wxStringWebViewFactoryMap wxWebView::m_factoryMap;
|
||||
|
||||
|
||||
@@ -31,6 +31,37 @@
|
||||
#include <JavaScriptCore/JSValueRef.h>
|
||||
#include <JavaScriptCore/JSStringRef.h>
|
||||
|
||||
// Helper function to get string from Webkit JS result
|
||||
bool wxGetStringFromJSResult(WebKitJavascriptResult* js_result, wxString* output)
|
||||
{
|
||||
JSGlobalContextRef context = webkit_javascript_result_get_global_context(js_result);
|
||||
JSValueRef value = webkit_javascript_result_get_value(js_result);
|
||||
|
||||
JSValueRef exception = NULL;
|
||||
wxJSStringRef js_value
|
||||
(
|
||||
JSValueIsObject(context, value)
|
||||
? JSValueCreateJSONString(context, value, 0, &exception)
|
||||
: JSValueToStringCopy(context, value, &exception)
|
||||
);
|
||||
|
||||
if ( exception )
|
||||
{
|
||||
if ( output )
|
||||
{
|
||||
wxJSStringRef ex_value(JSValueToStringCopy(context, exception, NULL));
|
||||
*output = ex_value.ToWxString();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( output != NULL )
|
||||
*output = js_value.ToWxString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// GTK callbacks
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -290,6 +321,21 @@ wxgtk_webview_webkit_leave_fullscreen(WebKitWebView *WXUNUSED(web_view),
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
wxgtk_webview_webkit_script_message_received(WebKitUserContentManager *WXUNUSED(content_manager),
|
||||
WebKitJavascriptResult *js_result,
|
||||
wxWebViewWebKit *webKitCtrl)
|
||||
{
|
||||
wxWebViewEvent event(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED,
|
||||
webKitCtrl->GetId(),
|
||||
webKitCtrl->GetCurrentURL(),
|
||||
"");
|
||||
wxString msgStr;
|
||||
if (wxGetStringFromJSResult(js_result, &msgStr))
|
||||
event.SetString(msgStr);
|
||||
webKitCtrl->HandleWindowEvent(event);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
wxgtk_webview_webkit_decide_policy(WebKitWebView *web_view,
|
||||
WebKitPolicyDecision *decision,
|
||||
@@ -698,6 +744,18 @@ float wxWebViewWebKit::GetWebkitZoom() const
|
||||
return webkit_web_view_get_zoom_level(m_web_view);
|
||||
}
|
||||
|
||||
void wxWebViewWebKit::EnableAccessToDevTools(bool enable)
|
||||
{
|
||||
WebKitSettings* settings = webkit_web_view_get_settings(m_web_view);
|
||||
webkit_settings_set_enable_developer_extras(settings, enable);
|
||||
}
|
||||
|
||||
bool wxWebViewWebKit::IsAccessToDevToolsEnabled() const
|
||||
{
|
||||
WebKitSettings* settings = webkit_web_view_get_settings(m_web_view);
|
||||
return webkit_settings_get_enable_developer_extras(settings);
|
||||
}
|
||||
|
||||
void wxWebViewWebKit::Stop()
|
||||
{
|
||||
webkit_web_view_stop_loading(m_web_view);
|
||||
@@ -1209,32 +1267,7 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output
|
||||
return false;
|
||||
}
|
||||
|
||||
JSGlobalContextRef context = webkit_javascript_result_get_global_context(js_result);
|
||||
JSValueRef value = webkit_javascript_result_get_value(js_result);
|
||||
|
||||
JSValueRef exception = NULL;
|
||||
wxJSStringRef js_value
|
||||
(
|
||||
JSValueIsObject(context, value)
|
||||
? JSValueCreateJSONString(context, value, 0, &exception)
|
||||
: JSValueToStringCopy(context, value, &exception)
|
||||
);
|
||||
|
||||
if ( exception )
|
||||
{
|
||||
if ( output )
|
||||
{
|
||||
wxJSStringRef ex_value(JSValueToStringCopy(context, exception, NULL));
|
||||
*output = ex_value.ToWxString();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( output != NULL )
|
||||
*output = js_value.ToWxString();
|
||||
|
||||
return true;
|
||||
return wxGetStringFromJSResult(js_result, output);
|
||||
}
|
||||
|
||||
bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const
|
||||
@@ -1266,6 +1299,57 @@ bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) co
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name)
|
||||
{
|
||||
if (!m_web_view)
|
||||
return false;
|
||||
|
||||
WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view);
|
||||
g_signal_connect(ucm, wxString::Format("script-message-received::%s", name).utf8_str(),
|
||||
G_CALLBACK(wxgtk_webview_webkit_script_message_received), this);
|
||||
bool res = webkit_user_content_manager_register_script_message_handler(ucm, name.utf8_str());
|
||||
if (res)
|
||||
{
|
||||
// Make webkit message handler available under common name
|
||||
wxString js = wxString::Format("window.%s = window.webkit.messageHandlers.%s;",
|
||||
name, name);
|
||||
AddUserScript(js);
|
||||
RunScript(js);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool wxWebViewWebKit::RemoveScriptMessageHandler(const wxString& name)
|
||||
{
|
||||
WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view);
|
||||
webkit_user_content_manager_unregister_script_message_handler(ucm, name.utf8_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxWebViewWebKit::AddUserScript(const wxString& javascript,
|
||||
wxWebViewUserScriptInjectionTime injectionTime)
|
||||
{
|
||||
WebKitUserScript* userScript = webkit_user_script_new(
|
||||
javascript.utf8_str(),
|
||||
WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
|
||||
(injectionTime == wxWEBVIEW_INJECT_AT_DOCUMENT_START) ?
|
||||
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START : WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
|
||||
NULL, NULL
|
||||
);
|
||||
WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view);
|
||||
webkit_user_content_manager_add_script(ucm, userScript);
|
||||
webkit_user_script_unref(userScript);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void wxWebViewWebKit::RemoveAllUserScripts()
|
||||
{
|
||||
WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view);
|
||||
webkit_user_content_manager_remove_all_scripts(ucm);
|
||||
}
|
||||
|
||||
void wxWebViewWebKit::RegisterHandler(wxSharedPtr<wxWebViewHandler> handler)
|
||||
{
|
||||
m_handlerList.push_back(handler);
|
||||
|
||||
@@ -75,6 +75,7 @@ wxWebViewEdgeImpl::~wxWebViewEdgeImpl()
|
||||
m_webView->remove_DocumentTitleChanged(m_documentTitleChangedToken);
|
||||
m_webView->remove_ContentLoading(m_contentLoadingToken);
|
||||
m_webView->remove_ContainsFullScreenElementChanged(m_containsFullScreenElementChangedToken);
|
||||
m_webView->remove_WebMessageReceived(m_webMessageReceivedToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ bool wxWebViewEdgeImpl::Create()
|
||||
m_initialized = false;
|
||||
m_isBusy = false;
|
||||
m_pendingContextMenuEnabled = -1;
|
||||
m_pendingAccessToDevToolsEnabled = -1;
|
||||
m_pendingAccessToDevToolsEnabled = 0;
|
||||
|
||||
m_historyLoadingFromList = false;
|
||||
m_historyEnabled = true;
|
||||
@@ -320,6 +321,37 @@ HRESULT wxWebViewEdgeImpl::OnContainsFullScreenElementChanged(ICoreWebView2* WXU
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT
|
||||
wxWebViewEdgeImpl::OnWebMessageReceived(ICoreWebView2* WXUNUSED(sender),
|
||||
ICoreWebView2WebMessageReceivedEventArgs* args)
|
||||
{
|
||||
wxCoTaskMemPtr<wchar_t> msgContent;
|
||||
|
||||
HRESULT hr = args->get_WebMessageAsJson(&msgContent);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
wxLogApiError("get_WebMessageAsJson", hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
wxWebViewEvent event(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, m_ctrl->GetId(),
|
||||
m_ctrl->GetCurrentURL(), wxString(),
|
||||
wxWEBVIEW_NAV_ACTION_NONE, m_scriptMsgHandlerName);
|
||||
event.SetEventObject(m_ctrl);
|
||||
|
||||
// Try to decode JSON string or return original
|
||||
// result if it's not a valid JSON string
|
||||
wxString msgStr;
|
||||
wxString msgJson(msgContent);
|
||||
if (!wxJSON::DecodeString(msgJson, &msgStr))
|
||||
msgStr = msgJson;
|
||||
event.SetString(msgStr);
|
||||
|
||||
m_ctrl->HandleWindowEvent(event);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT wxWebViewEdgeImpl::OnWebViewCreated(HRESULT result, ICoreWebView2Controller* webViewController)
|
||||
{
|
||||
if (FAILED(result))
|
||||
@@ -369,6 +401,10 @@ HRESULT wxWebViewEdgeImpl::OnWebViewCreated(HRESULT result, ICoreWebView2Control
|
||||
Callback<ICoreWebView2ContainsFullScreenElementChangedEventHandler>(
|
||||
this, &wxWebViewEdgeImpl::OnContainsFullScreenElementChanged).Get(),
|
||||
&m_containsFullScreenElementChangedToken);
|
||||
m_webView->add_WebMessageReceived(
|
||||
Callback<ICoreWebView2WebMessageReceivedEventHandler>(
|
||||
this, &wxWebViewEdgeImpl::OnWebMessageReceived).Get(),
|
||||
&m_webMessageReceivedToken);
|
||||
|
||||
if (m_pendingContextMenuEnabled != -1)
|
||||
{
|
||||
@@ -384,7 +420,18 @@ HRESULT wxWebViewEdgeImpl::OnWebViewCreated(HRESULT result, ICoreWebView2Control
|
||||
|
||||
wxCOMPtr<ICoreWebView2Settings> settings(GetSettings());
|
||||
if (settings)
|
||||
{
|
||||
settings->put_IsStatusBarEnabled(false);
|
||||
}
|
||||
UpdateWebMessageHandler();
|
||||
|
||||
if (!m_pendingUserScripts.empty())
|
||||
{
|
||||
for (wxVector<wxString>::iterator it = m_pendingUserScripts.begin();
|
||||
it != m_pendingUserScripts.end(); ++it)
|
||||
m_ctrl->AddUserScript(*it);
|
||||
m_pendingUserScripts.clear();
|
||||
}
|
||||
|
||||
if (!m_pendingURL.empty())
|
||||
{
|
||||
@@ -395,6 +442,24 @@ HRESULT wxWebViewEdgeImpl::OnWebViewCreated(HRESULT result, ICoreWebView2Control
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void wxWebViewEdgeImpl::UpdateWebMessageHandler()
|
||||
{
|
||||
wxCOMPtr<ICoreWebView2Settings> settings(GetSettings());
|
||||
if (!settings)
|
||||
return;
|
||||
|
||||
settings->put_IsWebMessageEnabled(!m_scriptMsgHandlerName.empty());
|
||||
|
||||
if (!m_scriptMsgHandlerName.empty())
|
||||
{
|
||||
// Make edge message handler available under common name
|
||||
wxString js = wxString::Format("window.%s = window.chrome.webview;",
|
||||
m_scriptMsgHandlerName);
|
||||
m_ctrl->AddUserScript(js);
|
||||
m_webView->ExecuteScript(js.wc_str(), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
ICoreWebView2Settings* wxWebViewEdgeImpl::GetSettings()
|
||||
{
|
||||
if (!m_webView)
|
||||
@@ -774,6 +839,65 @@ bool wxWebViewEdge::RunScript(const wxString& javascript, wxString* output) cons
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxWebViewEdge::AddScriptMessageHandler(const wxString& name)
|
||||
{
|
||||
// Edge only supports a single message handler
|
||||
if (!m_impl->m_scriptMsgHandlerName.empty())
|
||||
return false;
|
||||
|
||||
m_impl->m_scriptMsgHandlerName = name;
|
||||
m_impl->UpdateWebMessageHandler();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxWebViewEdge::RemoveScriptMessageHandler(const wxString& WXUNUSED(name))
|
||||
{
|
||||
m_impl->m_scriptMsgHandlerName.clear();
|
||||
m_impl->UpdateWebMessageHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
HRESULT wxWebViewEdgeImpl::OnAddScriptToExecuteOnDocumentedCreatedCompleted(HRESULT errorCode, LPCWSTR id)
|
||||
{
|
||||
if (SUCCEEDED(errorCode))
|
||||
m_userScriptIds.push_back(id);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
bool wxWebViewEdge::AddUserScript(const wxString& javascript,
|
||||
wxWebViewUserScriptInjectionTime injectionTime)
|
||||
{
|
||||
// Currently only AT_DOCUMENT_START is supported
|
||||
if (injectionTime != wxWEBVIEW_INJECT_AT_DOCUMENT_START)
|
||||
return false;
|
||||
|
||||
if (m_impl->m_webView)
|
||||
{
|
||||
HRESULT hr = m_impl->m_webView->AddScriptToExecuteOnDocumentCreated(javascript.wc_str(),
|
||||
Callback<ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler>(m_impl,
|
||||
&wxWebViewEdgeImpl::OnAddScriptToExecuteOnDocumentedCreatedCompleted).Get());
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
m_impl->m_pendingUserScripts.push_back(javascript);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void wxWebViewEdge::RemoveAllUserScripts()
|
||||
{
|
||||
m_impl->m_pendingUserScripts.clear();
|
||||
for (auto& scriptId : m_impl->m_userScriptIds)
|
||||
{
|
||||
HRESULT hr = m_impl->m_webView->RemoveScriptToExecuteOnDocumentCreated(scriptId.wc_str());
|
||||
if (FAILED(hr))
|
||||
wxLogApiError("RemoveScriptToExecuteOnDocumentCreated", hr);
|
||||
}
|
||||
m_impl->m_userScriptIds.clear();
|
||||
}
|
||||
|
||||
void wxWebViewEdge::RegisterHandler(wxSharedPtr<wxWebViewHandler> WXUNUSED(handler))
|
||||
{
|
||||
// TODO: could maybe be implemented via IWebView2WebView5::add_WebResourceRequested
|
||||
|
||||
@@ -87,6 +87,15 @@ wxEND_EVENT_TABLE()
|
||||
@end
|
||||
#endif // macOS 10.13+
|
||||
|
||||
@interface WebViewScriptMessageHandler: NSObject<WKScriptMessageHandler>
|
||||
{
|
||||
wxWebViewWebKit* webKitWindow;
|
||||
}
|
||||
|
||||
- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow;
|
||||
|
||||
@end
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// wxWebViewFactoryWebKit
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -385,6 +394,41 @@ bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) co
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name)
|
||||
{
|
||||
[m_webView.configuration.userContentController addScriptMessageHandler:
|
||||
[[WebViewScriptMessageHandler alloc] initWithWxWindow:this] name:wxCFStringRef(name).AsNSString()];
|
||||
// Make webkit message handler available under common name
|
||||
wxString js = wxString::Format("window.%s = window.webkit.messageHandlers.%s;",
|
||||
name, name);
|
||||
AddUserScript(js);
|
||||
RunScript(js);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxWebViewWebKit::RemoveScriptMessageHandler(const wxString& name)
|
||||
{
|
||||
[m_webView.configuration.userContentController removeScriptMessageHandlerForName:wxCFStringRef(name).AsNSString()];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxWebViewWebKit::AddUserScript(const wxString& javascript,
|
||||
wxWebViewUserScriptInjectionTime injectionTime)
|
||||
{
|
||||
WKUserScript* userScript =
|
||||
[[WKUserScript alloc] initWithSource:wxCFStringRef(javascript).AsNSString()
|
||||
injectionTime:(injectionTime == wxWEBVIEW_INJECT_AT_DOCUMENT_START) ?
|
||||
WKUserScriptInjectionTimeAtDocumentStart : WKUserScriptInjectionTimeAtDocumentEnd
|
||||
forMainFrameOnly:NO];
|
||||
[m_webView.configuration.userContentController addUserScript:userScript];
|
||||
return true;
|
||||
}
|
||||
|
||||
void wxWebViewWebKit::RemoveAllUserScripts()
|
||||
{
|
||||
[m_webView.configuration.userContentController removeAllUserScripts];
|
||||
}
|
||||
|
||||
void wxWebViewWebKit::LoadURL(const wxString& url)
|
||||
{
|
||||
[m_webView loadRequest:[NSURLRequest requestWithURL:
|
||||
@@ -891,4 +935,45 @@ WX_API_AVAILABLE_MACOS(10, 12)
|
||||
|
||||
@end
|
||||
|
||||
@implementation WebViewScriptMessageHandler
|
||||
|
||||
- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
webKitWindow = inWindow; // non retained
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)userContentController:(nonnull WKUserContentController *)userContentController
|
||||
didReceiveScriptMessage:(nonnull WKScriptMessage *)message
|
||||
{
|
||||
wxWebViewEvent event(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED,
|
||||
webKitWindow->GetId(),
|
||||
webKitWindow->GetCurrentURL(),
|
||||
"",
|
||||
wxWEBVIEW_NAV_ACTION_NONE,
|
||||
wxCFStringRef::AsString(message.name));
|
||||
if ([message.body isKindOfClass:NSString.class])
|
||||
event.SetString(wxCFStringRef::AsString(message.body));
|
||||
else if ([message.body isKindOfClass:NSNumber.class])
|
||||
event.SetString(wxCFStringRef::AsString(((NSNumber*)message.body).stringValue));
|
||||
else if ([message.body isKindOfClass:NSDate.class])
|
||||
event.SetString(wxCFStringRef::AsString(((NSDate*)message.body).description));
|
||||
else if ([message.body isKindOfClass:NSNull.class])
|
||||
event.SetString("null");
|
||||
else if ([message.body isKindOfClass:NSDictionary.class] || [message.body isKindOfClass:NSArray.class])
|
||||
{
|
||||
NSError* error = nil;
|
||||
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:message.body options:0 error:&error];
|
||||
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
event.SetString(wxCFStringRef::AsString(jsonString));
|
||||
}
|
||||
|
||||
webKitWindow->ProcessWindowEvent(event);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif //wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT
|
||||
|
||||
Reference in New Issue
Block a user