import { ELEMENT_KEY, type remote } from 'webdriver' import type { ElementReference } from '@wdio/protocols' import { PrimitiveType, NonPrimitiveType } from './constants.js' const TYPE_CONSTANT = 'type' const VALUE_CONSTANT = 'value' /** * Represents the types of remote reference. */ enum RemoteReferenceType { Handle = 'handle', SharedId = 'sharedId' } type Serializeable = string | number | boolean | unknown type LocalValueParam = Serializeable | (Serializeable)[] | [Serializeable, Serializeable][] /** * Represents a local value with a specified type and optional value. * Described in https://w3c.github.io/webdriver-bidi/#type-script-LocalValue */ export class LocalValue { type: PrimitiveType | NonPrimitiveType value?: Serializeable | (Serializeable)[] | [Serializeable, Serializeable][] constructor(type: PrimitiveType | NonPrimitiveType, value?: LocalValueParam) { if (type === PrimitiveType.Undefined || type === PrimitiveType.Null) { this.type = type } else { this.type = type this.value = value } } /** * Creates a new LocalValue object with a string value. * * @param {string} value - The string value to be stored in the LocalValue object. * @returns {LocalValue} - The created LocalValue object. */ static createStringValue(value: string) { return new LocalValue(PrimitiveType.String, value) } /** * Creates a new LocalValue object with a number value. * * @param {number} value - The number value. * @returns {LocalValue} - The created LocalValue object. */ static createNumberValue(value: number) { return new LocalValue(PrimitiveType.Number, value) } /** * Creates a new LocalValue object with a special number value. * * @param {number} value - The value of the special number. * @returns {LocalValue} - The created LocalValue object. */ static createSpecialNumberValue(value: number) { if (Number.isNaN(value)) { return new LocalValue(PrimitiveType.SpecialNumber, 'NaN') } if (Object.is(value, -0)) { return new LocalValue(PrimitiveType.SpecialNumber, '-0') } if (value === Infinity) { return new LocalValue(PrimitiveType.SpecialNumber, 'Infinity') } if (value === -Infinity) { return new LocalValue(PrimitiveType.SpecialNumber, '-Infinity') } return new LocalValue(PrimitiveType.SpecialNumber, value) } /** * Creates a new LocalValue object with an undefined value. * @returns {LocalValue} - The created LocalValue object. */ static createUndefinedValue() { return new LocalValue(PrimitiveType.Undefined) } /** * Creates a new LocalValue object with a null value. * @returns {LocalValue} - The created LocalValue object. */ static createNullValue() { return new LocalValue(PrimitiveType.Null) } /** * Creates a new LocalValue object with a boolean value. * * @param {boolean} value - The boolean value. * @returns {LocalValue} - The created LocalValue object. */ static createBooleanValue(value: boolean) { return new LocalValue(PrimitiveType.Boolean, value) } /** * Creates a new LocalValue object with a BigInt value. * * @param {BigInt} value - The BigInt value. * @returns {LocalValue} - The created LocalValue object. */ static createBigIntValue(value: bigint) { return new LocalValue(PrimitiveType.BigInt, value) } /** * Creates a new LocalValue object with an array. * * @param {Array} value - The array. * @returns {LocalValue} - The created LocalValue object. */ static createArrayValue(value: Array) { return new LocalValue(NonPrimitiveType.Array, value) } /** * Creates a new LocalValue object with date value. * * @param {string} value - The date. * @returns {LocalValue} - The created LocalValue object. */ static createDateValue(value: Date) { return new LocalValue(NonPrimitiveType.Date, value) } /** * Creates a new LocalValue object of map value. * @param {Map} map - The map. * @returns {LocalValue} - The created LocalValue object. */ static createMapValue(map: Map) { const value: [Serializeable, Serializeable][] = [] Object.entries(map).forEach((entry) => { value.push(entry) }) return new LocalValue(NonPrimitiveType.Map, value) } /** * Creates a new LocalValue object from the passed object. * * @param {Object} map - The object. * @returns {LocalValue} - The created LocalValue object. */ static createObjectValue(object: Record) { const value: [Serializeable, Serializeable][] = [] Object.entries(object).forEach(([key, val]) => { value.push([key, LocalValue.getArgument(val)]) }) return new LocalValue(NonPrimitiveType.Object, value) } /** * Creates a new LocalValue object of regular expression value. * * @param {string} value - The value of the regular expression. * @returns {LocalValue} - The created LocalValue object. */ static createRegularExpressionValue(value: { pattern: string, flags: string }) { return new LocalValue(NonPrimitiveType.RegularExpression, value) } /** * Creates a new LocalValue object with the specified value. * @param {Set} value - The value to be set. * @returns {LocalValue} - The created LocalValue object. */ static createSetValue(value: ([unknown, unknown] | ReferenceValue | LocalValue)[]) { return new LocalValue(NonPrimitiveType.Set, value) } /** * Creates a new LocalValue object with the given channel value * * @param {ChannelValue} value - The channel value. * @returns {LocalValue} - The created LocalValue object. */ static createChannelValue(value: unknown) { return new LocalValue(NonPrimitiveType.Channel, value) } static createReferenceValue(handle: string, sharedId: string) { return new ReferenceValue(handle, sharedId) } static getArgument(argument: unknown) { const type = typeof argument switch (type) { case PrimitiveType.String: return LocalValue.createStringValue(argument as string) case PrimitiveType.Number: if ( Number.isNaN(argument) || Object.is(argument, -0) || !Number.isFinite(argument) ) { return LocalValue.createSpecialNumberValue(argument as number) } return LocalValue.createNumberValue(argument as number) case PrimitiveType.Boolean: return LocalValue.createBooleanValue(argument as boolean) case PrimitiveType.BigInt: return LocalValue.createBigIntValue(argument as bigint) case PrimitiveType.Undefined: return LocalValue.createUndefinedValue() case NonPrimitiveType.Object: if (argument === null) { return LocalValue.createNullValue() } if (argument instanceof Date) { return LocalValue.createDateValue(argument) } if (argument instanceof Map) { const map: ([unknown, unknown] | LocalValue)[] = [] argument.forEach((value, key) => { const objectKey = typeof key === 'string' ? key : LocalValue.getArgument(key) const objectValue = LocalValue.getArgument(value) map.push([objectKey, objectValue]) }) return new LocalValue(NonPrimitiveType.Map, map) } if (argument instanceof Set) { const set: (ReferenceValue | LocalValue)[] = [] argument.forEach((value) => { set.push(LocalValue.getArgument(value)) }) return LocalValue.createSetValue(set) } if (argument instanceof Array) { const arr: (ReferenceValue | LocalValue)[] = [] argument.forEach((value) => { arr.push(LocalValue.getArgument(value)) }) return LocalValue.createArrayValue(arr) } if (argument instanceof RegExp) { return LocalValue.createRegularExpressionValue({ pattern: argument.source, flags: argument.flags, }) } if (argument && ELEMENT_KEY in (argument as ElementReference)) { return LocalValue.createReferenceValue( RemoteReferenceType.SharedId, (argument as ElementReference)[ELEMENT_KEY] ) } return LocalValue.createObjectValue(argument as Record) } throw new Error(`Unsupported type: ${type}`) } asMap() { return { [TYPE_CONSTANT]: this.type, ...(!(this.type === PrimitiveType.Null || this.type === PrimitiveType.Undefined) ? { [VALUE_CONSTANT]: this.value } : {}) } as remote.ScriptLocalValue } } /** * Represents a reference value in the protocol. * Described in https://w3c.github.io/webdriver-bidi/#type-script-RemoteReference. */ class ReferenceValue { handle?: string sharedId?: string /** * Constructs a new ReferenceValue object. * @param {string} handle - The handle value. * @param {string} sharedId - The shared ID value. */ constructor(handle: string, sharedId: string) { if (handle === RemoteReferenceType.Handle) { this.handle = sharedId } else if (handle === RemoteReferenceType.SharedId) { this.sharedId = sharedId } else { this.handle = handle this.sharedId = sharedId } } asMap() { const toReturn: { handle?: string, sharedId?: string } = {} if (typeof this.handle !== 'undefined') { toReturn[RemoteReferenceType.Handle] = this.handle } if (typeof this.sharedId !== 'undefined') { toReturn[RemoteReferenceType.SharedId] = this.sharedId } return toReturn } }