Skip to content

JSB - Forward binding result to BindObjectAsync #5091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

AgapovAlexsey
Copy link
Contributor

@AgapovAlexsey AgapovAlexsey commented Apr 27, 2025

Summary:

  • Replaced kPromiseCreatorScript with native promise CefV8Value::CreatePromise()
  • Get binding reslut: const result = await cefSharp.bindObjectAsync('boundAsync2'); const boundAsync2 = result.bindResult.boundAsync2;

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Updated documentation

Checklist:

  • Tested the code(if applicable)
  • Commented my code
  • Changed the documentation(if applicable)
  • New files have a license disclaimer
  • The formatting is consistent with the project (project supports .editorconfig)

Summary by CodeRabbit

  • New Features

    • Added enhanced validation in tests to ensure bind results include both lowercase and capitalized keys: count, message, success, and bindResult.
  • Refactor

    • Simplified promise handling for asynchronous JavaScript method callbacks, using a unified promise object.
    • Streamlined the creation and binding of JavaScript objects, now returning V8 values directly from conversion methods.
    • Consolidated response object creation for binding operations, improving consistency and maintainability.
  • Style

    • Updated test files to use single-quoted string literals throughout.
  • Tests

    • Added a new test to verify the structure of bind results and improved assertions in existing tests.

Copy link

coderabbitai bot commented Apr 27, 2025

Walkthrough

This set of changes refactors the internal handling of JavaScript object binding and promise resolution within the CEF/CefSharp browser subprocess. The code now uses direct V8 promise creation and a unified promise object for asynchronous callbacks, replacing manual script evaluation and separate resolve/reject handlers. Several binding methods are renamed and refactored to return V8 values directly, streamlining the conversion of managed objects to native JavaScript objects. Helper methods are introduced to standardize the structure of bind results. The test suite is updated to use single quotes and includes a new test to validate the structure of the binding result object.

Changes

File(s) Change Summary
CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodCallback.cpp,
CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodCallback.h
Replaced separate resolve/reject handlers with a unified promise object; updated constructor and internal logic to use the promise; updated destructor accordingly.
CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodHandler.cpp Replaced manual promise creation via script evaluation with direct V8 promise creation; updated callback construction to use the promise object.
CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncObjectWrapper.cpp,
CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncObjectWrapper.h
Refactored and renamed Bind to ConvertToV8Value, changing the method to return a new V8 object instead of modifying an external one.
CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h Refactored Execute to use a single V8 promise; added static helper CreateBindResult for standardized result objects.
CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp,
CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h
Removed manual promise creator script and its usage; replaced explicit result object construction with use of the new helper method.
CefSharp.BrowserSubprocess.Core/JavascriptObjectWrapper.cpp,
CefSharp.BrowserSubprocess.Core/JavascriptObjectWrapper.h
Renamed and refactored Bind to ConvertToV8Value, returning the V8 value directly; updated interface accordingly.
CefSharp.BrowserSubprocess.Core/JavascriptPropertyWrapper.cpp Updated complex property binding to use ConvertToV8Value and set the resulting object as a property.
CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.cpp,
CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h
Changed Bind to return a V8 array of bound objects; refactored to use new conversion methods and updated parameter naming.
CefSharp.Example/Resources/BindingTestAsync.js Replaced double quotes with single quotes; added a new QUnit test to validate the structure of the binding result object.

Sequence Diagram(s)

sequenceDiagram
    participant JS as JavaScript
    participant Handler as JavascriptAsyncMethodHandler
    participant Callback as JavascriptAsyncMethodCallback
    participant V8 as V8 Engine

    JS->>Handler: Call async method
    Handler->>V8: CreatePromise()
    Handler->>Callback: Construct with promise
    Handler->>JS: Return promise
    Callback-->>V8: On success/failure, call ResolvePromise/RejectPromise
    V8-->>JS: Promise resolved or rejected
Loading

Poem

In the warren of code, a promise was made,
No more scripts for bunnies to trade!
With unified vows, resolve and reject,
Our bindings are simpler, as you’d expect.
Now tests hop along with a single quote cheer,
And rabbits in code-land have nothing to fear!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodCallback.cpp (2)

16-26: Use RAII to guarantee Exit() even on unexpected exceptions

_context->Exit() is placed in a finally block which is fine in C++/CLI, however we still repeat the Enter()/Exit() dance in two methods and rely on the developer to remember to wrap new call-sites the same way.
A small RAII helper (e.g. ContextScope ctx(_context);) that calls Enter() in the ctor and Exit() in the dtor would:

  • remove the duplicated if (_context && _context->Enter()) … Exit() pattern
  • ensure Exit() is invoked even if a new return is inserted before the finally
  • make the code less error-prone for future contributors

No functional change now, but worth considering before more async callbacks are added.


31-41: Factor common logic between Success and Fail

Both branches only differ by the ResolvePromise/RejectPromise call yet repeat context handling and null checks.
Extracting a private templated helper that accepts a lambda ([](auto p){ p->ResolvePromise(val); }) will improve readability and keep the two public methods trivially thin.

CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncObjectWrapper.cpp (2)

21-23: Remove unused variable objectName

objectName is computed but never used after the refactor, leaving dead code and an unnecessary UTF-16→UTF-8 conversion.

- auto objectName = StringUtils::ToNative(object->JavascriptName);
🧰 Tools
🪛 Cppcheck (2.10-2)

[error] 21-21: syntax error

(syntaxError)


18-33: Consider early-out when object->Methods is empty

ConvertToV8Value allocates and registers bookkeeping objects even when no methods exist, leading to an empty V8 object and an unnecessary entry in _wrappedMethods.
A guard clause could skip wrapper allocation in this corner-case.

if(object->Methods->Count == 0)
{
    return javascriptObject; // nothing to bind
}
🧰 Tools
🪛 Cppcheck (2.10-2)

[error] 21-21: syntax error

(syntaxError)

CefSharp.Example/Resources/BindingTestAsync.js (1)

292-309: Simplify key-existence assertions

Using Object.getOwnPropertyDescriptors is verbose and yields descriptor objects that are then coerced to booleans.
assert.ok('count' in bindResult) (or hasOwnProperty) makes intent clearer and works for both data & accessor properties.

-const keys = Object.getOwnPropertyDescriptors(bindResult);
-assert.equal(true, !!keys['count'], 'count');
+assert.ok('count' in bindResult, 'count');

This also avoids false negatives if a property is inherited in a future refactor.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5d0cb9 and 3b3fcd9.

📒 Files selected for processing (14)
  • CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodCallback.cpp (2 hunks)
  • CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodCallback.h (1 hunks)
  • CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodHandler.cpp (1 hunks)
  • CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncObjectWrapper.cpp (2 hunks)
  • CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncObjectWrapper.h (1 hunks)
  • CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h (4 hunks)
  • CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp (1 hunks)
  • CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h (0 hunks)
  • CefSharp.BrowserSubprocess.Core/JavascriptObjectWrapper.cpp (2 hunks)
  • CefSharp.BrowserSubprocess.Core/JavascriptObjectWrapper.h (1 hunks)
  • CefSharp.BrowserSubprocess.Core/JavascriptPropertyWrapper.cpp (1 hunks)
  • CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.cpp (2 hunks)
  • CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h (1 hunks)
  • CefSharp.Example/Resources/BindingTestAsync.js (4 hunks)
💤 Files with no reviewable changes (1)
  • CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h
🧰 Additional context used
🧬 Code Graph Analysis (2)
CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h (1)
CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodCallback.h (1)
  • JavascriptAsyncMethodCallback (34-37)
CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.cpp (1)
CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h (1)
  • Bind (106-106)
🪛 Cppcheck (2.10-2)
CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.cpp

[error] 19-19: syntax error

(syntaxError)


[error] 21-21: syntax error

(syntaxError)

🔇 Additional comments (16)
CefSharp.BrowserSubprocess.Core/JavascriptPropertyWrapper.cpp (1)

25-29: Improved object conversion and property setting approach

The modification properly leverages the new ConvertToV8Value method to obtain a V8 object representation first, then explicitly sets it as a property on the target object. This approach is more consistent with the broader refactoring of JavaScript object binding across the codebase.

CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.h (1)

106-106: Method signature enhancement to return V8 value

The method signature change from void to CefRefPtr<CefV8Value> return type aligns with the broader refactoring to standardize the conversion and binding approach. This allows the binding result to be forwarded directly, supporting the PR objective of enabling direct access to the binding result.

CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncObjectWrapper.h (1)

41-41: Method renamed and improved to return V8 value

This change is a key part of the refactoring that replaces void binding methods with methods that return V8 values. Renaming from Bind to ConvertToV8Value more accurately describes the method's purpose and enables the promised functionality of forwarding binding results to BindObjectAsync.

CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp (2)

651-652: Capture and use binding result

This change effectively captures the result from the Bind method, which is now returning a V8 value. This is essential for forwarding the binding result to client code as stated in the PR objectives.


659-664: Standardized bind result creation

Using the CreateBindResult helper method provides a more consistent structure for bind results, enabling the new syntax shown in the PR objectives: const result = await cefSharp.bindObjectAsync('boundAsync2'). This is a cleaner approach than manually constructing the result object.

CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodHandler.cpp (1)

27-30: Great improvement using native promise creation instead of script evaluation

This change elegantly replaces the previous approach of manually evaluating a JavaScript script to create promises with the more direct CefV8Value::CreatePromise() API. This is more efficient and easier to maintain since it eliminates the dependency on a string-based script.

The simplified callback constructor now takes a single promise object rather than separate resolve/reject callbacks, which better aligns with how promises are conceptually meant to be used.

CefSharp.BrowserSubprocess.Core/JavascriptObjectWrapper.h (1)

59-59: Good API design change from void to return value

Renaming Bind to ConvertToV8Value and changing the signature to return a CefRefPtr<CefV8Value> rather than modifying a passed-in parameter is a solid improvement. This follows functional programming principles by reducing side effects and making the method's purpose more explicit.

CefSharp.BrowserSubprocess.Core/Async/JavascriptAsyncMethodCallback.h (2)

19-24: Clean up promise handling with unified member variable

Replacing separate _resolve and _reject members with a single _promise member simplifies the code and better represents the conceptual model of promises. The constructor signature change to accept a single promise object instead of separate callbacks is a natural extension of this refactoring.


31-31: Consistent cleanup in destructor

The destructor correctly resets the new _promise member instead of the previously separate callback references, ensuring proper resource cleanup.

CefSharp.BrowserSubprocess.Core/JavascriptObjectWrapper.cpp (2)

15-27: Method signature change improves API design

The implementation now properly creates and returns a V8 object directly instead of modifying a passed reference. This is consistent with the header changes and makes the method's purpose clearer.

🧰 Tools
🪛 Cppcheck (2.10-2)

[error] 19-19: syntax error

(syntaxError)


43-45: Clean return of constructed object

The method now returns the constructed JavaScript object directly, aligning with the refactored method signature. This makes the code flow more intuitive and eliminates the side effect of modifying a passed-in parameter.

CefSharp.BrowserSubprocess.Core/JavascriptRootObjectWrapper.cpp (1)

50-52: Duplicate storage of the same V8 object

The same v8Obj is assigned to both result and global.
If the intent is to supply a lightweight “summary” object back to JS callers, sharing references is fine; otherwise consider cloning or using different property attributes.
Please make sure later mutations from user code do not inadvertently leak back into the global instance.

CefSharp.BrowserSubprocess.Core/BindObjectAsyncHandler.h (4)

132-171: Improved promise handling with native CEF promise API.

The code now uses CefV8Value::CreatePromise() to create a native promise at the beginning of the frame validity check. This replaces the previous approach that likely used a JavaScript-based promise creation mechanism. The promise is now consistently passed through the code flow and properly resolved with a structured result created by the new CreateBindResult method.

This implementation aligns with the PR objective to replace the existing kPromiseCreatorScript with native promise creation and enables passing the binding result directly to the caller.


176-176: Simplified callback creation with unified promise object.

The JavascriptAsyncMethodCallback constructor now receives the entire promise object rather than separate resolve/reject functions. This simplifies the flow and reduces potential for errors in callback handling.


192-201: Consistent error handling with standardized result object.

When objects are already bound, the code now uses the same structured response format via CreateBindResult. This ensures a consistent interface for JavaScript consumers regardless of the binding outcome path. The single return value assignment (retval = resultPromise) at the end simplifies the code flow and makes it more maintainable.


222-241: Well-designed result structure with dual-case property support.

The new CreateBindResult method creates a standardized response object with both PascalCase and camelCase property names. This is an excellent practice for JavaScript interoperability, as it accommodates different coding conventions in the consuming code. The method encapsulates the creation logic, making it more maintainable and ensuring consistency across different response paths.

The properties included (Count, Message, Success, and BindResult) provide a comprehensive status report of the binding operation, which improves the developer experience.

Comment on lines +19 to 21
auto result = CefV8Value::CreateArray(objects->Count);
if (objects->Count > 0)
{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Returning an array whose numeric indices are unset may be misleading

result is created with objects->Count but only named properties are populated.
Callers iterating over for(let i=0;i<result.length;i++) will receive undefined values.

Two alternatives:

  1. Use CefV8Value::CreateObject instead of an array since consumers access objects by name (result.boundAsync2).
  2. Or, additionally set the numeric indices while building the array.
- auto result = CefV8Value::CreateArray(objects->Count);
+ auto result = CefV8Value::CreateObject(nullptr, nullptr);

Aligning the structure with actual usage avoids surprises.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto result = CefV8Value::CreateArray(objects->Count);
if (objects->Count > 0)
{
auto result = CefV8Value::CreateObject(nullptr, nullptr);
if (objects->Count > 0)
{
🧰 Tools
🪛 Cppcheck (2.10-2)

[error] 19-19: syntax error

(syntaxError)


[error] 21-21: syntax error

(syntaxError)

@AppVeyorBot
Copy link

@@ -2,209 +2,209 @@ QUnit.module('BindingTestAsync', (hooks) =>
{
hooks.before(async () =>
{
await CefSharp.BindObjectAsync("boundAsync");
await CefSharp.BindObjectAsync('boundAsync');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert the styling change, it creates too much noise and doesn't actually provide a functional change.

Makes it very hard to review what's actually changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry. I did it out of habit.

@amaitland
Copy link
Member

Firstly, thanks for the PR 👍 Will be a few days before I can review in detail.

  • Get binding reslut: const result = await cefSharp.bindObjectAsync('boundAsync2'); const boundAsync2 = result.bindResult.boundAsync2;

What's the use case for this?

@AgapovAlexsey
Copy link
Contributor Author

What's the use case for this?

I don't have any special case. It's a request from colleagues. Because it seems more logical: you call bindObjectAsync and get the object you need, instead of looking for it somewhere in the window.

@amaitland
Copy link
Member

Because it seems more logical: you call bindObjectAsync and get the object you need, instead of looking for it somewhere in the window.

I think a new method that creates a instance of the object without adding to the global scope would be preferable.

@AgapovAlexsey
Copy link
Contributor Author

@amaitland
I believe introducing a new method is unnecessary.
Instead, we can add a parameter to bindObjectAsync (similar to NotifyIfAlreadyBound).
This approach would make it easier for developers in the future — if we decide to completely drop binding to window, minimal code changes will be required.

Initially, I considered adding a parameter to explicitly control whether binding to window should be used.
However, I noticed that binding to window is also involved in the caching mechanism.
As a result, if we remove window binding, we will also need to revisit and update the caching logic accordingly.

@amaitland
Copy link
Member

I believe introducing a new method is unnecessary.

This is my preference, I believe this will make for a better user experience. Much easier to document and provide examples for.

Instead, we can add a parameter to bindObjectAsync (similar to NotifyIfAlreadyBound).

For me a new API with an explicit behavior would be preferable. Extra parameters with different behavior I don't believe is desirable.

if we decide to completely drop binding to window, minimal code changes will be required.

Unless Chromium makes changes that make this necessary I have no plans to do this. This would be an unnecessary breaking change.

@amaitland
Copy link
Member

  • Get binding reslut: const result = await cefSharp.bindObjectAsync('boundAsync2'); const boundAsync2 = result.bindResult.boundAsync2;

I'm not a fan of adding bindResult to the response object. I'd much prefer to see a new method added that explicitly creates a new object

const boundAsync2 = await cefSharp.createObjectAsync('boundAsync2'); 

I think doing that in a separate PR makes sense as there are some considerations regarding caching.

  • Replaced kPromiseCreatorScript with native promise CefV8Value::CreatePromise()

This is an excellent improvement and I'll cherry pick this change out of this PR. I'll try do this on the weekend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants