You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[🚀 Feature]: [dotnet] Provide a possibility to implement BiDi Permissions module #15329
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
varbidi=awaitdriver.AsBiDiAsync();// selenium responsibility// straightforward API (not yet approved)varpermissionsModule=newPermissionsModule(bidi);// or as extension to emphasize this is extensionvarpermissionsModule=bidi.AsPermissions();awaitpermissionsModule.QueryAsync();
@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.
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:
varbidi=awaitdriver.AsBiDiAsync();// W3C WebDriver implementationvarpermissions=awaitdriver.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.
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?
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.
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:
varbidi=driver.GetBidi();// existing functionalityvarpermissionsModule=bidi.AsPermissions();// here it is safe to modify JsonOptions!varbluetoothModule=bidi.AsBluetooth();// even safe, just adds one more TypeInfoResolver
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)
Usually builder pattern lives on top of existing functionality. I am still thinking the following is our primary way:
varpermissionsModule=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).
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).
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:
classCustomModule:Module,IModuleFactory<CustomModule>{}interfaceIModuleFactory<T>{staticabstractTCreate(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).
classBiDi{// new methodpublicTAsModule(Func<T,Broker>factory)whereT:Module// interesting how to "hide" it from regular user{// utilize cache}}
3rd-party modules
classPermissionsModule:Module{ctor(broker);// can be any params, even handle root bidi object}staticclassExtensions{publicstaticPermissionsModuleAsPermissions(thisBiDibidi){returnbidi.AsModule<PermissionsModule>(broker =>{broker.InjectJsonTypes();// another story, but easy resolvablereturnnewPermissionsModule(broker);});}}
I am happy with it for now, very straightforward. @RenderMichael your review is appreciated before implementation, I may miss something.
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).
Activity
github-actions commentedon Feb 24, 2025
@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-templatelabel.If the issue is a question, add the
I-questionlabel.If the issue is valid but there is no time to troubleshoot it, consider adding the
help wantedlabel.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 answerlabel.Thank you!
nvborisenko commentedon Mar 8, 2025
I am thinking more about it and coming to a conclusion that "reusing" existing BiDi connection is "not possible".
JsonSerializerContextis 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:
Then each
bidiandpermissionsare 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.
nvborisenko commentedon Mar 8, 2025
And I am entirely OK with it!
Permissionsmodule can be easily implemented (defining his own DTO types),Bluetoothmodule too.Appium is a separate topic, but seems yes, we consider Appium as yet another BiDi module (with his own isolated "context").
nvborisenko commentedon Mar 22, 2025
This way we inject JsonTypeInfoResolver, reusing entire core bidi infrastructure, and expose
permissionsobject 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?
RenderMichael commentedon Mar 22, 2025
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.
nvborisenko commentedon Mar 22, 2025
Trying to evaluate the statement "JsonOptions is immutable" and understood it is actually mutable. It is safe to inject
TypeInfoResolverwhen it is already constructed.Pseudo flow:
I hope it will be useful.
RenderMichael commentedon Mar 22, 2025
We can improve it even more with the
TypeInfoResolverChain, a new feature of STJ designed to make this process simpler.nvborisenko commentedon Mar 22, 2025
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.
RenderMichael commentedon Mar 23, 2025
What do we think about a builder pattern:
Extension creator can provide nice extensions on
BiDiBuilderso they can do whatever they need:RenderMichael commentedon Mar 23, 2025
With the above pattern, we can do things like make the
PermissionsModuleconstructor internal, to enforce using the extension method and guide users to use the builder instead (avoiding any of the problems with adding extensions)nvborisenko commentedon Mar 30, 2025
Usually builder pattern lives on top of existing functionality. I am still thinking the following is our primary way:
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).8 remaining items
nvborisenko commentedon Apr 20, 2025
This idea is good from performance view (nanoseconds), but might be wrong conceptually. Any caller should not know how
Brokerdeals with DTO types at transport level. Because of today it isjson, tomorrow it isbinary. Let's hold this knowledge atBrokerlayer, sooptions.GetTypeResolver()is appreciated (how it is currently used).nvborisenko commentedon Apr 23, 2025
Solution is pretty simple: expose
bidi.AsModule<T>() where T: Module(Broker).Why:
BiDican track already instantiated modules centralized to avoid duplication (including built-in thread safety)Note:
I guess we will useConsiderActivator.CreateInstance()API, need to check how friendly it with AOT.abstract static factory, it is AOT safe.Pseudo definition:
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).nvborisenko commentedon Apr 25, 2025
Summary
Selenium
3rd-party modules
I am happy with it for now, very straightforward. @RenderMichael your review is appreciated before implementation, I may miss something.
RenderMichael commentedon May 1, 2025
I think
BiDi.AsModuleshould be namedGetOrAddModulebut other than that it looks great!nvborisenko commentedon May 5, 2025
Concerns:
.AsModule<T>()should it return new object or cached... I guess it depends on whether an object isDisposable. I cannot image the use cases when we may dispose module, thus it is safe to cache modules.AsModule()looks better.github-actions commentedon Nov 1, 2025
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.
github-actions commentedon Nov 15, 2025
This issue was closed because it has been stalled for 14 days with no activity.
github-actions commentedon Dec 29, 2025
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.