From fd2920ff229e830d7ca886116ef4be53d9826acc Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Wed, 3 Nov 2021 10:17:10 +0100 Subject: [PATCH 01/16] Fix wxWebViewEdge tests Edge does not support the about: URL using it in the test resulted in various problems. Just use about:blank for now as the edge backend does not merge them for the history checks. --- tests/controls/webtest.cpp | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/controls/webtest.cpp b/tests/controls/webtest.cpp index 4396cd2e70..28b25088a2 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,15 @@ protected: if(i % 2 == 1) m_browser->LoadURL("about:blank"); else - m_browser->LoadURL("about:"); + m_browser->LoadURL(m_alternateHistoryURL); ENSURE_LOADED; } } wxWebView* const m_browser; EventCounter* const m_loaded; + wxString m_blankTitle; + wxString m_alternateHistoryURL; }; TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]") @@ -88,7 +101,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 +110,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 +349,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\"]"); From 2fe12ae6af13c1ff73256f666605a470a2b33e71 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Wed, 7 Apr 2021 12:03:27 +0200 Subject: [PATCH 02/16] Restructure javascript result wrapper --- include/wx/osx/webview_webkit.h | 2 - include/wx/private/jsscriptwrapper.h | 247 ++++++++++++++------------- src/osx/webview_webkit.mm | 69 ++------ 3 files changed, 146 insertions(+), 172 deletions(-) diff --git a/include/wx/osx/webview_webkit.h b/include/wx/osx/webview_webkit.h index dfec234ed0..968c69d4f5 100644 --- a/include/wx/osx/webview_webkit.h +++ b/include/wx/osx/webview_webkit.h @@ -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..9e6c63fb8d 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -24,18 +24,16 @@ class wxJSScriptWrapper { public: - wxJSScriptWrapper(const wxString& js, int* runScriptCount) - : m_escapedCode(js) - { - // 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)++); + enum OutputType { + 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 +67,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/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index e2fa2ab42e..f7286d58f9 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -371,76 +371,41 @@ bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType type) const } } -bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) const +bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const { - __block bool scriptExecuted = false; - __block wxString outputStr; - __block bool scriptSuccess = false; + wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING); + + __block int scriptResult = -1; + __block wxString result; // 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()); + result.assign(wxCFStringRef(error.localizedDescription).AsString()); + scriptResult = 0; } 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])); - - scriptSuccess = true; + if (obj) + result.assign(wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj])); + scriptResult = 1; } - - scriptExecuted = true; }]; // Wait for script exection - while (!scriptExecuted) + while (scriptResult == -1) 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 (scriptResult == 0) { - if (RunScriptSync(wrapJS.GetOutputCode() + ";", &result)) - { - if (output) - *output = result; - result.clear(); - } - - RunScriptSync(wrapJS.GetCleanUpCode()); - } - - if (!result.empty()) - { - wxLogWarning(_("Error running JavaScript: %s"), result); + if (output) + output->assign(result); return false; } - - return true; + else + return wxJSScriptWrapper::ExtractOutput(result, output); } bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name) From cf6a947dab2efd66f710f4f8dda66c6c52c72a03 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Wed, 7 Apr 2021 16:27:29 +0200 Subject: [PATCH 03/16] Use new JS wrapper with edge and IE --- include/wx/msw/webview_edge.h | 2 -- src/msw/webview_edge.cpp | 65 ++++++++++++++--------------------- src/msw/webview_ie.cpp | 39 +++++++-------------- 3 files changed, 38 insertions(+), 68 deletions(-) diff --git a/include/wx/msw/webview_edge.h b/include/wx/msw/webview_edge.h index fa33d16755..e04748f463 100644 --- a/include/wx/msw/webview_edge.h +++ b/include/wx/msw/webview_edge.h @@ -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/src/msw/webview_edge.cpp b/src/msw/webview_edge.cpp index ba213385ec..9dce63b27b 100644 --- a/src/msw/webview_edge.cpp +++ b/src/msw/webview_edge.cpp @@ -831,32 +831,37 @@ void wxWebViewEdge::MSWSetBrowserExecutableDir(const wxString & path) wxWebViewEdgeImpl::ms_browserExecutableDir = path; } -bool wxWebViewEdge::RunScriptSync(const wxString& javascript, wxString* output) const +bool wxWebViewEdge::RunScript(const wxString& javascript, wxString* output) const { - bool scriptExecuted = false; if (!m_impl->m_webView) return false; + wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING); + + int scriptResult = -1; + wxString scriptOutput; + // 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( + [&scriptResult, &executionResult, &scriptOutput](HRESULT error, PCWSTR result) -> HRESULT { // Handle script execution callback if (error == S_OK) { - if (output) - output->assign(result); + scriptOutput.assign(result); + scriptResult = 1; } else + { executionResult = error; - - scriptExecuted = true; + scriptResult = 0; + } return S_OK; }).Get()); // Wait for script exection - while (!scriptExecuted) + while (scriptResult == -1) wxYield(); if (FAILED(executionResult)) @@ -865,40 +870,22 @@ bool wxWebViewEdge::RunScriptSync(const wxString& javascript, wxString* output) output->Printf("%s (0x%08lx)", wxSysErrorMsgStr(executionResult), executionResult); return false; } - else - return true; -} -bool wxWebViewEdge::RunScript(const wxString& javascript, wxString* output) const -{ - wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); + wxString scriptDecodedResult; + // Try to decode JSON string or return original + // result if it's not a valid JSON string + if (!wxJSON::DecodeString(scriptOutput, &scriptDecodedResult)) + scriptDecodedResult = scriptOutput; - // 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(); - } + wxString scriptExtractedOutput; + bool success = wxJSScriptWrapper::ExtractOutput(scriptDecodedResult, &scriptExtractedOutput); + if (!success) + wxLogWarning(_("Error running JavaScript: %s"), scriptExtractedOutput); - RunScriptSync(wrapJS.GetCleanUpCode()); - } + if (output) + *output = scriptDecodedResult; - if (!result.empty()) - { - wxLogWarning(_("Error running JavaScript: %s"), result); - return false; - } - - return true; + return success; } 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) From e9dc74cb6d88b7f384b65ce5e84c93113d814791 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Wed, 7 Apr 2021 22:21:04 +0200 Subject: [PATCH 04/16] Add wxWebView::RunScriptAsync() --- include/wx/msw/webview_edge.h | 2 +- include/wx/webview.h | 9 +++++- src/common/webview.cpp | 46 ++++++++++++++++++++++++++++++ src/msw/webview_edge.cpp | 53 +++++++++++++---------------------- 4 files changed, 74 insertions(+), 36 deletions(-) diff --git a/include/wx/msw/webview_edge.h b/include/wx/msw/webview_edge.h index e04748f463..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, diff --git a/include/wx/webview.h b/include/wx/webview.h index 43a3bf39c1..d557d8483c 100644 --- a/include/wx/webview.h +++ b/include/wx/webview.h @@ -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,6 +268,9 @@ protected: bool QueryCommandEnabled(const wxString& command) const; void ExecCommand(const wxString& command); + void SendScriptResult(void* clientData, bool success, + const wxString& output) const; + // 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; @@ -276,6 +280,8 @@ private: 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; @@ -319,6 +325,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/src/common/webview.cpp b/src/common/webview.cpp index dacd00653a..1b6945c934 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, (void*)this); + + // 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/msw/webview_edge.cpp b/src/msw/webview_edge.cpp index 9dce63b27b..c26c5f35df 100644 --- a/src/msw/webview_edge.cpp +++ b/src/msw/webview_edge.cpp @@ -831,61 +831,46 @@ void wxWebViewEdge::MSWSetBrowserExecutableDir(const wxString & path) wxWebViewEdgeImpl::ms_browserExecutableDir = path; } -bool wxWebViewEdge::RunScript(const wxString& javascript, wxString* output) const +void wxWebViewEdge::RunScriptAsync(const wxString& javascript, void* clientData) const { if (!m_impl->m_webView) - return false; + { + SendScriptResult(clientData, false, ""); + return; // TODO: postpone execution + } wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING); - int scriptResult = -1; - wxString scriptOutput; - // Start script execution HRESULT executionResult = m_impl->m_webView->ExecuteScript(wrapJS.GetWrappedCode().wc_str(), Callback( - [&scriptResult, &executionResult, &scriptOutput](HRESULT error, PCWSTR result) -> HRESULT + [this, clientData](HRESULT error, PCWSTR result) -> HRESULT { // Handle script execution callback if (error == S_OK) { - scriptOutput.assign(result); - scriptResult = 1; + 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; - scriptResult = 0; + SendScriptResult(clientData, false, wxString::Format("%s (0x%08lx)", + wxSysErrorMsgStr(error), error)); } return S_OK; }).Get()); - - // Wait for script exection - while (scriptResult == -1) - 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)); } - - wxString scriptDecodedResult; - // Try to decode JSON string or return original - // result if it's not a valid JSON string - if (!wxJSON::DecodeString(scriptOutput, &scriptDecodedResult)) - scriptDecodedResult = scriptOutput; - - wxString scriptExtractedOutput; - bool success = wxJSScriptWrapper::ExtractOutput(scriptDecodedResult, &scriptExtractedOutput); - if (!success) - wxLogWarning(_("Error running JavaScript: %s"), scriptExtractedOutput); - - if (output) - *output = scriptDecodedResult; - - return success; } bool wxWebViewEdge::AddScriptMessageHandler(const wxString& name) From 7aa1d84e4abffb1bbcb167d03f6e29159192977e Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Thu, 8 Apr 2021 09:51:46 +0200 Subject: [PATCH 05/16] Implement RunScriptAsync for macOS --- include/wx/osx/webview_webkit.h | 2 +- src/osx/webview_webkit.mm | 29 ++++++++--------------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/include/wx/osx/webview_webkit.h b/include/wx/osx/webview_webkit.h index 968c69d4f5..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, diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index f7286d58f9..8cc462d414 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -371,41 +371,28 @@ bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType type) const } } -bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const +void wxWebViewWebKit::RunScriptAsync(const wxString& javascript, void* clientData) const { wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING); - __block int scriptResult = -1; - __block wxString result; - // Start script execution [m_webView evaluateJavaScript:wxCFStringRef(wrapJS.GetWrappedCode()).AsNSString() completionHandler:^(id _Nullable obj, NSError * _Nullable error) { if (error) { - result.assign(wxCFStringRef(error.localizedDescription).AsString()); - scriptResult = 0; + SendScriptResult(clientData, false, wxCFStringRef(error.localizedDescription).AsString()); } else { + wxString scriptResult; if (obj) - result.assign(wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj])); - scriptResult = 1; + scriptResult = wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj]); + wxString scriptOutput; + bool success = wxJSScriptWrapper::ExtractOutput(scriptResult, &scriptOutput); + + SendScriptResult(clientData, success, scriptOutput); } }]; - - // Wait for script exection - while (scriptResult == -1) - wxYield(); - - if (scriptResult == 0) - { - if (output) - output->assign(result); - return false; - } - else - return wxJSScriptWrapper::ExtractOutput(result, output); } bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name) From e1bd17d883dbca351ec255f08a290ca805da7c0f Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Fri, 9 Apr 2021 22:11:35 +0200 Subject: [PATCH 06/16] Use new JS wrapper with webkit2 --- src/gtk/webview_webkit2.cpp | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index 2bddbe8de8..8fb900ee23 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1287,31 +1287,26 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) 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. + bool success = false; wxString result; - if ( RunScriptSync(wrapJS.GetWrappedCode(), &result) - && result == wxS("true") ) + wxString scriptOutput; + if (RunScriptSync(wrapJS.GetWrappedCode(), &result)) { - if ( RunScriptSync(wrapJS.GetOutputCode(), &result) ) - { - if ( output ) - *output = result; - result.clear(); - } - - RunScriptSync(wrapJS.GetCleanUpCode()); + success = wxJSScriptWrapper::ExtractOutput(result, &scriptOutput); } - if ( !result.empty() ) + if (output) + output->assign(scriptOutput); + + if (!success) { - wxLogWarning(_("Error running JavaScript: %s"), result); + wxLogWarning(_("Error running JavaScript: %s"), scriptOutput); return false; } - - return true; + else + return true; } bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name) From 93f7df50d5e595e3a3b68e8eacdcce9967ad668b Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Fri, 9 Apr 2021 22:56:09 +0200 Subject: [PATCH 07/16] Implement RunScriptAsync for webkit2 --- include/wx/gtk/webview_webkit.h | 12 ++++- src/gtk/webview_webkit2.cpp | 77 ++++++++++++++------------------- 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/include/wx/gtk/webview_webkit.h b/include/wx/gtk/webview_webkit.h index 75a22e5343..63caf2dc13 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 methods 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/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index 8fb900ee23..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,75 +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, wxJSScriptWrapper::JS_OUTPUT_STRING); - bool success = false; - wxString result; - wxString scriptOutput; - if (RunScriptSync(wrapJS.GetWrappedCode(), &result)) - { - success = wxJSScriptWrapper::ExtractOutput(result, &scriptOutput); - } + // Collect parameters for access from the callback + wxWebKitRunScriptParams* params = new wxWebKitRunScriptParams; + params->webKitCtrl = this; + params->clientData = clientData; - if (output) - output->assign(scriptOutput); - - if (!success) - { - wxLogWarning(_("Error running JavaScript: %s"), scriptOutput); - return false; - } - else - 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) From d93db6bba582e7379d3120d14e2e8e41b5db2b0e Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Fri, 29 Oct 2021 10:26:26 +0200 Subject: [PATCH 08/16] Add initial RunScriptAsync() documentation --- interface/wx/webview.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/interface/wx/webview.h b/interface/wx/webview.h index d08d8bc30e..c34149a554 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 @@ -764,9 +768,27 @@ 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. + + @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. From 47833a6d244b184286082680b2047f6735d392a3 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Fri, 29 Oct 2021 13:24:27 +0200 Subject: [PATCH 09/16] Add RunScriptAsync() to webview sample --- samples/webview/webview.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index 0d9148c5d5..9a080a3c05 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,11 @@ void WebFrame::OnScriptMessage(wxWebViewEvent& evt) wxLogMessage("Script message received; value = %s, handler = %s", evt.GetString(), evt.GetMessageHandler()); } +void WebFrame::OnScriptResult(wxWebViewEvent& evt) +{ + wxLogMessage("Async script result received; value = %s", evt.GetString()); +} + void WebFrame::OnSetPage(wxCommandEvent& WXUNUSED(evt)) { m_browser->SetPage @@ -1199,6 +1210,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 From d31f1dc4000c0fbf16ad6f335ec639a813ee121d Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Wed, 3 Nov 2021 13:35:24 +0100 Subject: [PATCH 10/16] Add wxWebViewEvent::IsError() --- include/wx/webview.h | 1 + interface/wx/webview.h | 8 ++++++++ samples/webview/webview.cpp | 5 ++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/wx/webview.h b/include/wx/webview.h index d557d8483c..dfa409c943 100644 --- a/include/wx/webview.h +++ b/include/wx/webview.h @@ -300,6 +300,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; } diff --git a/interface/wx/webview.h b/interface/wx/webview.h index c34149a554..94a03e5085 100644 --- a/interface/wx/webview.h +++ b/interface/wx/webview.h @@ -1345,6 +1345,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 9a080a3c05..95f6f784d0 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -932,7 +932,10 @@ void WebFrame::OnScriptMessage(wxWebViewEvent& evt) void WebFrame::OnScriptResult(wxWebViewEvent& evt) { - wxLogMessage("Async script result received; value = %s", evt.GetString()); + 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)) From 9114122837252140fa14ba140ff1a1b43eaeb8e8 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Wed, 3 Nov 2021 13:41:01 +0100 Subject: [PATCH 11/16] Additional wxWebView::RunScriptAsync() documentation --- interface/wx/webview.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/interface/wx/webview.h b/interface/wx/webview.h index 94a03e5085..9ab49ff0b7 100644 --- a/interface/wx/webview.h +++ b/interface/wx/webview.h @@ -777,6 +777,11 @@ public: 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. @@ -1301,6 +1306,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 From 0921d0508afdb465c5e47280c95dd22343e61890 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Wed, 3 Nov 2021 14:02:21 +0100 Subject: [PATCH 12/16] Add tests for wxWebView::RunScriptAsync() --- tests/controls/webtest.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/controls/webtest.cpp b/tests/controls/webtest.cpp index 28b25088a2..c0eb77f529 100644 --- a/tests/controls/webtest.cpp +++ b/tests/controls/webtest.cpp @@ -69,10 +69,28 @@ protected: } } + 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]") @@ -394,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", ""); From 8613f3aa6bfb07baa5ad50620eb77fdadc397deb Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Fri, 5 Nov 2021 09:41:39 +0100 Subject: [PATCH 13/16] Apply suggestions from code review Co-authored-by: VZ --- include/wx/gtk/webview_webkit.h | 2 +- include/wx/private/jsscriptwrapper.h | 3 ++- src/common/webview.cpp | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/wx/gtk/webview_webkit.h b/include/wx/gtk/webview_webkit.h index 63caf2dc13..b43e98f283 100644 --- a/include/wx/gtk/webview_webkit.h +++ b/include/wx/gtk/webview_webkit.h @@ -156,7 +156,7 @@ public: bool m_creating; #if wxUSE_WEBVIEW_WEBKIT2 - // This methods needs to be public to make it callable from a callback + // This method needs to be public to make it callable from a callback void ProcessJavaScriptResult(GAsyncResult *res, wxWebKitRunScriptParams* params) const; #endif diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 9e6c63fb8d..a69b286b48 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -24,7 +24,8 @@ class wxJSScriptWrapper { public: - enum OutputType { + enum OutputType + { 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 diff --git a/src/common/webview.cpp b/src/common/webview.cpp index 1b6945c934..26705805a7 100644 --- a/src/common/webview.cpp +++ b/src/common/webview.cpp @@ -228,7 +228,7 @@ wxString wxWebView::GetUserAgent() const bool wxWebView::RunScript(const wxString& javascript, wxString* output) const { m_syncScriptResult = -1; - m_syncScriptOutput.Clear(); + m_syncScriptOutput.clear(); RunScriptAsync(javascript, (void*)this); // Wait for script exection @@ -249,7 +249,7 @@ void wxWebView::RunScriptAsync(const wxString& WXUNUSED(javascript), void wxWebView::SendScriptResult(void* clientData, bool success, const wxString& output) const { - // If currently running sync RunScript() don't send an event, but use + // If currently running sync RunScript(), don't send an event, but use // the scripts result directly if (m_syncScriptResult == -1) { From 9f9ccf33e172f8a7a54c2c1cf6c3bf9524dda1ce Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Fri, 5 Nov 2021 09:52:53 +0100 Subject: [PATCH 14/16] Initialize syncScriptResult and remove unused parameter --- include/wx/webview.h | 1 + src/common/webview.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/wx/webview.h b/include/wx/webview.h index dfa409c943..265ac24b0a 100644 --- a/include/wx/webview.h +++ b/include/wx/webview.h @@ -139,6 +139,7 @@ public: { m_showMenu = true; m_runScriptCount = 0; + m_syncScriptResult = 0; } virtual ~wxWebView() {} diff --git a/src/common/webview.cpp b/src/common/webview.cpp index 26705805a7..dbf8ed8451 100644 --- a/src/common/webview.cpp +++ b/src/common/webview.cpp @@ -229,7 +229,7 @@ bool wxWebView::RunScript(const wxString& javascript, wxString* output) const { m_syncScriptResult = -1; m_syncScriptOutput.clear(); - RunScriptAsync(javascript, (void*)this); + RunScriptAsync(javascript); // Wait for script exection while (m_syncScriptResult == -1) From 04e4b57a0565a70560e4acf9433e7e729342f2f3 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Fri, 5 Nov 2021 09:54:44 +0100 Subject: [PATCH 15/16] Remove unused m_runScriptCount This was no longer used after the changes to the javascript wrapper --- include/wx/webview.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/wx/webview.h b/include/wx/webview.h index 265ac24b0a..816b5a5ffc 100644 --- a/include/wx/webview.h +++ b/include/wx/webview.h @@ -138,7 +138,6 @@ public: wxWebView() { m_showMenu = true; - m_runScriptCount = 0; m_syncScriptResult = 0; } @@ -272,10 +271,6 @@ protected: void SendScriptResult(void* clientData, bool success, const wxString& output) const; - // 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; - private: static void InitFactoryMap(); static wxStringWebViewFactoryMap::iterator FindFactory(const wxString &backend); From ab7098b33bb0ba0bfd941fcbd173cc00261bf5c9 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Fri, 5 Nov 2021 10:04:45 +0100 Subject: [PATCH 16/16] Add note to RunScript() to recommend RunScriptAsync() --- interface/wx/webview.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/interface/wx/webview.h b/interface/wx/webview.h index 9ab49ff0b7..2c13aa4588 100644 --- a/interface/wx/webview.h +++ b/interface/wx/webview.h @@ -714,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