Restructure javascript result wrapper

This commit is contained in:
Tobias Taschner
2021-04-07 12:03:27 +02:00
committed by Tobias Taschner
parent fd2920ff22
commit 2fe12ae6af
3 changed files with 146 additions and 172 deletions

View File

@@ -118,8 +118,6 @@ private:
WX_NSObject m_navigationDelegate; WX_NSObject m_navigationDelegate;
WX_NSObject m_UIDelegate; WX_NSObject m_UIDelegate;
bool RunScriptSync(const wxString& javascript, wxString* output = NULL) const;
}; };
class WXDLLIMPEXP_WEBVIEW wxWebViewFactoryWebKit : public wxWebViewFactory class WXDLLIMPEXP_WEBVIEW wxWebViewFactoryWebKit : public wxWebViewFactory

View File

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

View File

@@ -371,76 +371,41 @@ bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType type) const
} }
} }
bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) const bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const
{ {
__block bool scriptExecuted = false; wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING);
__block wxString outputStr;
__block bool scriptSuccess = false; __block int scriptResult = -1;
__block wxString result;
// Start script execution // Start script execution
[m_webView evaluateJavaScript:wxCFStringRef(javascript).AsNSString() [m_webView evaluateJavaScript:wxCFStringRef(wrapJS.GetWrappedCode()).AsNSString()
completionHandler:^(id _Nullable obj, NSError * _Nullable error) { completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
if (error) if (error)
{ {
outputStr.assign(wxCFStringRef(error.localizedFailureReason).AsString()); result.assign(wxCFStringRef(error.localizedDescription).AsString());
scriptResult = 0;
} }
else else
{ {
if ([obj isKindOfClass:[NSNumber class]]) if (obj)
{ result.assign(wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj]));
NSNumber* num = (NSNumber*) obj; scriptResult = 1;
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]));
scriptSuccess = true;
} }
scriptExecuted = true;
}]; }];
// Wait for script exection // Wait for script exection
while (!scriptExecuted) while (scriptResult == -1)
wxYield(); wxYield();
if (output) if (scriptResult == 0)
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->assign(result);
if (output)
*output = result;
result.clear();
}
RunScriptSync(wrapJS.GetCleanUpCode());
}
if (!result.empty())
{
wxLogWarning(_("Error running JavaScript: %s"), result);
return false; return false;
} }
else
return true; return wxJSScriptWrapper::ExtractOutput(result, output);
} }
bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name) bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name)