diff --git a/docs/changes.txt b/docs/changes.txt index f3e82d2dce..7b2980dd0c 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -105,6 +105,7 @@ All: All (GUI): +- Allow wxWebView::RunScript() return values (Jose Lorenzo, GSoC 2017). - Allow using fractional pen widths with wxGraphicsContext (Adrien Tétar). - Improve wxSVGFileDC to support more of wxDC API (Maarten Bent). - Add support for wxAuiManager and wxAuiPaneInfo to XRC (Andrea Zanellato). diff --git a/include/wx/gtk/private/webkit.h b/include/wx/gtk/private/webkit.h new file mode 100644 index 0000000000..1a1025ffc5 --- /dev/null +++ b/include/wx/gtk/private/webkit.h @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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 "wx/buffer.h" + +#include +#include + +// ---------------------------------------------------------------------------- +// 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); + } + + operator WebKitJavascriptResult *() const { return m_jsresult; } + +private: + WebKitJavascriptResult *m_jsresult; + + 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); } + + wxString ToWxString() const + { + const 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..fa61f06ca5 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 RunScriptSync(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..1c9d154bae 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; @@ -121,7 +122,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 +144,10 @@ public: void onActiveXEvent(wxActiveXEvent& evt); void onEraseBg(wxEraseEvent&) {} + // 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(); protected: 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..7fd036302a --- /dev/null +++ b/include/wx/private/jsscriptwrapper.h @@ -0,0 +1,167 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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" + +// ---------------------------------------------------------------------------- +// 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: + wxJSScriptWrapper(const wxString& js, int* runScriptCount) + : m_escapedCode(js) + { + // We assign the return value of JavaScript snippet we execute to the + // variable with this name in order to be able to access it later if + // needed. + // + // Note that we use a different name for it for each call to + // RunScript() (which creates a new wxJSScriptWrapper every time) to + // avoid any possible conflict between different calls. + m_outputVarName = wxString::Format("__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"); + } + + // 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__) + 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) {" + "if (!(obj instanceof Object))" + "return typeof obj === \"string\"" + "? \'\"\' + obj + \'\"\'" + ": \'\' + obj;" + "else if (obj instanceof Array) {" + "if (obj[0] === undefined)" + "return \'[]\';" + "else {" + "var arr = [];" + "for (var i = 0; i < obj.length; i++)" + "arr.push(__wx$stringifyJSON(obj[i]));" + "return \'[\' + arr + \']\';" + "}" + "}" + "else if (typeof obj === \"object\") {" + "if (obj instanceof Date) {" + "if (!Date.prototype.toISOString) {" + "(function() {" + "function pad(number) {" + "return number < 10" + "? '0' + number" + ": number;" + "}" + "Date.prototype.toISOString = function() {" + "return this.getUTCFullYear() +" + "'-' + pad(this.getUTCMonth() + 1) +" + "'-' + pad(this.getUTCDate()) +" + "'T' + pad(this.getUTCHours()) +" + "':' + pad(this.getUTCMinutes()) +" + "':' + pad(this.getUTCSeconds()) +" + "'.' + (this.getUTCMilliseconds() / 1000)" + ".toFixed(3).slice(2, 5) + 'Z\"';" + "};" + "}());" + "}" + "return '\"' + obj.toISOString(); + '\"'" + "}" + "var objElements = [];" + "for (var key in obj)" + "{" + "if (typeof obj[key] === \"function\")" + "return \'{}\';" + "else {" + "objElements.push(\'\"\'" + "+ key + \'\":\' +" + "__wx$stringifyJSON(obj[key]));" + "}" + "}" + "return \'{\' + objElements + \'}\';" + "}" + "}" + "__wx$stringifyJSON(%s);" + "}" + "catch (e) { e.name + \": \" + e.message; }" + "}", + m_outputVarName, + m_outputVarName, + m_outputVarName, + m_outputVarName, + m_outputVarName + ); +#else + return m_outputVarName; +#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); + } + +private: + wxString m_escapedCode; + wxString m_outputVarName; + + 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..148d427360 100644 --- a/interface/wx/webview.h +++ b/interface/wx/webview.h @@ -460,11 +460,98 @@ 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 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 */ - virtual void RunScript(const wxString& javascript) = 0; + bool MSWSetModernEmulationLevel(bool modernLevel = true); + + /** + Runs the given JavaScript code. + + 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. + + 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 + + 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; /** 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..498a0fccd8 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("https://www.wxwidgets.org") { } @@ -115,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); @@ -133,7 +137,23 @@ 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); + 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; @@ -199,6 +235,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 @@ -346,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(); @@ -393,7 +433,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(); @@ -460,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, @@ -502,8 +561,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, @@ -811,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 */ @@ -969,13 +1066,118 @@ void WebFrame::OnHistory(wxCommandEvent& evt) m_browser->LoadHistoryItem(m_histMenuItems[evt.GetId()]); } -void WebFrame::OnRunScript(wxCommandEvent& WXUNUSED(evt)) +void WebFrame::RunScript(const wxString& javascript) { - wxTextEntryDialog dialog(this, "Enter JavaScript to run.", wxGetTextFromUserPromptStr, "", wxOK|wxCANCEL|wxCENTRE|wxTE_MULTILINE); - if(dialog.ShowModal() == wxID_OK) + // 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; + + wxLogMessage("Running JavaScript:\n%s\n", javascript); + + wxString result; + if ( m_browser->RunScript(javascript, &result) ) { - m_browser->RunScript(dialog.GetValue()); + wxLogMessage("RunScript() returned \"%s\"", result); } + else + { + wxLogWarning("RunScript() failed"); + } +} + +void WebFrame::OnRunScriptString(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("function f(a){return a;}f('Hello World!');"); +} + +void WebFrame::OnRunScriptInteger(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("function f(a){return a;}f(123);"); +} + +void WebFrame::OnRunScriptDouble(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("function f(a){return a;}f(2.34);"); +} + +void WebFrame::OnRunScriptBool(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("function f(a){return a;}f(false);"); +} + +void WebFrame::OnRunScriptObject(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("function f(){var person = new Object();person.name = 'Foo'; \ + person.lastName = 'Bar';return person;}f();"); +} + +void WebFrame::OnRunScriptArray(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("function f(){ return [\"foo\", \"bar\"]; }f();"); +} + +void WebFrame::OnRunScriptDOM(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("document.write(\"Hello World!\");"); +} + +void WebFrame::OnRunScriptUndefined(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("function f(){var person = new Object();}f();"); +} + +void WebFrame::OnRunScriptNull(wxCommandEvent& WXUNUSED(evt)) +{ + RunScript("function f(){return null;}f();"); +} + +void WebFrame::OnRunScriptDate(wxCommandEvent& WXUNUSED(evt)) +{ + 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();"); +} + +#if wxUSE_WEBVIEW_IE +void WebFrame::OnRunScriptObjectWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) +{ + wxWebViewIE::MSWSetModernEmulationLevel(); + RunScript("function f(){var person = new Object();person.name = 'Foo'; \ + person.lastName = 'Bar';return person;}f();"); + wxWebViewIE::MSWSetModernEmulationLevel(false); +} + +void WebFrame::OnRunScriptDateWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) +{ + wxWebViewIE::MSWSetModernEmulationLevel(); + 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();"); + wxWebViewIE::MSWSetModernEmulationLevel(false); +} + +void WebFrame::OnRunScriptArrayWithEmulationLevel(wxCommandEvent& WXUNUSED(evt)) +{ + wxWebViewIE::MSWSetModernEmulationLevel(); + RunScript("function f(){ return [\"foo\", \"bar\"]; }f();"); + wxWebViewIE::MSWSetModernEmulationLevel(false); +} +#endif + +void WebFrame::OnRunScriptCustom(wxCommandEvent& WXUNUSED(evt)) +{ + 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)) diff --git a/src/gtk/webview_webkit.cpp b/src/gtk/webview_webkit.cpp index bc45beb1bc..ea0eb0d967 100644 --- a/src/gtk/webview_webkit.cpp +++ b/src/gtk/webview_webkit.cpp @@ -949,10 +949,21 @@ wxString wxWebViewWebKit::GetPageText() const wxConvUTF8); } -void wxWebViewWebKit::RunScript(const wxString& javascript) +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")); + 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 16f82336b0..c6ae1914da 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 @@ -1098,13 +1104,112 @@ wxString wxWebViewWebKit::GetPageText() const return wxString(); } -void wxWebViewWebKit::RunScript(const wxString& javascript) +extern "C" { + +static void wxgtk_run_javascript_cb(GObject *, + GAsyncResult *res, + void *user_data) +{ + g_object_ref(res); + + GAsyncResult** res_out = static_cast(user_data); + *res_out = res; +} + +} // extern "C" + +// 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.mb_str(wxConvUTF8), + javascript.utf8_str(), NULL, - NULL, - NULL); + wxgtk_run_javascript_cb, + &result); + + GMainContext *main_context = g_main_context_get_thread_default(); + + while ( !result ) + g_main_context_iteration(main_context, TRUE); + + wxGtkError error; + wxWebKitJavascriptResult js_result + ( + webkit_web_view_run_javascript_finish + ( + m_web_view, + result, + error.Out() + ) + ); + + // Match g_object_ref() in wxgtk_run_javascript_cb() + g_object_unref(result); + + if ( !js_result ) + { + if ( output ) + *output = 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 ) + { + if ( output ) + { + wxJSStringRef ex_value(JSValueToStringCopy(context, exception, NULL)); + *output = ex_value.ToWxString(); + } + + return false; + } + + if ( output != NULL ) + *output = js_value.ToWxString(); + + return true; +} + +bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) +{ + wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); + + // This string is also used as an error indicator: it's cleared if there is + // no error or used in the warning message below if there is one. + wxString result; + if ( RunScriptSync(wrapJS.GetWrappedCode(), &result) + && result == wxS("true") ) + { + if ( RunScriptSync(wrapJS.GetOutputCode(), &result) ) + { + if ( output ) + *output = result; + result.clear(); + } + + RunScriptSync(wrapJS.GetCleanUpCode()); + } + + if ( !result.empty() ) + { + wxLogWarning(_("Error running JavaScript: %s"), result); + return false; + } + + return true; } void wxWebViewWebKit::RegisterHandler(wxSharedPtr handler) diff --git a/src/msw/webview_ie.cpp b/src/msw/webview_ie.cpp index 386e080765..46c26c3d21 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 @@ -853,25 +855,103 @@ wxString wxWebViewIE::GetPageText() const } } -void wxWebViewIE::RunScript(const wxString& javascript) +bool wxWebViewIE::MSWSetModernEmulationLevel(bool modernLevel) { - wxCOMPtr document(GetDocument()); + // 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"); - if(document) + wxRegKey key(wxRegKey::HKCU, IE_EMULATION_KEY); + if ( !key.Exists() ) { - wxCOMPtr window; - wxString language = "javascript"; - HRESULT hr = document->get_parentWindow(&window); - if(SUCCEEDED(hr)) + wxLogWarning(_("Failed to find web view emulation level in the registry")); + 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) ) { - VARIANT level; - VariantInit(&level); - V_VT(&level) = VT_EMPTY; - window->execScript(wxBasicString(javascript), - wxBasicString(language), - &level); + 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 +bool CallEval(const wxString& code, + wxAutomationObject& scriptAO, + wxVariant* varResult) +{ + wxVariant varCode(code); + return scriptAO.Invoke("eval", DISPATCH_METHOD, *varResult, 1, &varCode); +} + +bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) +{ + wxCOMPtr document(GetDocument()); + 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 object")); + return false; + } + + wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); + + wxAutomationObject scriptAO(scriptDispatch); + wxVariant varResult; + + wxString err; + if ( !CallEval(wrapJS.GetWrappedCode(), scriptAO, &varResult) ) + { + err = _("failed to evaluate"); + } + else if ( varResult.IsType("bool") && varResult.GetBool() ) + { + if ( output != NULL ) + { + if ( CallEval(wrapJS.GetOutputCode(), scriptAO, &varResult) ) + *output = varResult.MakeString(); + else + err = _("failed to retrieve execution result"); + } + + CallEval(wrapJS.GetCleanUpCode(), scriptAO, &varResult); + } + else // result available but not the expected "true" + { + err = varResult.MakeString(); + } + + 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 134b64d999..13b76e0626 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" @@ -408,13 +409,51 @@ 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 calling RunScript()") ); - [[m_webView windowScriptObject] evaluateWebScript: - wxCFStringRef( javascript ).AsNSString()]; + wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); + + NSString* result = [m_webView stringByEvaluatingJavaScriptFromString: + wxCFStringRef( wrapJS.GetWrappedCode() ).AsNSString()]; + + 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()]; + + [m_webView stringByEvaluatingJavaScriptFromString: + wxCFStringRef( wrapJS.GetCleanUpCode() ).AsNSString()]; + + if ( output != NULL ) + { + if ( result ) + *output = wxCFStringRef::AsString(result); + else + err = _("failed to retrieve execution result"); + } + } + else // result available but not the expected "true" + { + err = wxCFStringRef::AsString(result); + } + + if ( !err.empty() ) + { + wxLogWarning(_("Error running JavaScript: %s"), err); + return false; + } + + return true; } void wxWebViewWebKit::OnSize(wxSizeEvent &event) diff --git a/tests/controls/webtest.cpp b/tests/controls/webtest.cpp index 83a8f31c87..b6742e663b 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,9 @@ #include "wx/uiaction.h" #include "wx/webview.h" #include "asserthelper.h" +#if wxUSE_WEBVIEW_IE + #include "wx/msw/webview_ie.h" +#endif class WebTestCase : public CppUnit::TestCase { @@ -36,8 +39,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 +72,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 +82,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 +268,109 @@ 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()); + + // 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)); + 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(resetEmulationLevel.DoReset()); +#endif // wxUSE_WEBVIEW_IE + + 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); + + 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); + + // 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() @@ -277,4 +384,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 71e489e1e0..8bbaf4be70 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;