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
This commit is contained in:
Jose Lorenzo 2017-09-02 23:24:06 +01:00 committed by Vadim Zeitlin
parent 9f4f075034
commit af8f7f33c3
14 changed files with 757 additions and 49 deletions

View File

@ -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 <josee.loren@gmail.com>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_GTK_PRIVATE_WEBKIT_H_
#define _WX_GTK_PRIVATE_WEBKIT_H_
#include <webkit2/webkit2.h>
#include <JavaScriptCore/JSStringRef.h>
#include <wx/buffer.h>
// ----------------------------------------------------------------------------
// 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_

View File

@ -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<wxWebViewHandler> 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;

View File

@ -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<wxWebViewHandler> 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);
};

View File

@ -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<wxWebViewHandler> handler) wxOVERRIDE;

View File

@ -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 <josee.loren@gmail.com>
// 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_

View File

@ -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<wxWebViewHandler> 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);

View File

@ -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

View File

@ -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();

View File

@ -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<wxWebViewHandler> handler)

View File

@ -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 <webkit2/webkit2.h>
#include <JavaScriptCore/JSValueRef.h>
#include <JavaScriptCore/JSStringRef.h>
// ----------------------------------------------------------------------------
// 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<wxWebViewHandler> handler)

View File

@ -29,6 +29,8 @@
#include "wx/dynlib.h"
#include "wx/scopeguard.h"
#include "wx/private/jsscriptwrapper.h"
#include <initguid.h>
#include <wininet.h>
@ -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<IHTMLDocument2> document(GetDocument());
IDispatch* scriptDispatch = NULL;
if(document)
if ( !document )
{
wxCOMPtr<IHTMLWindow2> 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<wxWebViewHandler> handler)

View File

@ -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)

View File

@ -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("<html><head><script></script></head><body></body></html>", "");
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)

View File

@ -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;