Skip to content

Commit 37e9d70

Browse files
authored
Merge pull request #2231 from MicrosoftEdge/ExecuteScriptWithResult-draft
Create ExecuteScriptWithResult.md
2 parents 342866d + a4b5bd3 commit 37e9d70

File tree

1 file changed

+329
-0
lines changed

1 file changed

+329
-0
lines changed

specs/ExecuteScriptWithResult.md

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
# Background
2+
Our end developers have pointed out gaps in the existing [CoreWebView2.ExecuteScript](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.1150.38#executescript) method, and it is necessary
3+
to provide a new method to let our end developers get more information in a more convenient manner.
4+
The new ExecuteScriptWithResult method will provide exception information if the executed script
5+
failed, and provides a new method to try to get the script execution result as a string rather than as JSON
6+
in order to make it more convenient to interact with string results.
7+
8+
In this document we describe the updated API. We'd appreciate your feedback.
9+
10+
# Description
11+
We propose extending `CoreWebView2` to provide an `ExecuteScriptWithResult`
12+
method. The method acts like ExecuteScript, but returns a CoreWebView2ExecuteScriptResult object that can be used to
13+
get the script execution result as a JSON string or as a string value if execution succeeds, and can be used to get the exception when
14+
execution failed.
15+
16+
# Examples
17+
The following code snippets demonstrate how the ExecuteScriptWithResult can be used:
18+
## Win32 C++
19+
``` cpp
20+
// Tools function to generate the script code
21+
// Using std::wstringstream to generate script code,
22+
// it will generate the code like
23+
// '(() => { let str = "abc"; let n = str.replace("b", "d"); return n; })();'
24+
std::wstring GenerateScriptCode(LPCWSTR str, LPCWSTR reg, LPCWSTR item)
25+
{
26+
if (str == nullptr || reg == nullptr || item == nullptr)
27+
{
28+
return L"";
29+
}
30+
31+
std::wstringstream sw;
32+
33+
sw << L"(() => { let str = \"" << str << L"\"; let n = str.replace("
34+
<< reg << L", \"" << item << L"\"); return n; })();";
35+
36+
return sw.str();
37+
}
38+
39+
// This is a demo that uses regular expressions in
40+
// JavaScript to complete string replacement, it will handle
41+
// the case of successful execution and execution exception
42+
void MatchRegWithScript(wil::com_ptr<ICoreWebView2> webView
43+
, LPCWSTR str
44+
, LPCWSTR reg
45+
, LPCWSTR item)
46+
{
47+
wil::com_ptr<ICoreWebView2_10> webview2 = webView.try_query<ICoreWebView2_10>();
48+
if (!webview2)
49+
{
50+
// ExecuteScriptWithResult is not supported by this WebView.
51+
return;
52+
}
53+
54+
auto scriptCode = GenerateScriptCode(str, reg, item);
55+
webview2->ExecuteScriptWithResult(
56+
scriptCode.c_str(),
57+
Callback<ICoreWebView2ExecuteScriptWithResultCompletedHandler>(
58+
[](
59+
HRESULT errorCode, ICoreWebView2ExecuteScriptResult* result) -> HRESULT
60+
{
61+
// There is usually no failure here, if the assertion fails,
62+
// the runtime environment has an exception and needs to fail fast.
63+
CHECK_FAILURE(errorCode);
64+
65+
wil::unique_cotaskmem_string stringData;
66+
67+
BOOL isSuccess;
68+
result->get_Succeeded(&isSuccess);
69+
// Here is the successful execution.
70+
// We will use a MessageBox to print the replaced result.
71+
if (isSuccess)
72+
{
73+
// We try to use `TryGetResultAsString` to get the string result here.
74+
// We can check the `isString` to get if the result is string type.
75+
// Since the JavaScript platform's `string.replace` returns a string,
76+
// the call here will succeed.
77+
// If the script is replaced by `string.search`, the function will
78+
// return an int and the call will fail here.
79+
BOOL isString;
80+
if (result->TryGetResultAsString(&stringData, &isString) != S_OK || !isString)
81+
{
82+
MessageBox(
83+
nullptr, L"Get string failed", L"ExecuteScript Result", MB_OK);
84+
}
85+
else
86+
{
87+
MessageBox(nullptr, stringData.get(), L"ExecuteScript Result",
88+
MB_OK);
89+
}
90+
}
91+
// Here is the case of execution exception.
92+
// We will use MessageBox to print exception-related information
93+
else
94+
{
95+
wil::com_ptr<ICoreWebView2ScriptException> exception;
96+
97+
result->get_Exception(&exception);
98+
99+
// The ExceptionName property could be the empty string if script throws a non-Error object,
100+
// such as `throw 1`.
101+
wil::unique_cotaskmem_string exceptionName;
102+
exception->get_Name(&exceptionName);
103+
104+
// The ExceptionMessage property could be the empty string if script throws a non-Error object,
105+
// such as `throw 1`.
106+
wil::unique_cotaskmem_string exceptionMessage;
107+
exception->get_Message(&exceptionMessage)
108+
109+
// Get the location of the exception, note that the coordinates
110+
// here are 0 as the starting position.
111+
uint32_t lineNumber = 0;
112+
uint32_t columnNumber = 0;
113+
exception->get_LineNumber(&lineNumber);
114+
exception->get_ColumnNumber(&columnNumber);
115+
116+
auto exceptionInfo =
117+
L"The script execution failed." +
118+
L"\nName: " + exceptionName.get() +
119+
L"\nMessage: " + exceptionMessage.get() +
120+
L"\nLineNumber: " + std::to_wstring(lineNumber) +
121+
L", ColumnNumber:" + std::to_wstring(columnNumber);
122+
MessageBox(
123+
nullptr, exceptionInfo.c_str(),
124+
L"ExecuteScript Result", MB_OK);
125+
}
126+
127+
return S_OK;
128+
})
129+
.Get());
130+
}
131+
132+
```
133+
## .NET and WinRT
134+
```c#
135+
class ExecuteScriptWithResultDemo
136+
{
137+
private String GenerateScriptCode(String str, String reg, String item)
138+
{
139+
String ret = $"(() => {{ let str = \"{str}\"; let n = str.replace({reg}, \"{item}\"); return n; }})();";
140+
return ret;
141+
}
142+
143+
// This is a demo that uses regular expressions in
144+
// JavaScript to complete string replacement, it will handle
145+
// the case of successful execution and execution exception
146+
public void MatchRegWithScript(String str, String reg, String item)
147+
{
148+
String script = GenerateScriptCode(str, reg, item);
149+
CoreWebView2ExecuteScriptResult result = await ExecuteScriptWithResultAsync(script);
150+
151+
bool isSuccess = result.Succeeded;
152+
// Here is the successful execution.
153+
if (isSuccess) {
154+
// Try to get the string result, it will return 0
155+
// if the result type isn't string type.
156+
String stringResult;
157+
if (result.TryGetResultAsString(stringResult) != 0)
158+
{
159+
Debug.WriteLine($"replaced string: {stringResult}");
160+
}
161+
else
162+
{
163+
Debug.WriteLine($"Non-string message received");
164+
}
165+
}
166+
// Here is the case of execution exception.
167+
else
168+
{
169+
var exception = result.Exception;
170+
String exceptionInfo = "The script execution failed." +
171+
"\nName:" + exception.Name +
172+
"\nMessage: " + exception.Message +
173+
"\nLineNumber:" + exception.LineNumber +
174+
", ColumnNumber:" + exception.ColumnNumber;
175+
Debug.WriteLine($"{exceptionInfo}");
176+
}
177+
}
178+
}
179+
```
180+
181+
# API Details
182+
The spec file for `ExecuteScript` is [WebView2Feedback/specs/ExecuteScript.md](https://github.com/MicrosoftEdge/WebView2Feedback/blob/ca99cf43fde3f536a4d3981d53b8345edb0c9a62/specs/ExecuteScript.md).
183+
## Win32 C++
184+
```c++
185+
/// This interface represents a JavaScript exception.
186+
/// If the CoreWebView2.ExecuteScriptWithResult result has Succeeded as false,
187+
/// you can use the result's Exception property to get the script exception.
188+
[uuid(82F22B72-1B22-403E-A0B9-A8816C9C8E45), object, pointer_default(unique)]
189+
interface ICoreWebView2ScriptException : IUnknown {
190+
191+
/// The line number of the source where the exception occurred.
192+
/// In the JSON it is `exceptionDetail.lineNumber`.
193+
/// Note that this position starts at 0.
194+
[propget] HRESULT LineNumber([out, retval] UINT32* value);
195+
196+
/// The column number of the source where the exception occurred.
197+
/// In the JSON it is `exceptionDetail.columnNumber`.
198+
/// Note that this position starts at 0.
199+
[propget] HRESULT ColumnNumber([out, retval] UINT32* value);
200+
201+
/// The Name is the exception's class name.
202+
/// In the JSON it is `exceptionDetail.exception.className`.
203+
/// This is the empty string if the exception doesn't have a class name.
204+
/// This can happen if the script throws a non-Error object such as `throw "abc";`
205+
[propget] HRESULT Name([out, retval] LPWSTR* value);
206+
207+
/// The Message is the exception's message and potentially stack.
208+
/// In the JSON it is exceptionDetail.exception.description.
209+
/// This is the empty string if the exception doesn't have a description.
210+
/// This can happen if the script throws a non-Error object such as throw "abc";.
211+
[propget] HRESULT Message([out, retval] LPWSTR* value);
212+
213+
/// This will return all details of the exception as a JSON string.
214+
/// In the case that script has thrown a non-Error object such as `throw "abc";`
215+
/// or any other non-Error object, you can get object specific properties.
216+
[propget] HRESULT ToJson([out, retval] LPWSTR* value);
217+
}
218+
219+
/// This is the result for ExecuteScriptWithResult.
220+
[uuid(D2C59C5C-AD36-4CF4-87CF-2F5359F6D4CB), object, pointer_default(unique)]
221+
interface ICoreWebView2ExecuteScriptResult : IUnknown {
222+
223+
/// This property is true if ExecuteScriptWithResult successfully executed script with
224+
/// no unhandled exceptions and the result is available in the ResultAsJson property
225+
/// or via the TryGetResultAsString method.
226+
/// If it is false then the script execution had an unhandled exception which you
227+
/// can get via the Exception property.
228+
[propget] HRESULT Succeeded([out, retval] BOOL* value);
229+
230+
/// A function that has no explicit return value returns undefined. If the
231+
/// script that was run throws an unhandled exception, then the result is
232+
/// also "null". This method is applied asynchronously. If the method is
233+
/// run before `ContentLoading`, the script will not be executed
234+
/// and the string "null" will be returned.
235+
236+
/// The return value description is as follows
237+
/// 1. S_OK: Execution succeeds.
238+
/// 2. E_POINTER: When the `jsonResult` is nullptr.
239+
[propget] HRESULT ResultAsJson([out, retval] LPWSTR* jsonResult);
240+
241+
/// If Succeeded is true and the result of script execution is a string, this method provides the value of the string result,
242+
/// and we will get the `FALSE` var value when the js result is not string type.
243+
/// The return value description is as follows
244+
/// 1. S_OK: Execution succeeds.
245+
/// 2. E_POINTER: When the `stringResult` or `value` is nullptr.
246+
HRESULT TryGetResultAsString([out] LPWSTR* stringResult, [out, retval] BOOL* value);
247+
248+
/// If Succeeded is false, you can use this property to get the unhandled exception thrown by script execution
249+
/// Note that due to the compatibility of the WinRT/.NET interface,
250+
/// S_OK will be returned even if the acquisition fails.
251+
/// We can determine whether the acquisition is successful by judging whether the `exception` is nullptr.
252+
[propget] HRESULT Exception(
253+
[out, retval] ICoreWebView2ScriptException** exception);
254+
}
255+
256+
/// This is the callback for ExecuteScriptWithResult
257+
[uuid(CECDD25B-E6E8-4A4E-B890-BBF95932564F), object, pointer_default(unique)]
258+
interface ICoreWebView2ExecuteScriptWithResultCompletedHandler : IUnknown {
259+
260+
/// Provides the result of ExecuteScriptWithResult
261+
HRESULT Invoke(
262+
[in] HRESULT errorCode,
263+
[in] ICoreWebView2ExecuteScriptResult* result);
264+
}
265+
266+
/// This is the interface for getting string and exception with ExecuteScriptWithResult
267+
[uuid(67E0B57B-1AC7-4395-9793-5E4EF9C4B7D9), object, pointer_default(unique)]
268+
interface ICoreWebView2_10 : ICoreWebView2_9 {
269+
270+
/// Run JavaScript code from the JavaScript parameter in the current
271+
/// top-level document rendered in the WebView.
272+
/// The result of the execution is returned asynchronously in the CoreWebView2ExecuteScriptResult object
273+
/// which has methods and properties to obtain the successful result of script execution as well as any
274+
/// unhandled JavaScript exceptions.
275+
/// If this method is
276+
/// run after the NavigationStarting event during a navigation, the script
277+
/// runs in the new document when loading it, around the time
278+
/// ContentLoading is run. This operation executes the script even if
279+
/// ICoreWebView2Settings::IsScriptEnabled is set to FALSE.
280+
HRESULT ExecuteScriptWithResult(
281+
[in] LPCWSTR javaScript,
282+
[in] ICoreWebView2ExecuteScriptWithResultCompletedHandler* handler);
283+
}
284+
```
285+
286+
## .NET and WinRT
287+
The documentation for the old interface [CoreWebView2.ExecuteScriptAsync(String) Method](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.executescriptasync?view=webview2-dotnet-1.0.1150.38#microsoft-web-webview2-core-corewebview2-executescriptasync(system-string)) is here.
288+
```c#
289+
namespace Microsoft.Web.WebView2.Core
290+
{
291+
runtimeclass CoreWebView2;
292+
runtimeclass CoreWebView2ExecuteScriptResult;
293+
runtimeclass CoreWebView2ScriptException;
294+
295+
runtimeclass CoreWebView2
296+
{
297+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_10")]
298+
{
299+
Windows.Foundation.IAsyncOperation<CoreWebView2ExecuteScriptResult> ExecuteScriptWithResultAsync(String javaScript);
300+
}
301+
}
302+
303+
runtimeclass CoreWebView2ExecuteScriptResult
304+
{
305+
Boolean Succeeded { get; };
306+
307+
// The return value of this interface is the same as `ExecuteScript`.
308+
// You can refer to the demo and documentation of `ExecuteScript`.
309+
String ResultAsJson { get; };
310+
311+
CoreWebView2ScriptException Exception { get; };
312+
313+
Int32 TryGetResultAsString(out String stringResult);
314+
}
315+
316+
runtimeclass CoreWebView2ScriptException
317+
{
318+
UInt32 LineNumber { get; };
319+
320+
UInt32 ColumnNumber { get; };
321+
322+
String Name { get; };
323+
324+
String Message { get; };
325+
326+
String ToJson { get; };
327+
}
328+
}
329+
```

0 commit comments

Comments
 (0)