Merge commit '63f83c096ccf7b16ed96d60e56d08caacaaa6771'

This commit is contained in:
2021-11-29 14:55:52 +01:00
13 changed files with 390 additions and 324 deletions

View File

@@ -27,6 +27,8 @@ typedef struct _WebKitWebView WebKitWebView;
// wxWebViewWebKit
//-----------------------------------------------------------------------------
class wxWebKitRunScriptParams;
class WXDLLIMPEXP_WEBVIEW wxWebViewWebKit : public wxWebView
{
public:
@@ -119,13 +121,15 @@ public:
virtual wxString GetSelectedSource() const wxOVERRIDE;
virtual void ClearSelection() wxOVERRIDE;
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE;
#if wxUSE_WEBVIEW_WEBKIT2
virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const wxOVERRIDE;
virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool AddUserScript(const wxString& javascript,
wxWebViewUserScriptInjectionTime injectionTime = wxWEBVIEW_INJECT_AT_DOCUMENT_START) wxOVERRIDE;
virtual void RemoveAllUserScripts() wxOVERRIDE;
#else
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE;
#endif
//Virtual Filesystem Support
@@ -151,6 +155,11 @@ public:
//create-web-view signal and so we need to send a new window event
bool m_creating;
#if wxUSE_WEBVIEW_WEBKIT2
// This method needs to be public to make it callable from a callback
void ProcessJavaScriptResult(GAsyncResult *res, wxWebKitRunScriptParams* params) const;
#endif
protected:
virtual void DoSetPage(const wxString& html, const wxString& baseUrl) wxOVERRIDE;
@@ -173,7 +182,6 @@ private:
bool CanExecuteEditingCommand(const gchar* command) const;
void SetupWebExtensionServer();
GDBusProxy *GetExtensionProxy() const;
bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const;
#endif
WebKitWebView *m_web_view;

View File

@@ -88,7 +88,7 @@ public:
virtual bool SetUserAgent(const wxString& userAgent) wxOVERRIDE;
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE;
virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const wxOVERRIDE;
virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool AddUserScript(const wxString& javascript,
@@ -113,8 +113,6 @@ private:
void OnTopLevelParentIconized(wxIconizeEvent& event);
bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const;
wxDECLARE_DYNAMIC_CLASS(wxWebViewEdge);
};

View File

@@ -94,7 +94,7 @@ public:
virtual void SetEditable(bool enable = true) wxOVERRIDE;
virtual bool IsEditable() const wxOVERRIDE;
bool RunScript(const wxString& javascript, wxString* output = NULL) const wxOVERRIDE;
virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const wxOVERRIDE;
virtual bool AddScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool RemoveScriptMessageHandler(const wxString& name) wxOVERRIDE;
virtual bool AddUserScript(const wxString& javascript,
@@ -118,8 +118,6 @@ private:
WX_NSObject m_navigationDelegate;
WX_NSObject m_UIDelegate;
bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const;
};
class WXDLLIMPEXP_WEBVIEW wxWebViewFactoryWebKit : public wxWebViewFactory

View File

@@ -24,18 +24,17 @@
class wxJSScriptWrapper
{
public:
wxJSScriptWrapper(const wxString& js, int* runScriptCount)
: m_escapedCode(js)
enum OutputType
{
// 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(wxASCII_STR("__wxOut%i"), (*runScriptCount)++);
JS_OUTPUT_STRING, // All return types are converted to a string
JS_OUTPUT_WEBKIT, // Some return types will be processed
JS_OUTPUT_IE, // Most return types will be processed
JS_OUTPUT_RAW // The return types is returned as is
};
wxJSScriptWrapper(const wxString& js, OutputType outputType)
: m_escapedCode(js), m_outputType(outputType)
{
// Adds one escape level.
const char *charsNeededToBeEscaped = "\\\"\n\r\v\t\b\f";
for (
@@ -69,126 +68,139 @@ public:
}
}
// 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.
// Get the code to execute, its returned value will be either the value,
// if it executed successfully, or the exception message prefixed with
// "__wxexc:" if an error occurred.
//
// Execute GetOutputCode() later to get the real output after successful
// execution of this code.
// Either use SetOutput() to specify the script result or access it directly
// Using GetOutputRef()
//
// Execute ExtractOutput() later to get the real output after successful
// execution of this code or the proper error message.
wxString GetWrappedCode() const
{
return wxString::Format
(
wxASCII_STR("try { var %s = eval(\"%s\"); true; } "
"catch (e) { e.name + \": \" + e.message; }"),
m_outputVarName,
m_escapedCode
);
}
wxString code = wxString::Format(
wxASCII_STR("(function () { try { var res = eval(\"%s\"); "),
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
(
wxASCII_STR("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
(
wxASCII_STR("try {"
"(%s == null || typeof %s != 'object') ? String(%s)"
": JSON.stringify(%s);"
"}"
"catch (e) {"
switch (m_outputType)
{
case JS_OUTPUT_STRING:
code += wxASCII_STR(
"if (typeof res == 'object') return JSON.stringify(res);"
"else if (typeof res == 'undefined') return 'undefined';"
"else return String(res);"
);
break;
case JS_OUTPUT_WEBKIT:
code += wxASCII_STR(
"if (typeof res == 'object') return JSON.stringify(res);"
"else if (typeof res == 'undefined') return 'undefined';"
"else return res;"
);
break;
case JS_OUTPUT_IE:
code += wxASCII_STR(
"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);"
"return (res == null || typeof res != 'object') ? String(res)"
": JSON.stringify(res);"
"}"
"catch (e) { e.name + \": \" + e.message; }"
"}"),
m_outputVarName,
m_outputVarName,
m_outputVarName,
m_outputVarName,
m_outputVarName
);
#else
return m_outputVarName;
#endif
"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 + \'}\';"
"}"
"}"
"return __wx$stringifyJSON(res);"
"}"
"catch (e) { return \"__wxexc:\" + e.name + \": \" + e.message; }"
"}");
break;
case JS_OUTPUT_RAW:
code += wxASCII_STR("return res;");
break;
}
code +=
wxASCII_STR("} catch (e) { return \"__wxexc:\" + e.name + \": \" + e.message; }"
"})()");
return code;
}
const wxString& GetUnwrappedOutputCode() { return m_outputVarName; }
// Execute the code returned by this function to let the output of the code
// we executed be garbage-collected.
wxString GetCleanUpCode() const
// Extract the output value
//
// Returns true if executed successfully
// string of the result will be put into output
// Returns false when an exception occurred
// string will be the exception message
static bool ExtractOutput(const wxString& result, wxString* output)
{
return wxString::Format(wxASCII_STR("%s = undefined;"), m_outputVarName);
if (output)
*output = result;
if (result.starts_with(wxASCII_STR("__wxexc:")))
{
if (output)
output->Remove(0, 8);
return false;
}
else
{
return true;
}
}
private:
wxString m_escapedCode;
wxString m_outputVarName;
OutputType m_outputType;
wxDECLARE_NO_COPY_CLASS(wxJSScriptWrapper);
};

View File

@@ -138,7 +138,7 @@ public:
wxWebView()
{
m_showMenu = true;
m_runScriptCount = 0;
m_syncScriptResult = 0;
}
virtual ~wxWebView() {}
@@ -191,7 +191,8 @@ public:
virtual wxString GetUserAgent() const;
// Script
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const = 0;
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const;
virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const;
virtual bool AddScriptMessageHandler(const wxString& name)
{ wxUnusedVar(name); return false; }
virtual bool RemoveScriptMessageHandler(const wxString& name)
@@ -267,15 +268,16 @@ protected:
bool QueryCommandEnabled(const wxString& command) const;
void ExecCommand(const wxString& command);
// Count the number of calls to RunScript() in order to prevent
// the_same variable from being used twice in more than one call.
mutable int m_runScriptCount;
void SendScriptResult(void* clientData, bool success,
const wxString& output) const;
private:
static void InitFactoryMap();
static wxStringWebViewFactoryMap::iterator FindFactory(const wxString &backend);
bool m_showMenu;
mutable int m_syncScriptResult;
mutable wxString m_syncScriptOutput;
wxString m_findText;
static wxStringWebViewFactoryMap m_factoryMap;
@@ -294,6 +296,7 @@ public:
m_actionFlags(flags), m_messageHandler(messageHandler)
{}
bool IsError() const { return GetInt() == 0; }
const wxString& GetURL() const { return m_url; }
const wxString& GetTarget() const { return m_target; }
@@ -319,6 +322,7 @@ wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_NEWWINDOW, wxWebVie
wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_TITLE_CHANGED, wxWebViewEvent );
wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_FULLSCREEN_CHANGED, wxWebViewEvent);
wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, wxWebViewEvent);
wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_WEBVIEW, wxEVT_WEBVIEW_SCRIPT_RESULT, wxWebViewEvent);
typedef void (wxEvtHandler::*wxWebViewEventFunction)
(wxWebViewEvent&);

View File

@@ -485,6 +485,10 @@ public:
Process a @c wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED event
only available in wxWidgets 3.1.5 or later. For usage details see
AddScriptMessageHandler().
@event{wxEVT_WEBVIEW_SCRIPT_RESULT(id, func)}
Process a @c wxEVT_WEBVIEW_SCRIPT_RESULT event
only available in wxWidgets 3.1.6 or later. For usage details see
RunScriptAsync().
@endEventTable
@since 2.9.3
@@ -710,6 +714,11 @@ public:
/**
Runs the given JavaScript code.
@note Because of various potential issues it's recommended to use
RunScriptAsync() instead of this method. This is especially true
if you plan to run code from a webview event and will also prevent
unintended side effects on the UI outside of the webview.
JavaScript code is executed inside the browser control and has full
access to DOM and other browser-provided functionality. For example,
this code
@@ -764,9 +773,32 @@ public:
@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.
@see RunScriptAsync()
*/
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const = 0;
/**
Runs the given JavaScript code asynchronously and returns the result
via a @c wxEVT_WEBVIEW_SCRIPT_RESULT.
The script result value can be retrieved via wxWebViewEvent::GetString().
If the execution fails wxWebViewEvent::IsError() will return @true. In this
case additional script execution error information maybe available
via wxWebViewEvent::GetString().
@param javascript JavaScript code to execute.
@param clientData Arbirary pointer to data that can be retrieved from
the result event.
@note The IE backend does not support async script execution.
@since 3.1.6
@see RunScript()
*/
virtual void RunScriptAsync(const wxString& javascript, void* clientData = NULL) const;
/**
Add a script message handler with the given name.
@@ -1279,6 +1311,10 @@ public:
Process a @c wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED event
only available in wxWidgets 3.1.5 or later. For usage details see
wxWebView::AddScriptMessageHandler().
@event{wxEVT_WEBVIEW_SCRIPT_RESULT(id, func)}
Process a @c wxEVT_WEBVIEW_SCRIPT_RESULT event
only available in wxWidgets 3.1.6 or later. For usage details see
wxWebView::RunScriptAsync().
@endEventTable
@since 2.9.3
@@ -1323,6 +1359,14 @@ public:
@since 3.1.5
*/
const wxString& GetMessageHandler() const;
/**
Returns true the script execution failed. Only valid for events of type
@c wxEVT_WEBVIEW_SCRIPT_RESULT
@since 3.1.6
*/
bool IsError() const;
};

View File

@@ -122,6 +122,7 @@ public:
void OnTitleChanged(wxWebViewEvent& evt);
void OnFullScreenChanged(wxWebViewEvent& evt);
void OnScriptMessage(wxWebViewEvent& evt);
void OnScriptResult(wxWebViewEvent& evt);
void OnSetPage(wxCommandEvent& evt);
void OnViewSourceRequest(wxCommandEvent& evt);
void OnViewTextRequest(wxCommandEvent& evt);
@@ -159,6 +160,7 @@ public:
void OnRunScriptArrayWithEmulationLevel(wxCommandEvent& evt);
#endif
void OnRunScriptMessage(wxCommandEvent& evt);
void OnRunScriptAsync(wxCommandEvent& evt);
void OnRunScriptCustom(wxCommandEvent& evt);
void OnAddUserScript(wxCommandEvent& evt);
void OnSetCustomUserAgent(wxCommandEvent& evt);
@@ -233,6 +235,7 @@ private:
#endif
wxMenuItem* m_script_message;
wxMenuItem* m_script_custom;
wxMenuItem* m_script_async;
wxMenuItem* m_selection_clear;
wxMenuItem* m_selection_delete;
wxMenuItem* m_find;
@@ -492,6 +495,7 @@ WebFrame::WebFrame(const wxString& url) :
m_script_array_el = script_menu->Append(wxID_ANY, "Return array changing emulation level");
}
#endif
m_script_async = script_menu->Append(wxID_ANY, "Return String async");
m_script_message = script_menu->Append(wxID_ANY, "Send script message");
m_script_custom = script_menu->Append(wxID_ANY, "Custom script");
m_tools_menu->AppendSubMenu(script_menu, _("Run Script"));
@@ -551,6 +555,7 @@ WebFrame::WebFrame(const wxString& url) :
Bind(wxEVT_WEBVIEW_TITLE_CHANGED, &WebFrame::OnTitleChanged, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_FULLSCREEN_CHANGED, &WebFrame::OnFullScreenChanged, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebFrame::OnScriptMessage, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_SCRIPT_RESULT, &WebFrame::OnScriptResult, this, m_browser->GetId());
// Connect the menu events
Bind(wxEVT_MENU, &WebFrame::OnSetPage, this, setPage->GetId());
@@ -596,6 +601,7 @@ WebFrame::WebFrame(const wxString& url) :
#endif
Bind(wxEVT_MENU, &WebFrame::OnRunScriptMessage, this, m_script_message->GetId());
Bind(wxEVT_MENU, &WebFrame::OnRunScriptCustom, this, m_script_custom->GetId());
Bind(wxEVT_MENU, &WebFrame::OnRunScriptAsync, this, m_script_async->GetId());
Bind(wxEVT_MENU, &WebFrame::OnAddUserScript, this, addUserScript->GetId());
Bind(wxEVT_MENU, &WebFrame::OnSetCustomUserAgent, this, setCustomUserAgent->GetId());
Bind(wxEVT_MENU, &WebFrame::OnClearSelection, this, m_selection_clear->GetId());
@@ -924,6 +930,14 @@ void WebFrame::OnScriptMessage(wxWebViewEvent& evt)
wxLogMessage("Script message received; value = %s, handler = %s", evt.GetString(), evt.GetMessageHandler());
}
void WebFrame::OnScriptResult(wxWebViewEvent& evt)
{
if (evt.IsError())
wxLogError("Async script execution failed: %s", evt.GetString());
else
wxLogMessage("Async script result received; value = %s", evt.GetString());
}
void WebFrame::OnSetPage(wxCommandEvent& WXUNUSED(evt))
{
m_browser->SetPage
@@ -1199,6 +1213,11 @@ void WebFrame::OnRunScriptMessage(wxCommandEvent& WXUNUSED(evt))
RunScript("window.wx.postMessage('This is a web message');");
}
void WebFrame::OnRunScriptAsync(wxCommandEvent& WXUNUSED(evt))
{
m_browser->RunScriptAsync("function f(a){return a;}f('Hello World!');");
}
void WebFrame::OnRunScriptCustom(wxCommandEvent& WXUNUSED(evt))
{
wxTextEntryDialog dialog

View File

@@ -50,6 +50,7 @@ wxDEFINE_EVENT( wxEVT_WEBVIEW_NEWWINDOW, wxWebViewEvent );
wxDEFINE_EVENT( wxEVT_WEBVIEW_TITLE_CHANGED, wxWebViewEvent );
wxDEFINE_EVENT( wxEVT_WEBVIEW_FULLSCREEN_CHANGED, wxWebViewEvent);
wxDEFINE_EVENT( wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, wxWebViewEvent);
wxDEFINE_EVENT( wxEVT_WEBVIEW_SCRIPT_RESULT, wxWebViewEvent);
wxStringWebViewFactoryMap wxWebView::m_factoryMap;
@@ -224,6 +225,51 @@ wxString wxWebView::GetUserAgent() const
return userAgent;
}
bool wxWebView::RunScript(const wxString& javascript, wxString* output) const
{
m_syncScriptResult = -1;
m_syncScriptOutput.clear();
RunScriptAsync(javascript);
// Wait for script exection
while (m_syncScriptResult == -1)
wxYield();
if (m_syncScriptResult && output)
*output = m_syncScriptOutput;
return m_syncScriptResult == 1;
}
void wxWebView::RunScriptAsync(const wxString& WXUNUSED(javascript),
void* WXUNUSED(clientData)) const
{
wxLogError(_("RunScriptAsync not supported"));
}
void wxWebView::SendScriptResult(void* clientData, bool success,
const wxString& output) const
{
// If currently running sync RunScript(), don't send an event, but use
// the scripts result directly
if (m_syncScriptResult == -1)
{
if (!success)
wxLogWarning(_("Error running JavaScript: %s"), output);
m_syncScriptOutput = output;
m_syncScriptResult = success;
}
else
{
wxWebViewEvent evt(wxEVT_WEBVIEW_SCRIPT_RESULT, GetId(), "", "",
wxWEBVIEW_NAV_ACTION_NONE);
evt.SetEventObject(const_cast<wxWebView*>(this));
evt.SetClientData(clientData);
evt.SetInt(success);
evt.SetString(output);
HandleWindowEvent(evt);
}
}
// static
wxWebView* wxWebView::New(const wxString& backend)
{

View File

@@ -1231,6 +1231,13 @@ wxString wxWebViewWebKit::GetPageText() const
return wxString();
}
class wxWebKitRunScriptParams
{
public:
const wxWebViewWebKit* webKitCtrl;
void* clientData;
};
extern "C"
{
@@ -1238,80 +1245,55 @@ 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;
wxWebKitRunScriptParams* params = static_cast<wxWebKitRunScriptParams*>(user_data);
params->webKitCtrl->ProcessJavaScriptResult(res, params);
}
} // extern "C"
// Run the given script synchronously and return its result in output.
bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) const
void wxWebViewWebKit::ProcessJavaScriptResult(GAsyncResult *res, wxWebKitRunScriptParams* params) const
{
GAsyncResult *result = NULL;
webkit_web_view_run_javascript(m_web_view,
javascript.utf8_str(),
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,
res,
error.Out()
)
);
// Match g_object_ref() in wxgtk_run_javascript_cb()
g_object_unref(result);
if ( !js_result )
if ( js_result )
{
if ( output )
*output = error.GetMessage();
return false;
wxString scriptResult;
if ( wxGetStringFromJSResult(js_result, &scriptResult) )
{
wxString scriptOutput;
bool success = wxJSScriptWrapper::ExtractOutput(scriptResult, &scriptOutput);
SendScriptResult(params->clientData, success, scriptOutput);
}
}
else
SendScriptResult(params->clientData, false, error.GetMessage());
return wxGetStringFromJSResult(js_result, output);
delete params;
}
bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const
void wxWebViewWebKit::RunScriptAsync(const wxString& javascript, void* clientData) const
{
wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount);
wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING);
// 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();
}
// Collect parameters for access from the callback
wxWebKitRunScriptParams* params = new wxWebKitRunScriptParams;
params->webKitCtrl = this;
params->clientData = clientData;
RunScriptSync(wrapJS.GetCleanUpCode());
}
if ( !result.empty() )
{
wxLogWarning(_("Error running JavaScript: %s"), result);
return false;
}
return true;
webkit_web_view_run_javascript(m_web_view,
wrapJS.GetWrappedCode().utf8_str(),
NULL,
wxgtk_run_javascript_cb,
params);
}
bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name)

View File

@@ -831,74 +831,46 @@ void wxWebViewEdge::MSWSetBrowserExecutableDir(const wxString & path)
wxWebViewEdgeImpl::ms_browserExecutableDir = path;
}
bool wxWebViewEdge::RunScriptSync(const wxString& javascript, wxString* output) const
void wxWebViewEdge::RunScriptAsync(const wxString& javascript, void* clientData) const
{
bool scriptExecuted = false;
if (!m_impl->m_webView)
return false;
{
SendScriptResult(clientData, false, "");
return; // TODO: postpone execution
}
wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING);
// Start script execution
HRESULT executionResult = m_impl->m_webView->ExecuteScript(javascript.wc_str(), Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
[&scriptExecuted, &executionResult, output](HRESULT error, PCWSTR result) -> HRESULT
HRESULT executionResult = m_impl->m_webView->ExecuteScript(wrapJS.GetWrappedCode().wc_str(), Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
[this, clientData](HRESULT error, PCWSTR result) -> HRESULT
{
// Handle script execution callback
if (error == S_OK)
{
if (output)
output->assign(result);
wxString scriptDecodedResult;
// Try to decode JSON string or return original
// result if it's not a valid JSON string
if (!wxJSON::DecodeString(result, &scriptDecodedResult))
scriptDecodedResult = result;
wxString scriptExtractedOutput;
bool success = wxJSScriptWrapper::ExtractOutput(scriptDecodedResult, &scriptExtractedOutput);
SendScriptResult(clientData, success, scriptExtractedOutput);
}
else
executionResult = error;
scriptExecuted = true;
{
SendScriptResult(clientData, false, wxString::Format("%s (0x%08lx)",
wxSysErrorMsgStr(error), error));
}
return S_OK;
}).Get());
// Wait for script exection
while (!scriptExecuted)
wxYield();
if (FAILED(executionResult))
{
if (output)
output->Printf("%s (0x%08lx)", wxSysErrorMsgStr(executionResult), executionResult);
return false;
SendScriptResult(clientData, false, wxString::Format("%s (0x%08lx)",
wxSysErrorMsgStr(executionResult), executionResult));
}
else
return true;
}
bool wxWebViewEdge::RunScript(const wxString& javascript, wxString* output) const
{
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.GetUnwrappedOutputCode() + ";", &result))
{
if (output)
// Try to decode JSON string or return original
// result if it's not a valid JSON string
if (!wxJSON::DecodeString(result, output))
*output = result;
result.clear();
}
RunScriptSync(wrapJS.GetCleanUpCode());
}
if (!result.empty())
{
wxLogWarning(_("Error running JavaScript: %s"), result);
return false;
}
return true;
}
bool wxWebViewEdge::AddScriptMessageHandler(const wxString& name)

View File

@@ -1045,40 +1045,25 @@ bool wxWebViewIE::RunScript(const wxString& javascript, wxString* output) const
return false;
}
wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount);
wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_IE);
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");
}
bool success = false;
wxString scriptOutput;
if ( CallEval(wrapJS.GetWrappedCode(), scriptAO, &varResult) )
success = wxJSScriptWrapper::ExtractOutput(varResult.MakeString(), &scriptOutput);
else
scriptOutput = _("failed to evaluate");
CallEval(wrapJS.GetCleanUpCode(), scriptAO, &varResult);
}
else // result available but not the expected "true"
{
err = varResult.MakeString();
}
if (!success)
wxLogWarning(_("Error running JavaScript: %s"), scriptOutput);
if ( !err.empty() )
{
wxLogWarning(_("Error running JavaScript: %s"), varResult.MakeString());
return false;
}
if (success && output)
*output = scriptOutput;
return true;
return success;
}
void wxWebViewIE::RegisterHandler(wxSharedPtr<wxWebViewHandler> handler)

View File

@@ -371,76 +371,28 @@ bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType type) const
}
}
bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) const
void wxWebViewWebKit::RunScriptAsync(const wxString& javascript, void* clientData) const
{
__block bool scriptExecuted = false;
__block wxString outputStr;
__block bool scriptSuccess = false;
wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING);
// Start script execution
[m_webView evaluateJavaScript:wxCFStringRef(javascript).AsNSString()
[m_webView evaluateJavaScript:wxCFStringRef(wrapJS.GetWrappedCode()).AsNSString()
completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
if (error)
{
outputStr.assign(wxCFStringRef(error.localizedFailureReason).AsString());
SendScriptResult(clientData, false, wxCFStringRef(error.localizedDescription).AsString());
}
else
{
if ([obj isKindOfClass:[NSNumber class]])
{
NSNumber* num = (NSNumber*) obj;
CFTypeID numID = CFGetTypeID((__bridge CFTypeRef)(num));
if (numID == CFBooleanGetTypeID())
outputStr = num.boolValue ? "true" : "false";
else
outputStr = wxCFStringRef::AsString(num.stringValue);
}
else if (obj)
outputStr.assign(wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj]));
wxString scriptResult;
if (obj)
scriptResult = wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj]);
wxString scriptOutput;
bool success = wxJSScriptWrapper::ExtractOutput(scriptResult, &scriptOutput);
scriptSuccess = true;
SendScriptResult(clientData, success, scriptOutput);
}
scriptExecuted = true;
}];
// Wait for script exection
while (!scriptExecuted)
wxYield();
if (output)
output->assign(outputStr);
return scriptSuccess;
}
bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const
{
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;
}
bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name)

View File

@@ -35,6 +35,17 @@ public:
: m_browser(wxWebView::New()),
m_loaded(new EventCounter(m_browser, wxEVT_WEBVIEW_LOADED))
{
#ifdef __WXMSW__
if (wxWebView::IsBackendAvailable(wxWebViewBackendEdge))
{
// The blank page does not have an empty title with edge
m_blankTitle = "about:blank";
// Edge does not support about: url use a different URL instead
m_alternateHistoryURL = "about:blank";
}
else
#endif
m_alternateHistoryURL = "about:";
}
~WebViewTestCase()
@@ -53,13 +64,33 @@ protected:
if(i % 2 == 1)
m_browser->LoadURL("about:blank");
else
m_browser->LoadURL("about:");
m_browser->LoadURL(m_alternateHistoryURL);
ENSURE_LOADED;
}
}
void OnScriptResult(const wxWebViewEvent& evt)
{
m_asyncScriptResult = (evt.IsError()) ? 0 : 1;
m_asyncScriptString = evt.GetString();
}
void RunAsyncScript(const wxString& javascript)
{
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_RESULT, &WebViewTestCase::OnScriptResult, this);
m_asyncScriptResult = -1;
m_browser->RunScriptAsync(javascript);
while (m_asyncScriptResult == -1)
wxYield();
m_browser->Unbind(wxEVT_WEBVIEW_SCRIPT_RESULT, &WebViewTestCase::OnScriptResult, this);
}
wxWebView* const m_browser;
EventCounter* const m_loaded;
wxString m_blankTitle;
wxString m_alternateHistoryURL;
int m_asyncScriptResult;
wxString m_asyncScriptString;
};
TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
@@ -88,7 +119,7 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
//Test title after loading a url, we yield to let events process
LoadUrl();
CHECK(m_browser->GetCurrentTitle() == "");
CHECK(m_browser->GetCurrentTitle() == m_blankTitle);
}
SECTION("URL")
@@ -97,7 +128,7 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
//After first loading about:blank the next in the sequence is about:
LoadUrl();
CHECK(m_browser->GetCurrentURL() == "about:");
CHECK(m_browser->GetCurrentURL() == m_alternateHistoryURL);
}
SECTION("History")
@@ -336,9 +367,9 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
CHECK(m_browser->RunScript("function f(a){return a;}f(false);", &result));
CHECK(result == "false");
CHECK(m_browser->RunScript("function f(){var person = new Object();person.name = 'Foo'; \
person.lastName = 'Bar';return person;}f();", &result));
CHECK(result == "{\"name\":\"Foo\",\"lastName\":\"Bar\"}");
CHECK(m_browser->RunScript("function f(){var person = new Object();person.lastName = 'Bar'; \
person.name = 'Foo';return person;}f();", &result));
CHECK(result == "{\"lastName\":\"Bar\",\"name\":\"Foo\"}");
CHECK(m_browser->RunScript("function f(){ return [\"foo\", \"bar\"]; }f();", &result));
CHECK(result == "[\"foo\",\"bar\"]");
@@ -381,6 +412,21 @@ TEST_CASE_METHOD(WebViewTestCase, "WebView", "[wxWebView]")
CHECK(!m_browser->RunScript("x.y.z"));
}
SECTION("RunScriptAsync")
{
#ifdef __WXMSW__
// IE doesn't support async script execution
if (!wxWebView::IsBackendAvailable(wxWebViewBackendEdge))
return;
#endif
RunAsyncScript("function f(a){return a;}f('Hello World!');");
CHECK(m_asyncScriptResult == 1);
CHECK(m_asyncScriptString == "Hello World!");
RunAsyncScript("int main() { return 0; }");
CHECK(m_asyncScriptResult == 0);
}
SECTION("SetPage")
{
m_browser->SetPage("<html><body>text</body></html>", "");