Skip to content

Commit fd46a4c

Browse files
authored
Core - Add EvaluateScriptAsPromiseAsync (#3251)
- Add useImmediatelyInvokedFuncExpression param to EvaluateScriptAsync which wraps the script in an IIFE with the cefSharpInternalCallbackId variable defined in scope to allow for deferred completion - Wrap script in Immediately Invoked Function Expression and call promise.resolve to guarantee a promise. - Return 'CefSharpDefEvalScriptRes' as the result of the js function to defer execution of the callback - Call newly added cefSharp.sendEvalScriptResponse method in javascript when promise has been resolved/rejected This is built up of a few parts and can technically be used by the new user to defer the response until they are ready. Just return `'CefSharpDefEvalScriptRes'` and manually call cefSharp.sendEvalScriptResponse using the cefSharpInternalCallbackId variable which is injected into the script execution (wrapped in an IIFE to limit scope)
1 parent d0b53da commit fd46a4c

File tree

11 files changed

+231
-17
lines changed

11 files changed

+231
-17
lines changed

CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "BindObjectAsyncHandler.h"
1313
#include "JavascriptPostMessageHandler.h"
1414
#include "JavascriptRootObjectWrapper.h"
15+
#include "JavascriptPromiseHandler.h"
1516
#include "Async\JavascriptAsyncMethodCallback.h"
1617
#include "Serialization\V8Serialization.h"
1718
#include "Serialization\JsObjectsSerialization.h"
@@ -146,6 +147,7 @@ namespace CefSharp
146147
auto removeObjectFromCacheFunction = CefV8Value::CreateFunction(kRemoveObjectFromCache, new RegisterBoundObjectHandler(_javascriptObjects));
147148
auto isObjectCachedFunction = CefV8Value::CreateFunction(kIsObjectCached, new RegisterBoundObjectHandler(_javascriptObjects));
148149
auto postMessageFunction = CefV8Value::CreateFunction(kPostMessage, new JavascriptPostMessageHandler(rootObject == nullptr ? nullptr : rootObject->CallbackRegistry));
150+
auto promiseHandlerFunction = CefV8Value::CreateFunction(kSendEvalScriptResponse, new JavascriptPromiseHandler());
149151

150152
//By default We'll support both CefSharp and cefSharp, for those who prefer the JS style
151153
auto createCefSharpObj = !_jsBindingPropertyName.empty();
@@ -159,6 +161,7 @@ namespace CefSharp
159161
cefSharpObj->SetValue(kRemoveObjectFromCache, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
160162
cefSharpObj->SetValue(kIsObjectCached, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
161163
cefSharpObj->SetValue(kPostMessage, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
164+
cefSharpObj->SetValue(kSendEvalScriptResponse, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
162165
cefSharpObj->SetValue(kRenderProcessId, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
163166

164167
global->SetValue(_jsBindingPropertyName, cefSharpObj, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY);
@@ -172,6 +175,7 @@ namespace CefSharp
172175
cefSharpObjCamelCase->SetValue(kRemoveObjectFromCacheCamelCase, removeObjectFromCacheFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
173176
cefSharpObjCamelCase->SetValue(kIsObjectCachedCamelCase, isObjectCachedFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
174177
cefSharpObjCamelCase->SetValue(kPostMessageCamelCase, postMessageFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
178+
cefSharpObjCamelCase->SetValue(kSendEvalScriptResponseCamelCase, promiseHandlerFunction, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
175179
cefSharpObjCamelCase->SetValue(kRenderProcessIdCamelCase, CefV8Value::CreateInt(processId), CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_NONE);
176180

177181
global->SetValue(_jsBindingPropertyNameCamelCase, cefSharpObjCamelCase, CefV8Value::PropertyAttribute::V8_PROPERTY_ATTRIBUTE_READONLY);
@@ -376,6 +380,7 @@ namespace CefSharp
376380
//these messages are roughly handled the same way
377381
if (name == kEvaluateJavascriptRequest || name == kJavascriptCallbackRequest)
378382
{
383+
bool sendResponse = true;
379384
bool success = false;
380385
CefRefPtr<CefV8Value> result;
381386
CefString errorMessage;
@@ -433,8 +438,17 @@ namespace CefSharp
433438
//we need to do this here to be able to store the v8context
434439
if (success)
435440
{
436-
auto responseArgList = response->GetArgumentList();
437-
SerializeV8Object(result, responseArgList, 2, callbackRegistry);
441+
//If the response is a string of CefSharpDefEvalScriptRes then
442+
//we don't send the response, we'll let that happen when the promise has completed.
443+
if (result->IsString() && result->GetStringValue() == "CefSharpDefEvalScriptRes")
444+
{
445+
sendResponse = false;
446+
}
447+
else
448+
{
449+
auto responseArgList = response->GetArgumentList();
450+
SerializeV8Object(result, responseArgList, 2, callbackRegistry);
451+
}
438452
}
439453
else
440454
{
@@ -521,14 +535,17 @@ namespace CefSharp
521535
}
522536
}
523537

524-
auto responseArgList = response->GetArgumentList();
525-
responseArgList->SetBool(0, success);
526-
SetInt64(responseArgList, 1, callbackId);
527-
if (!success)
538+
if (sendResponse)
528539
{
529-
responseArgList->SetString(2, errorMessage);
540+
auto responseArgList = response->GetArgumentList();
541+
responseArgList->SetBool(0, success);
542+
SetInt64(responseArgList, 1, callbackId);
543+
if (!success)
544+
{
545+
responseArgList->SetString(2, errorMessage);
546+
}
547+
frame->SendProcessMessage(sourceProcessId, response);
530548
}
531-
frame->SendProcessMessage(sourceProcessId, response);
532549

533550
handled = true;
534551
}

CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
<ClInclude Include="BindObjectAsyncHandler.h" />
176176
<ClCompile Include="BrowserSubprocessExecutable.h" />
177177
<ClInclude Include="Cef.h" />
178+
<ClInclude Include="JavascriptPromiseHandler.h" />
178179
<ClInclude Include="SubProcessApp.h" />
179180
<ClInclude Include="Async\JavascriptAsyncMethodCallback.h" />
180181
<ClInclude Include="Async\JavascriptAsyncMethodHandler.h" />

CefSharp.BrowserSubprocess.Core/CefSharp.BrowserSubprocess.Core.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@
119119
<ClInclude Include="Cef.h">
120120
<Filter>Header Files</Filter>
121121
</ClInclude>
122+
<ClInclude Include="JavascriptPromiseHandler.h">
123+
<Filter>Header Files</Filter>
124+
</ClInclude>
122125
</ItemGroup>
123126
<ItemGroup>
124127
<ClCompile Include="AssemblyInfo.cpp">
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright © 2020 The CefSharp Authors. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
4+
5+
#pragma once
6+
7+
#include "include/cef_v8.h"
8+
#include "..\CefSharp.Core\Internals\Messaging\Messages.h"
9+
#include "..\CefSharp.Core\Internals\Serialization\Primitives.h"
10+
#include "Serialization\V8Serialization.h"
11+
12+
using namespace System;
13+
using namespace CefSharp::Internals::Messaging;
14+
using namespace CefSharp::Internals::Serialization;
15+
16+
namespace CefSharp
17+
{
18+
const CefString kSendEvalScriptResponse = CefString("SendEvalScriptResponse");
19+
const CefString kSendEvalScriptResponseCamelCase = CefString("sendEvalScriptResponse");
20+
21+
private class JavascriptPromiseHandler : public CefV8Handler
22+
{
23+
public:
24+
25+
virtual bool Execute(const CefString& name,
26+
CefRefPtr<CefV8Value> object,
27+
const CefV8ValueList& arguments,
28+
CefRefPtr<CefV8Value>& retval,
29+
CefString& exception)
30+
{
31+
if (arguments.size() < 2)
32+
{
33+
//TODO: Improve error message
34+
exception = "Must specify a callback Id and then/catch.";
35+
36+
return true;
37+
}
38+
39+
auto callbackId = arguments[0]->GetIntValue();
40+
41+
if (callbackId == 0)
42+
{
43+
exception = "Invalid callback Id";
44+
45+
return true;
46+
}
47+
48+
auto success = arguments[1]->GetBoolValue();
49+
50+
auto response = CefProcessMessage::Create(kEvaluateJavascriptResponse);
51+
52+
auto responseArgList = response->GetArgumentList();
53+
//Success
54+
responseArgList->SetBool(0, success);
55+
//Callback Id
56+
SetInt64(responseArgList, 1, callbackId);
57+
if (exception == "")
58+
{
59+
if (success)
60+
{
61+
SerializeV8Object(arguments[2], responseArgList, 2, nullptr);
62+
}
63+
else
64+
{
65+
auto reason = arguments[2];
66+
responseArgList->SetString(2, reason->GetStringValue());
67+
}
68+
}
69+
else
70+
{
71+
responseArgList->SetString(2, exception);
72+
}
73+
74+
auto context = CefV8Context::GetCurrentContext();
75+
76+
auto frame = context->GetFrame();
77+
78+
frame->SendProcessMessage(CefProcessId::PID_BROWSER, response);
79+
80+
return false;
81+
}
82+
83+
IMPLEMENT_REFCOUNTING(JavascriptPromiseHandler);
84+
};
85+
}

CefSharp.BrowserSubprocess.Core/Wrapper/Frame.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ void Frame::ExecuteJavaScriptAsync(String^ code, String^ scriptUrl, int startLin
165165
_frame->ExecuteJavaScript(StringUtils::ToNative(code), StringUtils::ToNative(scriptUrl), startLine);
166166
}
167167

168-
Task<JavascriptResponse^>^ Frame::EvaluateScriptAsync(String^ script, String^ scriptUrl, int startLine, Nullable<TimeSpan> timeout)
168+
Task<JavascriptResponse^>^ Frame::EvaluateScriptAsync(String^ script, String^ scriptUrl, int startLine, Nullable<TimeSpan> timeout, bool useImmediatelyInvokedFuncExpression)
169169
{
170170
throw gcnew NotImplementedException();
171171
}

CefSharp.BrowserSubprocess.Core/Wrapper/Frame.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ namespace CefSharp
164164
/*--cef(optional_param=script_url)--*/
165165
virtual void ExecuteJavaScriptAsync(String^ code, String^ scriptUrl, int startLine);
166166

167-
virtual Task<JavascriptResponse^>^ EvaluateScriptAsync(String^ script, String^ scriptUrl, int startLine, Nullable<TimeSpan> timeout);
167+
virtual Task<JavascriptResponse^>^ EvaluateScriptAsync(String^ script, String^ scriptUrl, int startLine, Nullable<TimeSpan> timeout, bool useImmediatelyInvokedFuncExpression);
168168

169169
///
170170
// Returns true if this is the main (top-level) frame.

CefSharp.Core/Internals/CefFrameWrapper.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ void CefFrameWrapper::ExecuteJavaScriptAsync(String^ code, String^ scriptUrl, in
225225
_frame->ExecuteJavaScript(StringUtils::ToNative(code), StringUtils::ToNative(scriptUrl), startLine);
226226
}
227227

228-
Task<JavascriptResponse^>^ CefFrameWrapper::EvaluateScriptAsync(String^ script, String^ scriptUrl, int startLine, Nullable<TimeSpan> timeout)
228+
Task<JavascriptResponse^>^ CefFrameWrapper::EvaluateScriptAsync(String^ script, String^ scriptUrl, int startLine, Nullable<TimeSpan> timeout, bool useImmediatelyInvokedFuncExpression)
229229
{
230230
ThrowIfDisposed();
231231
ThrowIfFrameInvalid();
@@ -246,6 +246,11 @@ Task<JavascriptResponse^>^ CefFrameWrapper::EvaluateScriptAsync(String^ script,
246246
//create a new taskcompletionsource
247247
auto idAndComplectionSource = pendingTaskRepository->CreatePendingTask(timeout);
248248

249+
if (useImmediatelyInvokedFuncExpression)
250+
{
251+
script = "(function() { let cefSharpInternalCallbackId = " + idAndComplectionSource.Key + "; " + script + " })();";
252+
}
253+
249254
auto message = CefProcessMessage::Create(kEvaluateJavascriptRequest);
250255
auto argList = message->GetArgumentList();
251256
SetInt64(argList, 0, idAndComplectionSource.Key);

CefSharp.Core/Internals/CefFrameWrapper.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ namespace CefSharp
165165
/*--cef(optional_param=script_url)--*/
166166
virtual void ExecuteJavaScriptAsync(String^ code, String^ scriptUrl, int startLine);
167167

168-
virtual Task<JavascriptResponse^>^ EvaluateScriptAsync(String^ script, String^ scriptUrl, int startLine, Nullable<TimeSpan> timeout);
168+
virtual Task<JavascriptResponse^>^ EvaluateScriptAsync(String^ script, String^ scriptUrl, int startLine, Nullable<TimeSpan> timeout, bool useImmediatelyInvokedFuncExpression);
169169

170170
///
171171
// Returns true if this is the main (top-level) frame.

CefSharp.Test/OffScreen/OffScreenBrowserBasicFacts.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,34 @@ public async Task CanEvaluateScriptAsyncWithEncodedStringArguments()
199199
}
200200
}
201201

202+
[Theory]
203+
[InlineData("return 42;", true, "42")]
204+
[InlineData("return new Promise(function(resolve, reject) { resolve(42); });", true, "42")]
205+
[InlineData("return new Promise(function(resolve, reject) { reject('reject test'); });", false, "reject test")]
206+
public async Task CanEvaluateScriptAsPromiseAsync(string script, bool success, string expected)
207+
{
208+
using (var browser = new ChromiumWebBrowser("http://www.google.com"))
209+
{
210+
await browser.LoadPageAsync();
211+
212+
var mainFrame = browser.GetMainFrame();
213+
Assert.True(mainFrame.IsValid);
214+
215+
var javascriptResponse = await browser.EvaluateScriptAsPromiseAsync(script);
216+
217+
Assert.Equal(success, javascriptResponse.Success);
218+
219+
if (success)
220+
{
221+
Assert.Equal(expected, javascriptResponse.Result.ToString());
222+
}
223+
else
224+
{
225+
Assert.Equal(expected, javascriptResponse.Message);
226+
}
227+
}
228+
}
229+
202230
[Fact]
203231
public async Task CanMakeFrameUrlRequest()
204232
{

CefSharp/IFrame.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,12 @@ public interface IFrame : IDisposable
124124
/// <param name="scriptUrl">is the URL where the script in question can be found, if any.</param>
125125
/// <param name="startLine">is the base line number to use for error reporting.</param>
126126
/// <param name="timeout">The timeout after which the Javascript code execution should be aborted.</param>
127+
/// <param name="useImmediatelyInvokedFuncExpression">When true the script is wrapped in a self executing function.
128+
/// Make sure to use a return statement in your javascript. e.g. (function () { return 42; })();
129+
/// When false don't include a return statement e.g. 42;
130+
/// </param>
127131
/// <returns>A Task that can be awaited to perform the script execution</returns>
128-
Task<JavascriptResponse> EvaluateScriptAsync(string script, string scriptUrl = "about:blank", int startLine = 1, TimeSpan? timeout = null);
132+
Task<JavascriptResponse> EvaluateScriptAsync(string script, string scriptUrl = "about:blank", int startLine = 1, TimeSpan? timeout = null, bool useImmediatelyInvokedFuncExpression = false);
129133

130134
/// <summary>
131135
/// Returns true if this is the main (top-level) frame.

0 commit comments

Comments
 (0)