selenium-webdriver 4.47.0 → 4.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,345 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ const { register, resolve } = require('./registry')
19
+
20
+ class ValidationError extends Error {}
21
+
22
+ // Validates a *present* value against a resolved type node, and returns the value to
23
+ // assign for it — usually the same value, but for a nested ref-to-record/union parsed
24
+ // inbound (fromWire()/build() results, not the raw wire object) and for list/map/inline
25
+ // record values (a fresh, deeply-frozen copy, so validity can't be corrupted after the
26
+ // fact by mutating an array or object a caller still holds a reference to).
27
+ // Outbound nested refs deliberately keep the caller's own value instead of the
28
+ // newly-constructed instance — the nested `new referenced.RecordClass(value)` /
29
+ // `referenced.build(value)` call below exists only to validate, matching the
30
+ // constructor's existing outbound behavior of trusting the caller's own object shape.
31
+ // `direction` only affects how a nested ref-to-record/union is itself validated/parsed.
32
+ function validateValue(typeNode, value, path, direction) {
33
+ if (value === null) {
34
+ // `primitive: 'null'` (project_bidi_schema.mjs's projectRef(), for a type whose
35
+ // every alternative was null) means null itself is the valid value — accept it
36
+ // even though `nullable` (a *different* value also being permitted alongside a
37
+ // non-null base type) wasn't separately set.
38
+ if (typeNode.nullable || typeNode.primitive === 'null') return null
39
+ throw new ValidationError(`${path}: null is not allowed`)
40
+ }
41
+
42
+ if (typeNode.primitive !== undefined) {
43
+ // A present, non-null value never satisfies a `primitive: 'null'` node — value
44
+ // being null is the only thing the branch above would have already returned for.
45
+ if (typeNode.primitive === 'null') {
46
+ throw new ValidationError(`${path}: expected null, got ${typeof value}`)
47
+ }
48
+ const expected = { string: 'string', integer: 'number', number: 'number', boolean: 'boolean' }[typeNode.primitive]
49
+ if (expected && typeof value !== expected) {
50
+ throw new ValidationError(`${path}: expected ${typeNode.primitive}, got ${typeof value}`)
51
+ }
52
+ // JSON has no representation for NaN/±Infinity — reject them for both numeric
53
+ // primitives before the integer-specific check narrows further. (Number.isInteger
54
+ // already excludes them too, so this is only load-bearing for a bare `number`.)
55
+ if ((typeNode.primitive === 'integer' || typeNode.primitive === 'number') && !Number.isFinite(value)) {
56
+ throw new ValidationError(`${path}: expected a finite ${typeNode.primitive}, got ${value}`)
57
+ }
58
+ // `number` admits any JSON number; `integer` rejects a fractional value
59
+ // (5.7) while still accepting one written 5.0 (Number.isInteger(5.0) is true).
60
+ if (typeNode.primitive === 'integer' && !Number.isInteger(value)) {
61
+ throw new ValidationError(`${path}: expected an integer, got ${value}`)
62
+ }
63
+ // An inline literal choice (project_bidi_schema.mjs's enumNode()) carries both
64
+ // `primitive` and `enum` — the primitive check above narrows the type, but the
65
+ // closed vocabulary below still needs to run, so only return early when there
66
+ // is no `enum` to fall through to.
67
+ if (typeNode.enum === undefined) return value
68
+ }
69
+
70
+ if (typeNode.const !== undefined) {
71
+ if (value !== typeNode.const) {
72
+ throw new ValidationError(
73
+ `${path}: expected constant ${JSON.stringify(typeNode.const)}, got ${JSON.stringify(value)}`,
74
+ )
75
+ }
76
+ return value
77
+ }
78
+
79
+ if (typeNode.enum !== undefined) {
80
+ if (!typeNode.enum.includes(value)) {
81
+ throw new ValidationError(
82
+ `${path}: "${value}" is not a valid value; expected one of: ${typeNode.enum.join(', ')}`,
83
+ )
84
+ }
85
+ return value
86
+ }
87
+
88
+ if (typeNode.list !== undefined) {
89
+ if (!Array.isArray(value)) {
90
+ throw new ValidationError(`${path}: expected a list, got ${typeof value}`)
91
+ }
92
+ return Object.freeze(value.map((item, i) => validateValue(typeNode.list, item, `${path}[${i}]`, direction)))
93
+ }
94
+
95
+ if (typeNode.map !== undefined) {
96
+ if (typeof value !== 'object' || Array.isArray(value) || value === null) {
97
+ throw new ValidationError(`${path}: expected an object, got ${typeof value}`)
98
+ }
99
+ // Object.create(null), not `{}`: `key` is wire-controlled and a literal "__proto__"
100
+ // entry assigned via bracket notation would hijack result's prototype instead of
101
+ // becoming a data property (CWE-1321) — a null-prototype object has no such trap.
102
+ const result = Object.create(null)
103
+ for (const [key, entry] of Object.entries(value)) {
104
+ result[key] = validateValue(typeNode.map, entry, `${path}.${key}`, direction)
105
+ }
106
+ return Object.freeze(result)
107
+ }
108
+
109
+ if (typeNode.ref !== undefined) {
110
+ const referenced = resolve(typeNode.ref)
111
+ if (referenced === undefined) return value // not yet registered — best-effort, skip deep validation
112
+
113
+ if (referenced.kind === 'enum') {
114
+ if (!referenced.includes(value)) {
115
+ throw new ValidationError(
116
+ `${path}: "${value}" is not a valid ${typeNode.ref} value; expected one of: ${referenced.values.join(', ')}`,
117
+ )
118
+ }
119
+ return value
120
+ }
121
+
122
+ if (referenced.kind === 'record') {
123
+ if (value instanceof referenced.RecordClass) return value // already validated
124
+ if (typeof value !== 'object' || Array.isArray(value) || value === null) {
125
+ throw new ValidationError(`${path}: expected an object, got ${typeof value}`)
126
+ }
127
+ // Recurse through the same-direction path so a nested field gets the same
128
+ // tolerance (inbound) or strictness (outbound) as its parent. Inbound returns
129
+ // the parsed instance itself, so a typed parent record ends up with a typed
130
+ // nested value instead of the raw wire object; outbound only validates this
131
+ // way (the caller's own value is what gets kept, see the note above).
132
+ if (direction === 'inbound') {
133
+ return referenced.RecordClass.fromWire(value)
134
+ }
135
+ new referenced.RecordClass(value)
136
+ return value
137
+ }
138
+
139
+ if (referenced.kind === 'union') {
140
+ if (direction === 'inbound') {
141
+ return referenced.fromWire(value)
142
+ }
143
+ referenced.build(value)
144
+ return value
145
+ }
146
+
147
+ if (referenced.kind === 'alias') {
148
+ return validateValue(referenced.type, value, path, direction)
149
+ }
150
+
151
+ return value
152
+ }
153
+
154
+ if (typeNode.union !== undefined) {
155
+ const errors = []
156
+ for (const variant of typeNode.union) {
157
+ try {
158
+ return validateValue(variant, value, path, direction)
159
+ } catch (err) {
160
+ errors.push(err.message)
161
+ }
162
+ }
163
+ throw new ValidationError(`${path}: value did not match any variant (${errors.join('; ')})`)
164
+ }
165
+
166
+ // An inline (unnamed) record type node — project_bidi_schema.mjs's projectEntry()
167
+ // emits this for an anonymous CDDL group (e.g. a field typed as an inline `{ ... }`
168
+ // rather than a ref to a named, defineRecord()'d type) — same FieldSpec shape as a
169
+ // named record's `fields`, just with nowhere to register a class. Gets the same
170
+ // directional required/extra/nested-value handling a named record's constructor/
171
+ // fromWire gives its fields, just built inline instead of through a Record class.
172
+ if (typeNode.record !== undefined) {
173
+ if (typeof value !== 'object' || Array.isArray(value) || value === null) {
174
+ throw new ValidationError(`${path}: expected an object, got ${typeof value}`)
175
+ }
176
+ const byWire = new Map(typeNode.record.map((f) => [f.wire, f]))
177
+ const result = {}
178
+ for (const field of typeNode.record) {
179
+ if (!Object.hasOwn(value, field.wire)) {
180
+ if (field.required) {
181
+ throw new ValidationError(`${path}.${field.wire}: required field is missing`)
182
+ }
183
+ continue
184
+ }
185
+ result[field.name] = validateValue(field.type, value[field.wire], `${path}.${field.wire}`, direction)
186
+ }
187
+ for (const wireKey of Object.keys(value)) {
188
+ if (byWire.has(wireKey)) continue
189
+ // No `extensible` concept exists for an inline record (project_bidi_schema.mjs
190
+ // never sets it there) — undeclared keys get exactly the non-extensible named-
191
+ // record treatment: rejected outbound, dropped-with-a-warning inbound.
192
+ if (direction === 'inbound') {
193
+ process.emitWarning(`${path}: undeclared property "${wireKey}"`, 'BiDiSchemaWarning')
194
+ } else {
195
+ throw new ValidationError(`${path}: unknown property "${wireKey}"`)
196
+ }
197
+ }
198
+ return Object.freeze(result)
199
+ }
200
+
201
+ return value
202
+ }
203
+
204
+ /**
205
+ * Registers a schema `record` — a fixed set of named fields, each independently
206
+ * validated on the way out (constructor) and in (fromWire()).
207
+ * @param {string} name Schema type name, e.g. 'network.AddInterceptParameters'.
208
+ * @param {Array<{name: string, wire: string, required: boolean, type: object}>} fields
209
+ * @param {{extensible?: boolean}} [options]
210
+ * @returns {{new (data: object): object, fromWire: function(unknown): object}}
211
+ * The generated Record class — `new Record(data)` validates and constructs
212
+ * outbound, `Record.fromWire(payload)` validates and parses inbound.
213
+ */
214
+ function defineRecord(name, fields, options = {}) {
215
+ const { extensible = false } = options
216
+ const byWire = new Map(fields.map((f) => [f.wire, f]))
217
+ // JS property name -> wire key, the inverse of byWire — lets toJSON() below map an
218
+ // outbound instance's own (JS-facing) properties back to the wire's declared names.
219
+ const byName = new Map(fields.map((f) => [f.name, f.wire]))
220
+
221
+ class Record {
222
+ // Outbound: strict. Any value that doesn't match its declared shape is an error here.
223
+ constructor(data) {
224
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
225
+ throw new ValidationError(`${name}: expected an object`)
226
+ }
227
+
228
+ for (const field of fields) {
229
+ if (!Object.hasOwn(data, field.wire)) {
230
+ if (field.required) {
231
+ throw new ValidationError(`${name}.${field.wire}: required field is missing`)
232
+ }
233
+ continue
234
+ }
235
+ const value = data[field.wire]
236
+ this[field.name] = validateValue(field.type, value, `${name}.${field.wire}`, 'outbound')
237
+ }
238
+
239
+ for (const wireKey of Object.keys(data)) {
240
+ if (byWire.has(wireKey)) continue
241
+ if (!extensible) {
242
+ throw new ValidationError(`${name}: unknown property "${wireKey}"`)
243
+ }
244
+ // Object.defineProperty, not `this[wireKey] = ...`: wireKey is caller-supplied
245
+ // and a literal "__proto__" key assigned via bracket notation hijacks this
246
+ // instance's actual prototype instead of becoming a field (CWE-1321).
247
+ // defineProperty always creates a genuine own data property, regardless of name.
248
+ Object.defineProperty(this, wireKey, {
249
+ value: data[wireKey], // vendor extras reach the wire on an extensible type
250
+ enumerable: true,
251
+ writable: true,
252
+ configurable: true,
253
+ })
254
+ }
255
+
256
+ Object.freeze(this)
257
+ }
258
+
259
+ // Inbound: tolerant of undeclared properties, but a missing required field
260
+ // is rejected just like a structurally invalid value — omission used to be
261
+ // tolerated here, but that's no longer required.
262
+ // Bypasses the constructor above entirely — a single constructor enforcing
263
+ // both directions symmetrically would make tolerated undeclared-property
264
+ // retention impossible.
265
+ static fromWire(payload) {
266
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
267
+ throw new ValidationError(`${name}: expected an object on the wire, got ${typeof payload}`)
268
+ }
269
+
270
+ const instance = Object.create(Record.prototype)
271
+
272
+ for (const field of fields) {
273
+ if (!Object.hasOwn(payload, field.wire)) {
274
+ if (field.required) {
275
+ throw new ValidationError(`${name}.${field.wire}: required field is missing`)
276
+ }
277
+ continue // left genuinely absent, optional field
278
+ }
279
+ const value = payload[field.wire]
280
+ // A present value's shape is never tolerated, inbound or outbound.
281
+ instance[field.name] = validateValue(field.type, value, `${name}.${field.wire}`, 'inbound')
282
+ }
283
+
284
+ for (const wireKey of Object.keys(payload)) {
285
+ if (byWire.has(wireKey)) continue
286
+ // An extensible type preserves an undeclared field silently — no narrower
287
+ // criterion than "extensible" itself (not, say, only fields that happen to be
288
+ // sendable back on some other type). A non-extensible type warns and drops it
289
+ // instead — the warning belongs only to the drop, not the retention.
290
+ if (extensible) {
291
+ // Object.defineProperty, not `instance[wireKey] = ...` — see the matching
292
+ // comment in the constructor above: a literal "__proto__" key from an
293
+ // untrusted wire payload must become a field, not swap the prototype (CWE-1321).
294
+ Object.defineProperty(instance, wireKey, {
295
+ value: payload[wireKey],
296
+ enumerable: true,
297
+ writable: true,
298
+ configurable: true,
299
+ })
300
+ } else {
301
+ process.emitWarning(`${name}: undeclared property "${wireKey}"`, 'BiDiSchemaWarning')
302
+ }
303
+ }
304
+
305
+ Object.freeze(instance)
306
+ return instance
307
+ }
308
+
309
+ // The JSON.stringify hook: an instance stores its fields under their JS-facing
310
+ // property names (this[field.name]), but the wire needs the spec's own key
311
+ // (field.wire) — the two differ whenever a generator picks an idiomatic JS name
312
+ // distinct from the raw spec key. Runs automatically wherever this instance is
313
+ // serialized (directly, or nested inside another value being stringified), so a
314
+ // caller never has to remember to call it.
315
+ toJSON() {
316
+ // Object.create(null), not `{}`: an extra's key is wire-controlled (extensible
317
+ // types preserve undeclared properties verbatim, see the constructor above), and
318
+ // a literal "__proto__" key assigned via bracket notation would hijack wire's
319
+ // prototype instead of becoming a data property (CWE-1321) — same hazard the
320
+ // constructor/fromWire already guard against for the instance itself.
321
+ const wire = Object.create(null)
322
+ for (const key of Object.keys(this)) {
323
+ wire[byName.get(key) ?? key] = this[key] // extras have no JS-name mapping — already wire-keyed
324
+ }
325
+ return wire
326
+ }
327
+ }
328
+
329
+ Object.defineProperty(Record, 'name', { value: name })
330
+ register(name, { kind: 'record', RecordClass: Record })
331
+ return Record
332
+ }
333
+
334
+ /**
335
+ * Registers a schema `alias` — a name with no fields of its own, just a
336
+ * pointer to another type node (e.g. `network.Intercept` aliasing a plain
337
+ * string). A ref to an alias validates through the aliased type node.
338
+ * @param {string} name
339
+ * @param {object} type The schema's `type` node this name aliases.
340
+ */
341
+ function defineAlias(name, type) {
342
+ register(name, { kind: 'alias', type })
343
+ }
344
+
345
+ module.exports = { defineRecord, defineAlias, ValidationError }
@@ -0,0 +1,34 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ // Shared type registry: every generated type registers itself here by its
19
+ // exact schema name (e.g. 'network.InterceptPhase'), so a field whose type is
20
+ // a `ref` can look up what the referenced type actually is — without every
21
+ // domain file needing to import every other domain file directly, and without
22
+ // needing types defined in dependency order (resolution happens at validation
23
+ // time, not at define time, so forward and circular refs both work).
24
+ const types = new Map()
25
+
26
+ function register(name, entry) {
27
+ types.set(name, entry)
28
+ }
29
+
30
+ function resolve(name) {
31
+ return types.get(name)
32
+ }
33
+
34
+ module.exports = { register, resolve }
@@ -0,0 +1,102 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ const { register, resolve } = require('./registry')
19
+ const { ValidationError } = require('./record')
20
+
21
+ // Resolves the variant ref a value/payload matches, per the schema's selector shape:
22
+ // { by, variants: [{value, ref}], default? } - discriminated: match `data[by]` against
23
+ // each variant's value.
24
+ // { ordered: [{ref, requires}] } - structural: first variant whose `requires`
25
+ // keys are all present in `data`, in spec order.
26
+ function selectVariant(selector, data, hasKey) {
27
+ if (selector.by) {
28
+ const tag = hasKey(data, selector.by) ? data[selector.by] : undefined
29
+ const match = selector.variants.find((v) => v.value === tag)
30
+ if (match) return match.ref
31
+ return selector.default
32
+ }
33
+ if (selector.ordered) {
34
+ for (const variant of selector.ordered) {
35
+ if (variant.requires.every((key) => hasKey(data, key))) return variant.ref
36
+ }
37
+ return undefined
38
+ }
39
+ return undefined // correlated: resolved by request id elsewhere, not from the payload
40
+ }
41
+
42
+ /**
43
+ * Registers a schema `union` — a value that may be any one of several variant
44
+ * record types, resolved by a discriminator field or by structural shape.
45
+ * @param {string} name Schema type name, e.g. 'session.ProxyConfiguration'.
46
+ * @param {object} selector The schema's `selector` node for this union.
47
+ * @param {{objectOnly?: boolean}} [options]
48
+ * @returns {{build: function(unknown): object, fromWire: function(unknown): object}}
49
+ * The registered union — `build(data)` resolves and constructs the matching
50
+ * variant outbound, `fromWire(payload)` resolves and parses it inbound.
51
+ */
52
+ function defineUnion(name, selector, options = {}) {
53
+ const { objectOnly = false } = options
54
+
55
+ const union = {
56
+ kind: 'union',
57
+
58
+ // Outbound: resolve which variant `data` describes, then delegate to that
59
+ // variant's own (strict) constructor. A discriminated selector's `default`
60
+ // catch-all can itself resolve to another union — not just a record — e.g.
61
+ // LocalValue's untyped RemoteReference arm (see unionSelector() in
62
+ // project_bidi_schema.mjs) — so recurse through that union's own dispatch
63
+ // rather than assuming every resolved ref is a record.
64
+ build(data) {
65
+ if (objectOnly && (typeof data !== 'object' || data === null || Array.isArray(data))) {
66
+ throw new ValidationError(`${name}: expected an object`)
67
+ }
68
+ const ref = selectVariant(selector, data, (d, key) => Object.hasOwn(d, key))
69
+ if (ref === undefined) {
70
+ throw new ValidationError(`${name}: value does not match any known variant`)
71
+ }
72
+ const variant = resolve(ref)
73
+ if (variant.kind === 'union') {
74
+ return variant.build(data)
75
+ }
76
+ return new variant.RecordClass(data)
77
+ },
78
+
79
+ // Inbound: resolve which variant `payload` matches. An unresolvable payload is a
80
+ // closed-vocabulary miss — always an error, never a warning, since there is no
81
+ // valid typed object to fall back to. Same nested-union case as build() above.
82
+ fromWire(payload) {
83
+ if (objectOnly && (typeof payload !== 'object' || payload === null || Array.isArray(payload))) {
84
+ throw new ValidationError(`${name}: expected an object on the wire, got ${typeof payload}`)
85
+ }
86
+ const ref = selectVariant(selector, payload, (d, key) => Object.hasOwn(d, key))
87
+ if (ref === undefined) {
88
+ throw new ValidationError(`${name}: received a variant not in this binding's BiDi schema`)
89
+ }
90
+ const variant = resolve(ref)
91
+ if (variant.kind === 'union') {
92
+ return variant.fromWire(payload)
93
+ }
94
+ return variant.RecordClass.fromWire(payload)
95
+ },
96
+ }
97
+
98
+ register(name, union)
99
+ return union
100
+ }
101
+
102
+ module.exports = { defineUnion }
Binary file
Binary file