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;