Merge branch 'webview_runscript_improvements' of https://github.com/TcT2k/wxWidgets

Add WebView::RunScriptAsync() for running scripts asynchronously.

See https://github.com/wxWidgets/wxWidgets/pull/2316
This commit is contained in:
Vadim Zeitlin
2021-11-16 17:30:55 +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&);