Skip to content

[🚀 Feature]: [dotnet] Provide a possibility to implement BiDi Permissions module #15329

Closed

Description

@nvborisenko

Feature and motivation

https://www.w3.org/TR/permissions/

The implementation should not rely on internal members. The goal is to expose minimal required to make it possible to implement new extendable by the third-partied modules.

Usage example

var bidi = await driver.AsBiDiAsync(); // selenium responsibility

// straightforward API (not yet approved)
var permissionsModule = new PermissionsModule(bidi);

// or as extension to emphasize this is extension
var permissionsModule = bidi.AsPermissions();

await permissionsModule.QueryAsync();

Activity

  1. github-actions commented on Feb 24, 2025

    @github-actions
    Contributor

    @nvborisenko, thank you for creating this issue. We will troubleshoot it as soon as we can.


    Info for maintainers

    Triage this issue by using labels.

    If information is missing, add a helpful comment and then I-issue-template label.

    If the issue is a question, add the I-question label.

    If the issue is valid but there is no time to troubleshoot it, consider adding the help wanted label.

    If the issue requires changes or fixes from an external project (e.g., ChromeDriver, GeckoDriver, MSEdgeDriver, W3C), add the applicable G-* label, and it will provide the correct link and auto-close the issue.

    After troubleshooting the issue, please add the R-awaiting answer label.

    Thank you!

  2. nvborisenko commented on Mar 8, 2025

    @nvborisenko
    MemberAuthor

    I am thinking more about it and coming to a conclusion that "reusing" existing BiDi connection is "not possible". JsonSerializerContext is immutable, reading it as third-party module should initialize his own connection (Broker) providing described json context.

    So, I think the following will be more appreciated:

    var bidi = await driver.AsBiDiAsync(); // W3C WebDriver implementation
    
    var permissions = await driver.AsBiDiPermissionsAsync(); // It initializes new Broker/WebSocketConnection/JsonContext

    Then each bidi and permissions are isolated, and both have own "context" including everything needed to deal with remote end (still immutable). The trick is that remote end send data (responses/events) per WebSocketConnection.

    I think this is a good solution. But semantic of this solution is not related to extensibility.

  3. nvborisenko commented on Mar 8, 2025

    @nvborisenko
    MemberAuthor

    But semantic of this solution is not related to extensibility.

    And I am entirely OK with it! Permissions module can be easily implemented (defining his own DTO types), Bluetooth module too.

    Appium is a separate topic, but seems yes, we consider Appium as yet another BiDi module (with his own isolated "context").

  4. nvborisenko commented on Mar 22, 2025

    @nvborisenko
    MemberAuthor
    var bidi = await driver.AsBiDiAsync(opts => opts.WithPermissions(out var permissions));
    
    await permissions.QueryAsync();

    This way we inject JsonTypeInfoResolver, reusing entire core bidi infrastructure, and expose permissions object with methods related to Permissions only. This way also guarantees there is no performance impact even if we will have 100 attached extensions.

    Researching continued. @RenderMichael what do you think?

  5. RenderMichael commented on Mar 22, 2025

    @RenderMichael
    Contributor

    I think the configuration builder would work well, the main drawback is that it requires all of BiDi for an extension. If we want to have Permissions separate from BiDi, then we would need a separate flow.

  6. nvborisenko commented on Mar 22, 2025

    @nvborisenko
    MemberAuthor

    Trying to evaluate the statement "JsonOptions is immutable" and understood it is actually mutable. It is safe to inject TypeInfoResolver when it is already constructed.

    Pseudo flow:

    var bidi = driver.GetBidi(); // existing functionality
    
    var permissionsModule = bidi.AsPermissions(); // here it is safe to modify JsonOptions!
    
    var bluetoothModule = bidi.AsBluetooth(); // even safe, just adds one more TypeInfoResolver

    I hope it will be useful.

  7. RenderMichael commented on Mar 22, 2025

    @RenderMichael
    Contributor

    We can improve it even more with the TypeInfoResolverChain, a new feature of STJ designed to make this process simpler.

  8. nvborisenko commented on Mar 22, 2025

    @nvborisenko
    MemberAuthor

    Yes, kind of "inject TypeInfoResolver". I am more curios it helps us in general. From end user point of view this is absolutely legal operation.

  9. RenderMichael commented on Mar 23, 2025

    @RenderMichael
    Contributor

    What do we think about a builder pattern:

    var bidi = await driver.AsBiDiBuilder()
        .AddExtension(connection => new PermissionsModule(connection), out var permissions)
        .BuildAsync();

    Extension creator can provide nice extensions on BiDiBuilder so they can do whatever they need:

    public static BiDiBuilder AddPermissions(this BiDiBuilder builder, out PermissionsModule permissions)
    {
        return builder.AddExtension(connection =>
        {
            connection.AddSerializerContextAndConverters(PermissionsJsonSerializerContext.Default)
            return new PermissionsModule(connection);
        },
        out permissions);
    }
    var bidi = await driver.AsBiDiBuilder()
        .AddPermissions(out var permissions)
        .BuildAsync();
  10. RenderMichael commented on Mar 23, 2025

    @RenderMichael
    Contributor

    With the above pattern, we can do things like make the PermissionsModule constructor internal, to enforce using the extension method and guide users to use the builder instead (avoiding any of the problems with adding extensions)

  11. nvborisenko commented on Mar 30, 2025

    @nvborisenko
    MemberAuthor

    Usually builder pattern lives on top of existing functionality. I am still thinking the following is our primary way:

    var permissionsModule = bidi.AsPermissions();

    But I understand selenium should expose "something" to be public. If my thoughts are true, then I just want to make this "something" hidden from regular users. When our users type bidi. then intellisense should not promote "hidden something" (ideally).

  12. 8 remaining items

  13. nvborisenko commented on Apr 20, 2025

    @nvborisenko
    MemberAuthor

    I am still in context of Broker holds websocket, but se/deserialize json according JsonInfoType provided by the caller.

    Broker.ExecuteCommandAsync(........, _IknowHereMyTypeResolver, _IKnowHereMyResultResolver); // type info resolver goes to brooker
    

    This idea is good from performance view (nanoseconds), but might be wrong conceptually. Any caller should not know how Broker deals with DTO types at transport level. Because of today it is json, tomorrow it is binary. Let's hold this knowledge at Broker layer, so options.GetTypeResolver() is appreciated (how it is currently used).

  14. nvborisenko commented on Apr 23, 2025

    @nvborisenko
    MemberAuthor

    Solution is pretty simple: expose bidi.AsModule<T>() where T: Module(Broker).

    Why:

    • We expose only one extra user-facing method
    • This method also works with built-in modules, so we can consider all modules to be custom (even built-in)
    • Module registers additional json types only when user requires it (performance boost)
    • BiDi can track already instantiated modules centralized to avoid duplication (including built-in thread safety)

    Note: I guess we will use Activator.CreateInstance() API, need to check how friendly it with AOT. Consider abstract static factory, it is AOT safe.

    Pseudo definition:

    class CustomModule: Module, IModuleFactory<CustomModule> {}
    
    interface IModuleFactory<T>
    {
      static abstract T Create(obj); // where "obj" is any params required to inject json options
    }

    Upd:
    Tried static factory and got error "Target runtime doesn't support static abstract members in interfaces." because of netstandard2.0...
    Can be negotiated via delegates: bidi.AsModule<T>(Func<T, Broker>) where T: Module. Not ideal for end users, but ideal for modules developers. We can even make this method as an extension just to hide it from end users (moving it to another namespace, or via [BrowserableAttribute], forgot exact attr name).

  15. nvborisenko commented on Apr 25, 2025

    @nvborisenko
    MemberAuthor

    Summary

    Selenium

    class BiDi
    {
      // new method
      public T AsModule(Func<T, Broker> factory) where T: Module // interesting how to "hide" it from regular user
      {
        // utilize cache
      }
    }

    3rd-party modules

    class PermissionsModule : Module
    {
      ctor(broker); // can be any params, even handle root bidi object
    }
    
    static class Extensions
    {
      public static PermissionsModule AsPermissions(this BiDi bidi)
      {
        return bidi.AsModule<PermissionsModule>(broker =>
        {
          broker.InjectJsonTypes(); // another story, but easy resolvable
    
          return new PermissionsModule(broker);
        });
      }
    }

    I am happy with it for now, very straightforward. @RenderMichael your review is appreciated before implementation, I may miss something.

  16. RenderMichael commented on May 1, 2025

    @RenderMichael
    Contributor

    I think BiDi.AsModule should be named GetOrAddModule but other than that it looks great!

  17. nvborisenko commented on May 5, 2025

    @nvborisenko
    MemberAuthor

    Concerns:

    • Cache module? - Each time when user calls .AsModule<T>() should it return new object or cached... I guess it depends on whether an object is Disposable. I cannot image the use cases when we may dispose module, thus it is safe to cache modules.
    • GetOrAddModule() vs AsModule() - the 1st option explicitly says the object is cached (see the 1st my concern). I think we are still not sure whether it has to be cached. If in future we will change behavior, then AsModule() looks better.
    • Transitive json types - modules are cross-referenced, meaning that some DTO types are reused across modules. If we go with "each module is self json defined", then it means each module has to be self-defined including 3rd-party dependencies (converters, anything else?). I think of it is OK. Worse thing here that now each module should manage json dependencies, meaning we should keep them in mind and as a part of module json definition. I am considering it as acceptable, given that entire infrastructure becomes more modular (performance boost).
  18. github-actions commented on Nov 1, 2025

    @github-actions
    Contributor

    This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 14 days.

  19. added
    J-staleApplied to issues that become stale, and eventually closed.
    on Nov 1, 2025
  20. github-actions commented on Nov 15, 2025

    @github-actions
    Contributor

    This issue was closed because it has been stalled for 14 days with no activity.

  21. removed
    J-staleApplied to issues that become stale, and eventually closed.
    on Nov 16, 2025
  22. github-actions commented on Dec 29, 2025

    @github-actions
    Contributor

    This issue has been automatically locked since there has not been any recent activity since it was closed. Please open a new issue for related bugs.

  23. locked and limited conversation to collaborators on Dec 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions