From af8f7f33c3eab7d4688266596d06b2a82089ac62 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Date: Sat, 2 Sep 2017 23:24:06 +0100 Subject: [PATCH 01/39] Merge wxWebView JavaScript improvements branch This is a squashed commit of the SOC2017_WEBVIEW_JS branch from https://github.com/joseeloren/wxWidgets.git Closes https://github.com/wxWidgets/wxWidgets/pull/538 --- include/wx/gtk/private/webkit.h | 57 ++++++++ include/wx/gtk/webview_webkit.h | 3 +- include/wx/msw/webview_ie.h | 14 +- include/wx/osx/webview_webkit.h | 2 +- include/wx/private/jsscriptwrapper.h | 146 +++++++++++++++++++ include/wx/webview.h | 7 +- interface/wx/webview.h | 47 +++++- samples/webview/webview.cpp | 206 +++++++++++++++++++++++++-- src/gtk/webview_webkit.cpp | 10 +- src/gtk/webview_webkit2.cpp | 82 ++++++++++- src/msw/webview_ie.cpp | 91 ++++++++++-- src/osx/webview_webkit.mm | 36 ++++- tests/controls/webtest.cpp | 99 ++++++++++++- tests/testprec.h | 6 +- 14 files changed, 757 insertions(+), 49 deletions(-) create mode 100644 include/wx/gtk/private/webkit.h create mode 100644 include/wx/private/jsscriptwrapper.h diff --git a/include/wx/gtk/private/webkit.h b/include/wx/gtk/private/webkit.h new file mode 100644 index 0000000000..d254e66819 --- /dev/null +++ b/include/wx/gtk/private/webkit.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/gtk/private/webkit.h +// Purpose: wxWebKitGtk RAII wrappers declaration +// Author: Jose Lorenzo +// Created: 2017-08-21 +// Copyright: (c) 2017 Jose Lorenzo +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_GTK_PRIVATE_WEBKIT_H_ +#define _WX_GTK_PRIVATE_WEBKIT_H_ + +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Convenience class for WebKitJavascriptResult on scope exit automatically +// ---------------------------------------------------------------------------- + +class wxWebKitJavascriptResult +{ +public: + explicit wxWebKitJavascriptResult(WebKitJavascriptResult *r) : m_jsresult(r) { } + ~wxWebKitJavascriptResult() { webkit_javascript_result_unref (m_jsresult); } + + operator WebKitJavascriptResult *() const { return m_jsresult; } + +private: + WebKitJavascriptResult *m_jsresult; + + wxDECLARE_NO_COPY_CLASS(wxWebKitJavascriptResult); +}; + +class wxJSStringRef +{ +public: + explicit wxJSStringRef(JSStringRef r) : m_jssref(r) { } + ~wxJSStringRef() { JSStringRelease (m_jssref); } + + const wxString ToWxString() + { + size_t length = JSStringGetMaximumUTF8CStringSize(m_jssref); + wxCharBuffer str(length); + + JSStringGetUTF8CString(m_jssref, str.data(), length); + + return wxString::FromUTF8(str); + } + +private: + JSStringRef m_jssref; + + wxDECLARE_NO_COPY_CLASS(wxJSStringRef); +}; + +#endif // _WX_GTK_PRIVATE_WEBKIT_H_ diff --git a/include/wx/gtk/webview_webkit.h b/include/wx/gtk/webview_webkit.h index 95a5bac4e4..f0f1873ea6 100644 --- a/include/wx/gtk/webview_webkit.h +++ b/include/wx/gtk/webview_webkit.h @@ -114,7 +114,7 @@ public: virtual wxString GetSelectedSource() const wxOVERRIDE; virtual void ClearSelection() wxOVERRIDE; - virtual void RunScript(const wxString& javascript) wxOVERRIDE; + virtual bool RunScript(const wxString& javascript, wxString* output = NULL) wxOVERRIDE; //Virtual Filesystem Support virtual void RegisterHandler(wxSharedPtr handler) wxOVERRIDE; @@ -160,6 +160,7 @@ private: #if wxUSE_WEBVIEW_WEBKIT2 bool CanExecuteEditingCommand(const gchar* command) const; void SetupWebExtensionServer(); + bool RunScriptInternal(const wxString& javascript, wxString* output = NULL); #endif WebKitWebView *m_web_view; diff --git a/include/wx/msw/webview_ie.h b/include/wx/msw/webview_ie.h index a2b8f891ea..e1235fa40d 100644 --- a/include/wx/msw/webview_ie.h +++ b/include/wx/msw/webview_ie.h @@ -24,6 +24,7 @@ #include "wx/msw/webview_missing.h" #include "wx/sharedptr.h" #include "wx/vector.h" +#include "wx/msw/private.h" struct IHTMLDocument2; struct IHTMLElement; @@ -35,6 +36,11 @@ class DocHostUIHandler; class wxFindPointers; class wxIInternetProtocol; +#define wxIE_EMULATION_LEVEL 8000 + +//Registry key where emulation level for programs are set +#define wxREGISTRY_IE_PATH wxT("SOFTWARE\\Microsoft\\Internet Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION") + class WXDLLIMPEXP_WEBVIEW wxWebViewIE : public wxWebView { public: @@ -121,7 +127,7 @@ public: virtual wxString GetSelectedSource() const wxOVERRIDE; virtual void ClearSelection() wxOVERRIDE; - virtual void RunScript(const wxString& javascript) wxOVERRIDE; + virtual bool RunScript(const wxString& javascript, wxString* output = NULL) wxOVERRIDE; //Virtual Filesystem Support virtual void RegisterHandler(wxSharedPtr handler) wxOVERRIDE; @@ -143,6 +149,9 @@ public: void onActiveXEvent(wxActiveXEvent& evt); void onEraseBg(wxEraseEvent&) {} + //Establish EmulationLevel for RunScript IE + static bool MSWSetModernEmulationLevel(bool modernLevel = true); + wxDECLARE_EVENT_TABLE(); protected: @@ -191,6 +200,9 @@ private: //Toggles control features see INTERNETFEATURELIST for values. bool EnableControlFeature(long flag, bool enable = true); + bool RunScriptInternal(wxVariant varJavascript, + wxAutomationObject* scriptAO, wxVariant* varResult); + wxDECLARE_DYNAMIC_CLASS(wxWebViewIE); }; diff --git a/include/wx/osx/webview_webkit.h b/include/wx/osx/webview_webkit.h index 14b566966f..dc54ec061c 100644 --- a/include/wx/osx/webview_webkit.h +++ b/include/wx/osx/webview_webkit.h @@ -112,7 +112,7 @@ public: virtual wxString GetSelectedSource() const wxOVERRIDE; virtual void ClearSelection() wxOVERRIDE; - void RunScript(const wxString& javascript) wxOVERRIDE; + bool RunScript(const wxString& javascript, wxString* output = NULL) wxOVERRIDE; //Virtual Filesystem Support virtual void RegisterHandler(wxSharedPtr handler) wxOVERRIDE; diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h new file mode 100644 index 0000000000..a4c3ccf035 --- /dev/null +++ b/include/wx/private/jsscriptwrapper.h @@ -0,0 +1,146 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/private/jsscriptwrapper.h +// Purpose: JS Script Wrapper for wxWebView +// Author: Jose Lorenzo +// Created: 2017-08-12 +// Copyright: (c) 2017 Jose Lorenzo +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_PRIVATE_JSSCRIPTWRAPPER_H_ +#define _WX_PRIVATE_JSSCRIPTWRAPPER_H_ + +#include "wx/regex.h" + +// ---------------------------------------------------------------------------- +// JS Script Wrapper for wxWebView +// ---------------------------------------------------------------------------- + +class wxJSScriptWrapper +{ +public: + + explicit wxJSScriptWrapper(const wxString& js, int* runScriptCount) : m_jsscript(js) + { + // __wx$counter is used to have a different variable on every + // RunScript call, to not lose variable values between calls + m_wx$counter = wxString::Format("__wx$%i", (*runScriptCount)++); + } + + // This method is used to add a double quote level into a JavasSript code + // in order to get it working when eval is called programmatically. + const wxString GetWrappedCode() + { + // Adds one escape level if there is a single quote, double quotes or + // esacape characters + wxRegEx escapeDoubleQuotes("(\\\\*)(['\"\n\r\v\t\b\f])"); + escapeDoubleQuotes.Replace(&m_jsscript,"\\1\\1\\\\\\2"); + + return wxString::Format("try { var %s = eval(\"%s\"); true; } \ + catch (e) { e.name + \": \" + e.message; }", m_wx$counter, m_jsscript);; + } + + const wxString GetOutputCode() + { +#if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT && defined(__WXOSX__) + return wxString::Format("if (typeof %s == 'object') \ + JSON.stringify(%s); \ + else if (typeof %s == 'undefined') \ + 'undefined'; \ + else \ + %s;", + m_wx$counter, m_wx$counter, m_wx$counter, m_wx$counter); +#elif wxUSE_WEBVIEW && wxUSE_WEBVIEW_IE + return wxString::Format("try {(%s == null || typeof %s != 'object') ? String(%s) : JSON.stringify(%s);} \ + catch (e) { \ + try \ + { \ + function __wx$stringifyJSON(obj) \ + { \ + var objElements = []; \ + 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) \ + { \ + if (number < 10) \ + return '0' + number; \ + return 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(); + '\"' \ + } \ + 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; }}", + m_wx$counter, m_wx$counter, m_wx$counter, m_wx$counter, m_wx$counter); +#else + return m_wx$counter; +#endif + } + + const wxString GetCleanUpCode() + { + return wxString::Format("%s = undefined;", m_wx$counter); + } + + const wxString GetOutputJSVariable() + { + return m_wx$counter; + } + +private: + wxString m_jsscript; + wxString m_wx$counter; + + wxDECLARE_NO_COPY_CLASS(wxJSScriptWrapper); +}; + +#endif // _WX_PRIVATE_JSSCRIPTWRAPPER_H_ diff --git a/include/wx/webview.h b/include/wx/webview.h index 4f18bada70..d83f9a84f1 100644 --- a/include/wx/webview.h +++ b/include/wx/webview.h @@ -117,6 +117,7 @@ public: wxWebView() { m_showMenu = true; + m_runScriptCount = 0; } virtual ~wxWebView() {} @@ -161,7 +162,7 @@ public: virtual void Print() = 0; virtual void RegisterHandler(wxSharedPtr handler) = 0; virtual void Reload(wxWebViewReloadFlags flags = wxWEBVIEW_RELOAD_DEFAULT) = 0; - virtual void RunScript(const wxString& javascript) = 0; + virtual bool RunScript(const wxString& javascript, wxString* output = NULL) = 0; virtual void SetEditable(bool enable = true) = 0; void SetPage(const wxString& html, const wxString& baseUrl) { @@ -223,6 +224,10 @@ public: protected: virtual void DoSetPage(const wxString& html, const wxString& baseUrl) = 0; + // Count the number of calls to RunScript() in order to prevent + // the_same variable from being used twice in more than one call. + int m_runScriptCount; + private: static void InitFactoryMap(); static wxStringWebViewFactoryMap::iterator FindFactory(const wxString &backend); diff --git a/interface/wx/webview.h b/interface/wx/webview.h index d9e93e55b7..73be3277dc 100644 --- a/interface/wx/webview.h +++ b/interface/wx/webview.h @@ -460,11 +460,50 @@ public: virtual void Reload(wxWebViewReloadFlags flags = wxWEBVIEW_RELOAD_DEFAULT) = 0; /** - Runs the given javascript code. - @note When using wxWEBVIEW_BACKEND_IE you must wait for the current - page to finish loading before calling RunScript(). + Sets IE emulation level to a modern one, changing MSW registry. It is used to enable + JSON object on Runscript(), because it wouldn't work with default emulation level. + @see RunScript() for further details. + @param modernLevel @true to set level to IE8, @false to set level to old mode. + @return @true if level can be set, @false if it is not. + @since 3.1.1 */ - virtual void RunScript(const wxString& javascript) = 0; + bool MSWSetModernEmulationLevel(bool modernLevel = true) = 0; + + /** + Runs the given JavaScript. It returns true if the script was run successfully and + additionally returns the script return value in output. + It returns strings, integers, floating point numbers, booleans and objects (as JSON). + @note When using wxWEBVIEW_WEBKIT (GTK), it returns true always unless you pass + an output param to the method. In that case, it would return false. RunScript + outputs are supported on wxWEBVIEW_WEBKIT2 (GTK3). + + When using wxWEBVIEW_WEBKIT (OSX), there are two limits: + 1) Javascript allocations greater than 10MB. + 2) Javascript that takes longer than 10 seconds to execute. + + When using wxWEBVIEW_BACKEND_IE you must wait for the current + page to finish loading before calling RunScript(). + It is compulsory to have a script tag inside HTML to run Javascript, on MSW. + + When using wxWEBVIEW_BACKEND_IE, JSON is not available in Quirks or + IE6/7 standards mode, which is unfortunately the default one for the embedded browser control, see + https://docs.microsoft.com/en-us/scripting/javascript/reference/json-object-javascript#requirements + and see here how to make a program run use "modern" modes + https://msdn.microsoft.com/en-us/library/ee330730(v=vs.85)#browser_emulation + There are two ways to get JSON working: + 1) You can use MSWModernEmulationLevel to change emulation level (recommended), for example: + @code + MSWModernEmulationLevel(); + wxString result; + browser->RunScript("some JS code that uses JSON", &result); + @endcode + 2) There is an implementation of JSON.stringify on RunScript that helps to return objects. + You don't need to change IE emulation level but it could not work for some cases. + @param javascript A wxString containing the Javascript. + @param output wxString result pointer. It can be NULL and it is new in wxWidgets 3.1.1. + @return @true if there is a result, @false if there is an error. + */ + virtual bool RunScript(const wxString& javascript, wxString* output = NULL) = 0; /** Set the editable property of the web control. Enabling allows the user diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index 42234e1730..0ca29d6d78 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -30,6 +30,9 @@ #include "wx/notifmsg.h" #include "wx/settings.h" #include "wx/webview.h" +#if wxUSE_WEBVIEW_IE +#include "wx/msw/webview_ie.h" +#endif #include "wx/webviewarchivehandler.h" #include "wx/webviewfshandler.h" #include "wx/infobar.h" @@ -63,7 +66,7 @@ class WebApp : public wxApp { public: WebApp() : - m_url("http://www.wxwidgets.org") + m_url("http://wiki.wxwidgets.org") { } @@ -133,7 +136,24 @@ public: void OnScrollLineDown(wxCommandEvent&) { m_browser->LineDown(); } void OnScrollPageUp(wxCommandEvent&) { m_browser->PageUp(); } void OnScrollPageDown(wxCommandEvent&) { m_browser->PageDown(); } - void OnRunScript(wxCommandEvent& evt); + void RunScript(const wxString& javascript, wxString* result = NULL, + const wxString& messege = "Click OK to run JavaScript."); + void OnRunScriptString(wxCommandEvent& evt); + void OnRunScriptInteger(wxCommandEvent& evt); + void OnRunScriptDouble(wxCommandEvent& evt); + void OnRunScriptBool(wxCommandEvent& evt); + void OnRunScriptObject(wxCommandEvent& evt); + void OnRunScriptArray(wxCommandEvent& evt); + void OnRunScriptDOM(wxCommandEvent& evt); + void OnRunScriptUndefined(wxCommandEvent& evt); + void OnRunScriptNull(wxCommandEvent& evt); + void OnRunScriptDate(wxCommandEvent& evt); +#if wxUSE_WEBVIEW_IE + void OnRunScriptObjectWithEmulationLevel(wxCommandEvent& evt); + void OnRunScriptDateWithEmulationLevel(wxCommandEvent& evt); + void OnRunScriptArrayWithEmulationLevel(wxCommandEvent& evt); +#endif + void OnRunScriptCustom(wxCommandEvent& evt); void OnClearSelection(wxCommandEvent& evt); void OnDeleteSelection(wxCommandEvent& evt); void OnSelectAll(wxCommandEvent& evt); @@ -186,6 +206,22 @@ private: wxMenuItem* m_scroll_line_down; wxMenuItem* m_scroll_page_up; wxMenuItem* m_scroll_page_down; + wxMenuItem* m_script_string; + wxMenuItem* m_script_integer; + wxMenuItem* m_script_double; + wxMenuItem* m_script_bool; + wxMenuItem* m_script_object; + wxMenuItem* m_script_array; + wxMenuItem* m_script_dom; + wxMenuItem* m_script_undefined; + wxMenuItem* m_script_null; + wxMenuItem* m_script_date; +#if wxUSE_WEBVIEW_IE + wxMenuItem* m_script_object_el; + wxMenuItem* m_script_date_el; + wxMenuItem* m_script_array_el; +#endif + wxMenuItem* m_script_custom; wxMenuItem* m_selection_clear; wxMenuItem* m_selection_delete; wxMenuItem* m_find; @@ -393,7 +429,24 @@ WebFrame::WebFrame(const wxString& url) : m_scroll_page_down = scroll_menu->Append(wxID_ANY, "Page d&own"); m_tools_menu->AppendSubMenu(scroll_menu, "Scroll"); - wxMenuItem* script = m_tools_menu->Append(wxID_ANY, _("Run Script")); + wxMenu* script_menu = new wxMenu; + m_script_string = script_menu->Append(wxID_ANY, "Return String"); + m_script_integer = script_menu->Append(wxID_ANY, "Return integer"); + m_script_double = script_menu->Append(wxID_ANY, "Return double"); + m_script_bool = script_menu->Append(wxID_ANY, "Return bool"); + m_script_object = script_menu->Append(wxID_ANY, "Return JSON object"); + m_script_array = script_menu->Append(wxID_ANY, "Return array"); + m_script_dom = script_menu->Append(wxID_ANY, "Modify DOM"); + m_script_undefined = script_menu->Append(wxID_ANY, "Return undefined"); + m_script_null = script_menu->Append(wxID_ANY, "Return null"); + m_script_date = script_menu->Append(wxID_ANY, "Return Date"); +#if wxUSE_WEBVIEW_IE + m_script_object_el = script_menu->Append(wxID_ANY, "Return JSON object changing emulation level"); + m_script_date_el = script_menu->Append(wxID_ANY, "Return Date changing emulation level"); + m_script_array_el = script_menu->Append(wxID_ANY, "Return array changing emulation level"); +#endif + m_script_custom = script_menu->Append(wxID_ANY, "Custom script"); + m_tools_menu->AppendSubMenu(script_menu, _("Run Script")); //Selection menu wxMenu* selection = new wxMenu(); @@ -502,8 +555,36 @@ WebFrame::WebFrame(const wxString& url) : wxCommandEventHandler(WebFrame::OnScrollPageUp), NULL, this ); Connect(m_scroll_page_down->GetId(), wxEVT_MENU, wxCommandEventHandler(WebFrame::OnScrollPageDown), NULL, this ); - Connect(script->GetId(), wxEVT_MENU, - wxCommandEventHandler(WebFrame::OnRunScript), NULL, this ); + Connect(m_script_string->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptString), NULL, this ); + Connect(m_script_integer->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptInteger), NULL, this ); + Connect(m_script_double->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptDouble), NULL, this ); + Connect(m_script_bool->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptBool), NULL, this ); + Connect(m_script_object->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptObject), NULL, this ); + Connect(m_script_array->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptArray), NULL, this ); + Connect(m_script_dom->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptDOM), NULL, this ); + Connect(m_script_undefined->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptUndefined), NULL, this ); + Connect(m_script_null->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptNull), NULL, this ); + Connect(m_script_date->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptDate), NULL, this ); +#if wxUSE_WEBVIEW_IE + Connect(m_script_object_el->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptObjectWithEmulationLevel), NULL, this ); + Connect(m_script_date_el->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptDateWithEmulationLevel), NULL, this ); + Connect(m_script_array_el->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptArrayWithEmulationLevel), NULL, this ); +#endif + Connect(m_script_custom->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnRunScriptCustom), NULL, this ); Connect(m_selection_clear->GetId(), wxEVT_MENU, wxCommandEventHandler(WebFrame::OnClearSelection), NULL, this ); Connect(m_selection_delete->GetId(), wxEVT_MENU, @@ -969,15 +1050,122 @@ void WebFrame::OnHistory(wxCommandEvent& evt) m_browser->LoadHistoryItem(m_histMenuItems[evt.GetId()]); } -void WebFrame::OnRunScript(wxCommandEvent& WXUNUSED(evt)) +void WebFrame::RunScript(const wxString& javascript, wxString* result, const wxString& messege) { - wxTextEntryDialog dialog(this, "Enter JavaScript to run.", wxGetTextFromUserPromptStr, "", wxOK|wxCANCEL|wxCENTRE|wxTE_MULTILINE); - if(dialog.ShowModal() == wxID_OK) + wxTextEntryDialog dialog(this, messege, wxGetTextFromUserPromptStr, javascript, wxOK|wxCANCEL|wxCENTRE|wxTE_MULTILINE); + if( dialog.ShowModal() == wxID_OK ) { - m_browser->RunScript(dialog.GetValue()); + if ( m_browser->RunScript(dialog.GetValue(), result)) + { + if ( result != NULL ) + wxLogMessage(_("RunScript result: %s"), *result); + else + wxLogMessage(_("RunScript ran properly")); + } + else + { + wxLogWarning(_("RunScript returned false")); + } } } +void WebFrame::OnRunScriptString(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("function f(a){return a;}f('Hello World!');", &result); +} + +void WebFrame::OnRunScriptInteger(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("function f(a){return a;}f(123);", &result); +} + +void WebFrame::OnRunScriptDouble(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("function f(a){return a;}f(2.34);", &result); +} + +void WebFrame::OnRunScriptBool(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("function f(a){return a;}f(false);", &result); +} + +void WebFrame::OnRunScriptObject(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("function f(){var person = new Object();person.name = 'Foo'; \ + person.lastName = 'Bar';return person;}f();", &result); +} + +void WebFrame::OnRunScriptArray(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result); +} + +void WebFrame::OnRunScriptDOM(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("document.write(\"Hello World!\");"); +} + +void WebFrame::OnRunScriptUndefined(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("function f(){var person = new Object();}f();", &result); +} + +void WebFrame::OnRunScriptNull(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("function f(){return null;}f();", &result); +} + +void WebFrame::OnRunScriptDate(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("function f(){var d = new Date('10/08/2017 21:30:40'); \ + var tzoffset = d.getTimezoneOffset() * 60000; \ + return new Date(d.getTime() - tzoffset);}f();", &result); +} + +#if wxUSE_WEBVIEW_IE +void WebFrame::OnRunScriptObjectWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) +{ + wxWebViewIE::MSWSetModernEmulationLevel(); + wxString result; + RunScript("function f(){var person = new Object();person.name = 'Foo'; \ + person.lastName = 'Bar';return person;}f();", &result); + wxWebViewIE::MSWSetModernEmulationLevel(false); +} + +void WebFrame::OnRunScriptDateWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) +{ + wxWebViewIE::MSWSetModernEmulationLevel(); + wxString result; + RunScript("function f(){var d = new Date('10/08/2017 21:30:40'); \ + var tzoffset = d.getTimezoneOffset() * 60000; return \ + new Date(d.getTime() - tzoffset);}f();", &result); + wxWebViewIE::MSWSetModernEmulationLevel(false); +} + +void WebFrame::OnRunScriptArrayWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) +{ + wxWebViewIE::MSWSetModernEmulationLevel(); + wxString result; + RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result); + wxWebViewIE::MSWSetModernEmulationLevel(false); +} +#endif + +void WebFrame::OnRunScriptCustom(wxCommandEvent& WXUNUSED(evt)) +{ + wxString result; + RunScript("", &result, "Enter JavaScript to run."); +} + void WebFrame::OnClearSelection(wxCommandEvent& WXUNUSED(evt)) { m_browser->ClearSelection(); diff --git a/src/gtk/webview_webkit.cpp b/src/gtk/webview_webkit.cpp index bc45beb1bc..0430ff364d 100644 --- a/src/gtk/webview_webkit.cpp +++ b/src/gtk/webview_webkit.cpp @@ -949,10 +949,18 @@ wxString wxWebViewWebKit::GetPageText() const wxConvUTF8); } -void wxWebViewWebKit::RunScript(const wxString& javascript) +bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) { + if ( output != NULL ) + { + wxLogWarning(_("Returning output is not supported on WEBKIT1")); + return false; + } + webkit_web_view_execute_script(m_web_view, javascript.mb_str(wxConvUTF8)); + + return true; } void wxWebViewWebKit::RegisterHandler(wxSharedPtr handler) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index 807c76d148..5b7b15ae53 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -19,7 +19,13 @@ #include "wx/base64.h" #include "wx/log.h" #include "wx/gtk/private/webview_webkit2_extension.h" +#include "wx/gtk/private/string.h" +#include "wx/gtk/private/webkit.h" +#include "wx/gtk/private/error.h" +#include "wx/private/jsscriptwrapper.h" #include +#include +#include // ---------------------------------------------------------------------------- // GTK callbacks @@ -1093,13 +1099,81 @@ wxString wxWebViewWebKit::GetPageText() const return wxString(); } -void wxWebViewWebKit::RunScript(const wxString& javascript) +static void wxgtk_run_javascript_cb(WebKitWebView *, + GAsyncResult *res, + GAsyncResult **res_out) { + g_object_ref(res); + *res_out = res; +} + +bool JSResultToString(GObject *object, GAsyncResult *result, wxString* output) +{ + wxGtkError error; + wxWebKitJavascriptResult js_result(webkit_web_view_run_javascript_finish(WEBKIT_WEB_VIEW (object), + (GAsyncResult *)result, error.Out())); + + if ( !js_result ) + { + wxLogWarning(_("Error running Javascript: %s"), error.GetMessage()); + return false; + } + + JSGlobalContextRef context = webkit_javascript_result_get_global_context (&*js_result); + JSValueRef value = webkit_javascript_result_get_value (&*js_result); + + JSValueRef exception = NULL; + wxJSStringRef js_value(JSValueIsObject(context, value) ? + JSValueCreateJSONString(context, value, 0, &exception) : + JSValueToStringCopy (context, value, &exception)); + + if ( exception ) + { + wxJSStringRef ex_value(JSValueToStringCopy(context, exception, NULL)); + wxLogWarning(_("Exception running Javascript: %s"), ex_value.ToWxString()); + + return false; + } + + if ( output != NULL ) + *output = js_value.ToWxString(); + + return true; +} + +bool wxWebViewWebKit::RunScriptInternal(const wxString& javascript, wxString* output) +{ + GAsyncResult *result = NULL; webkit_web_view_run_javascript(m_web_view, - javascript.mb_str(wxConvUTF8), + javascript, NULL, - NULL, - NULL); + (GAsyncReadyCallback)wxgtk_run_javascript_cb, + &result); + + GMainContext *main_context = g_main_context_get_thread_default(); + + while ( !result ) + g_main_context_iteration(main_context, TRUE); + + return JSResultToString((GObject*)m_web_view, result, output); +} + +bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) +{ + wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); + + wxString result; + bool isValidJS = RunScriptInternal(wrapJS.GetWrappedCode(), &result); + + if ( isValidJS && result == "true" ) + { + RunScriptInternal(wrapJS.GetOutputCode(), output); + RunScriptInternal(wrapJS.GetCleanUpCode()); + return true; + } + + wxLogWarning(_("Javascript error: %s"), result); + return false; } void wxWebViewWebKit::RegisterHandler(wxSharedPtr handler) diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index 386e080765..ad4fa9b626 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -29,6 +29,8 @@ #include "wx/dynlib.h" #include "wx/scopeguard.h" +#include "wx/private/jsscriptwrapper.h" + #include #include @@ -109,6 +111,7 @@ bool wxWebViewIE::Create(wxWindow* parent, EnableControlFeature(21 /* FEATURE_DISABLE_NAVIGATION_SOUNDS */); LoadURL(url); + return true; } @@ -853,25 +856,85 @@ wxString wxWebViewIE::GetPageText() const } } -void wxWebViewIE::RunScript(const wxString& javascript) +bool wxWebViewIE::MSWSetModernEmulationLevel(bool modernLevel) +{ + wxRegKey key(wxRegKey::HKCU, wxREGISTRY_IE_PATH); + if ( key.Exists() ) + { + wxString programName = wxGetFullModuleName().AfterLast('\\'); + if ( modernLevel ) + { + if ( !key.SetValue(programName, wxIE_EMULATION_LEVEL) ) + { + wxLogWarning(_("Failed to set the current browser control emulation level")); + return false; + } + } + else + { + key.DeleteValue(programName); + } + return true; + } + wxLogWarning(_("Failed to find current browser control emulation level")); + return false; +} + +bool wxWebViewIE::RunScriptInternal(wxVariant varJavascript, wxAutomationObject* scriptAO, + wxVariant* varResult) +{ + if ( !scriptAO->Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varJavascript) ) + { + wxLogWarning(_("Can't run Javascript")); + return false; + } + + return true; +} + +bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) { wxCOMPtr document(GetDocument()); + IDispatch* scriptDispatch = NULL; - if(document) + if ( !document ) { - wxCOMPtr window; - wxString language = "javascript"; - HRESULT hr = document->get_parentWindow(&window); - if(SUCCEEDED(hr)) - { - VARIANT level; - VariantInit(&level); - V_VT(&level) = VT_EMPTY; - window->execScript(wxBasicString(javascript), - wxBasicString(language), - &level); - } + wxLogWarning(_("Can't run Javascript script without a valid HTML document")); + return false; } + + if ( FAILED(document->get_Script(&scriptDispatch)) ) + { + wxLogWarning(_("Can't get the Javascript")); + return false; + } + + wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); + + wxAutomationObject scriptAO(scriptDispatch); + wxVariant varJavascript(wrapJS.GetWrappedCode()); + wxVariant varResult; + + if ( !RunScriptInternal(varJavascript, &scriptAO, &varResult) ) + return false; + + if ( varResult.IsType("bool") && varResult.GetBool() ) + { + varJavascript = wrapJS.GetOutputCode(); + if ( !RunScriptInternal(varJavascript, &scriptAO, &varResult) ) + return false; + + if ( output != NULL ) + *output = varResult.MakeString(); + + varJavascript = wrapJS.GetCleanUpCode(); + if ( !RunScriptInternal(varJavascript, &scriptAO, &varResult) ) + return false; + return true; + } + + wxLogWarning(_("Javascript error: %s"), varResult.MakeString()); + return false; } void wxWebViewIE::RegisterHandler(wxSharedPtr handler) diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index 134b64d999..202bb93a99 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -24,6 +24,7 @@ #include "wx/osx/private.h" #include "wx/osx/core/cfref.h" +#include "wx/private/jsscriptwrapper.h" #include "wx/hashmap.h" #include "wx/filesys.h" @@ -152,6 +153,7 @@ bool wxWebViewWebKit::Create(wxWindow *parent, [NSURLProtocol registerClass:[WebViewCustomProtocol class]]; LoadURL(strURL); + return true; } @@ -408,13 +410,39 @@ wxString wxWebViewWebKit::GetSelectedText() const return wxCFStringRef::AsString([dr toString]); } -void wxWebViewWebKit::RunScript(const wxString& javascript) +bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) { if ( !m_webView ) - return; + { + wxCHECK_MSG( m_webView, false, + wxS("wxWebView must be created before running JS scripts") ); + return false; + } - [[m_webView windowScriptObject] evaluateWebScript: - wxCFStringRef( javascript ).AsNSString()]; + wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); + + NSString* result = [m_webView stringByEvaluatingJavaScriptFromString: + wxCFStringRef( wrapJS.GetWrappedCode() ).AsNSString()]; + + if ( result != nil && [result isEqualToString:@"true"] ) + { + result = [m_webView stringByEvaluatingJavaScriptFromString: + wxCFStringRef( wrapJS.GetOutputCode() ).AsNSString()]; + + [m_webView stringByEvaluatingJavaScriptFromString: + wxCFStringRef( wrapJS.GetCleanUpCode() ). + AsNSString()]; + + if ( result != nil && output != NULL ) + *output = wxCFStringRef::AsString(result); + } + else + { + if ( result != nil ) + wxLogWarning(_("Javascript error: %s"), wxCFStringRef::AsString(result)); + return false; + } + return true; } void wxWebViewWebKit::OnSize(wxSizeEvent &event) diff --git a/tests/controls/webtest.cpp b/tests/controls/webtest.cpp index 83a8f31c87..e492ad0170 100644 --- a/tests/controls/webtest.cpp +++ b/tests/controls/webtest.cpp @@ -8,7 +8,7 @@ #include "testprec.h" -#if wxUSE_WEBVIEW && (wxUSE_WEBVIEW_WEBKIT || wxUSE_WEBVIEW_IE) +#if wxUSE_WEBVIEW && (wxUSE_WEBVIEW_WEBKIT || wxUSE_WEBVIEW_WEBKIT2 || wxUSE_WEBVIEW_IE) #ifdef __BORLANDC__ #pragma hdrstop @@ -22,6 +22,10 @@ #include "wx/uiaction.h" #include "wx/webview.h" #include "asserthelper.h" +#if wxUSE_WEBVIEW_IE +#include "wx/msw/registry.h" +#include "wx/msw/webview_ie.h" +#endif class WebTestCase : public CppUnit::TestCase { @@ -36,8 +40,11 @@ private: CPPUNIT_TEST( Title ); CPPUNIT_TEST( Url ); CPPUNIT_TEST( History ); +#if !wxUSE_WEBVIEW_WEBKIT2 + //This is not implemented on WEBKIT2. See implementation. CPPUNIT_TEST( HistoryEnable ); CPPUNIT_TEST( HistoryClear ); +#endif CPPUNIT_TEST( HistoryList ); CPPUNIT_TEST( Editable ); CPPUNIT_TEST( Selection ); @@ -66,7 +73,7 @@ private: }; //Convenience macro -#define ENSURE_LOADED WX_ASSERT_EVENT_OCCURS((*m_loaded), 1) +#define ENSURE_LOADED WX_ASSERT_EVENT_OCCURS_IN((*m_loaded), 1, 1000) // register in the unnamed registry so that these tests are run by default CPPUNIT_TEST_SUITE_REGISTRATION( WebTestCase ); @@ -76,10 +83,10 @@ CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( WebTestCase, "WebTestCase" ); void WebTestCase::setUp() { - m_browser = wxWebView::New(wxTheApp->GetTopWindow(), wxID_ANY); - + m_browser = wxWebView::New(); m_loaded = new EventCounter(m_browser, wxEVT_WEBVIEW_LOADED); - m_browser->LoadURL("about:blank"); + + m_browser -> Create(wxTheApp->GetTopWindow(), wxID_ANY); ENSURE_LOADED; } @@ -262,8 +269,86 @@ void WebTestCase::Zoom() void WebTestCase::RunScript() { - m_browser->RunScript("document.write(\"Hello World!\");"); + m_browser-> + SetPage("", ""); + ENSURE_LOADED; + + wxString result; +#if wxUSE_WEBVIEW_IE + CPPUNIT_ASSERT(wxWebViewIE::MSWSetModernEmulationLevel()); + wxRegKey key(wxRegKey::HKCU, wxREGISTRY_IE_PATH); + long val = 0; + wxString programName = wxGetFullModuleName().AfterLast('\\'); + key.QueryValue(programName, &val); + CPPUNIT_ASSERT_EQUAL(val, wxIE_EMULATION_LEVEL); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(){var person = new Object();person.name = 'Bar'; \ + person.lastName = 'Foo';return person;}f();", &result)); + CPPUNIT_ASSERT_EQUAL("{\"name\":\"Bar\",\"lastName\":\"Foo\"}", result); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result)); + CPPUNIT_ASSERT_EQUAL("[\"foo\",\"bar\"]", result); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(){var d = new Date('10/08/2017 21:30:40'); \ + var tzoffset = d.getTimezoneOffset() * 60000; return new Date(d.getTime() - tzoffset);}f();", + &result)); + CPPUNIT_ASSERT_EQUAL("\"2017-10-08T21:30:40.000Z\"", result); + + CPPUNIT_ASSERT(wxWebViewIE::MSWSetModernEmulationLevel(false)); + val = 0; + key.QueryValue(programName, &val); + CPPUNIT_ASSERT_EQUAL(val, 0); +#endif + + CPPUNIT_ASSERT(m_browser->RunScript("document.write(\"Hello World!\");")); CPPUNIT_ASSERT_EQUAL("Hello World!", m_browser->GetPageText()); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(a){return a;}f('Hello World!');", &result)); + CPPUNIT_ASSERT_EQUAL(_("Hello World!"), result); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(a){return a;}f(123);", &result)); + CPPUNIT_ASSERT_EQUAL(123, wxAtoi(result)); + + CPPUNIT_ASSERT(m_browser-> + RunScript("function f(a){return a;}f(2.34);", &result)); + double value; + result.ToDouble(&value); + CPPUNIT_ASSERT_EQUAL(2.34, value); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(a){return a;}f(false);", &result)); + CPPUNIT_ASSERT_EQUAL(false, (result == "false") ? false : true); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(){var person = new Object();person.name = 'Foo'; \ + person.lastName = 'Bar';return person;}f();", &result)); + CPPUNIT_ASSERT_EQUAL("{\"name\":\"Foo\",\"lastName\":\"Bar\"}", result); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result)); + CPPUNIT_ASSERT_EQUAL("[\"foo\",\"bar\"]", result); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(){var person = new Object();}f();", &result)); + CPPUNIT_ASSERT_EQUAL("undefined", result); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(){return null;}f();", &result)); + CPPUNIT_ASSERT_EQUAL("null", result); + + result = ""; + CPPUNIT_ASSERT(!m_browser->RunScript("int main() { return 0; }", &result)); + CPPUNIT_ASSERT(!result); + + CPPUNIT_ASSERT(m_browser->RunScript("function a() { return eval(\"function b() { \ + return eval(\\\"function c() { return eval(\\\\\\\"function d() { \ + return \\\\\\\\\\\\\\\"test\\\\\\\\\\\\\\\"; } d();\\\\\\\"); } \ + c();\\\"); } b();\"); } a();", &result)); + CPPUNIT_ASSERT_EQUAL("test", result); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(a){return a;}f(\"This is a backslash: \\\\\");", + &result)); + CPPUNIT_ASSERT_EQUAL("This is a backslash: \\", result); + + CPPUNIT_ASSERT(m_browser->RunScript("function f(){var d = new Date('10/08/2016 21:30:40'); \ + var tzoffset = d.getTimezoneOffset() * 60000; return new Date(d.getTime() - tzoffset);}f();", + &result)); + CPPUNIT_ASSERT_EQUAL("\"2016-10-08T21:30:40.000Z\"", result); } void WebTestCase::SetPage() @@ -277,4 +362,4 @@ void WebTestCase::SetPage() CPPUNIT_ASSERT_EQUAL("other text", m_browser->GetPageText()); } -#endif //wxUSE_WEBVIEW && (wxUSE_WEBVIEW_WEBKIT || wxUSE_WEBVIEW_IE) +#endif //wxUSE_WEBVIEW && (wxUSE_WEBVIEW_WEBKIT || wxUSE_WEBVIEW_WEBKIT2 || wxUSE_WEBVIEW_IE) diff --git a/tests/testprec.h b/tests/testprec.h index 2c98096250..e5d179dbff 100644 --- a/tests/testprec.h +++ b/tests/testprec.h @@ -100,13 +100,13 @@ public: #define WX_ASSERT_FAILS_WITH_ASSERT(cond) #endif -#define WX_ASSERT_EVENT_OCCURS(eventcounter, count) \ +#define WX_ASSERT_EVENT_OCCURS_IN(eventcounter, count, ms) \ {\ wxStopWatch sw; \ wxEventLoopBase* loop = wxEventLoopBase::GetActive(); \ while(eventcounter.GetCount() < count) \ { \ - if(sw.Time() < 100) \ + if(sw.Time() < ms) \ loop->Dispatch(); \ else \ { \ @@ -119,6 +119,8 @@ public: eventcounter.Clear(); \ } +#define WX_ASSERT_EVENT_OCCURS(eventcounter,count) WX_ASSERT_EVENT_OCCURS_IN(eventcounter, count, 100) + // these functions can be used to hook into wxApp event processing and are // currently used by the events propagation test class WXDLLIMPEXP_FWD_BASE wxEvent; From 6930fff0f440339bd9d464485a9d92f87816e93a Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:09:22 +0200 Subject: [PATCH 02/39] Minor style changes to private webkit helper classes Include wx headers using quotes, not angle brackets. Wrap over long lines. Don't put space between the function name and its arguments. Make wxJSStringRef::ToWxString() itself, not its return value, const. Improve comments. --- include/wx/gtk/private/webkit.h | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/include/wx/gtk/private/webkit.h b/include/wx/gtk/private/webkit.h index d254e66819..1a1025ffc5 100644 --- a/include/wx/gtk/private/webkit.h +++ b/include/wx/gtk/private/webkit.h @@ -10,19 +10,27 @@ #ifndef _WX_GTK_PRIVATE_WEBKIT_H_ #define _WX_GTK_PRIVATE_WEBKIT_H_ +#include "wx/buffer.h" + #include #include -#include // ---------------------------------------------------------------------------- -// Convenience class for WebKitJavascriptResult on scope exit automatically +// RAII wrapper of WebKitJavascriptResult taking care of freeing it // ---------------------------------------------------------------------------- class wxWebKitJavascriptResult { public: - explicit wxWebKitJavascriptResult(WebKitJavascriptResult *r) : m_jsresult(r) { } - ~wxWebKitJavascriptResult() { webkit_javascript_result_unref (m_jsresult); } + explicit wxWebKitJavascriptResult(WebKitJavascriptResult *r) + : m_jsresult(r) + { + } + + ~wxWebKitJavascriptResult() + { + webkit_javascript_result_unref(m_jsresult); + } operator WebKitJavascriptResult *() const { return m_jsresult; } @@ -32,15 +40,20 @@ private: wxDECLARE_NO_COPY_CLASS(wxWebKitJavascriptResult); }; +// ---------------------------------------------------------------------------- +// RAII wrapper of JSStringRef, also providing conversion to wxString +// ---------------------------------------------------------------------------- + class wxJSStringRef { public: explicit wxJSStringRef(JSStringRef r) : m_jssref(r) { } - ~wxJSStringRef() { JSStringRelease (m_jssref); } + ~wxJSStringRef() { JSStringRelease(m_jssref); } - const wxString ToWxString() + wxString ToWxString() const { - size_t length = JSStringGetMaximumUTF8CStringSize(m_jssref); + const size_t length = JSStringGetMaximumUTF8CStringSize(m_jssref); + wxCharBuffer str(length); JSStringGetUTF8CString(m_jssref, str.data(), length); From 8c6ce2cd59742fc2d7ce61848a58dbc862de7580 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:10:42 +0200 Subject: [PATCH 03/39] Improve RunScript() error message when using old WebKit Don't put WebKit in all capitals and, more importantly, make it clear that this is related to running JavaScript to the user. --- src/gtk/webview_webkit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gtk/webview_webkit.cpp b/src/gtk/webview_webkit.cpp index 0430ff364d..5f82094ec4 100644 --- a/src/gtk/webview_webkit.cpp +++ b/src/gtk/webview_webkit.cpp @@ -953,7 +953,7 @@ bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) { if ( output != NULL ) { - wxLogWarning(_("Returning output is not supported on WEBKIT1")); + wxLogWarning(_("Retrieving JavaScript script output is not supported with WebKit v1")); return false; } From b309487ef662752f48abf77732f2a2e17cf98b92 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:12:21 +0200 Subject: [PATCH 04/39] Rename RunScriptInternal() to RunScriptSync() in wxGTK This helper function runs a JavaScript script and blocks until it finishes executing, so try to use a name at least hinting at this instead of being totally generic and useless. --- include/wx/gtk/webview_webkit.h | 2 +- src/gtk/webview_webkit2.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/wx/gtk/webview_webkit.h b/include/wx/gtk/webview_webkit.h index f0f1873ea6..fa61f06ca5 100644 --- a/include/wx/gtk/webview_webkit.h +++ b/include/wx/gtk/webview_webkit.h @@ -160,7 +160,7 @@ private: #if wxUSE_WEBVIEW_WEBKIT2 bool CanExecuteEditingCommand(const gchar* command) const; void SetupWebExtensionServer(); - bool RunScriptInternal(const wxString& javascript, wxString* output = NULL); + bool RunScriptSync(const wxString& javascript, wxString* output = NULL); #endif WebKitWebView *m_web_view; diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index 5b7b15ae53..47afb82094 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1141,7 +1141,8 @@ bool JSResultToString(GObject *object, GAsyncResult *result, wxString* output) return true; } -bool wxWebViewWebKit::RunScriptInternal(const wxString& javascript, wxString* output) +// Run the given script synchronously and return its result in output. +bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) { GAsyncResult *result = NULL; webkit_web_view_run_javascript(m_web_view, @@ -1163,12 +1164,12 @@ bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); wxString result; - bool isValidJS = RunScriptInternal(wrapJS.GetWrappedCode(), &result); + bool isValidJS = RunScriptSync(wrapJS.GetWrappedCode(), &result); if ( isValidJS && result == "true" ) { - RunScriptInternal(wrapJS.GetOutputCode(), output); - RunScriptInternal(wrapJS.GetCleanUpCode()); + RunScriptSync(wrapJS.GetOutputCode(), output); + RunScriptSync(wrapJS.GetCleanUpCode()); return true; } From 4476bd646dfe661f6b6fe26c08bb9b8b4c3ee5ea Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:14:43 +0200 Subject: [PATCH 05/39] Get rid of separate JSResultToString() function This function was used only once and didn't really help understanding the code and just required extra casts when passing arguments to it, for some reason. Just merge it into RunScriptSync() itself. --- src/gtk/webview_webkit2.cpp | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index 47afb82094..e451f6f236 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1107,10 +1107,23 @@ static void wxgtk_run_javascript_cb(WebKitWebView *, *res_out = res; } -bool JSResultToString(GObject *object, GAsyncResult *result, wxString* output) +// Run the given script synchronously and return its result in output. +bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) { + GAsyncResult *result = NULL; + webkit_web_view_run_javascript(m_web_view, + javascript, + NULL, + (GAsyncReadyCallback)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(WEBKIT_WEB_VIEW (object), + wxWebKitJavascriptResult js_result(webkit_web_view_run_javascript_finish(WEBKIT_WEB_VIEW (m_web_view), (GAsyncResult *)result, error.Out())); if ( !js_result ) @@ -1141,24 +1154,6 @@ bool JSResultToString(GObject *object, GAsyncResult *result, wxString* output) return true; } -// Run the given script synchronously and return its result in output. -bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) -{ - GAsyncResult *result = NULL; - webkit_web_view_run_javascript(m_web_view, - javascript, - NULL, - (GAsyncReadyCallback)wxgtk_run_javascript_cb, - &result); - - GMainContext *main_context = g_main_context_get_thread_default(); - - while ( !result ) - g_main_context_iteration(main_context, TRUE); - - return JSResultToString((GObject*)m_web_view, result, output); -} - bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) { wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); From 55beb9f7e868a8cb8a74b90569a7cb7eae595124 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:16:28 +0200 Subject: [PATCH 06/39] Get rid of unnecessary casts in RunScriptSync() No real changes, just simplify the code. --- src/gtk/webview_webkit2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index e451f6f236..c33b02712b 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1123,8 +1123,8 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output g_main_context_iteration(main_context, TRUE); wxGtkError error; - wxWebKitJavascriptResult js_result(webkit_web_view_run_javascript_finish(WEBKIT_WEB_VIEW (m_web_view), - (GAsyncResult *)result, error.Out())); + wxWebKitJavascriptResult js_result(webkit_web_view_run_javascript_finish(m_web_view, + result, error.Out())); if ( !js_result ) { From 0e2f6e5c52034ee52c54e5241bb8394822f837bf Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:17:14 +0200 Subject: [PATCH 07/39] Fix memory leak in wxGTK RunScriptSync() We must call g_object_unref() on an object that we called g_object_ref() before. --- src/gtk/webview_webkit2.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index c33b02712b..dcd8639335 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1103,7 +1103,10 @@ static void wxgtk_run_javascript_cb(WebKitWebView *, GAsyncResult *res, GAsyncResult **res_out) { + // Ensure that it doesn't get freed by the time we use it in + // RunScriptSync() itself. g_object_ref(res); + *res_out = res; } @@ -1126,6 +1129,9 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output wxWebKitJavascriptResult js_result(webkit_web_view_run_javascript_finish(m_web_view, result, error.Out())); + // Match g_object_ref() in wxgtk_run_javascript_cb() + g_object_unref(result); + if ( !js_result ) { wxLogWarning(_("Error running Javascript: %s"), error.GetMessage()); From 9d9f692024184446e2defac850052e0679d420e4 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:17:40 +0200 Subject: [PATCH 08/39] Get rid of nonsensical "&*" construct in RunScriptSync() Just pass JS result object directly to the WebKit function, there is really no need to dereference it and then take its address for this. --- src/gtk/webview_webkit2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index dcd8639335..a7cb27434b 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1138,8 +1138,8 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output return false; } - JSGlobalContextRef context = webkit_javascript_result_get_global_context (&*js_result); - JSValueRef value = webkit_javascript_result_get_value (&*js_result); + JSGlobalContextRef context = webkit_javascript_result_get_global_context(js_result); + JSValueRef value = webkit_javascript_result_get_value(js_result); JSValueRef exception = NULL; wxJSStringRef js_value(JSValueIsObject(context, value) ? From ec54ddb66b59f90bbc500876eaf6d9a599805e82 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:25:11 +0200 Subject: [PATCH 09/39] No real changes, just reformat the code a little Put long function calls on multiple lines. --- src/gtk/webview_webkit2.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index a7cb27434b..bf0d60eaa1 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1126,8 +1126,15 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output g_main_context_iteration(main_context, TRUE); wxGtkError error; - wxWebKitJavascriptResult js_result(webkit_web_view_run_javascript_finish(m_web_view, - result, error.Out())); + wxWebKitJavascriptResult js_result + ( + webkit_web_view_run_javascript_finish + ( + m_web_view, + result, + error.Out() + ) + ); // Match g_object_ref() in wxgtk_run_javascript_cb() g_object_unref(result); @@ -1142,9 +1149,12 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output JSValueRef value = webkit_javascript_result_get_value(js_result); JSValueRef exception = NULL; - wxJSStringRef js_value(JSValueIsObject(context, value) ? - JSValueCreateJSONString(context, value, 0, &exception) : - JSValueToStringCopy (context, value, &exception)); + wxJSStringRef js_value + ( + JSValueIsObject(context, value) + ? JSValueCreateJSONString(context, value, 0, &exception) + : JSValueToStringCopy(context, value, &exception) + ); if ( exception ) { From ee9615b9ac3293fb14dcaf7cf7ebe64dfe29216d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:26:42 +0200 Subject: [PATCH 10/39] Consistently capitalize "JavaScript" This is the proper name of the language, not "Javascript". --- interface/wx/webview.h | 8 ++++---- src/gtk/webview_webkit2.cpp | 6 +++--- src/msw/webview_ie.cpp | 8 ++++---- src/osx/webview_webkit.mm | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/interface/wx/webview.h b/interface/wx/webview.h index 73be3277dc..1eea09b681 100644 --- a/interface/wx/webview.h +++ b/interface/wx/webview.h @@ -478,12 +478,12 @@ public: outputs are supported on wxWEBVIEW_WEBKIT2 (GTK3). When using wxWEBVIEW_WEBKIT (OSX), there are two limits: - 1) Javascript allocations greater than 10MB. - 2) Javascript that takes longer than 10 seconds to execute. + 1) JavaScript allocations greater than 10MB. + 2) JavaScript that takes longer than 10 seconds to execute. When using wxWEBVIEW_BACKEND_IE you must wait for the current page to finish loading before calling RunScript(). - It is compulsory to have a script tag inside HTML to run Javascript, on MSW. + It is compulsory to have a script tag inside HTML to run JavaScript, on MSW. When using wxWEBVIEW_BACKEND_IE, JSON is not available in Quirks or IE6/7 standards mode, which is unfortunately the default one for the embedded browser control, see @@ -499,7 +499,7 @@ public: @endcode 2) There is an implementation of JSON.stringify on RunScript that helps to return objects. You don't need to change IE emulation level but it could not work for some cases. - @param javascript A wxString containing the Javascript. + @param javascript A wxString containing the JavaScript. @param output wxString result pointer. It can be NULL and it is new in wxWidgets 3.1.1. @return @true if there is a result, @false if there is an error. */ diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index bf0d60eaa1..ebc67fcb85 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1141,7 +1141,7 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output if ( !js_result ) { - wxLogWarning(_("Error running Javascript: %s"), error.GetMessage()); + wxLogWarning(_("Error running JavaScript: %s"), error.GetMessage()); return false; } @@ -1159,7 +1159,7 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output if ( exception ) { wxJSStringRef ex_value(JSValueToStringCopy(context, exception, NULL)); - wxLogWarning(_("Exception running Javascript: %s"), ex_value.ToWxString()); + wxLogWarning(_("Exception running JavaScript: %s"), ex_value.ToWxString()); return false; } @@ -1184,7 +1184,7 @@ bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) return true; } - wxLogWarning(_("Javascript error: %s"), result); + wxLogWarning(_("JavaScript error: %s"), result); return false; } diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index ad4fa9b626..6f74235498 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -885,7 +885,7 @@ bool wxWebViewIE::RunScriptInternal(wxVariant varJavascript, wxAutomationObject* { if ( !scriptAO->Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varJavascript) ) { - wxLogWarning(_("Can't run Javascript")); + wxLogWarning(_("Can't run JavaScript")); return false; } @@ -899,13 +899,13 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) if ( !document ) { - wxLogWarning(_("Can't run Javascript script without a valid HTML document")); + wxLogWarning(_("Can't run JavaScript script without a valid HTML document")); return false; } if ( FAILED(document->get_Script(&scriptDispatch)) ) { - wxLogWarning(_("Can't get the Javascript")); + wxLogWarning(_("Can't get the JavaScript")); return false; } @@ -933,7 +933,7 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) return true; } - wxLogWarning(_("Javascript error: %s"), varResult.MakeString()); + wxLogWarning(_("JavaScript error: %s"), varResult.MakeString()); return false; } diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index 202bb93a99..d7e7ab6a54 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -439,7 +439,7 @@ bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) else { if ( result != nil ) - wxLogWarning(_("Javascript error: %s"), wxCFStringRef::AsString(result)); + wxLogWarning(_("JavaScript error: %s"), wxCFStringRef::AsString(result)); return false; } return true; From 74e60b39f8a816cbeadb1116aba9824d9d6a187d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:34:02 +0200 Subject: [PATCH 11/39] No changes, just get rid of gratuitous difference Don't add an extra line unrelated to JavaScript changes on this branch. --- src/msw/webview_ie.cpp | 1 - src/osx/webview_webkit.mm | 1 - 2 files changed, 2 deletions(-) diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index 6f74235498..cc447313bf 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -111,7 +111,6 @@ bool wxWebViewIE::Create(wxWindow* parent, EnableControlFeature(21 /* FEATURE_DISABLE_NAVIGATION_SOUNDS */); LoadURL(url); - return true; } diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index d7e7ab6a54..2e7fb12f14 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -153,7 +153,6 @@ bool wxWebViewWebKit::Create(wxWindow *parent, [NSURLProtocol registerClass:[WebViewCustomProtocol class]]; LoadURL(strURL); - return true; } From 9884b0b4ea44b54f45a2ee6d45771fb63a61d2ee Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:35:57 +0200 Subject: [PATCH 12/39] Remove redundant check for m_webView in wxOSX code wxCHECK_MSG() is enough, there is really no need to check for the same condition twice, this is just confusing. --- src/osx/webview_webkit.mm | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index 2e7fb12f14..ea75162a9d 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -411,12 +411,8 @@ wxString wxWebViewWebKit::GetSelectedText() const bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) { - if ( !m_webView ) - { - wxCHECK_MSG( m_webView, false, - wxS("wxWebView must be created before running JS scripts") ); - return false; - } + wxCHECK_MSG( m_webView, false, + wxS("wxWebView must be created before calling RunScript()") ); wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); From 70c8979223bd758e13324800cc4ae847c4e41153 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 21 Oct 2017 23:38:46 +0200 Subject: [PATCH 13/39] Add wxCHECK_MSG() to wxGTK RunScript() implementation too Don't crash if RunScript() is called on a not yet created object. This is also consistent with the macOS version of the code. --- src/gtk/webview_webkit.cpp | 3 +++ src/gtk/webview_webkit2.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/gtk/webview_webkit.cpp b/src/gtk/webview_webkit.cpp index 5f82094ec4..ea0eb0d967 100644 --- a/src/gtk/webview_webkit.cpp +++ b/src/gtk/webview_webkit.cpp @@ -951,6 +951,9 @@ wxString wxWebViewWebKit::GetPageText() const bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) { + wxCHECK_MSG( m_web_view, false, + wxS("wxWebView must be created before calling RunScript()") ); + if ( output != NULL ) { wxLogWarning(_("Retrieving JavaScript script output is not supported with WebKit v1")); diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index ebc67fcb85..a8c8cb1280 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1172,6 +1172,9 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) { + wxCHECK_MSG( m_web_view, false, + wxS("wxWebView must be created before calling RunScript()") ); + wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); wxString result; From ce0de68f133669ff5dcd976123cf724db7d33064 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 00:00:26 +0200 Subject: [PATCH 14/39] Fix recurring "messege" typo in webview sample --- samples/webview/webview.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index 0ca29d6d78..9f5e44a43e 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -136,8 +136,9 @@ public: void OnScrollLineDown(wxCommandEvent&) { m_browser->LineDown(); } void OnScrollPageUp(wxCommandEvent&) { m_browser->PageUp(); } void OnScrollPageDown(wxCommandEvent&) { m_browser->PageDown(); } - void RunScript(const wxString& javascript, wxString* result = NULL, - const wxString& messege = "Click OK to run JavaScript."); + void RunScript(const wxString& javascript, + wxString* result = NULL, + const wxString& message = "Click OK to run JavaScript."); void OnRunScriptString(wxCommandEvent& evt); void OnRunScriptInteger(wxCommandEvent& evt); void OnRunScriptDouble(wxCommandEvent& evt); @@ -1050,9 +1051,9 @@ void WebFrame::OnHistory(wxCommandEvent& evt) m_browser->LoadHistoryItem(m_histMenuItems[evt.GetId()]); } -void WebFrame::RunScript(const wxString& javascript, wxString* result, const wxString& messege) +void WebFrame::RunScript(const wxString& javascript, wxString* result, const wxString& message) { - wxTextEntryDialog dialog(this, messege, wxGetTextFromUserPromptStr, javascript, wxOK|wxCANCEL|wxCENTRE|wxTE_MULTILINE); + wxTextEntryDialog dialog(this, message, wxGetTextFromUserPromptStr, javascript, wxOK|wxCANCEL|wxCENTRE|wxTE_MULTILINE); if( dialog.ShowModal() == wxID_OK ) { if ( m_browser->RunScript(dialog.GetValue(), result)) From b66632fc6dbf2a132f7b3f2322578304d2214288 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 00:07:21 +0200 Subject: [PATCH 15/39] Simplify RunScript()-related code in webview sample There is no need to pass "result" to this function when it's never used outside of it. And it can use the correct message depending on the value of its input instead of having to provide it in the caller. --- samples/webview/webview.cpp | 76 ++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index 9f5e44a43e..246d1d8697 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -136,9 +136,7 @@ public: void OnScrollLineDown(wxCommandEvent&) { m_browser->LineDown(); } void OnScrollPageUp(wxCommandEvent&) { m_browser->PageUp(); } void OnScrollPageDown(wxCommandEvent&) { m_browser->PageDown(); } - void RunScript(const wxString& javascript, - wxString* result = NULL, - const wxString& message = "Click OK to run JavaScript."); + void RunScript(const wxString& javascript = wxString()); void OnRunScriptString(wxCommandEvent& evt); void OnRunScriptInteger(wxCommandEvent& evt); void OnRunScriptDouble(wxCommandEvent& evt); @@ -1051,60 +1049,59 @@ void WebFrame::OnHistory(wxCommandEvent& evt) m_browser->LoadHistoryItem(m_histMenuItems[evt.GetId()]); } -void WebFrame::RunScript(const wxString& javascript, wxString* result, const wxString& message) +void WebFrame::RunScript(const wxString& javascript) { - wxTextEntryDialog dialog(this, message, wxGetTextFromUserPromptStr, javascript, wxOK|wxCANCEL|wxCENTRE|wxTE_MULTILINE); - if( dialog.ShowModal() == wxID_OK ) + wxString message; + if ( javascript.empty() ) + message = "Please enter JavaScript code to execute"; + else + message = "Click \"OK\" to accept running this JavaScript code"; + + wxTextEntryDialog dialog(this, message, wxGetTextFromUserPromptStr, javascript, + wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE); + if( dialog.ShowModal() != wxID_OK ) + return; + + wxString result; + if ( m_browser->RunScript(dialog.GetValue(), &result) ) { - if ( m_browser->RunScript(dialog.GetValue(), result)) - { - if ( result != NULL ) - wxLogMessage(_("RunScript result: %s"), *result); - else - wxLogMessage(_("RunScript ran properly")); - } - else - { - wxLogWarning(_("RunScript returned false")); - } + wxLogMessage("RunScript() returned \"%s\"", result); + } + else + { + wxLogWarning("RunScript() failed"); } } void WebFrame::OnRunScriptString(wxCommandEvent& WXUNUSED(evt)) { - wxString result; - RunScript("function f(a){return a;}f('Hello World!');", &result); + RunScript("function f(a){return a;}f('Hello World!');"); } void WebFrame::OnRunScriptInteger(wxCommandEvent& WXUNUSED(evt)) { - wxString result; - RunScript("function f(a){return a;}f(123);", &result); + RunScript("function f(a){return a;}f(123);"); } void WebFrame::OnRunScriptDouble(wxCommandEvent& WXUNUSED(evt)) { - wxString result; - RunScript("function f(a){return a;}f(2.34);", &result); + RunScript("function f(a){return a;}f(2.34);"); } void WebFrame::OnRunScriptBool(wxCommandEvent& WXUNUSED(evt)) { - wxString result; - RunScript("function f(a){return a;}f(false);", &result); + RunScript("function f(a){return a;}f(false);"); } void WebFrame::OnRunScriptObject(wxCommandEvent& WXUNUSED(evt)) { - wxString result; RunScript("function f(){var person = new Object();person.name = 'Foo'; \ - person.lastName = 'Bar';return person;}f();", &result); + person.lastName = 'Bar';return person;}f();"); } void WebFrame::OnRunScriptArray(wxCommandEvent& WXUNUSED(evt)) { - wxString result; - RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result); + RunScript("function f(){ return [\"foo\", \"bar\"]; }f();"); } void WebFrame::OnRunScriptDOM(wxCommandEvent& WXUNUSED(evt)) @@ -1114,57 +1111,50 @@ void WebFrame::OnRunScriptDOM(wxCommandEvent& WXUNUSED(evt)) void WebFrame::OnRunScriptUndefined(wxCommandEvent& WXUNUSED(evt)) { - wxString result; - RunScript("function f(){var person = new Object();}f();", &result); + RunScript("function f(){var person = new Object();}f();"); } void WebFrame::OnRunScriptNull(wxCommandEvent& WXUNUSED(evt)) { - wxString result; - RunScript("function f(){return null;}f();", &result); + RunScript("function f(){return null;}f();"); } void WebFrame::OnRunScriptDate(wxCommandEvent& WXUNUSED(evt)) { - wxString result; RunScript("function f(){var d = new Date('10/08/2017 21:30:40'); \ var tzoffset = d.getTimezoneOffset() * 60000; \ - return new Date(d.getTime() - tzoffset);}f();", &result); + return new Date(d.getTime() - tzoffset);}f();"); } #if wxUSE_WEBVIEW_IE void WebFrame::OnRunScriptObjectWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) { wxWebViewIE::MSWSetModernEmulationLevel(); - wxString result; RunScript("function f(){var person = new Object();person.name = 'Foo'; \ - person.lastName = 'Bar';return person;}f();", &result); + person.lastName = 'Bar';return person;}f();"); wxWebViewIE::MSWSetModernEmulationLevel(false); } void WebFrame::OnRunScriptDateWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) { wxWebViewIE::MSWSetModernEmulationLevel(); - wxString result; RunScript("function f(){var d = new Date('10/08/2017 21:30:40'); \ var tzoffset = d.getTimezoneOffset() * 60000; return \ - new Date(d.getTime() - tzoffset);}f();", &result); + new Date(d.getTime() - tzoffset);}f();"); wxWebViewIE::MSWSetModernEmulationLevel(false); } void WebFrame::OnRunScriptArrayWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) { wxWebViewIE::MSWSetModernEmulationLevel(); - wxString result; - RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result); + RunScript("function f(){ return [\"foo\", \"bar\"]; }f();"); wxWebViewIE::MSWSetModernEmulationLevel(false); } #endif void WebFrame::OnRunScriptCustom(wxCommandEvent& WXUNUSED(evt)) { - wxString result; - RunScript("", &result, "Enter JavaScript to run."); + RunScript(); } void WebFrame::OnClearSelection(wxCommandEvent& WXUNUSED(evt)) From 408ee0523c956ffbe80e227572f2a7aaf6cb9a08 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 00:17:28 +0200 Subject: [PATCH 16/39] Move RunScriptInternal() out of wxWebViewIE and rename There is no need for this function to be a class member. --- include/wx/msw/webview_ie.h | 3 --- src/msw/webview_ie.cpp | 9 +++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/include/wx/msw/webview_ie.h b/include/wx/msw/webview_ie.h index e1235fa40d..edd2926f8f 100644 --- a/include/wx/msw/webview_ie.h +++ b/include/wx/msw/webview_ie.h @@ -200,9 +200,6 @@ private: //Toggles control features see INTERNETFEATURELIST for values. bool EnableControlFeature(long flag, bool enable = true); - bool RunScriptInternal(wxVariant varJavascript, - wxAutomationObject* scriptAO, wxVariant* varResult); - wxDECLARE_DYNAMIC_CLASS(wxWebViewIE); }; diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index cc447313bf..12a98d011e 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -879,7 +879,8 @@ bool wxWebViewIE::MSWSetModernEmulationLevel(bool modernLevel) return false; } -bool wxWebViewIE::RunScriptInternal(wxVariant varJavascript, wxAutomationObject* scriptAO, +static +bool CallEval(wxVariant varJavascript, wxAutomationObject* scriptAO, wxVariant* varResult) { if ( !scriptAO->Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varJavascript) ) @@ -914,20 +915,20 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) wxVariant varJavascript(wrapJS.GetWrappedCode()); wxVariant varResult; - if ( !RunScriptInternal(varJavascript, &scriptAO, &varResult) ) + if ( !CallEval(varJavascript, &scriptAO, &varResult) ) return false; if ( varResult.IsType("bool") && varResult.GetBool() ) { varJavascript = wrapJS.GetOutputCode(); - if ( !RunScriptInternal(varJavascript, &scriptAO, &varResult) ) + if ( !CallEval(varJavascript, &scriptAO, &varResult) ) return false; if ( output != NULL ) *output = varResult.MakeString(); varJavascript = wrapJS.GetCleanUpCode(); - if ( !RunScriptInternal(varJavascript, &scriptAO, &varResult) ) + if ( !CallEval(varJavascript, &scriptAO, &varResult) ) return false; return true; } From 8f42fec21af87c93689f435b74eb0d79d7486a33 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 00:19:06 +0200 Subject: [PATCH 17/39] Pass wxAutomationObject by reference to CallEval() Using pointer here makes no sense: this parameter can never be null and we don't even have the excuse of making it simpler to call like this as the caller has an object and not a pointer to it, so this even results in extra typing. --- src/msw/webview_ie.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index 12a98d011e..0dcdd16385 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -880,10 +880,10 @@ bool wxWebViewIE::MSWSetModernEmulationLevel(bool modernLevel) } static -bool CallEval(wxVariant varJavascript, wxAutomationObject* scriptAO, +bool CallEval(wxVariant varJavascript, wxAutomationObject& scriptAO, wxVariant* varResult) { - if ( !scriptAO->Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varJavascript) ) + if ( !scriptAO.Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varJavascript) ) { wxLogWarning(_("Can't run JavaScript")); return false; @@ -915,20 +915,20 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) wxVariant varJavascript(wrapJS.GetWrappedCode()); wxVariant varResult; - if ( !CallEval(varJavascript, &scriptAO, &varResult) ) + if ( !CallEval(varJavascript, scriptAO, &varResult) ) return false; if ( varResult.IsType("bool") && varResult.GetBool() ) { varJavascript = wrapJS.GetOutputCode(); - if ( !CallEval(varJavascript, &scriptAO, &varResult) ) + if ( !CallEval(varJavascript, scriptAO, &varResult) ) return false; if ( output != NULL ) *output = varResult.MakeString(); varJavascript = wrapJS.GetCleanUpCode(); - if ( !CallEval(varJavascript, &scriptAO, &varResult) ) + if ( !CallEval(varJavascript, scriptAO, &varResult) ) return false; return true; } From d047bf2beb78e9ca3e390a7edf83ba81aa69f2b0 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 00:20:54 +0200 Subject: [PATCH 18/39] Make CallEval() simpler to use by taking a string in it Don't force the caller to convert the string to wxVariant when we can perfectly well do it ourselves. --- src/msw/webview_ie.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index 0dcdd16385..642aff1245 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -880,10 +880,12 @@ bool wxWebViewIE::MSWSetModernEmulationLevel(bool modernLevel) } static -bool CallEval(wxVariant varJavascript, wxAutomationObject& scriptAO, - wxVariant* varResult) +bool CallEval(const wxString& code, + wxAutomationObject& scriptAO, + wxVariant* varResult) { - if ( !scriptAO.Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varJavascript) ) + wxVariant varCode(code); + if ( !scriptAO.Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varCode) ) { wxLogWarning(_("Can't run JavaScript")); return false; @@ -912,23 +914,20 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); wxAutomationObject scriptAO(scriptDispatch); - wxVariant varJavascript(wrapJS.GetWrappedCode()); wxVariant varResult; - if ( !CallEval(varJavascript, scriptAO, &varResult) ) + if ( !CallEval(wrapJS.GetWrappedCode(), scriptAO, &varResult) ) return false; if ( varResult.IsType("bool") && varResult.GetBool() ) { - varJavascript = wrapJS.GetOutputCode(); - if ( !CallEval(varJavascript, scriptAO, &varResult) ) + if ( !CallEval(wrapJS.GetOutputCode(), scriptAO, &varResult) ) return false; if ( output != NULL ) *output = varResult.MakeString(); - varJavascript = wrapJS.GetCleanUpCode(); - if ( !CallEval(varJavascript, scriptAO, &varResult) ) + if ( !CallEval(wrapJS.GetCleanUpCode(), scriptAO, &varResult) ) return false; return true; } From b3bd9c77afc859392cfd5afd7a7b3b1d6db7abb4 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 00:21:44 +0200 Subject: [PATCH 19/39] No changes, just declare the variable before its use Move variable declaration where it's really needed. --- src/msw/webview_ie.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index 642aff1245..66944cab0a 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -897,14 +897,13 @@ bool CallEval(const wxString& code, bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) { wxCOMPtr document(GetDocument()); - IDispatch* scriptDispatch = NULL; - if ( !document ) { wxLogWarning(_("Can't run JavaScript script without a valid HTML document")); return false; } + IDispatch* scriptDispatch = NULL; if ( FAILED(document->get_Script(&scriptDispatch)) ) { wxLogWarning(_("Can't get the JavaScript")); From 1bb8cd860aa3ca0e09224c452ed743f487e8cd73 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 00:22:24 +0200 Subject: [PATCH 20/39] Improve and harmonize error reporting in RunScript() Errors were reported in different ways (including not being reported at all in wxMSW) in different ports. Try to consistently do it in the same way now and use exactly the same sentences to facilitate translators life. --- src/gtk/webview_webkit2.cpp | 38 ++++++++++++++++++++----------- src/msw/webview_ie.cpp | 45 ++++++++++++++++++++----------------- src/osx/webview_webkit.mm | 36 ++++++++++++++++++++--------- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index a8c8cb1280..59fa602c63 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1141,7 +1141,8 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output if ( !js_result ) { - wxLogWarning(_("Error running JavaScript: %s"), error.GetMessage()); + if ( output ) + *output = error.GetMessage(); return false; } @@ -1158,8 +1159,11 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output if ( exception ) { - wxJSStringRef ex_value(JSValueToStringCopy(context, exception, NULL)); - wxLogWarning(_("Exception running JavaScript: %s"), ex_value.ToWxString()); + if ( output ) + { + wxJSStringRef ex_value(JSValueToStringCopy(context, exception, NULL)); + *output = ex_value.ToWxString(); + } return false; } @@ -1172,23 +1176,31 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) { - wxCHECK_MSG( m_web_view, false, - wxS("wxWebView must be created before calling RunScript()") ); - 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; - bool isValidJS = RunScriptSync(wrapJS.GetWrappedCode(), &result); - - if ( isValidJS && result == "true" ) + if ( RunScriptSync(wrapJS.GetWrappedCode(), &result) + && result == wxS("true") ) { - RunScriptSync(wrapJS.GetOutputCode(), output); + if ( RunScriptSync(wrapJS.GetOutputCode(), &result) ) + { + if ( output ) + *output = result; + result.clear(); + } + RunScriptSync(wrapJS.GetCleanUpCode()); - return true; } - wxLogWarning(_("JavaScript error: %s"), result); - return false; + if ( !result.empty() ) + { + wxLogWarning(_("Error running JavaScript: %s"), result); + return false; + } + + return true; } void wxWebViewWebKit::RegisterHandler(wxSharedPtr handler) diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index 66944cab0a..2234f51012 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -885,13 +885,7 @@ bool CallEval(const wxString& code, wxVariant* varResult) { wxVariant varCode(code); - if ( !scriptAO.Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varCode) ) - { - wxLogWarning(_("Can't run JavaScript")); - return false; - } - - return true; + return scriptAO.Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varCode); } bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) @@ -906,7 +900,7 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) IDispatch* scriptDispatch = NULL; if ( FAILED(document->get_Script(&scriptDispatch)) ) { - wxLogWarning(_("Can't get the JavaScript")); + wxLogWarning(_("Can't get the JavaScript object")); return false; } @@ -915,24 +909,35 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) wxAutomationObject scriptAO(scriptDispatch); wxVariant varResult; + wxString err; if ( !CallEval(wrapJS.GetWrappedCode(), scriptAO, &varResult) ) - return false; - - if ( varResult.IsType("bool") && varResult.GetBool() ) { - if ( !CallEval(wrapJS.GetOutputCode(), scriptAO, &varResult) ) - return false; - + err = _("failed to evaluate"); + } + else if ( varResult.IsType("bool") && varResult.GetBool() ) + { if ( output != NULL ) - *output = varResult.MakeString(); + { + if ( CallEval(wrapJS.GetOutputCode(), scriptAO, &varResult) ) + *output = varResult.MakeString(); + else + err = _("failed to retrieve execution result"); + } - if ( !CallEval(wrapJS.GetCleanUpCode(), scriptAO, &varResult) ) - return false; - return true; + CallEval(wrapJS.GetCleanUpCode(), scriptAO, &varResult); + } + else // result available but not the expected "true" + { + err = varResult.MakeString(); } - wxLogWarning(_("JavaScript error: %s"), varResult.MakeString()); - return false; + if ( !err.empty() ) + { + wxLogWarning(_("Error running JavaScript: %s"), varResult.MakeString()); + return false; + } + + return true; } void wxWebViewIE::RegisterHandler(wxSharedPtr handler) diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index ea75162a9d..13b76e0626 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -417,26 +417,42 @@ bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); NSString* result = [m_webView stringByEvaluatingJavaScriptFromString: - wxCFStringRef( wrapJS.GetWrappedCode() ).AsNSString()]; + wxCFStringRef( wrapJS.GetWrappedCode() ).AsNSString()]; - if ( result != nil && [result isEqualToString:@"true"] ) + wxString err; + if ( result == nil ) + { + // This is not very informative, but we just don't have any other + // information in this case. + err = _("failed to evaluate"); + } + else if ( [result isEqualToString:@"true"] ) { result = [m_webView stringByEvaluatingJavaScriptFromString: - wxCFStringRef( wrapJS.GetOutputCode() ).AsNSString()]; + wxCFStringRef( wrapJS.GetOutputCode() ).AsNSString()]; [m_webView stringByEvaluatingJavaScriptFromString: - wxCFStringRef( wrapJS.GetCleanUpCode() ). - AsNSString()]; + wxCFStringRef( wrapJS.GetCleanUpCode() ).AsNSString()]; - if ( result != nil && output != NULL ) - *output = wxCFStringRef::AsString(result); + if ( output != NULL ) + { + if ( result ) + *output = wxCFStringRef::AsString(result); + else + err = _("failed to retrieve execution result"); + } } - else + else // result available but not the expected "true" { - if ( result != nil ) - wxLogWarning(_("JavaScript error: %s"), wxCFStringRef::AsString(result)); + err = wxCFStringRef::AsString(result); + } + + if ( !err.empty() ) + { + wxLogWarning(_("Error running JavaScript: %s"), err); return false; } + return true; } From b0d23759410d32b90a912de4a689a747f1b6a914 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 00:30:37 +0200 Subject: [PATCH 21/39] Revert "Changed url on webview sample" This reverts commit 92ae25ec3c240217f5584372abadaabedd3952df. There doesn't seem to be any good reason to use wiki.wxwidgets.org instead of the main site. --- samples/webview/webview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index 246d1d8697..65a11a98ba 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -66,7 +66,7 @@ class WebApp : public wxApp { public: WebApp() : - m_url("http://wiki.wxwidgets.org") + m_url("http://www.wxwidgets.org") { } From 9d6bd9a52d4caa1539330aa2e6e2d185f99158ad Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 00:35:29 +0200 Subject: [PATCH 22/39] Fix webkit_web_view_run_javascript() callback signature Don't use wrong types and then cast the function pointer to the right type, but just use the correct type from the beginning. Also make the callback extern "C", as it should be, to be called from C code. --- src/gtk/webview_webkit2.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index 59fa602c63..ff5076fffc 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1099,17 +1099,21 @@ wxString wxWebViewWebKit::GetPageText() const return wxString(); } -static void wxgtk_run_javascript_cb(WebKitWebView *, - GAsyncResult *res, - GAsyncResult **res_out) +extern "C" +{ + +static void wxgtk_run_javascript_cb(GObject *, + GAsyncResult *res, + void *user_data) { - // Ensure that it doesn't get freed by the time we use it in - // RunScriptSync() itself. g_object_ref(res); + GAsyncResult** res_out = static_cast(user_data); *res_out = res; } +} // extern "C" + // Run the given script synchronously and return its result in output. bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) { @@ -1117,7 +1121,7 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output webkit_web_view_run_javascript(m_web_view, javascript, NULL, - (GAsyncReadyCallback)wxgtk_run_javascript_cb, + wxgtk_run_javascript_cb, &result); GMainContext *main_context = g_main_context_get_thread_default(); From 732cb97af3a2e53af40d860424cdee1a37837fa2 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 03:23:40 +0200 Subject: [PATCH 23/39] Don't prompt before running canned JavaScript in the sample This is more annoying than helpful, just run the script without prompting and do remember the last snippet we ran to show it in the interactive "Run Script" dialog later if necessary. --- samples/webview/webview.cpp | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index 65a11a98ba..a7376f3f92 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -136,7 +136,7 @@ public: void OnScrollLineDown(wxCommandEvent&) { m_browser->LineDown(); } void OnScrollPageUp(wxCommandEvent&) { m_browser->PageUp(); } void OnScrollPageDown(wxCommandEvent&) { m_browser->PageDown(); } - void RunScript(const wxString& javascript = wxString()); + void RunScript(const wxString& javascript); void OnRunScriptString(wxCommandEvent& evt); void OnRunScriptInteger(wxCommandEvent& evt); void OnRunScriptDouble(wxCommandEvent& evt); @@ -234,6 +234,9 @@ private: wxMenuHistoryMap m_histMenuItems; wxString m_findText; int m_findFlags, m_findCount; + + // Last executed JavaScript snippet, for convenience. + wxString m_javascript; }; class SourceViewDialog : public wxDialog @@ -1051,19 +1054,14 @@ void WebFrame::OnHistory(wxCommandEvent& evt) void WebFrame::RunScript(const wxString& javascript) { - wxString message; - if ( javascript.empty() ) - message = "Please enter JavaScript code to execute"; - else - message = "Click \"OK\" to accept running this JavaScript code"; + // Remember the script we run in any case, so the next time the user opens + // the "Run Script" dialog box, it is shown there for convenient updating. + m_javascript = javascript; - wxTextEntryDialog dialog(this, message, wxGetTextFromUserPromptStr, javascript, - wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE); - if( dialog.ShowModal() != wxID_OK ) - return; + wxLogMessage("Running JavaScript:\n%s\n", javascript); wxString result; - if ( m_browser->RunScript(dialog.GetValue(), &result) ) + if ( m_browser->RunScript(javascript, &result) ) { wxLogMessage("RunScript() returned \"%s\"", result); } @@ -1154,7 +1152,18 @@ void WebFrame::OnRunScriptArrayWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) void WebFrame::OnRunScriptCustom(wxCommandEvent& WXUNUSED(evt)) { - RunScript(); + wxTextEntryDialog dialog + ( + this, + "Please enter JavaScript code to execute", + wxGetTextFromUserPromptStr, + m_javascript, + wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE + ); + if( dialog.ShowModal() != wxID_OK ) + return; + + RunScript(dialog.GetValue()); } void WebFrame::OnClearSelection(wxCommandEvent& WXUNUSED(evt)) From ae88141fa0c425c4a34dcd133cbc0688512c9330 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:07:18 +0200 Subject: [PATCH 24/39] Don't define unnecessary constants in public webview_ie.h There is no need to check that calling MSWSetModernEmulationLevel() changed the registry key in the test, this is just an implementation detail of this function. This makes it unnecessary to define wxIE_EMULATION_LEVEL and wxREGISTRY_IE_PATH (which are both badly named) in the public header. Finally, improve the error message in MSWSetModernEmulationLevel() and add another one for failing to reset the emulation level too. --- include/wx/msw/webview_ie.h | 8 ++---- src/msw/webview_ie.cpp | 50 ++++++++++++++++++++++++------------- tests/controls/webtest.cpp | 41 +++++++++++++++++++++--------- 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/include/wx/msw/webview_ie.h b/include/wx/msw/webview_ie.h index edd2926f8f..1c9d154bae 100644 --- a/include/wx/msw/webview_ie.h +++ b/include/wx/msw/webview_ie.h @@ -36,11 +36,6 @@ class DocHostUIHandler; class wxFindPointers; class wxIInternetProtocol; -#define wxIE_EMULATION_LEVEL 8000 - -//Registry key where emulation level for programs are set -#define wxREGISTRY_IE_PATH wxT("SOFTWARE\\Microsoft\\Internet Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION") - class WXDLLIMPEXP_WEBVIEW wxWebViewIE : public wxWebView { public: @@ -149,7 +144,8 @@ public: void onActiveXEvent(wxActiveXEvent& evt); void onEraseBg(wxEraseEvent&) {} - //Establish EmulationLevel for RunScript IE + // Establish sufficiently modern emulation level for the browser control to + // allow RunScript() to return any kind of values. static bool MSWSetModernEmulationLevel(bool modernLevel = true); wxDECLARE_EVENT_TABLE(); diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index 2234f51012..46c26c3d21 100644 --- a/src/msw/webview_ie.cpp +++ b/src/msw/webview_ie.cpp @@ -857,26 +857,40 @@ wxString wxWebViewIE::GetPageText() const bool wxWebViewIE::MSWSetModernEmulationLevel(bool modernLevel) { - wxRegKey key(wxRegKey::HKCU, wxREGISTRY_IE_PATH); - if ( key.Exists() ) + // Registry key where emulation level for programs are set + static const wxChar* IE_EMULATION_KEY = + wxT("SOFTWARE\\Microsoft\\Internet Explorer\\Main") + wxT("\\FeatureControl\\FEATURE_BROWSER_EMULATION"); + + wxRegKey key(wxRegKey::HKCU, IE_EMULATION_KEY); + if ( !key.Exists() ) { - wxString programName = wxGetFullModuleName().AfterLast('\\'); - if ( modernLevel ) - { - if ( !key.SetValue(programName, wxIE_EMULATION_LEVEL) ) - { - wxLogWarning(_("Failed to set the current browser control emulation level")); - return false; - } - } - else - { - key.DeleteValue(programName); - } - return true; + wxLogWarning(_("Failed to find web view emulation level in the registry")); + return false; } - wxLogWarning(_("Failed to find current browser control emulation level")); - return false; + + const wxString programName = wxGetFullModuleName().AfterLast('\\'); + if ( modernLevel ) + { + // IE8 (8000) is sufficiently modern for our needs, see + // https://msdn.microsoft.com/library/ee330730.aspx#browser_emulation + // for other values that could be used here. + if ( !key.SetValue(programName, 8000) ) + { + wxLogWarning(_("Failed to set web view to modern emulation level")); + return false; + } + } + else + { + if ( !key.DeleteValue(programName) ) + { + wxLogWarning(_("Failed to reset web view to standard emulation level")); + return false; + } + } + + return true; } static diff --git a/tests/controls/webtest.cpp b/tests/controls/webtest.cpp index e492ad0170..eeecb2ec08 100644 --- a/tests/controls/webtest.cpp +++ b/tests/controls/webtest.cpp @@ -23,8 +23,7 @@ #include "wx/webview.h" #include "asserthelper.h" #if wxUSE_WEBVIEW_IE -#include "wx/msw/registry.h" -#include "wx/msw/webview_ie.h" + #include "wx/msw/webview_ie.h" #endif class WebTestCase : public CppUnit::TestCase @@ -276,11 +275,32 @@ void WebTestCase::RunScript() wxString result; #if wxUSE_WEBVIEW_IE CPPUNIT_ASSERT(wxWebViewIE::MSWSetModernEmulationLevel()); - wxRegKey key(wxRegKey::HKCU, wxREGISTRY_IE_PATH); - long val = 0; - wxString programName = wxGetFullModuleName().AfterLast('\\'); - key.QueryValue(programName, &val); - CPPUNIT_ASSERT_EQUAL(val, wxIE_EMULATION_LEVEL); + + // Define a specialized scope guard ensuring that we reset the emulation + // level to its default value even if any asserts below fail. + class ResetEmulationLevel + { + public: + ResetEmulationLevel() + { + m_reset = true; + } + + bool DoReset() + { + m_reset = false; + return wxWebViewIE::MSWSetModernEmulationLevel(false); + } + + ~ResetEmulationLevel() + { + if ( m_reset ) + DoReset(); + } + + private: + bool m_reset; + } resetEmulationLevel; CPPUNIT_ASSERT(m_browser->RunScript("function f(){var person = new Object();person.name = 'Bar'; \ person.lastName = 'Foo';return person;}f();", &result)); @@ -294,11 +314,8 @@ void WebTestCase::RunScript() &result)); CPPUNIT_ASSERT_EQUAL("\"2017-10-08T21:30:40.000Z\"", result); - CPPUNIT_ASSERT(wxWebViewIE::MSWSetModernEmulationLevel(false)); - val = 0; - key.QueryValue(programName, &val); - CPPUNIT_ASSERT_EQUAL(val, 0); -#endif + CPPUNIT_ASSERT(resetEmulationLevel.DoReset()); +#endif // wxUSE_WEBVIEW_IE CPPUNIT_ASSERT(m_browser->RunScript("document.write(\"Hello World!\");")); CPPUNIT_ASSERT_EQUAL("Hello World!", m_browser->GetPageText()); From b22e36124581b0ea1244f2eee52e0f4f0c744283 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:16:49 +0200 Subject: [PATCH 25/39] Restore explicit conversion to UTF-8 in wxGTK wxWebView code This was somehow removed by af8f7f33c3eab7d4688266596d06b2a82089ac62 but is needed: implicit conversion uses the current locale encoding, which is often, but _not_ always, UTF-8, while we always need to use UTF-8 with WebKit functions. --- src/gtk/webview_webkit2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gtk/webview_webkit2.cpp b/src/gtk/webview_webkit2.cpp index ff5076fffc..b7c03b4651 100644 --- a/src/gtk/webview_webkit2.cpp +++ b/src/gtk/webview_webkit2.cpp @@ -1119,7 +1119,7 @@ bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output { GAsyncResult *result = NULL; webkit_web_view_run_javascript(m_web_view, - javascript, + javascript.utf8_str(), NULL, wxgtk_run_javascript_cb, &result); From 148c590017475556d9787e16e1917fff0c9ac0a8 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:21:14 +0200 Subject: [PATCH 26/39] Get rid of unnecessary "explicit" in wxJSScriptWrapper This ctor has more than one argument and so can't be used implicitly. --- include/wx/private/jsscriptwrapper.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index a4c3ccf035..1f61fb52a3 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -19,8 +19,7 @@ class wxJSScriptWrapper { public: - - explicit wxJSScriptWrapper(const wxString& js, int* runScriptCount) : m_jsscript(js) + wxJSScriptWrapper(const wxString& js, int* runScriptCount) : m_jsscript(js) { // __wx$counter is used to have a different variable on every // RunScript call, to not lose variable values between calls From c45e674fa05ac9ee5a829a9d3b9980e1383f660b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:24:53 +0200 Subject: [PATCH 27/39] Don't use dollar sign in C++ identifiers in wxJSScriptWrapper This is a non-standard extension in the first place and the variable name didn't make any sense too, so rename it to be standard-conforming and actually correspond to what it contains. --- include/wx/private/jsscriptwrapper.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 1f61fb52a3..50180e1293 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -21,9 +21,9 @@ class wxJSScriptWrapper public: wxJSScriptWrapper(const wxString& js, int* runScriptCount) : m_jsscript(js) { - // __wx$counter is used to have a different variable on every + // __outputVarName is used to have a different variable on every // RunScript call, to not lose variable values between calls - m_wx$counter = wxString::Format("__wx$%i", (*runScriptCount)++); + m_outputVarName = wxString::Format("__wx$%i", (*runScriptCount)++); } // This method is used to add a double quote level into a JavasSript code @@ -36,7 +36,7 @@ public: escapeDoubleQuotes.Replace(&m_jsscript,"\\1\\1\\\\\\2"); return wxString::Format("try { var %s = eval(\"%s\"); true; } \ - catch (e) { e.name + \": \" + e.message; }", m_wx$counter, m_jsscript);; + catch (e) { e.name + \": \" + e.message; }", m_outputVarName, m_jsscript);; } const wxString GetOutputCode() @@ -48,7 +48,7 @@ public: 'undefined'; \ else \ %s;", - m_wx$counter, m_wx$counter, m_wx$counter, m_wx$counter); + m_outputVarName, m_outputVarName, m_outputVarName, m_outputVarName); #elif wxUSE_WEBVIEW && wxUSE_WEBVIEW_IE return wxString::Format("try {(%s == null || typeof %s != 'object') ? String(%s) : JSON.stringify(%s);} \ catch (e) { \ @@ -119,25 +119,25 @@ public: __wx$stringifyJSON(%s); \ } \ catch (e) { e.name + \": \" + e.message; }}", - m_wx$counter, m_wx$counter, m_wx$counter, m_wx$counter, m_wx$counter); + m_outputVarName, m_outputVarName, m_outputVarName, m_outputVarName, m_outputVarName); #else - return m_wx$counter; + return m_outputVarName; #endif } const wxString GetCleanUpCode() { - return wxString::Format("%s = undefined;", m_wx$counter); + return wxString::Format("%s = undefined;", m_outputVarName); } const wxString GetOutputJSVariable() { - return m_wx$counter; + return m_outputVarName; } private: wxString m_jsscript; - wxString m_wx$counter; + wxString m_outputVarName; wxDECLARE_NO_COPY_CLASS(wxJSScriptWrapper); }; From 71522731c6452c6dab6166b884d680fea9d5e7ae Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:26:36 +0200 Subject: [PATCH 28/39] Fix typos in wxJSScriptWrapper comments No real changes. --- include/wx/private/jsscriptwrapper.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 50180e1293..399d6c3ec5 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -26,12 +26,12 @@ public: m_outputVarName = wxString::Format("__wx$%i", (*runScriptCount)++); } - // This method is used to add a double quote level into a JavasSript code + // This method is used to add a double quote level into a JavaScript code // in order to get it working when eval is called programmatically. const wxString GetWrappedCode() { // Adds one escape level if there is a single quote, double quotes or - // esacape characters + // escape characters wxRegEx escapeDoubleQuotes("(\\\\*)(['\"\n\r\v\t\b\f])"); escapeDoubleQuotes.Replace(&m_jsscript,"\\1\\1\\\\\\2"); From 1b7bb4656818c322ccc38478271ab09063ac4f33 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:30:30 +0200 Subject: [PATCH 29/39] Don't use dollar sign in the JavaScript variable name neither While it is allowed there, it is confusing and unnecessary. Also improve the comment explaining why do we need this output variable name at all and why does it need to be unique. --- include/wx/private/jsscriptwrapper.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 399d6c3ec5..367b41d19e 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -21,9 +21,14 @@ class wxJSScriptWrapper public: wxJSScriptWrapper(const wxString& js, int* runScriptCount) : m_jsscript(js) { - // __outputVarName is used to have a different variable on every - // RunScript call, to not lose variable values between calls - m_outputVarName = wxString::Format("__wx$%i", (*runScriptCount)++); + // 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("__wxOut%i", (*runScriptCount)++); } // This method is used to add a double quote level into a JavaScript code From 836c874c884dba306677c3546e9dca88b9230797 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:34:37 +0200 Subject: [PATCH 30/39] Escape JavaScript code in wxJSScriptWrapper constructor We can prepare the escaped code directly here, instead of waiting for GetWrappedCode() call, this is more clear and safer as it avoids escaping the code twice accidentally if GetWrappedCode() ends up being called twice somehow. --- include/wx/private/jsscriptwrapper.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 367b41d19e..7daf9cc927 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -20,6 +20,7 @@ class wxJSScriptWrapper { public: wxJSScriptWrapper(const wxString& js, int* runScriptCount) : m_jsscript(js) + : 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 @@ -29,19 +30,19 @@ public: // RunScript() (which creates a new wxJSScriptWrapper every time) to // avoid any possible conflict between different calls. m_outputVarName = wxString::Format("__wxOut%i", (*runScriptCount)++); + + // Adds one escape level if there is a single quote, double quotes or + // escape characters + wxRegEx escapeDoubleQuotes("(\\\\*)(['\"\n\r\v\t\b\f])"); + escapeDoubleQuotes.Replace(&m_escapedCode,"\\1\\1\\\\\\2"); } // This method is used to add a double quote level into a JavaScript code // in order to get it working when eval is called programmatically. const wxString GetWrappedCode() { - // Adds one escape level if there is a single quote, double quotes or - // escape characters - wxRegEx escapeDoubleQuotes("(\\\\*)(['\"\n\r\v\t\b\f])"); - escapeDoubleQuotes.Replace(&m_jsscript,"\\1\\1\\\\\\2"); - return wxString::Format("try { var %s = eval(\"%s\"); true; } \ - catch (e) { e.name + \": \" + e.message; }", m_outputVarName, m_jsscript);; + catch (e) { e.name + \": \" + e.message; }", m_outputVarName, m_escapedCode); } const wxString GetOutputCode() @@ -141,7 +142,7 @@ public: } private: - wxString m_jsscript; + wxString m_escapedCode; wxString m_outputVarName; wxDECLARE_NO_COPY_CLASS(wxJSScriptWrapper); From 6563f38496d61982e776e50b7b89bc912008d184 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:36:27 +0200 Subject: [PATCH 31/39] Make wxJSScriptWrapper methods const Do not mark as const their returned values, which is useless, and seems to have been a result of some confusion about how "const" works in C++. --- include/wx/private/jsscriptwrapper.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 7daf9cc927..77ada41db3 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -19,7 +19,7 @@ class wxJSScriptWrapper { public: - wxJSScriptWrapper(const wxString& js, int* runScriptCount) : m_jsscript(js) + wxJSScriptWrapper(const wxString& js, int* runScriptCount) : m_escapedCode(js) { // We assign the return value of JavaScript snippet we execute to the @@ -39,13 +39,13 @@ public: // This method is used to add a double quote level into a JavaScript code // in order to get it working when eval is called programmatically. - const wxString GetWrappedCode() + wxString GetWrappedCode() const { return wxString::Format("try { var %s = eval(\"%s\"); true; } \ catch (e) { e.name + \": \" + e.message; }", m_outputVarName, m_escapedCode); } - const wxString GetOutputCode() + wxString GetOutputCode() const { #if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT && defined(__WXOSX__) return wxString::Format("if (typeof %s == 'object') \ @@ -131,12 +131,12 @@ public: #endif } - const wxString GetCleanUpCode() + wxString GetCleanUpCode() const { return wxString::Format("%s = undefined;", m_outputVarName); } - const wxString GetOutputJSVariable() + wxString GetOutputJSVariable() const { return m_outputVarName; } From eff7a2e07f674ccb326bf4727e491d7ff5c501bc Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:39:29 +0200 Subject: [PATCH 32/39] Improve wxJSScriptWrapper methods comments Explain more clearly what each of them does. --- include/wx/private/jsscriptwrapper.h | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 77ada41db3..e454cbc14d 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -13,9 +13,14 @@ #include "wx/regex.h" // ---------------------------------------------------------------------------- -// JS Script Wrapper for wxWebView +// Helper for wxWebView::RunScript() // ---------------------------------------------------------------------------- +// This class provides GetWrappedCode(), GetOutputCode() and GetCleanUpCode() +// functions that should be executed in the backend-appropriate way by each +// wxWebView implementation in order to actually execute the user-provided +// JavaScript code, retrieve its result (if it executed successfully) and +// perform the cleanup at the end. class wxJSScriptWrapper { public: @@ -37,14 +42,20 @@ public: escapeDoubleQuotes.Replace(&m_escapedCode,"\\1\\1\\\\\\2"); } - // This method is used to add a double quote level into a JavaScript code - // in order to get it working when eval is called programmatically. + // 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. + // + // Execute GetOutputCode() later to get the real output after successful + // execution of this code. wxString GetWrappedCode() const { return wxString::Format("try { var %s = eval(\"%s\"); true; } \ catch (e) { e.name + \": \" + e.message; }", m_outputVarName, 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__) @@ -131,6 +142,8 @@ public: #endif } + // Execute the code returned by this function to let the output of the code + // we executed be garbage-collected. wxString GetCleanUpCode() const { return wxString::Format("%s = undefined;", m_outputVarName); From 390752409814a271d2fdc3e7fe5f2c2a50ac6d70 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:41:48 +0200 Subject: [PATCH 33/39] Remove unused wxJSScriptWrapper::GetOutputJSVariable() method There doesn't seem to be any reason to keep it. --- include/wx/private/jsscriptwrapper.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index e454cbc14d..6300de396f 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -149,11 +149,6 @@ public: return wxString::Format("%s = undefined;", m_outputVarName); } - wxString GetOutputJSVariable() const - { - return m_outputVarName; - } - private: wxString m_escapedCode; wxString m_outputVarName; From d1c15bfe0e860baf62cda2388fd19bfe6359c2a3 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 17:58:46 +0200 Subject: [PATCH 34/39] Reformat JavaScript code in wxJSScriptWrapper No real changes, but just use string concatenation instead of line continuation backslashes and also replace hard TABs with spaces. Notice that the code is still completely unreadable as JavaScript, but this shouldn't be a problem as nobody will see it there anyhow, so it's quite enough to have it more readable in its string form in C++. --- include/wx/private/jsscriptwrapper.h | 166 ++++++++++++++------------- 1 file changed, 87 insertions(+), 79 deletions(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 6300de396f..0477df07a6 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -50,8 +50,13 @@ public: // execution of this code. wxString GetWrappedCode() const { - return wxString::Format("try { var %s = eval(\"%s\"); true; } \ - catch (e) { e.name + \": \" + e.message; }", m_outputVarName, m_escapedCode); + return wxString::Format + ( + "try { var %s = eval(\"%s\"); true; } " + "catch (e) { e.name + \": \" + e.message; }", + m_outputVarName, + m_escapedCode + ); } // Get code returning the result of the last successful execution of the @@ -59,84 +64,87 @@ public: wxString GetOutputCode() const { #if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT && defined(__WXOSX__) - return wxString::Format("if (typeof %s == 'object') \ - JSON.stringify(%s); \ - else if (typeof %s == 'undefined') \ - 'undefined'; \ - else \ - %s;", - m_outputVarName, m_outputVarName, m_outputVarName, m_outputVarName); + return wxString::Format + ( + "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("try {(%s == null || typeof %s != 'object') ? String(%s) : JSON.stringify(%s);} \ - catch (e) { \ - try \ - { \ - function __wx$stringifyJSON(obj) \ - { \ - var objElements = []; \ - 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) \ - { \ - if (number < 10) \ - return '0' + number; \ - return 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(); + '\"' \ - } \ - 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; }}", - m_outputVarName, m_outputVarName, m_outputVarName, m_outputVarName, m_outputVarName); + return wxString::Format + ( + "try {" + "(%s == null || typeof %s != 'object') ? String(%s)" + ": JSON.stringify(%s);" + "}" + "catch (e) {" + "try {" + "function __wx$stringifyJSON(obj) {" + "var objElements = [];" + "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(); + '\"'" + "}" + "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; }" + "}", + m_outputVarName, + m_outputVarName, + m_outputVarName, + m_outputVarName, + m_outputVarName + ); #else return m_outputVarName; #endif From 40b629641be3a10385a6861fe3367aae2af17d43 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 18:01:04 +0200 Subject: [PATCH 35/39] Don't define "objElements" in JavaScript code unless we need it This variable is only used when dumping object fields, so define it there both as a micro-optimization and for clarity. --- include/wx/private/jsscriptwrapper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/wx/private/jsscriptwrapper.h b/include/wx/private/jsscriptwrapper.h index 0477df07a6..7fd036302a 100644 --- a/include/wx/private/jsscriptwrapper.h +++ b/include/wx/private/jsscriptwrapper.h @@ -84,7 +84,6 @@ public: "catch (e) {" "try {" "function __wx$stringifyJSON(obj) {" - "var objElements = [];" "if (!(obj instanceof Object))" "return typeof obj === \"string\"" "? \'\"\' + obj + \'\"\'" @@ -122,6 +121,7 @@ public: "}" "return '\"' + obj.toISOString(); + '\"'" "}" + "var objElements = [];" "for (var key in obj)" "{" "if (typeof obj[key] === \"function\")" From 6644a489e8a6b03979239ac77a89c4c6d1a7f0a6 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 18:04:50 +0200 Subject: [PATCH 36/39] Test that RunScript() returns false in case of errors Testing only for successful return is insufficient, we also need to check that errors are detected. --- tests/controls/webtest.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/controls/webtest.cpp b/tests/controls/webtest.cpp index eeecb2ec08..31322f383f 100644 --- a/tests/controls/webtest.cpp +++ b/tests/controls/webtest.cpp @@ -366,6 +366,11 @@ void WebTestCase::RunScript() var tzoffset = d.getTimezoneOffset() * 60000; return new Date(d.getTime() - tzoffset);}f();", &result)); CPPUNIT_ASSERT_EQUAL("\"2016-10-08T21:30:40.000Z\"", result); + + // Check for errors too. + CPPUNIT_ASSERT(!m_browser->RunScript("syntax(error")); + CPPUNIT_ASSERT(!m_browser->RunScript("syntax(error", &result)); + CPPUNIT_ASSERT(!m_browser->RunScript("x.y.z")); } void WebTestCase::SetPage() From b82af43361497402ca6e054e69608a75fe26b380 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 18:17:56 +0200 Subject: [PATCH 37/39] Improve RunScript() and MSWSetModernEmulationLevel() documentation Try to explain things better and add a couple of examples. --- interface/wx/webview.h | 116 +++++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 34 deletions(-) diff --git a/interface/wx/webview.h b/interface/wx/webview.h index 1eea09b681..148d427360 100644 --- a/interface/wx/webview.h +++ b/interface/wx/webview.h @@ -460,47 +460,95 @@ public: virtual void Reload(wxWebViewReloadFlags flags = wxWEBVIEW_RELOAD_DEFAULT) = 0; /** - Sets IE emulation level to a modern one, changing MSW registry. It is used to enable - JSON object on Runscript(), because it wouldn't work with default emulation level. - @see RunScript() for further details. - @param modernLevel @true to set level to IE8, @false to set level to old mode. - @return @true if level can be set, @false if it is not. + Sets emulation level to more modern level. + + This function is useful to enable some minimally modern emulation level + of the system browser control used for wxWebView implementation under + MSW, rather than using the currently default, IE7-compatible, + emulation level. Currently the modern emulation level is only IE8, but + this could change in the future and shouldn't be relied on. + + Please notice that this function works by modifying the per-user part + of MSW registry, which has several implications: first, it is + sufficient to call it only once (per user) as the changes done by it + are persistent and, second, if you do not want them to be persistent, + you need to call it with @false argument explicitly. + + In particular, this function should be called to allow RunScript() to + work for JavaScript code returning arbitrary objects, which is not + supported at the default emulation level. + + This function is MSW-specific and doesn't exist under other platforms. + + See https://msdn.microsoft.com/en-us/library/ee330730#browser_emulation + for more information about browser control emulation levels. + + @param modernLevel @true to set level to a level modern enough to allow + all wxWebView features to work (currently IE8), @false to reset the + emulation level to its default, compatible value. + @return @true on success, @false on failure (a warning message is also + logged in the latter case). + @since 3.1.1 */ - bool MSWSetModernEmulationLevel(bool modernLevel = true) = 0; + bool MSWSetModernEmulationLevel(bool modernLevel = true); /** - Runs the given JavaScript. It returns true if the script was run successfully and - additionally returns the script return value in output. - It returns strings, integers, floating point numbers, booleans and objects (as JSON). - @note When using wxWEBVIEW_WEBKIT (GTK), it returns true always unless you pass - an output param to the method. In that case, it would return false. RunScript - outputs are supported on wxWEBVIEW_WEBKIT2 (GTK3). + Runs the given JavaScript code. - When using wxWEBVIEW_WEBKIT (OSX), there are two limits: - 1) JavaScript allocations greater than 10MB. - 2) JavaScript that takes longer than 10 seconds to execute. + JavaScript code is executed inside the browser control and has full + access to DOM and other browser-provided functionality. For example, + this code + @code + webview->RunScript("document.write('Hello from wxWidgets!')"); + @endcode + will replace the current page contents with the provided string. - When using wxWEBVIEW_BACKEND_IE you must wait for the current - page to finish loading before calling RunScript(). - It is compulsory to have a script tag inside HTML to run JavaScript, on MSW. + If @a output is non-null, it is filled with the result of executing + this code on success, e.g. a JavaScript value such as a string, a + number (integer or floating point), a boolean or JSON representation + for non-primitive types such as arrays and objects. For example: + @code + wxString result; + if ( webview->RunScript + ( + "document.getElementById('some_id').innderHTML", + &result + ) ) + { + ... result contains the contents of the given element ... + } + //else: the element with this ID probably doesn't exist. + @endcode - When using wxWEBVIEW_BACKEND_IE, JSON is not available in Quirks or - IE6/7 standards mode, which is unfortunately the default one for the embedded browser control, see - https://docs.microsoft.com/en-us/scripting/javascript/reference/json-object-javascript#requirements - and see here how to make a program run use "modern" modes - https://msdn.microsoft.com/en-us/library/ee330730(v=vs.85)#browser_emulation - There are two ways to get JSON working: - 1) You can use MSWModernEmulationLevel to change emulation level (recommended), for example: - @code - MSWModernEmulationLevel(); - wxString result; - browser->RunScript("some JS code that uses JSON", &result); - @endcode - 2) There is an implementation of JSON.stringify on RunScript that helps to return objects. - You don't need to change IE emulation level but it could not work for some cases. - @param javascript A wxString containing the JavaScript. - @param output wxString result pointer. It can be NULL and it is new in wxWidgets 3.1.1. + This function has a few platform-specific limitations: + + - When using WebKit v1 in wxGTK2, retrieving the result of JavaScript + execution is unsupported and this function will always return false + if @a output is non-null to indicate this. This functionality is + fully supported when using WebKit v2 or later in wxGTK3. + + - When using WebKit under macOS, code execution is limited to at most + 10MiB of memory and 10 seconds of execution time. + + - When using IE backend under MSW, scripts can only be executed when + the current page is fully loaded (i.e. @c wxEVT_WEBVIEW_LOADED event + was received). A script tag inside the page HTML is required in order + to run JavaScript. + + Also notice that under MSW converting JavaScript objects to JSON is not + supported in the default emulation mode. wxWebView implements its own + object-to-JSON conversion as a fallback for this case, however it is + not as full-featured, well-tested or performing as the implementation + of this functionality in the browser control itself, so it is + recommended to use MSWSetModernEmulationLevel() to change emulation + level to a more modern one in which JSON conversion is done by the + control itself. + + @param javascript JavaScript code to execute. + @param output Pointer to a string to be filled with the result value or + @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. */ virtual bool RunScript(const wxString& javascript, wxString* output = NULL) = 0; From 49401c960cd315c82d5391a6d66bee776bc03cca Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 22:15:34 +0200 Subject: [PATCH 38/39] Use HTTPS by default in the webview sample This allows the sample to load the initial page when built using macOS 10.11 or later SDK which enables "app transport security" (ATS), which prevents HTTP connections by default. --- samples/webview/webview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index a7376f3f92..64e3cf3842 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -66,7 +66,7 @@ class WebApp : public wxApp { public: WebApp() : - m_url("http://www.wxwidgets.org") + m_url("https://www.wxwidgets.org") { } From 46fa106b10b619846a88fb16345c576ea89c449c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 22 Oct 2017 23:02:25 +0200 Subject: [PATCH 39/39] Demonstrate using wxWebView::SetPage() in the sample This allows to verify, at least interactively, that the expected events are generated when using SetPage() too, and not only LoadPage(). --- samples/webview/webview.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/samples/webview/webview.cpp b/samples/webview/webview.cpp index 64e3cf3842..498a0fccd8 100644 --- a/samples/webview/webview.cpp +++ b/samples/webview/webview.cpp @@ -118,6 +118,7 @@ public: void OnDocumentLoaded(wxWebViewEvent& evt); void OnNewWindow(wxWebViewEvent& evt); void OnTitleChanged(wxWebViewEvent& evt); + void OnSetPage(wxCommandEvent& evt); void OnViewSourceRequest(wxCommandEvent& evt); void OnViewTextRequest(wxCommandEvent& evt); void OnToolsClicked(wxCommandEvent& evt); @@ -384,6 +385,7 @@ WebFrame::WebFrame(const wxString& url) : // Create the Tools menu m_tools_menu = new wxMenu(); wxMenuItem* print = m_tools_menu->Append(wxID_ANY , _("Print")); + wxMenuItem* setPage = m_tools_menu->Append(wxID_ANY , _("Set page text")); wxMenuItem* viewSource = m_tools_menu->Append(wxID_ANY , _("View Source")); wxMenuItem* viewText = m_tools_menu->Append(wxID_ANY, _("View Text")); m_tools_menu->AppendSeparator(); @@ -515,6 +517,8 @@ WebFrame::WebFrame(const wxString& url) : wxWebViewEventHandler(WebFrame::OnTitleChanged), NULL, this); // Connect the menu events + Connect(setPage->GetId(), wxEVT_MENU, + wxCommandEventHandler(WebFrame::OnSetPage), NULL, this ); Connect(viewSource->GetId(), wxEVT_MENU, wxCommandEventHandler(WebFrame::OnViewSourceRequest), NULL, this ); Connect(viewText->GetId(), wxEVT_MENU, @@ -894,6 +898,16 @@ void WebFrame::OnTitleChanged(wxWebViewEvent& evt) wxLogMessage("%s", "Title changed; title='" + evt.GetString() + "'"); } +void WebFrame::OnSetPage(wxCommandEvent& WXUNUSED(evt)) +{ + m_browser->SetPage + ( + "New Page" + "Created using SetPage() method.", + wxString() + ); +} + /** * Invoked when user selects the "View Source" menu item */