diff --git a/include/wx/gtk/webview_webkit.h b/include/wx/gtk/webview_webkit.h index 75a22e5343..b43e98f283 100644 --- a/include/wx/gtk/webview_webkit.h +++ b/include/wx/gtk/webview_webkit.h @@ -27,6 +27,8 @@ typedef struct _WebKitWebView WebKitWebView; // wxWebViewWebKit //----------------------------------------------------------------------------- +class wxWebKitRunScriptParams; + class WXDLLIMPEXP_WEBVIEW wxWebViewWebKit : public wxWebView { public: @@ -119,13 +121,15 @@ public: virtual wxString GetSelectedSource() const wxOVERRIDE; virtual void ClearSelection() wxOVERRIDE; - virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE; #if wxUSE_WEBVIEW_WEBKIT2 + virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const wxOVERRIDE; virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool AddUserScript(const wxString& javascript, wxWebViewUserScriptInjectionTime injectionTime = wxWEBVIEW_INJECT_AT_DOCUMENT_START) wxOVERRIDE; virtual void RemoveAllUserScripts() wxOVERRIDE; +#else + virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE; #endif //Virtual Filesystem Support @@ -151,6 +155,11 @@ public: //create-web-view signal and so we need to send a new window event 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: virtual void DoSetPage(const wxString& html, const wxString& baseUrl) wxOVERRIDE; @@ -173,7 +182,6 @@ private: bool CanExecuteEditingCommand(const gchar* command) const; void SetupWebExtensionServer(); GDBusProxy *GetExtensionProxy() const; - bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const; #endif WebKitWebView *m_web_view; diff --git a/include/wx/msw/webview_edge.h b/include/wx/msw/webview_edge.h index fa33d16755..d84b74d927 100644 --- a/include/wx/msw/webview_edge.h +++ b/include/wx/msw/webview_edge.h @@ -88,7 +88,7 @@ public: 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 RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool AddUserScript(const wxString& javascript, @@ -113,8 +113,6 @@ private: void OnTopLevelParentIconized(wxIconizeEvent& event); - bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const; - wxDECLARE_DYNAMIC_CLASS(wxWebViewEdge); }; diff --git a/include/wx/osx/webview_webkit.h b/include/wx/osx/webview_webkit.h index dfec234ed0..420b8f6452 100644 --- a/include/wx/osx/webview_webkit.h +++ b/include/wx/osx/webview_webkit.h @@ -94,7 +94,7 @@ public: virtual void SetEditable(bool enable = true) 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 RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE; virtual bool AddUserScript(const wxString& javascript, @@ -118,8 +118,6 @@ private: WX_NSObject m_navigationDelegate; WX_NSObject m_UIDelegate; - - bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const; }; class WXDLLIMPEXP_WEBVIEW wxWebViewFactoryWebKit : public wxWebViewFactory diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 7a904b1707..a69b286b48 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -24,18 +24,17 @@ class wxJSScriptWrapper { public: - wxJSScriptWrapper(const wxString& js, int* runScriptCount) - : m_escapedCode(js) + enum OutputType { - // We assign the return value of JavaScript snippet we execute to the - // variable with this name in order to be able to access it later if - // needed. - // - // 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)++); + JS_OUTPUT_STRING, // All return types are converted to a string + JS_OUTPUT_WEBKIT, // Some return types will be processed + JS_OUTPUT_IE, // Most return types will be processed + JS_OUTPUT_RAW // The return types is returned as is + }; + wxJSScriptWrapper(const wxString& js, OutputType outputType) + : m_escapedCode(js), m_outputType(outputType) + { // Adds one escape level. const char *charsNeededToBeEscaped = "\\\"\n\r\v\t\b\f"; for ( @@ -69,126 +68,139 @@ public: } } - // Get the code to execute, its returned value will be either boolean true, - // if it executed successfully, or the exception message if an error - // occurred. + // Get the code to execute, its returned value will be either the value, + // if it executed successfully, or the exception message prefixed with + // "__wxexc:" if an error occurred. // - // Execute GetOutputCode() later to get the real output after successful - // execution of this code. + // Either use SetOutput() to specify the script result or access it directly + // Using GetOutputRef() + // + // Execute ExtractOutput() later to get the real output after successful + // execution of this code or the proper error message. wxString GetWrappedCode() const { - return wxString::Format - ( - wxASCII_STR("try { var %s = eval(\"%s\"); true; } " - "catch (e) { e.name + \": \" + e.message; }"), - m_outputVarName, - m_escapedCode - ); - } + wxString code = wxString::Format( + wxASCII_STR("(function () { try { var res = eval(\"%s\"); "), + m_escapedCode); - // Get code returning the result of the last successful execution of the - // code returned by GetWrappedCode(). - wxString GetOutputCode() const - { -#if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT && defined(__WXOSX__) - return wxString::Format - ( - wxASCII_STR("if (typeof %s == 'object') JSON.stringify(%s);" - "else if (typeof %s == 'undefined') 'undefined';" - "else %s;"), - m_outputVarName, - m_outputVarName, - m_outputVarName, - m_outputVarName - ); -#elif wxUSE_WEBVIEW && wxUSE_WEBVIEW_IE - return wxString::Format - ( - wxASCII_STR("try {" - "(%s == null || typeof %s != 'object') ? String(%s)" - ": JSON.stringify(%s);" - "}" - "catch (e) {" + switch (m_outputType) + { + case JS_OUTPUT_STRING: + code += wxASCII_STR( + "if (typeof res == 'object') return JSON.stringify(res);" + "else if (typeof res == 'undefined') return 'undefined';" + "else return String(res);" + ); + break; + case JS_OUTPUT_WEBKIT: + code += wxASCII_STR( + "if (typeof res == 'object') return JSON.stringify(res);" + "else if (typeof res == 'undefined') return 'undefined';" + "else return res;" + ); + break; + case JS_OUTPUT_IE: + code += wxASCII_STR( "try {" - "function __wx$stringifyJSON(obj) {" - "if (!(obj instanceof Object))" - "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);" + "return (res == null || typeof res != 'object') ? String(res)" + ": JSON.stringify(res);" "}" - "catch (e) { e.name + \": \" + e.message; }" - "}"), - m_outputVarName, - m_outputVarName, - m_outputVarName, - m_outputVarName, - m_outputVarName - ); -#else - return m_outputVarName; -#endif + "catch (e) {" + "try {" + "function __wx$stringifyJSON(obj) {" + "if (!(obj instanceof Object))" + "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 + \'}\';" + "}" + "}" + "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; } - - // Execute the code returned by this function to let the output of the code - // we executed be garbage-collected. - wxString GetCleanUpCode() const + // Extract the output value + // + // Returns true if executed successfully + // string of the result will be put into output + // 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: wxString m_escapedCode; - wxString m_outputVarName; + OutputType m_outputType; wxDECLARE_NO_COPY_CLASS(wxJSScriptWrapper); }; diff --git a/include/wx/webview.h b/include/wx/webview.h index 43a3bf39c1..816b5a5ffc 100644 --- a/include/wx/webview.h +++ b/include/wx/webview.h @@ -138,7 +138,7 @@ public: wxWebView() { m_showMenu = true; - m_runScriptCount = 0; + m_syncScriptResult = 0; } virtual ~wxWebView() {} @@ -191,7 +191,8 @@ public: virtual wxString GetUserAgent() const; // 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) { wxUnusedVar(name); return false; } virtual bool RemoveScriptMessageHandler(const wxString& name) @@ -267,15 +268,16 @@ protected: bool QueryCommandEnabled(const wxString& command) const; void ExecCommand(const wxString& command); - // Count the number of calls to RunScript() in order to prevent - // the_same variable from being used twice in more than one call. - mutable int m_runScriptCount; + void SendScriptResult(void* clientData, bool success, + const wxString& output) const; private: static void InitFactoryMap(); static wxStringWebViewFactoryMap::iterator FindFactory(const wxString &backend); bool m_showMenu; + mutable int m_syncScriptResult; + mutable wxString m_syncScriptOutput; wxString m_findText; static wxStringWebViewFactoryMap m_factoryMap; @@ -294,6 +296,7 @@ public: m_actionFlags(flags), m_messageHandler(messageHandler) {} + bool IsError() const { return GetInt() == 0; } const wxString& GetURL() const { return m_url; } 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_FULLSCREEN_CHANGED, 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) (wxWebViewEvent&); diff --git a/interface/wx/webview.h b/interface/wx/webview.h index d08d8bc30e..2c13aa4588 100644 --- a/interface/wx/webview.h +++ b/interface/wx/webview.h @@ -485,6 +485,10 @@ public: Process a @c wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED event only available in wxWidgets 3.1.5 or later. For usage details see 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 @since 2.9.3 @@ -710,6 +714,11 @@ public: /** 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 access to DOM and other browser-provided functionality. For example, this code @@ -764,9 +773,32 @@ public: @NULL if it is not needed. This parameter is new since wxWidgets version 3.1.1. @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; + /** + 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. @@ -1279,6 +1311,10 @@ public: Process a @c wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED event only available in wxWidgets 3.1.5 or later. For usage details see 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 @since 2.9.3 @@ -1323,6 +1359,14 @@ public: @since 3.1.5 */ 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; }; diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index 0d9148c5d5..95f6f784d0 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -122,6 +122,7 @@ public: void OnTitleChanged(wxWebViewEvent& evt); void OnFullScreenChanged(wxWebViewEvent& evt); void OnScriptMessage(wxWebViewEvent& evt); + void OnScriptResult(wxWebViewEvent& evt); void OnSetPage(wxCommandEvent& evt); void OnViewSourceRequest(wxCommandEvent& evt); void OnViewTextRequest(wxCommandEvent& evt); @@ -159,6 +160,7 @@ public: void OnRunScriptArrayWithEmulationLevel(wxCommandEvent& evt); #endif void OnRunScriptMessage(wxCommandEvent& evt); + void OnRunScriptAsync(wxCommandEvent& evt); void OnRunScriptCustom(wxCommandEvent& evt); void OnAddUserScript(wxCommandEvent& evt); void OnSetCustomUserAgent(wxCommandEvent& evt); @@ -233,6 +235,7 @@ private: #endif wxMenuItem* m_script_message; wxMenuItem* m_script_custom; + wxMenuItem* m_script_async; wxMenuItem* m_selection_clear; wxMenuItem* m_selection_delete; 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"); } #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_custom = script_menu->Append(wxID_ANY, "Custom 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_FULLSCREEN_CHANGED, &WebFrame::OnFullScreenChanged, 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 Bind(wxEVT_MENU, &WebFrame::OnSetPage, this, setPage->GetId()); @@ -596,6 +601,7 @@ WebFrame::WebFrame(const wxString& url) : #endif Bind(wxEVT_MENU, &WebFrame::OnRunScriptMessage, this, m_script_message->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::OnSetCustomUserAgent, this, setCustomUserAgent->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()); } +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)) { m_browser->SetPage @@ -1199,6 +1213,11 @@ void WebFrame::OnRunScriptMessage(wxCommandEvent& WXUNUSED(evt)) 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)) { wxTextEntryDialog dialog diff --git a/src/common/webview.cpp b/src/common/webview.cpp index dacd00653a..dbf8ed8451 100644 --- a/src/common/webview.cpp +++ b/src/common/webview.cpp @@ -50,6 +50,7 @@ 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); +wxDEFINE_EVENT( wxEVT_WEBVIEW_SCRIPT_RESULT, wxWebViewEvent); wxStringWebViewFactoryMap wxWebView::m_factoryMap; @@ -224,6 +225,51 @@ wxString wxWebView::GetUserAgent() const 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(this)); + evt.SetClientData(clientData); + evt.SetInt(success); + evt.SetString(output); + HandleWindowEvent(evt); + } +} + // static wxWebView* wxWebView::New(const wxString& backend) { diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index 2bddbe8de8..a0161243f9 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1231,6 +1231,13 @@ wxString wxWebViewWebKit::GetPageText() const return wxString(); } +class wxWebKitRunScriptParams +{ +public: + const wxWebViewWebKit* webKitCtrl; + void* clientData; +}; + extern "C" { @@ -1238,80 +1245,55 @@ static void wxgtk_run_javascript_cb(GObject *, GAsyncResult *res, void *user_data) { - g_object_ref(res); - - GAsyncResult** res_out = static_cast(user_data); - *res_out = res; + wxWebKitRunScriptParams* params = static_cast(user_data); + params->webKitCtrl->ProcessJavaScriptResult(res, params); } } // extern "C" -// Run the given script synchronously and return its result in output. -bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) const +void wxWebViewWebKit::ProcessJavaScriptResult(GAsyncResult *res, wxWebKitRunScriptParams* params) 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; wxWebKitJavascriptResult js_result ( webkit_web_view_run_javascript_finish ( m_web_view, - result, + res, error.Out() ) ); - // Match g_object_ref() in wxgtk_run_javascript_cb() - g_object_unref(result); - - if ( !js_result ) + if ( js_result ) { - if ( output ) - *output = error.GetMessage(); - return false; + wxString scriptResult; + if ( wxGetStringFromJSResult(js_result, &scriptResult) ) + { + 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 - // 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(); - } + // Collect parameters for access from the callback + wxWebKitRunScriptParams* params = new wxWebKitRunScriptParams; + params->webKitCtrl = this; + params->clientData = clientData; - RunScriptSync(wrapJS.GetCleanUpCode()); - } - - if ( !result.empty() ) - { - wxLogWarning(_("Error running JavaScript: %s"), result); - return false; - } - - return true; + webkit_web_view_run_javascript(m_web_view, + wrapJS.GetWrappedCode().utf8_str(), + NULL, + wxgtk_run_javascript_cb, + params); } bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name) diff --git a/src/msw/webview_edge.cpp b/src/msw/webview_edge.cpp index ba213385ec..c26c5f35df 100644 --- a/src/msw/webview_edge.cpp +++ b/src/msw/webview_edge.cpp @@ -831,74 +831,46 @@ void wxWebViewEdge::MSWSetBrowserExecutableDir(const wxString & 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) - return false; + { + SendScriptResult(clientData, false, ""); + return; // TODO: postpone execution + } + + wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING); // Start script execution - HRESULT executionResult = m_impl->m_webView->ExecuteScript(javascript.wc_str(), Callback( - [&scriptExecuted, &executionResult, output](HRESULT error, PCWSTR result) -> HRESULT + HRESULT executionResult = m_impl->m_webView->ExecuteScript(wrapJS.GetWrappedCode().wc_str(), Callback( + [this, clientData](HRESULT error, PCWSTR result) -> HRESULT { // Handle script execution callback if (error == S_OK) { - if (output) - output->assign(result); + wxString scriptDecodedResult; + // 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 - executionResult = error; - - scriptExecuted = true; + { + SendScriptResult(clientData, false, wxString::Format("%s (0x%08lx)", + wxSysErrorMsgStr(error), error)); + } return S_OK; }).Get()); - - // Wait for script exection - while (!scriptExecuted) - wxYield(); - if (FAILED(executionResult)) { - if (output) - output->Printf("%s (0x%08lx)", wxSysErrorMsgStr(executionResult), executionResult); - return false; + SendScriptResult(clientData, false, wxString::Format("%s (0x%08lx)", + wxSysErrorMsgStr(executionResult), executionResult)); } - 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) diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index 6b0f194dc7..6ed7dd6555 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -1045,40 +1045,25 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) const return false; } - wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); + wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_IE); wxAutomationObject scriptAO(scriptDispatch); wxVariant varResult; - wxString err; - if ( !CallEval(wrapJS.GetWrappedCode(), scriptAO, &varResult) ) - { - err = _("failed to evaluate"); - } - else if ( varResult.IsType("bool") && varResult.GetBool() ) - { - if ( output != NULL ) - { - if ( CallEval(wrapJS.GetOutputCode(), scriptAO, &varResult) ) - *output = varResult.MakeString(); - else - err = _("failed to retrieve execution result"); - } + bool success = false; + wxString scriptOutput; + if ( CallEval(wrapJS.GetWrappedCode(), scriptAO, &varResult) ) + success = wxJSScriptWrapper::ExtractOutput(varResult.MakeString(), &scriptOutput); + else + scriptOutput = _("failed to evaluate"); - CallEval(wrapJS.GetCleanUpCode(), scriptAO, &varResult); - } - else // result available but not the expected "true" - { - err = varResult.MakeString(); - } + if (!success) + wxLogWarning(_("Error running JavaScript: %s"), scriptOutput); - if ( !err.empty() ) - { - wxLogWarning(_("Error running JavaScript: %s"), varResult.MakeString()); - return false; - } + if (success && output) + *output = scriptOutput; - return true; + return success; } void wxWebViewIE::RegisterHandler(wxSharedPtr handler) diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index e2fa2ab42e..8cc462d414 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -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; - __block wxString outputStr; - __block bool scriptSuccess = false; + wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING); // Start script execution - [m_webView evaluateJavaScript:wxCFStringRef(javascript).AsNSString() + [m_webView evaluateJavaScript:wxCFStringRef(wrapJS.GetWrappedCode()).AsNSString() completionHandler:^(id _Nullable obj, NSError * _Nullable error) { if (error) { - outputStr.assign(wxCFStringRef(error.localizedFailureReason).AsString()); + SendScriptResult(clientData, false, wxCFStringRef(error.localizedDescription).AsString()); } else { - if ([obj isKindOfClass:[NSNumber class]]) - { - NSNumber* num = (NSNumber*) obj; - CFTypeID numID = CFGetTypeID((__bridge CFTypeRef)(num)); - if (numID == CFBooleanGetTypeID()) - outputStr = num.boolValue ? "true" : "false"; - else - outputStr = wxCFStringRef::AsString(num.stringValue); - } - else if (obj) - outputStr.assign(wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj])); + wxString scriptResult; + if (obj) + scriptResult = wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj]); + wxString scriptOutput; + bool success = wxJSScriptWrapper::ExtractOutput(scriptResult, &scriptOutput); - 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) diff --git a/tests/controls/webtest.cpp b/tests/controls/webtest.cpp index 4396cd2e70..c0eb77f529 100644 --- a/tests/controls/webtest.cpp +++ b/tests/controls/webtest.cpp @@ -35,6 +35,17 @@ public: : m_browser(wxWebView::New()), 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() @@ -53,13 +64,33 @@ protected: if(i % 2 == 1) m_browser->LoadURL("about:blank"); else - m_browser->LoadURL("about:"); + m_browser->LoadURL(m_alternateHistoryURL); 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; EventCounter* const m_loaded; + wxString m_blankTitle; + wxString m_alternateHistoryURL; + int m_asyncScriptResult; + wxString m_asyncScriptString; }; 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 LoadUrl(); - CHECK(m_browser->GetCurrentTitle() == ""); + CHECK(m_browser->GetCurrentTitle() == m_blankTitle); } SECTION("URL") @@ -97,7 +128,7 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]") //After first loading about:blank the next in the sequence is about: LoadUrl(); - CHECK(m_browser->GetCurrentURL() == "about:"); + CHECK(m_browser->GetCurrentURL() == m_alternateHistoryURL); } 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(result == "false"); - CHECK(m_browser->RunScript("function f(){var person = new Object();person.name = 'Foo'; \ - person.lastName = 'Bar';return person;}f();", &result)); - CHECK(result == "{\"name\":\"Foo\",\"lastName\":\"Bar\"}"); + CHECK(m_browser->RunScript("function f(){var person = new Object();person.lastName = 'Bar'; \ + person.name = 'Foo';return person;}f();", &result)); + CHECK(result == "{\"lastName\":\"Bar\",\"name\":\"Foo\"}"); CHECK(m_browser->RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result)); CHECK(result == "[\"foo\",\"bar\"]"); @@ -381,6 +412,21 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]") 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") { m_browser->SetPage("text", "");