Merge branch 'webview-js-retval'

Integrate GSoC 2017 work by Jose Lorenzo on allowing returning values
from JavaScript code via wxWebView::RunScript().
This commit is contained in:
Vadim Zeitlin 2017-11-04 14:33:42 +01:00
commit 7e0b6d4d81
15 changed files with 931 additions and 49 deletions

View File

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

View File

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

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 RunScriptSync(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;
@ -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<wxWebViewHandler> 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:

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,167 @@
///////////////////////////////////////////////////////////////////////////////
// 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"
// ----------------------------------------------------------------------------
// 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_

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

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("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
(
"<html><title>New Page</title>"
"<body>Created using <tt>SetPage()</tt> method.</body></html>",
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))

View File

@ -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<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
@ -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<GAsyncResult**>(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<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>
@ -853,25 +855,103 @@ wxString wxWebViewIE::GetPageText() const
}
}
void wxWebViewIE::RunScript(const wxString& javascript)
bool wxWebViewIE::MSWSetModernEmulationLevel(bool modernLevel)
{
wxCOMPtr<IHTMLDocument2> 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<IHTMLWindow2> 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<IHTMLDocument2> 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<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"
@ -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)

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

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;