Skip to content

Commit 5dc02c2

Browse files
Merge edf59d5 into d78d01a
2 parents d78d01a + edf59d5 commit 5dc02c2

File tree

2 files changed

+652
-0
lines changed

2 files changed

+652
-0
lines changed

CefSharp.Test/Javascript/JavascriptCallbackTests.cs

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,5 +321,292 @@ public async Task ShouldWorkWhenExecutedMultipleTimes()
321321
Assert.Equal(42, callbackResponse.Result);
322322
}
323323
}
324+
325+
[Fact]
326+
public async Task ShouldHandleCallbackAfterMultipleContextChanges()
327+
{
328+
AssertInitialLoadComplete();
329+
330+
// Test that callbacks are properly cleaned up after multiple context changes
331+
var javascriptResponse1 = await Browser.EvaluateScriptAsync("(function() { return Promise.resolve(42); })");
332+
Assert.True(javascriptResponse1.Success);
333+
var callback1 = (IJavascriptCallback)javascriptResponse1.Result;
334+
335+
// Change context
336+
await Browser.LoadUrlAsync(CefExample.HelloWorldUrl);
337+
338+
var javascriptResponse2 = await Browser.EvaluateScriptAsync("(function() { return Promise.resolve(84); })");
339+
Assert.True(javascriptResponse2.Success);
340+
var callback2 = (IJavascriptCallback)javascriptResponse2.Result;
341+
342+
// Execute the new callback - should work
343+
var callbackResponse2 = await callback2.ExecuteAsync();
344+
Assert.True(callbackResponse2.Success);
345+
Assert.Equal(84, callbackResponse2.Result);
346+
347+
// Old callback should fail gracefully
348+
var callbackResponse1 = await callback1.ExecuteAsync();
349+
Assert.False(callbackResponse1.Success);
350+
Assert.Contains("Frame with Id:", callbackResponse1.Message);
351+
}
352+
353+
[Fact]
354+
public async Task ShouldProperlyCleanupCallbacksOnFrameDestruction()
355+
{
356+
using (var browser = new CefSharp.OffScreen.ChromiumWebBrowser(automaticallyCreateBrowser: false))
357+
{
358+
await browser.CreateBrowserAsync();
359+
await browser.LoadUrlAsync(CefExample.HelloWorldUrl);
360+
361+
var javascriptResponse = await browser.EvaluateScriptAsync("(function() { return Promise.resolve('test'); })");
362+
Assert.True(javascriptResponse.Success);
363+
var callback = (IJavascriptCallback)javascriptResponse.Result;
364+
var frameId = browser.GetMainFrame().Identifier;
365+
366+
// Execute callback successfully first
367+
var result1 = await callback.ExecuteAsync();
368+
Assert.True(result1.Success);
369+
Assert.Equal("test", result1.Result);
370+
371+
// Load new page to destroy frame
372+
await browser.LoadUrlAsync("about:blank");
373+
374+
// Callback should now fail with frame-specific error
375+
var result2 = await callback.ExecuteAsync();
376+
Assert.False(result2.Success);
377+
Assert.Contains($"Frame with Id:{frameId}", result2.Message);
378+
}
379+
}
380+
381+
[Fact]
382+
public async Task ShouldHandleCallbacksFromDifferentFrames()
383+
{
384+
using (var browser = new CefSharp.OffScreen.ChromiumWebBrowser(automaticallyCreateBrowser: false))
385+
{
386+
await browser.CreateBrowserAsync();
387+
388+
// Load a page with iframe
389+
await browser.LoadHtmlAsync(@"
390+
<html>
391+
<body>
392+
<h1>Main Frame</h1>
393+
<iframe id='testFrame' src='about:blank'></iframe>
394+
</body>
395+
</html>");
396+
397+
// Create callback in main frame
398+
var mainFrameResponse = await browser.EvaluateScriptAsync("(function() { return Promise.resolve('main'); })");
399+
Assert.True(mainFrameResponse.Success);
400+
var mainCallback = (IJavascriptCallback)mainFrameResponse.Result;
401+
402+
// Execute main frame callback
403+
var mainResult = await mainCallback.ExecuteAsync();
404+
Assert.True(mainResult.Success);
405+
Assert.Equal("main", mainResult.Result);
406+
}
407+
}
408+
409+
[Theory]
410+
[InlineData("(function() { return Promise.resolve(null); })", null)]
411+
[InlineData("(function() { return Promise.resolve(undefined); })", null)]
412+
public async Task ShouldHandleNullAndUndefinedCallbackResults(string script, object expected)
413+
{
414+
AssertInitialLoadComplete();
415+
416+
var javascriptResponse = await Browser.EvaluateScriptAsync(script);
417+
Assert.True(javascriptResponse.Success);
418+
419+
var callback = (IJavascriptCallback)javascriptResponse.Result;
420+
var callbackResponse = await callback.ExecuteAsync();
421+
422+
Assert.True(callbackResponse.Success);
423+
Assert.Equal(expected, callbackResponse.Result);
424+
}
425+
426+
[Fact]
427+
public async Task ShouldHandleNestedCallbackExecution()
428+
{
429+
AssertInitialLoadComplete();
430+
431+
// Create a callback that returns another function
432+
var javascriptResponse = await Browser.EvaluateScriptAsync(@"
433+
(function() {
434+
return function(x) {
435+
return Promise.resolve(x * 2);
436+
};
437+
})");
438+
Assert.True(javascriptResponse.Success);
439+
440+
var callback = (IJavascriptCallback)javascriptResponse.Result;
441+
442+
// Execute with parameter
443+
var callbackResponse = await callback.ExecuteAsync(21);
444+
Assert.True(callbackResponse.Success);
445+
Assert.Equal(42, callbackResponse.Result);
446+
}
447+
448+
[Fact]
449+
public async Task ShouldHandleCallbackExecutionWithComplexObjects()
450+
{
451+
AssertInitialLoadComplete();
452+
453+
var javascriptResponse = await Browser.EvaluateScriptAsync(@"
454+
(function(obj) {
455+
return Promise.resolve({
456+
doubled: obj.value * 2,
457+
message: 'Result: ' + obj.value
458+
});
459+
})");
460+
Assert.True(javascriptResponse.Success);
461+
462+
var callback = (IJavascriptCallback)javascriptResponse.Result;
463+
464+
var inputObj = new { value = 42 };
465+
var callbackResponse = await callback.ExecuteAsync(inputObj);
466+
467+
Assert.True(callbackResponse.Success);
468+
dynamic result = callbackResponse.Result;
469+
Assert.Equal(84, (int)result.doubled);
470+
Assert.Equal("Result: 42", (string)result.message);
471+
}
472+
473+
[Theory]
474+
[InlineData(1)]
475+
[InlineData(5)]
476+
[InlineData(10)]
477+
public async Task ShouldHandleMultipleSequentialCallbackExecutions(int executionCount)
478+
{
479+
AssertInitialLoadComplete();
480+
481+
var javascriptResponse = await Browser.EvaluateScriptAsync(@"
482+
(function(x) {
483+
return Promise.resolve(x + 1);
484+
})");
485+
Assert.True(javascriptResponse.Success);
486+
487+
var callback = (IJavascriptCallback)javascriptResponse.Result;
488+
489+
for (var i = 0; i < executionCount; i++)
490+
{
491+
var callbackResponse = await callback.ExecuteAsync(i);
492+
Assert.True(callbackResponse.Success);
493+
Assert.Equal(i + 1, callbackResponse.Result);
494+
}
495+
}
496+
497+
[Fact]
498+
public async Task ShouldHandleCallbackWithLongRunningOperation()
499+
{
500+
AssertInitialLoadComplete();
501+
502+
var javascriptResponse = await Browser.EvaluateScriptAsync(@"
503+
(function() {
504+
return new Promise(resolve => {
505+
setTimeout(() => resolve('completed'), 2000);
506+
});
507+
})");
508+
Assert.True(javascriptResponse.Success);
509+
510+
var callback = (IJavascriptCallback)javascriptResponse.Result;
511+
512+
var callbackResponse = await callback.ExecuteAsync();
513+
Assert.True(callbackResponse.Success);
514+
Assert.Equal("completed", callbackResponse.Result);
515+
}
516+
517+
[Fact]
518+
public async Task ShouldHandleCallbackErrorsGracefully()
519+
{
520+
AssertInitialLoadComplete();
521+
522+
var javascriptResponse = await Browser.EvaluateScriptAsync(@"
523+
(function() {
524+
return Promise.reject(new Error('Custom error message'));
525+
})");
526+
Assert.True(javascriptResponse.Success);
527+
528+
var callback = (IJavascriptCallback)javascriptResponse.Result;
529+
var callbackResponse = await callback.ExecuteAsync();
530+
531+
Assert.False(callbackResponse.Success);
532+
Assert.Contains("Custom error message", callbackResponse.Message);
533+
}
534+
535+
[Fact]
536+
public async Task ShouldVerifyCallbackRegistryCleanup()
537+
{
538+
// Test that callbacks are properly cleaned up when context is released
539+
using (var browser = new CefSharp.OffScreen.ChromiumWebBrowser(automaticallyCreateBrowser: false))
540+
{
541+
await browser.CreateBrowserAsync();
542+
await browser.LoadUrlAsync(CefExample.HelloWorldUrl);
543+
544+
var callbacks = new List<IJavascriptCallback>();
545+
546+
// Create multiple callbacks
547+
for (int i = 0; i < 5; i++)
548+
{
549+
var response = await browser.EvaluateScriptAsync($"(function() {{ return Promise.resolve({i}); }})");
550+
Assert.True(response.Success);
551+
callbacks.Add((IJavascriptCallback)response.Result);
552+
}
553+
554+
// Verify all callbacks work
555+
for (int i = 0; i < callbacks.Count; i++)
556+
{
557+
var result = await callbacks[i].ExecuteAsync();
558+
Assert.True(result.Success);
559+
Assert.Equal(i, result.Result);
560+
}
561+
562+
// Destroy context
563+
await browser.LoadUrlAsync("about:blank");
564+
565+
// Verify all callbacks are now invalid
566+
foreach (var callback in callbacks)
567+
{
568+
var result = await callback.ExecuteAsync();
569+
Assert.False(result.Success);
570+
}
571+
}
572+
}
573+
574+
[Fact]
575+
public async Task ShouldHandleCallbackWithArrayParameter()
576+
{
577+
AssertInitialLoadComplete();
578+
579+
var javascriptResponse = await Browser.EvaluateScriptAsync(@"
580+
(function(arr) {
581+
return Promise.resolve(arr.reduce((a, b) => a + b, 0));
582+
})");
583+
Assert.True(javascriptResponse.Success);
584+
585+
var callback = (IJavascriptCallback)javascriptResponse.Result;
586+
var callbackResponse = await callback.ExecuteAsync(new[] { 1, 2, 3, 4, 5 });
587+
588+
Assert.True(callbackResponse.Success);
589+
Assert.Equal(15, callbackResponse.Result);
590+
}
591+
592+
[Fact]
593+
public async Task ShouldHandleCallbackReturningArray()
594+
{
595+
AssertInitialLoadComplete();
596+
597+
var javascriptResponse = await Browser.EvaluateScriptAsync(@"
598+
(function() {
599+
return Promise.resolve([1, 2, 3, 4, 5]);
600+
})");
601+
Assert.True(javascriptResponse.Success);
602+
603+
var callback = (IJavascriptCallback)javascriptResponse.Result;
604+
var callbackResponse = await callback.ExecuteAsync();
605+
606+
Assert.True(callbackResponse.Success);
607+
var resultArray = callbackResponse.Result as object[];
608+
Assert.NotNull(resultArray);
609+
Assert.Equal(5, resultArray.Length);
610+
}
324611
}
325612
}

0 commit comments

Comments
 (0)