-
-
Notifications
You must be signed in to change notification settings - Fork 8.7k
[js] Add serialization and domain layer #17927
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you under the Apache License, Version 2.0 (the | ||
| // "License"); you may not use this file except in compliance | ||
| // with the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, | ||
| // software distributed under the License is distributed on an | ||
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| // KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| export interface EventDescriptor<T> { | ||
| readonly method: string | ||
| readonly type?: { fromWire(payload: unknown): T } | ||
| } | ||
|
|
||
| /** | ||
| * Describes one subscribable BiDi event, for use with Domain#addCallback(). | ||
| * @param {string} method The event's wire method name, e.g. 'log.entryAdded'. | ||
| * @param {{fromWire(payload: unknown): T}} [type] Runtime record/union class for the | ||
| * event's params, if the schema declares one — addCallback() parses each delivered | ||
| * payload through it before the caller's handler runs. | ||
| * @returns {EventDescriptor<T>} The event descriptor, ready to pass to Domain#addCallback(). | ||
| */ | ||
| export function event<T>(method: string, type?: { fromWire(payload: unknown): T }): EventDescriptor<T> | ||
|
|
||
| /** Internal construction guard — only a generated `Class.create(driver)` passes this. Never use directly. */ | ||
| export const DOMAIN_TOKEN: unique symbol | ||
|
|
||
| export declare class Domain { | ||
| protected constructor(bidi: unknown, token: typeof DOMAIN_TOKEN) | ||
| protected static connect(driver: unknown): Promise<unknown> | ||
| protected send(method: string, params: Record<string, unknown>): Promise<unknown> | ||
|
|
||
| /** | ||
| * Subscribes `handler` to a BiDi event — asks the remote end to start sending it, | ||
| * then attaches `handler` as a local listener for it. Placeholder for now: always | ||
| * subscribes/unsubscribes remotely on every call, with no ref-counting across | ||
| * callers — connection-wide listener bookkeeping is follow-up work. | ||
| * @param {EventDescriptor<T>} descriptor An event descriptor from event(). | ||
| * @param {function(T): void} handler Invoked with the event's parsed params each time it fires. | ||
| * @returns {Promise<{unsubscribe: function(): Promise<void>}>} A handle for this | ||
| * subscription; call `unsubscribe()` to stop receiving the event. | ||
| */ | ||
| addCallback<T>( | ||
| descriptor: EventDescriptor<T>, | ||
| handler: (params: T) => void, | ||
| ): Promise<{ unsubscribe(): Promise<void> }> | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you under the Apache License, Version 2.0 (the | ||
| // "License"); you may not use this file except in compliance | ||
| // with the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, | ||
| // software distributed under the License is distributed on an | ||
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| // KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| const { getBidiConnection } = require('../lib/bidi_connection') | ||
|
|
||
| // Gates Domain's constructor so `new Network(someRandomThing)` fails loudly | ||
| // instead of silently producing a broken instance. A Symbol can't be forged | ||
| // or guessed, so this is real runtime enforcement, not just a TS annotation — | ||
| // only a generated `Class.create(driver)` (and this package's own tests) may | ||
| // pass it. It's exported deliberately, not hidden: the point is to stop | ||
| // accidental misuse of the normal `new Network(x)` shape, not to defend | ||
| // against someone who deliberately imports and passes this. | ||
| const DOMAIN_TOKEN = Symbol('Domain internal construction token — obtained only via Class.create(driver)') | ||
|
|
||
| /** | ||
| * Describes one subscribable BiDi event, for use with Domain#addCallback(). | ||
| * @param {string} method | ||
| * @param {{fromWire(payload: unknown): unknown}} [type] Runtime record/union | ||
| * class for the event's params, if the schema declares one. When present, | ||
| * addCallback() parses each delivered payload through it before the | ||
| * caller's handler runs — inbound wire payloads are validated against | ||
| * their resolved type; an event's params is such a payload just as much | ||
| * as a command's result is. | ||
| * @returns {{method: string, type: ({fromWire(payload: unknown): unknown}|undefined)}} | ||
| * The event descriptor, ready to pass to Domain#addCallback(). | ||
| */ | ||
| function event(method, type) { | ||
| return { method, type } | ||
| } | ||
|
|
||
| /** Shared base for every generated BiDi domain class. See domain.d.ts for the typed surface. */ | ||
| class Domain { | ||
| #bidi | ||
|
|
||
| constructor(bidi, token) { | ||
| if (token !== DOMAIN_TOKEN) { | ||
| throw new TypeError(`${new.target.name} must be constructed via ${new.target.name}.create(driver), not \`new\``) | ||
| } | ||
| this.#bidi = bidi | ||
| } | ||
|
|
||
| static async connect(driver) { | ||
| return getBidiConnection(driver) | ||
| } | ||
|
|
||
| async send(method, params) { | ||
| const response = await this.#bidi.send({ method, params }) | ||
| if (response?.error !== undefined) { | ||
| throw new Error(`${response.error}: ${response.message}`) | ||
| } | ||
| return response?.result | ||
| } | ||
|
|
||
| /** | ||
| * Subscribes `handler` to a BiDi event: tells the remote end to start sending it | ||
| * (the underlying connection's `subscribe()`), then attaches `handler` as a local | ||
| * listener for it. Placeholder for now — always subscribes/unsubscribes remotely on | ||
| * every call, with no ref-counting across callers or connection-wide listener | ||
| * bookkeeping (that belongs at the connection level, shared across every Domain | ||
| * instance, mirroring Ruby's WebSocketConnection#add_callback/remove_callback or | ||
| * Java's equivalent — tracked as follow-up work, not folded into this PR). | ||
| * @param {{method: string, type: ({fromWire(payload: unknown): unknown}|undefined)}} descriptor | ||
| * An event descriptor from event(). | ||
| * @param {function(unknown): void} handler Invoked with the event's params | ||
| * (parsed through descriptor.type first, if one was given) each time it fires. | ||
| * @returns {Promise<{unsubscribe: function(): Promise<void>}>} | ||
| * A handle for this subscription — call `unsubscribe()` to stop receiving the event. | ||
| */ | ||
| async addCallback(descriptor, handler) { | ||
|
qodo-code-review[bot] marked this conversation as resolved.
|
||
| const dispatch = descriptor.type === undefined ? handler : (params) => handler(descriptor.type.fromWire(params)) | ||
| await this.#bidi.subscribe(descriptor.method) | ||
| this.#bidi.on(descriptor.method, dispatch) | ||
|
qodo-code-review[bot] marked this conversation as resolved.
|
||
| return { | ||
| unsubscribe: async () => { | ||
| this.#bidi.off(descriptor.method, dispatch) | ||
| await this.#bidi.unsubscribe(descriptor.method) | ||
| }, | ||
|
qodo-code-review[bot] marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| module.exports = { Domain, event, DOMAIN_TOKEN } | ||
30 changes: 30 additions & 0 deletions
30
javascript/selenium-webdriver/bidi/serialization/enum.d.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you under the Apache License, Version 2.0 (the | ||
| // "License"); you may not use this file except in compliance | ||
| // with the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, | ||
| // software distributed under the License is distributed on an | ||
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| // KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| export interface EnumEntry<T extends string> { | ||
| readonly values: readonly T[] | ||
| includes(value: unknown): value is T | ||
| } | ||
|
|
||
| /** | ||
| * Registers a schema `enum` — a closed set of string values a field may hold. | ||
| * @param name Schema type name, e.g. 'network.InterceptPhase'. | ||
| * @param values The enum's valid values. | ||
| * @returns The registered entry, used by validateValue() to check a ref'd value's | ||
| * membership; also returned so a generator can build a discoverable constant from it. | ||
| */ | ||
| export function defineEnum<T extends string>(name: string, values: readonly T[]): EnumEntry<T> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you under the Apache License, Version 2.0 (the | ||
| // "License"); you may not use this file except in compliance | ||
| // with the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, | ||
| // software distributed under the License is distributed on an | ||
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| // KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| const { register } = require('./registry') | ||
|
|
||
| /** | ||
| * Registers a schema `enum` — a closed set of string values a field may hold. | ||
| * @param {string} name Schema type name, e.g. 'network.InterceptPhase'. | ||
| * @param {string[]} values The enum's valid values. | ||
| * @returns {{kind: 'enum', values: string[], includes: function(unknown): boolean}} | ||
| * The registered entry, used by validateValue() to check a ref'd value's | ||
| * membership; also returned so a generator can build a discoverable constant from it. | ||
| */ | ||
| function defineEnum(name, values) { | ||
|
qodo-code-review[bot] marked this conversation as resolved.
|
||
| const allowed = new Set(values) | ||
| const entry = { kind: 'enum', values, includes: (value) => allowed.has(value) } | ||
| register(name, entry) | ||
| return entry | ||
| } | ||
|
|
||
| module.exports = { defineEnum } | ||
70 changes: 70 additions & 0 deletions
70
javascript/selenium-webdriver/bidi/serialization/record.d.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| // Licensed to the Software Freedom Conservancy (SFC) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The SFC licenses this file | ||
| // to you under the Apache License, Version 2.0 (the | ||
| // "License"); you may not use this file except in compliance | ||
| // with the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, | ||
| // software distributed under the License is distributed on an | ||
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| // KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| // Mirrors bidi_schema.json's type-ref vocabulary (see project_bidi_schema.mjs). | ||
| export interface TypeNode { | ||
| primitive?: string | ||
| const?: unknown | ||
| ref?: string | ||
| enum?: string[] | ||
| list?: TypeNode | ||
| map?: TypeNode | ||
| union?: TypeNode[] | ||
| // An inline (unnamed) record — project_bidi_schema.mjs's projectEntry() emits this | ||
| // for an anonymous CDDL group instead of hoisting it to a named, ref'able type. | ||
| record?: FieldSpec[] | ||
| nullable?: boolean | ||
| // Present on an inline union with a bare-scalar arm — the primitive(s) that | ||
| // arm accepts (see unionNode() in project_bidi_schema.mjs). Not consumed by | ||
| // validateValue yet; declared so embedding a real schema node type-checks. | ||
| scalar?: string | string[] | ||
| // The exact `const` literals a bare-scalar union arm admits (e.g. | ||
| // input.Origin's "viewport"/"pointer") — see unionNode(). Same status as | ||
| // `scalar`: not yet consumed by validateValue, declared for the embed. | ||
| scalarValues?: unknown[] | ||
| } | ||
|
|
||
| export interface FieldSpec { | ||
| name: string | ||
| wire: string | ||
| required: boolean | ||
| type: TypeNode | ||
| } | ||
|
|
||
| export interface RecordOptions { | ||
| extensible?: boolean | ||
| } | ||
|
|
||
| export declare class ValidationError extends Error {} | ||
|
|
||
| export interface RecordClass<T> { | ||
| new (data: T): Readonly<T> | ||
| fromWire(payload: unknown): Readonly<T> | ||
| } | ||
|
|
||
| /** | ||
| * Registers a schema `record` — a fixed set of named fields, each independently | ||
| * validated on the way out (constructor) and in (fromWire()). | ||
| * @param name Schema type name, e.g. 'network.AddInterceptParameters'. | ||
| * @param fields The record's field specs. | ||
| * @param options | ||
| * @returns The generated Record class — `new Record(data)` validates and constructs | ||
| * outbound, `Record.fromWire(payload)` validates and parses inbound. | ||
| */ | ||
| export function defineRecord<T>(name: string, fields: FieldSpec[], options?: RecordOptions): RecordClass<T> | ||
|
|
||
| export function defineAlias(name: string, type: TypeNode): void |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.