Merge branch 'webview_runscript_improvements' of https://github.com/TcT2k/wxWidgets

Add WebView::RunScriptAsync() for running scripts asynchronously.

See https://github.com/wxWidgets/wxWidgets/pull/2316
This commit is contained in:
Vadim Zeitlin
2021-11-16 17:30:55 +01:00
13 changed files with 390 additions and 324 deletions

View File

@@ -27,6 +27,8 @@ typedef struct _WebKitWebView WebKitWebView;
// wxWebViewWebKit // wxWebViewWebKit
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
class wxWebKitRunScriptParams;
class WXDLLIMPEXP_WEBVIEW wxWebViewWebKit : public wxWebView class WXDLLIMPEXP_WEBVIEW wxWebViewWebKit : public wxWebView
{ {
public: public:
@@ -119,13 +121,15 @@ public:
virtual wxString GetSelectedSource() const wxOVERRIDE; virtual wxString GetSelectedSource() const wxOVERRIDE;
virtual void ClearSelection() wxOVERRIDE; virtual void ClearSelection() wxOVERRIDE;
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE;
#if wxUSE_WEBVIEW_WEBKIT2 #if wxUSE_WEBVIEW_WEBKIT2
virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const wxOVERRIDE;
virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool AddUserScript(const wxString& javascript, virtual bool AddUserScript(const wxString& javascript,
wxWebViewUserScriptInjectionTime injectionTime = wxWEBVIEW_INJECT_AT_DOCUMENT_START) wxOVERRIDE; wxWebViewUserScriptInjectionTime injectionTime = wxWEBVIEW_INJECT_AT_DOCUMENT_START) wxOVERRIDE;
virtual void RemoveAllUserScripts() wxOVERRIDE; virtual void RemoveAllUserScripts() wxOVERRIDE;
#else
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE;
#endif #endif
//Virtual Filesystem Support //Virtual Filesystem Support
@@ -151,6 +155,11 @@ public:
//create-web-view signal and so we need to send a new window event //create-web-view signal and so we need to send a new window event
bool m_creating; bool m_creating;
#if wxUSE_WEBVIEW_WEBKIT2
// This method needs to be public to make it callable from a callback
void ProcessJavaScriptResult(GAsyncResult *res, wxWebKitRunScriptParams* params) const;
#endif
protected: protected:
virtual void DoSetPage(const wxString& html, const wxString& baseUrl) wxOVERRIDE; virtual void DoSetPage(const wxString& html, const wxString& baseUrl) wxOVERRIDE;
@@ -173,7 +182,6 @@ private:
bool CanExecuteEditingCommand(const gchar* command) const; bool CanExecuteEditingCommand(const gchar* command) const;
void SetupWebExtensionServer(); void SetupWebExtensionServer();
GDBusProxy *GetExtensionProxy() const; GDBusProxy *GetExtensionProxy() const;
bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const;
#endif #endif
WebKitWebView *m_web_view; WebKitWebView *m_web_view;

View File

@@ -88,7 +88,7 @@ public:
virtual bool SetUserAgent(const wxString& userAgent) wxOVERRIDE; virtual bool SetUserAgent(const wxString& userAgent) wxOVERRIDE;
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE; virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const wxOVERRIDE;
virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool AddUserScript(const wxString& javascript, virtual bool AddUserScript(const wxString& javascript,
@@ -113,8 +113,6 @@ private:
void OnTopLevelParentIconized(wxIconizeEvent& event); void OnTopLevelParentIconized(wxIconizeEvent& event);
bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const;
wxDECLARE_DYNAMIC_CLASS(wxWebViewEdge); wxDECLARE_DYNAMIC_CLASS(wxWebViewEdge);
}; };

View File

@@ -94,7 +94,7 @@ public:
virtual void SetEditable(bool enable = true) wxOVERRIDE; virtual void SetEditable(bool enable = true) wxOVERRIDE;
virtual bool IsEditable() const wxOVERRIDE; virtual bool IsEditable() const wxOVERRIDE;
bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE; virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const wxOVERRIDE;
virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool AddUserScript(const wxString& javascript, virtual bool AddUserScript(const wxString& javascript,
@@ -118,8 +118,6 @@ private:
WX_NSObject m_navigationDelegate; WX_NSObject m_navigationDelegate;
WX_NSObject m_UIDelegate; WX_NSObject m_UIDelegate;
bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const;
}; };
class WXDLLIMPEXP_WEBVIEW wxWebViewFactoryWebKit : public wxWebViewFactory class WXDLLIMPEXP_WEBVIEW wxWebViewFactoryWebKit : public wxWebViewFactory

View File

@@ -24,18 +24,17 @@
class wxJSScriptWrapper class wxJSScriptWrapper
{ {
public: public:
wxJSScriptWrapper(const wxString& js, int* runScriptCount) enum OutputType
: m_escapedCode(js)
{ {
// We assign the return value of JavaScript snippet we execute to the JS_OUTPUT_STRING, // All return types are converted to a string
// variable with this name in order to be able to access it later if JS_OUTPUT_WEBKIT, // Some return types will be processed
// needed. JS_OUTPUT_IE, // Most return types will be processed
// JS_OUTPUT_RAW // The return types is returned as is
// Note that we use a different name for it for each call to };
// RunScript() (which creates a new wxJSScriptWrapper every time) to
// avoid any possible conflict between different calls.
m_outputVarName = wxString::Format(wxASCII_STR("__wxOut%i"), (*runScriptCount)++);
wxJSScriptWrapper(const wxString& js, OutputType outputType)
: m_escapedCode(js), m_outputType(outputType)
{
// Adds one escape level. // Adds one escape level.
const char *charsNeededToBeEscaped = "\\\"\n\r\v\t\b\f"; const char *charsNeededToBeEscaped = "\\\"\n\r\v\t\b\f";
for ( for (
@@ -69,126 +68,139 @@ public:
} }
} }
// Get the code to execute, its returned value will be either boolean true, // Get the code to execute, its returned value will be either the value,
// if it executed successfully, or the exception message if an error // if it executed successfully, or the exception message prefixed with
// occurred. // "__wxexc:" if an error occurred.
// //
// Execute GetOutputCode() later to get the real output after successful // Either use SetOutput() to specify the script result or access it directly
// execution of this code. // Using GetOutputRef()
//
// Execute ExtractOutput() later to get the real output after successful
// execution of this code or the proper error message.
wxString GetWrappedCode() const wxString GetWrappedCode() const
{ {
return wxString::Format wxString code = wxString::Format(
( wxASCII_STR("(function () { try { var res = eval(\"%s\"); "),
wxASCII_STR("try { var %s = eval(\"%s\"); true; } " m_escapedCode);
"catch (e) { e.name + \": \" + e.message; }"),
m_outputVarName,
m_escapedCode
);
}
// Get code returning the result of the last successful execution of the switch (m_outputType)
// code returned by GetWrappedCode(). {
wxString GetOutputCode() const case JS_OUTPUT_STRING:
{ code += wxASCII_STR(
#if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT && defined(__WXOSX__) "if (typeof res == 'object') return JSON.stringify(res);"
return wxString::Format "else if (typeof res == 'undefined') return 'undefined';"
( "else return String(res);"
wxASCII_STR("if (typeof %s == 'object') JSON.stringify(%s);" );
"else if (typeof %s == 'undefined') 'undefined';" break;
"else %s;"), case JS_OUTPUT_WEBKIT:
m_outputVarName, code += wxASCII_STR(
m_outputVarName, "if (typeof res == 'object') return JSON.stringify(res);"
m_outputVarName, "else if (typeof res == 'undefined') return 'undefined';"
m_outputVarName "else return res;"
); );
#elif wxUSE_WEBVIEW && wxUSE_WEBVIEW_IE break;
return wxString::Format case JS_OUTPUT_IE:
( code += wxASCII_STR(
wxASCII_STR("try {"
"(%s == null || typeof %s != 'object') ? String(%s)"
": JSON.stringify(%s);"
"}"
"catch (e) {"
"try {" "try {"
"function __wx$stringifyJSON(obj) {" "return (res == null || typeof res != 'object') ? String(res)"
"if (!(obj instanceof Object))" ": JSON.stringify(res);"
"return typeof obj === \"string\""
"? \'\"\' + obj + \'\"\'"
": \'\' + obj;"
"else if (obj instanceof Array) {"
"if (obj[0] === undefined)"
"return \'[]\';"
"else {"
"var arr = [];"
"for (var i = 0; i < obj.length; i++)"
"arr.push(__wx$stringifyJSON(obj[i]));"
"return \'[\' + arr + \']\';"
"}"
"}"
"else if (typeof obj === \"object\") {"
"if (obj instanceof Date) {"
"if (!Date.prototype.toISOString) {"
"(function() {"
"function pad(number) {"
"return number < 10"
"? '0' + number"
": number;"
"}"
"Date.prototype.toISOString = function() {"
"return this.getUTCFullYear() +"
"'-' + pad(this.getUTCMonth() + 1) +"
"'-' + pad(this.getUTCDate()) +"
"'T' + pad(this.getUTCHours()) +"
"':' + pad(this.getUTCMinutes()) +"
"':' + pad(this.getUTCSeconds()) +"
"'.' + (this.getUTCMilliseconds() / 1000)"
".toFixed(3).slice(2, 5) + 'Z\"';"
"};"
"}());"
"}"
"return '\"' + obj.toISOString(); + '\"'"
"}"
"var objElements = [];"
"for (var key in obj)"
"{"
"if (typeof obj[key] === \"function\")"
"return \'{}\';"
"else {"
"objElements.push(\'\"\'"
"+ key + \'\":\' +"
"__wx$stringifyJSON(obj[key]));"
"}"
"}"
"return \'{\' + objElements + \'}\';"
"}"
"}"
"__wx$stringifyJSON(%s);"
"}" "}"
"catch (e) { e.name + \": \" + e.message; }" "catch (e) {"
"}"), "try {"
m_outputVarName, "function __wx$stringifyJSON(obj) {"
m_outputVarName, "if (!(obj instanceof Object))"
m_outputVarName, "return typeof obj === \"string\""
m_outputVarName, "? \'\"\' + obj + \'\"\'"
m_outputVarName ": \'\' + obj;"
); "else if (obj instanceof Array) {"
#else "if (obj[0] === undefined)"
return m_outputVarName; "return \'[]\';"
#endif "else {"
"var arr = [];"
"for (var i = 0; i < obj.length; i++)"
"arr.push(__wx$stringifyJSON(obj[i]));"
"return \'[\' + arr + \']\';"
"}"
"}"
"else if (typeof obj === \"object\") {"
"if (obj instanceof Date) {"
"if (!Date.prototype.toISOString) {"
"(function() {"
"function pad(number) {"
"return number < 10"
"? '0' + number"
": number;"
"}"
"Date.prototype.toISOString = function() {"
"return this.getUTCFullYear() +"
"'-' + pad(this.getUTCMonth() + 1) +"
"'-' + pad(this.getUTCDate()) +"
"'T' + pad(this.getUTCHours()) +"
"':' + pad(this.getUTCMinutes()) +"
"':' + pad(this.getUTCSeconds()) +"
"'.' + (this.getUTCMilliseconds() / 1000)"
".toFixed(3).slice(2, 5) + 'Z\"';"
"};"
"}());"
"}"
"return '\"' + obj.toISOString(); + '\"'"
"}"
"var objElements = [];"
"for (var key in obj)"
"{"
"if (typeof obj[key] === \"function\")"
"return \'{}\';"
"else {"
"objElements.push(\'\"\'"
"+ key + \'\":\' +"
"__wx$stringifyJSON(obj[key]));"
"}"
"}"
"return \'{\' + objElements + \'}\';"
"}"
"}"
"return __wx$stringifyJSON(res);"
"}"
"catch (e) { return \"__wxexc:\" + e.name + \": \" + e.message; }"
"}");
break;
case JS_OUTPUT_RAW:
code += wxASCII_STR("return res;");
break;
}
code +=
wxASCII_STR("} catch (e) { return \"__wxexc:\" + e.name + \": \" + e.message; }"
"})()");
return code;
} }
const wxString& GetUnwrappedOutputCode() { return m_outputVarName; } // Extract the output value
//
// Execute the code returned by this function to let the output of the code // Returns true if executed successfully
// we executed be garbage-collected. // string of the result will be put into output
wxString GetCleanUpCode() const // Returns false when an exception occurred
// string will be the exception message
static bool ExtractOutput(const wxString& result, wxString* output)
{ {
return wxString::Format(wxASCII_STR("%s = undefined;"), m_outputVarName); if (output)
*output = result;
if (result.starts_with(wxASCII_STR("__wxexc:")))
{
if (output)
output->Remove(0, 8);
return false;
}
else
{
return true;
}
} }
private: private:
wxString m_escapedCode; wxString m_escapedCode;
wxString m_outputVarName; OutputType m_outputType;
wxDECLARE_NO_COPY_CLASS(wxJSScriptWrapper); wxDECLARE_NO_COPY_CLASS(wxJSScriptWrapper);
}; };

View File

@@ -138,7 +138,7 @@ public:
wxWebView() wxWebView()
{ {
m_showMenu = true; m_showMenu = true;
m_runScriptCount = 0; m_syncScriptResult = 0;
} }
virtual ~wxWebView() {} virtual ~wxWebView() {}
@@ -191,7 +191,8 @@ public:
virtual wxString GetUserAgent() const; virtual wxString GetUserAgent() const;
// Script // Script
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const = 0; virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const;
virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const;
virtual bool AddScriptMessageHandler(const wxString& name) virtual bool AddScriptMessageHandler(const wxString& name)
{ wxUnusedVar(name); return false; } { wxUnusedVar(name); return false; }
virtual bool RemoveScriptMessageHandler(const wxString& name) virtual bool RemoveScriptMessageHandler(const wxString& name)
@@ -267,15 +268,16 @@ protected:
bool QueryCommandEnabled(const wxString& command) const; bool QueryCommandEnabled(const wxString& command) const;
void ExecCommand(const wxString& command); void ExecCommand(const wxString& command);
// Count the number of calls to RunScript() in order to prevent void SendScriptResult(void* clientData, bool success,
// the_same variable from being used twice in more than one call. const wxString& output) const;
mutable int m_runScriptCount;
private: private:
static void InitFactoryMap(); static void InitFactoryMap();
static wxStringWebViewFactoryMap::iterator FindFactory(const wxString &backend); static wxStringWebViewFactoryMap::iterator FindFactory(const wxString &backend);
bool m_showMenu; bool m_showMenu;
mutable int m_syncScriptResult;
mutable wxString m_syncScriptOutput;
wxString m_findText; wxString m_findText;
static wxStringWebViewFactoryMap m_factoryMap; static wxStringWebViewFactoryMap m_factoryMap;
@@ -294,6 +296,7 @@ public:
m_actionFlags(flags), m_messageHandler(messageHandler) m_actionFlags(flags), m_messageHandler(messageHandler)
{} {}
bool IsError() const { return GetInt() == 0; }
const wxString& GetURL() const { return m_url; } const wxString& GetURL() const { return m_url; }
const wxString& GetTarget() const { return m_target; } const wxString& GetTarget() const { return m_target; }
@@ -319,6 +322,7 @@ wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_NEWWINDOW, wxWebVie
wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_TITLE_CHANGED, wxWebViewEvent ); wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_TITLE_CHANGED, wxWebViewEvent );
wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_FULLSCREEN_CHANGED, wxWebViewEvent); wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_FULLSCREEN_CHANGED, wxWebViewEvent);
wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, wxWebViewEvent); wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, wxWebViewEvent);
wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_SCRIPT_RESULT, wxWebViewEvent);
typedef void (wxEvtHandler::*wxWebViewEventFunction) typedef void (wxEvtHandler::*wxWebViewEventFunction)
(wxWebViewEvent&); (wxWebViewEvent&);

View File

@@ -485,6 +485,10 @@ public:
Process a @c wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED event Process a @c wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED event
only available in wxWidgets 3.1.5 or later. For usage details see only available in wxWidgets 3.1.5 or later. For usage details see
AddScriptMessageHandler(). AddScriptMessageHandler().
@event{wxEVT_WEBVIEW_SCRIPT_RESULT(id, func)}
Process a @c wxEVT_WEBVIEW_SCRIPT_RESULT event
only available in wxWidgets 3.1.6 or later. For usage details see
RunScriptAsync().
@endEventTable @endEventTable
@since 2.9.3 @since 2.9.3
@@ -710,6 +714,11 @@ public:
/** /**
Runs the given JavaScript code. Runs the given JavaScript code.
@note Because of various potential issues it's recommended to use
RunScriptAsync() instead of this method. This is especially true
if you plan to run code from a webview event and will also prevent
unintended side effects on the UI outside of the webview.
JavaScript code is executed inside the browser control and has full JavaScript code is executed inside the browser control and has full
access to DOM and other browser-provided functionality. For example, access to DOM and other browser-provided functionality. For example,
this code this code
@@ -764,9 +773,32 @@ public:
@NULL if it is not needed. This parameter is new since wxWidgets @NULL if it is not needed. This parameter is new since wxWidgets
version 3.1.1. version 3.1.1.
@return @true if there is a result, @false if there is an error. @return @true if there is a result, @false if there is an error.
@see RunScriptAsync()
*/ */
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const = 0; virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const = 0;
/**
Runs the given JavaScript code asynchronously and returns the result
via a @c wxEVT_WEBVIEW_SCRIPT_RESULT.
The script result value can be retrieved via wxWebViewEvent::GetString().
If the execution fails wxWebViewEvent::IsError() will return @true. In this
case additional script execution error information maybe available
via wxWebViewEvent::GetString().
@param javascript JavaScript code to execute.
@param clientData Arbirary pointer to data that can be retrieved from
the result event.
@note The IE backend does not support async script execution.
@since 3.1.6
@see RunScript()
*/
virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const;
/** /**
Add a script message handler with the given name. Add a script message handler with the given name.
@@ -1279,6 +1311,10 @@ public:
Process a @c wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED event Process a @c wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED event
only available in wxWidgets 3.1.5 or later. For usage details see only available in wxWidgets 3.1.5 or later. For usage details see
wxWebView::AddScriptMessageHandler(). wxWebView::AddScriptMessageHandler().
@event{wxEVT_WEBVIEW_SCRIPT_RESULT(id, func)}
Process a @c wxEVT_WEBVIEW_SCRIPT_RESULT event
only available in wxWidgets 3.1.6 or later. For usage details see
wxWebView::RunScriptAsync().
@endEventTable @endEventTable
@since 2.9.3 @since 2.9.3
@@ -1323,6 +1359,14 @@ public:
@since 3.1.5 @since 3.1.5
*/ */
const wxString& GetMessageHandler() const; const wxString& GetMessageHandler() const;
/**
Returns true the script execution failed. Only valid for events of type
@c wxEVT_WEBVIEW_SCRIPT_RESULT
@since 3.1.6
*/
bool IsError() const;
}; };

View File

@@ -122,6 +122,7 @@ public:
void OnTitleChanged(wxWebViewEvent& evt); void OnTitleChanged(wxWebViewEvent& evt);
void OnFullScreenChanged(wxWebViewEvent& evt); void OnFullScreenChanged(wxWebViewEvent& evt);
void OnScriptMessage(wxWebViewEvent& evt); void OnScriptMessage(wxWebViewEvent& evt);
void OnScriptResult(wxWebViewEvent& evt);
void OnSetPage(wxCommandEvent& evt); void OnSetPage(wxCommandEvent& evt);
void OnViewSourceRequest(wxCommandEvent& evt); void OnViewSourceRequest(wxCommandEvent& evt);
void OnViewTextRequest(wxCommandEvent& evt); void OnViewTextRequest(wxCommandEvent& evt);
@@ -159,6 +160,7 @@ public:
void OnRunScriptArrayWithEmulationLevel(wxCommandEvent& evt); void OnRunScriptArrayWithEmulationLevel(wxCommandEvent& evt);
#endif #endif
void OnRunScriptMessage(wxCommandEvent& evt); void OnRunScriptMessage(wxCommandEvent& evt);
void OnRunScriptAsync(wxCommandEvent& evt);
void OnRunScriptCustom(wxCommandEvent& evt); void OnRunScriptCustom(wxCommandEvent& evt);
void OnAddUserScript(wxCommandEvent& evt); void OnAddUserScript(wxCommandEvent& evt);
void OnSetCustomUserAgent(wxCommandEvent& evt); void OnSetCustomUserAgent(wxCommandEvent& evt);
@@ -233,6 +235,7 @@ private:
#endif #endif
wxMenuItem* m_script_message; wxMenuItem* m_script_message;
wxMenuItem* m_script_custom; wxMenuItem* m_script_custom;
wxMenuItem* m_script_async;
wxMenuItem* m_selection_clear; wxMenuItem* m_selection_clear;
wxMenuItem* m_selection_delete; wxMenuItem* m_selection_delete;
wxMenuItem* m_find; wxMenuItem* m_find;
@@ -492,6 +495,7 @@ WebFrame::WebFrame(const wxString& url) :
m_script_array_el = script_menu->Append(wxID_ANY, "Return array changing emulation level"); m_script_array_el = script_menu->Append(wxID_ANY, "Return array changing emulation level");
} }
#endif #endif
m_script_async = script_menu->Append(wxID_ANY, "Return String async");
m_script_message = script_menu->Append(wxID_ANY, "Send script message"); m_script_message = script_menu->Append(wxID_ANY, "Send script message");
m_script_custom = script_menu->Append(wxID_ANY, "Custom script"); m_script_custom = script_menu->Append(wxID_ANY, "Custom script");
m_tools_menu->AppendSubMenu(script_menu, _("Run Script")); m_tools_menu->AppendSubMenu(script_menu, _("Run Script"));
@@ -551,6 +555,7 @@ WebFrame::WebFrame(const wxString& url) :
Bind(wxEVT_WEBVIEW_TITLE_CHANGED, &WebFrame::OnTitleChanged, this, m_browser->GetId()); Bind(wxEVT_WEBVIEW_TITLE_CHANGED, &WebFrame::OnTitleChanged, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_FULLSCREEN_CHANGED, &WebFrame::OnFullScreenChanged, this, m_browser->GetId()); Bind(wxEVT_WEBVIEW_FULLSCREEN_CHANGED, &WebFrame::OnFullScreenChanged, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebFrame::OnScriptMessage, this, m_browser->GetId()); Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebFrame::OnScriptMessage, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_SCRIPT_RESULT, &WebFrame::OnScriptResult, this, m_browser->GetId());
// Connect the menu events // Connect the menu events
Bind(wxEVT_MENU, &WebFrame::OnSetPage, this, setPage->GetId()); Bind(wxEVT_MENU, &WebFrame::OnSetPage, this, setPage->GetId());
@@ -596,6 +601,7 @@ WebFrame::WebFrame(const wxString& url) :
#endif #endif
Bind(wxEVT_MENU, &WebFrame::OnRunScriptMessage, this, m_script_message->GetId()); Bind(wxEVT_MENU, &WebFrame::OnRunScriptMessage, this, m_script_message->GetId());
Bind(wxEVT_MENU, &WebFrame::OnRunScriptCustom, this, m_script_custom->GetId()); Bind(wxEVT_MENU, &WebFrame::OnRunScriptCustom, this, m_script_custom->GetId());
Bind(wxEVT_MENU, &WebFrame::OnRunScriptAsync, this, m_script_async->GetId());
Bind(wxEVT_MENU, &WebFrame::OnAddUserScript, this, addUserScript->GetId()); Bind(wxEVT_MENU, &WebFrame::OnAddUserScript, this, addUserScript->GetId());
Bind(wxEVT_MENU, &WebFrame::OnSetCustomUserAgent, this, setCustomUserAgent->GetId()); Bind(wxEVT_MENU, &WebFrame::OnSetCustomUserAgent, this, setCustomUserAgent->GetId());
Bind(wxEVT_MENU, &WebFrame::OnClearSelection, this, m_selection_clear->GetId()); Bind(wxEVT_MENU, &WebFrame::OnClearSelection, this, m_selection_clear->GetId());
@@ -924,6 +930,14 @@ void WebFrame::OnScriptMessage(wxWebViewEvent& evt)
wxLogMessage("Script message received; value = %s, handler = %s", evt.GetString(), evt.GetMessageHandler()); wxLogMessage("Script message received; value = %s, handler = %s", evt.GetString(), evt.GetMessageHandler());
} }
void WebFrame::OnScriptResult(wxWebViewEvent& evt)
{
if (evt.IsError())
wxLogError("Async script execution failed: %s", evt.GetString());
else
wxLogMessage("Async script result received; value = %s", evt.GetString());
}
void WebFrame::OnSetPage(wxCommandEvent& WXUNUSED(evt)) void WebFrame::OnSetPage(wxCommandEvent& WXUNUSED(evt))
{ {
m_browser->SetPage m_browser->SetPage
@@ -1199,6 +1213,11 @@ void WebFrame::OnRunScriptMessage(wxCommandEvent& WXUNUSED(evt))
RunScript("window.wx.postMessage('This is a web message');"); RunScript("window.wx.postMessage('This is a web message');");
} }
void WebFrame::OnRunScriptAsync(wxCommandEvent& WXUNUSED(evt))
{
m_browser->RunScriptAsync("function f(a){return a;}f('Hello World!');");
}
void WebFrame::OnRunScriptCustom(wxCommandEvent& WXUNUSED(evt)) void WebFrame::OnRunScriptCustom(wxCommandEvent& WXUNUSED(evt))
{ {
wxTextEntryDialog dialog wxTextEntryDialog dialog

View File

@@ -50,6 +50,7 @@ wxDEFINE_EVENT( wxEVT_WEBVIEW_NEWWINDOW, wxWebViewEvent );
wxDEFINE_EVENT( wxEVT_WEBVIEW_TITLE_CHANGED, wxWebViewEvent ); wxDEFINE_EVENT( wxEVT_WEBVIEW_TITLE_CHANGED, wxWebViewEvent );
wxDEFINE_EVENT( wxEVT_WEBVIEW_FULLSCREEN_CHANGED, wxWebViewEvent); wxDEFINE_EVENT( wxEVT_WEBVIEW_FULLSCREEN_CHANGED, wxWebViewEvent);
wxDEFINE_EVENT( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, wxWebViewEvent); wxDEFINE_EVENT( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, wxWebViewEvent);
wxDEFINE_EVENT( wxEVT_WEBVIEW_SCRIPT_RESULT, wxWebViewEvent);
wxStringWebViewFactoryMap wxWebView::m_factoryMap; wxStringWebViewFactoryMap wxWebView::m_factoryMap;
@@ -224,6 +225,51 @@ wxString wxWebView::GetUserAgent() const
return userAgent; return userAgent;
} }
bool wxWebView::RunScript(const wxString& javascript, wxString* output) const
{
m_syncScriptResult = -1;
m_syncScriptOutput.clear();
RunScriptAsync(javascript);
// Wait for script exection
while (m_syncScriptResult == -1)
wxYield();
if (m_syncScriptResult && output)
*output = m_syncScriptOutput;
return m_syncScriptResult == 1;
}
void wxWebView::RunScriptAsync(const wxString& WXUNUSED(javascript),
void* WXUNUSED(clientData)) const
{
wxLogError(_("RunScriptAsync not supported"));
}
void wxWebView::SendScriptResult(void* clientData, bool success,
const wxString& output) const
{
// If currently running sync RunScript(), don't send an event, but use
// the scripts result directly
if (m_syncScriptResult == -1)
{
if (!success)
wxLogWarning(_("Error running JavaScript: %s"), output);
m_syncScriptOutput = output;
m_syncScriptResult = success;
}
else
{
wxWebViewEvent evt(wxEVT_WEBVIEW_SCRIPT_RESULT, GetId(), "", "",
wxWEBVIEW_NAV_ACTION_NONE);
evt.SetEventObject(const_cast<wxWebView*>(this));
evt.SetClientData(clientData);
evt.SetInt(success);
evt.SetString(output);
HandleWindowEvent(evt);
}
}
// static // static
wxWebView* wxWebView::New(const wxString& backend) wxWebView* wxWebView::New(const wxString& backend)
{ {

View File

@@ -1231,6 +1231,13 @@ wxString wxWebViewWebKit::GetPageText() const
return wxString(); return wxString();
} }
class wxWebKitRunScriptParams
{
public:
const wxWebViewWebKit* webKitCtrl;
void* clientData;
};
extern "C" extern "C"
{ {
@@ -1238,80 +1245,55 @@ static void wxgtk_run_javascript_cb(GObject *,
GAsyncResult *res, GAsyncResult *res,
void *user_data) void *user_data)
{ {
g_object_ref(res); wxWebKitRunScriptParams* params = static_cast<wxWebKitRunScriptParams*>(user_data);
params->webKitCtrl->ProcessJavaScriptResult(res, params);
GAsyncResult** res_out = static_cast<GAsyncResult**>(user_data);
*res_out = res;
} }
} // extern "C" } // extern "C"
// Run the given script synchronously and return its result in output. void wxWebViewWebKit::ProcessJavaScriptResult(GAsyncResult *res, wxWebKitRunScriptParams* params) const
bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) const
{ {
GAsyncResult *result = NULL;
webkit_web_view_run_javascript(m_web_view,
javascript.utf8_str(),
NULL,
wxgtk_run_javascript_cb,
&result);
GMainContext *main_context = g_main_context_get_thread_default();
while ( !result )
g_main_context_iteration(main_context, TRUE);
wxGtkError error; wxGtkError error;
wxWebKitJavascriptResult js_result wxWebKitJavascriptResult js_result
( (
webkit_web_view_run_javascript_finish webkit_web_view_run_javascript_finish
( (
m_web_view, m_web_view,
result, res,
error.Out() error.Out()
) )
); );
// Match g_object_ref() in wxgtk_run_javascript_cb() if ( js_result )
g_object_unref(result);
if ( !js_result )
{ {
if ( output ) wxString scriptResult;
*output = error.GetMessage(); if ( wxGetStringFromJSResult(js_result, &scriptResult) )
return false; {
wxString scriptOutput;
bool success = wxJSScriptWrapper::ExtractOutput(scriptResult, &scriptOutput);
SendScriptResult(params->clientData, success, scriptOutput);
}
} }
else
SendScriptResult(params->clientData, false, error.GetMessage());
return wxGetStringFromJSResult(js_result, output); delete params;
} }
bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const void wxWebViewWebKit::RunScriptAsync(const wxString& javascript, void* clientData) const
{ {
wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING);
// This string is also used as an error indicator: it's cleared if there is // Collect parameters for access from the callback
// no error or used in the warning message below if there is one. wxWebKitRunScriptParams* params = new wxWebKitRunScriptParams;
wxString result; params->webKitCtrl = this;
if ( RunScriptSync(wrapJS.GetWrappedCode(), &result) params->clientData = clientData;
&& result == wxS("true") )
{
if ( RunScriptSync(wrapJS.GetOutputCode(), &result) )
{
if ( output )
*output = result;
result.clear();
}
RunScriptSync(wrapJS.GetCleanUpCode()); webkit_web_view_run_javascript(m_web_view,
} wrapJS.GetWrappedCode().utf8_str(),
NULL,
if ( !result.empty() ) wxgtk_run_javascript_cb,
{ params);
wxLogWarning(_("Error running JavaScript: %s"), result);
return false;
}
return true;
} }
bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name) bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name)

View File

@@ -831,74 +831,46 @@ void wxWebViewEdge::MSWSetBrowserExecutableDir(const wxString & path)
wxWebViewEdgeImpl::ms_browserExecutableDir = path; wxWebViewEdgeImpl::ms_browserExecutableDir = path;
} }
bool wxWebViewEdge::RunScriptSync(const wxString& javascript, wxString* output) const void wxWebViewEdge::RunScriptAsync(const wxString& javascript, void* clientData) const
{ {
bool scriptExecuted = false;
if (!m_impl->m_webView) if (!m_impl->m_webView)
return false; {
SendScriptResult(clientData, false, "");
return; // TODO: postpone execution
}
wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING);
// Start script execution // Start script execution
HRESULT executionResult = m_impl->m_webView->ExecuteScript(javascript.wc_str(), Callback<ICoreWebView2ExecuteScriptCompletedHandler>( HRESULT executionResult = m_impl->m_webView->ExecuteScript(wrapJS.GetWrappedCode().wc_str(), Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
[&scriptExecuted, &executionResult, output](HRESULT error, PCWSTR result) -> HRESULT [this, clientData](HRESULT error, PCWSTR result) -> HRESULT
{ {
// Handle script execution callback // Handle script execution callback
if (error == S_OK) if (error == S_OK)
{ {
if (output) wxString scriptDecodedResult;
output->assign(result); // Try to decode JSON string or return original
// result if it's not a valid JSON string
if (!wxJSON::DecodeString(result, &scriptDecodedResult))
scriptDecodedResult = result;
wxString scriptExtractedOutput;
bool success = wxJSScriptWrapper::ExtractOutput(scriptDecodedResult, &scriptExtractedOutput);
SendScriptResult(clientData, success, scriptExtractedOutput);
} }
else else
executionResult = error; {
SendScriptResult(clientData, false, wxString::Format("%s (0x%08lx)",
scriptExecuted = true; wxSysErrorMsgStr(error), error));
}
return S_OK; return S_OK;
}).Get()); }).Get());
// Wait for script exection
while (!scriptExecuted)
wxYield();
if (FAILED(executionResult)) if (FAILED(executionResult))
{ {
if (output) SendScriptResult(clientData, false, wxString::Format("%s (0x%08lx)",
output->Printf("%s (0x%08lx)", wxSysErrorMsgStr(executionResult), executionResult); wxSysErrorMsgStr(executionResult), executionResult));
return false;
} }
else
return true;
}
bool wxWebViewEdge::RunScript(const wxString& javascript, wxString* output) const
{
wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount);
// This string is also used as an error indicator: it's cleared if there is
// no error or used in the warning message below if there is one.
wxString result;
if (RunScriptSync(wrapJS.GetWrappedCode(), &result)
&& result == wxS("true"))
{
if (RunScriptSync(wrapJS.GetUnwrappedOutputCode() + ";", &result))
{
if (output)
// Try to decode JSON string or return original
// result if it's not a valid JSON string
if (!wxJSON::DecodeString(result, output))
*output = result;
result.clear();
}
RunScriptSync(wrapJS.GetCleanUpCode());
}
if (!result.empty())
{
wxLogWarning(_("Error running JavaScript: %s"), result);
return false;
}
return true;
} }
bool wxWebViewEdge::AddScriptMessageHandler(const wxString& name) bool wxWebViewEdge::AddScriptMessageHandler(const wxString& name)

View File

@@ -1045,40 +1045,25 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) const
return false; return false;
} }
wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_IE);
wxAutomationObject scriptAO(scriptDispatch); wxAutomationObject scriptAO(scriptDispatch);
wxVariant varResult; wxVariant varResult;
wxString err; bool success = false;
if ( !CallEval(wrapJS.GetWrappedCode(), scriptAO, &varResult) ) wxString scriptOutput;
{ if ( CallEval(wrapJS.GetWrappedCode(), scriptAO, &varResult) )
err = _("failed to evaluate"); success = wxJSScriptWrapper::ExtractOutput(varResult.MakeString(), &scriptOutput);
} else
else if ( varResult.IsType("bool") && varResult.GetBool() ) scriptOutput = _("failed to evaluate");
{
if ( output != NULL )
{
if ( CallEval(wrapJS.GetOutputCode(), scriptAO, &varResult) )
*output = varResult.MakeString();
else
err = _("failed to retrieve execution result");
}
CallEval(wrapJS.GetCleanUpCode(), scriptAO, &varResult); if (!success)
} wxLogWarning(_("Error running JavaScript: %s"), scriptOutput);
else // result available but not the expected "true"
{
err = varResult.MakeString();
}
if ( !err.empty() ) if (success && output)
{ *output = scriptOutput;
wxLogWarning(_("Error running JavaScript: %s"), varResult.MakeString());
return false;
}
return true; return success;
} }
void wxWebViewIE::RegisterHandler(wxSharedPtr<wxWebViewHandler> handler) void wxWebViewIE::RegisterHandler(wxSharedPtr<wxWebViewHandler> handler)

View File

@@ -371,76 +371,28 @@ bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType type) const
} }
} }
bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) const void wxWebViewWebKit::RunScriptAsync(const wxString& javascript, void* clientData) const
{ {
__block bool scriptExecuted = false; wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING);
__block wxString outputStr;
__block bool scriptSuccess = false;
// Start script execution // Start script execution
[m_webView evaluateJavaScript:wxCFStringRef(javascript).AsNSString() [m_webView evaluateJavaScript:wxCFStringRef(wrapJS.GetWrappedCode()).AsNSString()
completionHandler:^(id _Nullable obj, NSError * _Nullable error) { completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
if (error) if (error)
{ {
outputStr.assign(wxCFStringRef(error.localizedFailureReason).AsString()); SendScriptResult(clientData, false, wxCFStringRef(error.localizedDescription).AsString());
} }
else else
{ {
if ([obj isKindOfClass:[NSNumber class]]) wxString scriptResult;
{ if (obj)
NSNumber* num = (NSNumber*) obj; scriptResult = wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj]);
CFTypeID numID = CFGetTypeID((__bridge CFTypeRef)(num)); wxString scriptOutput;
if (numID == CFBooleanGetTypeID()) bool success = wxJSScriptWrapper::ExtractOutput(scriptResult, &scriptOutput);
outputStr = num.boolValue ? "true" : "false";
else
outputStr = wxCFStringRef::AsString(num.stringValue);
}
else if (obj)
outputStr.assign(wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj]));
scriptSuccess = true; SendScriptResult(clientData, success, scriptOutput);
} }
scriptExecuted = true;
}]; }];
// Wait for script exection
while (!scriptExecuted)
wxYield();
if (output)
output->assign(outputStr);
return scriptSuccess;
}
bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const
{
wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount);
// This string is also used as an error indicator: it's cleared if there is
// no error or used in the warning message below if there is one.
wxString result;
if (RunScriptSync(wrapJS.GetWrappedCode(), &result)
&& result == wxS("true"))
{
if (RunScriptSync(wrapJS.GetOutputCode() + ";", &result))
{
if (output)
*output = result;
result.clear();
}
RunScriptSync(wrapJS.GetCleanUpCode());
}
if (!result.empty())
{
wxLogWarning(_("Error running JavaScript: %s"), result);
return false;
}
return true;
} }
bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name) bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name)

View File

@@ -35,6 +35,17 @@ public:
: m_browser(wxWebView::New()), : m_browser(wxWebView::New()),
m_loaded(new EventCounter(m_browser, wxEVT_WEBVIEW_LOADED)) m_loaded(new EventCounter(m_browser, wxEVT_WEBVIEW_LOADED))
{ {
#ifdef __WXMSW__
if (wxWebView::IsBackendAvailable(wxWebViewBackendEdge))
{
// The blank page does not have an empty title with edge
m_blankTitle = "about:blank";
// Edge does not support about: url use a different URL instead
m_alternateHistoryURL = "about:blank";
}
else
#endif
m_alternateHistoryURL = "about:";
} }
~WebViewTestCase() ~WebViewTestCase()
@@ -53,13 +64,33 @@ protected:
if(i % 2 == 1) if(i % 2 == 1)
m_browser->LoadURL("about:blank"); m_browser->LoadURL("about:blank");
else else
m_browser->LoadURL("about:"); m_browser->LoadURL(m_alternateHistoryURL);
ENSURE_LOADED; ENSURE_LOADED;
} }
} }
void OnScriptResult(const wxWebViewEvent& evt)
{
m_asyncScriptResult = (evt.IsError()) ? 0 : 1;
m_asyncScriptString = evt.GetString();
}
void RunAsyncScript(const wxString& javascript)
{
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_RESULT, &WebViewTestCase::OnScriptResult, this);
m_asyncScriptResult = -1;
m_browser->RunScriptAsync(javascript);
while (m_asyncScriptResult == -1)
wxYield();
m_browser->Unbind(wxEVT_WEBVIEW_SCRIPT_RESULT, &WebViewTestCase::OnScriptResult, this);
}
wxWebView* const m_browser; wxWebView* const m_browser;
EventCounter* const m_loaded; EventCounter* const m_loaded;
wxString m_blankTitle;
wxString m_alternateHistoryURL;
int m_asyncScriptResult;
wxString m_asyncScriptString;
}; };
TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]") TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
@@ -88,7 +119,7 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
//Test title after loading a url, we yield to let events process //Test title after loading a url, we yield to let events process
LoadUrl(); LoadUrl();
CHECK(m_browser->GetCurrentTitle() == ""); CHECK(m_browser->GetCurrentTitle() == m_blankTitle);
} }
SECTION("URL") SECTION("URL")
@@ -97,7 +128,7 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
//After first loading about:blank the next in the sequence is about: //After first loading about:blank the next in the sequence is about:
LoadUrl(); LoadUrl();
CHECK(m_browser->GetCurrentURL() == "about:"); CHECK(m_browser->GetCurrentURL() == m_alternateHistoryURL);
} }
SECTION("History") SECTION("History")
@@ -336,9 +367,9 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
CHECK(m_browser->RunScript("function f(a){return a;}f(false);", &result)); CHECK(m_browser->RunScript("function f(a){return a;}f(false);", &result));
CHECK(result == "false"); CHECK(result == "false");
CHECK(m_browser->RunScript("function f(){var person = new Object();person.name = 'Foo'; \ CHECK(m_browser->RunScript("function f(){var person = new Object();person.lastName = 'Bar'; \
person.lastName = 'Bar';return person;}f();", &result)); person.name = 'Foo';return person;}f();", &result));
CHECK(result == "{\"name\":\"Foo\",\"lastName\":\"Bar\"}"); CHECK(result == "{\"lastName\":\"Bar\",\"name\":\"Foo\"}");
CHECK(m_browser->RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result)); CHECK(m_browser->RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result));
CHECK(result == "[\"foo\",\"bar\"]"); CHECK(result == "[\"foo\",\"bar\"]");
@@ -381,6 +412,21 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
CHECK(!m_browser->RunScript("x.y.z")); CHECK(!m_browser->RunScript("x.y.z"));
} }
SECTION("RunScriptAsync")
{
#ifdef __WXMSW__
// IE doesn't support async script execution
if (!wxWebView::IsBackendAvailable(wxWebViewBackendEdge))
return;
#endif
RunAsyncScript("function f(a){return a;}f('Hello World!');");
CHECK(m_asyncScriptResult == 1);
CHECK(m_asyncScriptString == "Hello World!");
RunAsyncScript("int main() { return 0; }");
CHECK(m_asyncScriptResult == 0);
}
SECTION("SetPage") SECTION("SetPage")
{ {
m_browser->SetPage("<html><body>text</body></html>", ""); m_browser->SetPage("<html><body>text</body></html>", "");