selenium-webdriver 4.46.0 → 4.47.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.
- checksums.yaml +4 -4
- data/CHANGES +29 -0
- data/bin/linux/selenium-manager +0 -0
- data/bin/macos/selenium-manager +0 -0
- data/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +5971 -0
- data/bin/selenium-manager.cdx.json +11629 -0
- data/bin/windows/selenium-manager.exe +0 -0
- data/lib/selenium/webdriver/{common/driver_extensions/has_bidi.rb → bidi/error.rb} +21 -13
- data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +53 -23
- data/lib/selenium/webdriver/bidi/protocol/browser.rb +43 -15
- data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +172 -68
- data/lib/selenium/webdriver/bidi/protocol/domain.rb +8 -2
- data/lib/selenium/webdriver/bidi/protocol/emulation.rb +123 -9
- data/lib/selenium/webdriver/bidi/protocol/error_code.rb +66 -0
- data/lib/selenium/webdriver/bidi/protocol/input.rb +101 -28
- data/lib/selenium/webdriver/bidi/protocol/log.rb +17 -6
- data/lib/selenium/webdriver/bidi/protocol/network.rb +146 -63
- data/lib/selenium/webdriver/bidi/protocol/permissions.rb +4 -2
- data/lib/selenium/webdriver/bidi/protocol/script.rb +264 -86
- data/lib/selenium/webdriver/bidi/protocol/session.rb +48 -3
- data/lib/selenium/webdriver/bidi/protocol/speculation.rb +4 -2
- data/lib/selenium/webdriver/bidi/protocol/storage.rb +32 -6
- data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +7 -2
- data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +49 -10
- data/lib/selenium/webdriver/bidi/protocol.rb +3 -0
- data/lib/selenium/webdriver/bidi/serialization/record.rb +194 -18
- data/lib/selenium/webdriver/bidi/serialization/union.rb +45 -3
- data/lib/selenium/webdriver/bidi/serialization.rb +13 -1
- data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +529 -45
- data/lib/selenium/webdriver/bidi/support/check_generated.rb +12 -4
- data/lib/selenium/webdriver/bidi/transport.rb +8 -3
- data/lib/selenium/webdriver/chrome/service.rb +4 -1
- data/lib/selenium/webdriver/chromium/driver.rb +0 -1
- data/lib/selenium/webdriver/chromium/options.rb +20 -0
- data/lib/selenium/webdriver/common/driver.rb +11 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +0 -7
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +0 -7
- data/lib/selenium/webdriver/common/driver_finder.rb +4 -3
- data/lib/selenium/webdriver/common/options.rb +21 -1
- data/lib/selenium/webdriver/common/proxy.rb +0 -8
- data/lib/selenium/webdriver/common/selenium_manager.rb +2 -1
- data/lib/selenium/webdriver/common/takes_screenshot.rb +1 -1
- data/lib/selenium/webdriver/common.rb +0 -1
- data/lib/selenium/webdriver/edge/service.rb +4 -1
- data/lib/selenium/webdriver/firefox/driver.rb +0 -3
- data/lib/selenium/webdriver/firefox/options.rb +19 -0
- data/lib/selenium/webdriver/firefox/profile.rb +13 -6
- data/lib/selenium/webdriver/remote/bidi_bridge.rb +39 -12
- data/lib/selenium/webdriver/remote/bridge.rb +1 -1
- data/lib/selenium/webdriver/remote/driver.rb +3 -1
- data/lib/selenium/webdriver/remote/http/default.rb +1 -1
- data/lib/selenium/webdriver/safari/options.rb +16 -1
- data/lib/selenium/webdriver/support/guards/guard.rb +20 -3
- data/lib/selenium/webdriver/support/guards.rb +9 -3
- data/lib/selenium/webdriver/version.rb +1 -1
- metadata +6 -3
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
require 'json'
|
|
21
21
|
require 'erb'
|
|
22
22
|
require 'fileutils'
|
|
23
|
+
require_relative '../../../../../support/generated_note'
|
|
23
24
|
|
|
24
25
|
# Generates Ruby WebDriver BiDi protocol modules from the shared, binding-neutral
|
|
25
26
|
# BiDi schema produced by the JavaScript generator (see PR #17700):
|
|
@@ -31,8 +32,8 @@ require 'fileutils'
|
|
|
31
32
|
#
|
|
32
33
|
# Invoked via `bazel run //rb/lib/selenium/webdriver:bidi-generate`. Bazel passes
|
|
33
34
|
# the schema path (resolved through runfiles) plus the workspace-relative output
|
|
34
|
-
# directory as ARGV
|
|
35
|
-
#
|
|
35
|
+
# directory as ARGV, and supplies the shared generated-note text as a runfile, so
|
|
36
|
+
# this is not runnable directly from a source checkout.
|
|
36
37
|
#
|
|
37
38
|
# @api private
|
|
38
39
|
module BiDiGenerate
|
|
@@ -104,6 +105,9 @@ module BiDiGenerate
|
|
|
104
105
|
# Append underscore to a field name that would shadow a core method; the wire
|
|
105
106
|
# name is unaffected, only the Ruby reader is renamed.
|
|
106
107
|
def self.safe_field_name(name)
|
|
108
|
+
# A vendor-prefixed wire name carries a colon (moz:allowPrivateBrowsing); swap it
|
|
109
|
+
# for an underscore so the Ruby reader is a legal identifier. The wire key is kept.
|
|
110
|
+
name = name.tr(':', '_')
|
|
107
111
|
RESERVED_FIELD_NAMES.include?(name) ? "#{name}_" : name
|
|
108
112
|
end
|
|
109
113
|
|
|
@@ -172,7 +176,7 @@ module BiDiGenerate
|
|
|
172
176
|
# command); union_params picks its variant via `.build` rather than `.new`. result_ref
|
|
173
177
|
# is the Protocol-relative result class path, or nil to return the raw hash.
|
|
174
178
|
Command = Struct.new(:wire_name, :method_name, :params, :result_ref, :params_class,
|
|
175
|
-
:union_params, keyword_init: true) do
|
|
179
|
+
:union_params, :spec_href, keyword_init: true) do
|
|
176
180
|
def required_params = params.select(&:required)
|
|
177
181
|
def optional_params = params.reject(&:required)
|
|
178
182
|
def enum_checks(indent) = params.filter_map { |p| p.enum_check(indent) }
|
|
@@ -222,6 +226,81 @@ module BiDiGenerate
|
|
|
222
226
|
end
|
|
223
227
|
end
|
|
224
228
|
|
|
229
|
+
# A browser-specific extension to a command, kept out of the shared class so a
|
|
230
|
+
# non-matching browser never sees it. shared_params are the base command's own
|
|
231
|
+
# (required) params, forwarded verbatim; vendor_params are the typed extra fields,
|
|
232
|
+
# composed into the extensible params record's passthrough bag under their exact
|
|
233
|
+
# wire keys. params_class/result_ref/wire_name mirror the base command.
|
|
234
|
+
VendorCommand = Struct.new(:method_name, :wire_name, :result_ref, :params_class,
|
|
235
|
+
:shared_params, :vendor_params, :spec_href, keyword_init: true) do
|
|
236
|
+
def def_header(indent)
|
|
237
|
+
BiDiGenerate.wrap_call("def #{method_name}", shared_params.map(&:sig_part) + vendor_params.map(&:sig_part),
|
|
238
|
+
indent)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# The full `def … end` method block, fully indented from `indent`. Optional vendor
|
|
242
|
+
# fields are placed into the passthrough bag only when set (UNSET stays omitted), so
|
|
243
|
+
# they serialize exactly like a field on the extensible record.
|
|
244
|
+
def render_lines(indent)
|
|
245
|
+
body = ' ' * (indent + 2)
|
|
246
|
+
[*doc_lines(' ' * indent), "#{' ' * indent}#{def_header(indent)}", *extensions_lines(body, indent),
|
|
247
|
+
params_line(body, indent), execute_line(body, indent), "#{' ' * indent}end"]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def doc_lines(pad)
|
|
251
|
+
lines = ["#{pad}# @api private", "#{pad}# @see #{BiDiGenerate::BIDI_DOC_URL}"]
|
|
252
|
+
lines << "#{pad}# @see #{spec_href}" if spec_href
|
|
253
|
+
lines
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# The extensible passthrough bag, carrying each set vendor field under its exact wire key.
|
|
257
|
+
def extensions_lines(body, indent)
|
|
258
|
+
inner = ' ' * (indent + 4)
|
|
259
|
+
entries = vendor_params.map { |p| "#{inner}'#{p.wire_name}' => #{p.ruby_name}" }.join(",\n")
|
|
260
|
+
["#{body}extensions = {", entries, "#{body}}.reject { |_, value| Serialization::UNSET.equal?(value) }"]
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def params_line(body, indent)
|
|
264
|
+
kwargs = shared_params.map { |p| "#{p.ruby_name}: #{p.ruby_name}" } + ['extensions: extensions']
|
|
265
|
+
"#{body}#{BiDiGenerate.wrap_call("params = #{params_class}.new", kwargs, indent + 2)}"
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def execute_line(body, indent)
|
|
269
|
+
args = ["cmd: '#{wire_name}'", 'params: params']
|
|
270
|
+
args << "result: #{result_ref}" if result_ref
|
|
271
|
+
"#{body}#{BiDiGenerate.wrap_call('execute', args, indent + 2)}"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def rbs_signature
|
|
275
|
+
params = (shared_params.map(&:rbs_part) + vendor_params.map(&:rbs_part)).join(', ')
|
|
276
|
+
ret = result_ref ? "::Selenium::WebDriver::BiDi::Protocol::#{result_ref}" : 'untyped'
|
|
277
|
+
"(#{params}) -> #{ret}"
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# A namespaced group of browser-specific command extensions (e.g. Firefox's `moz:`
|
|
282
|
+
# fields), emitted as a subclass of the domain that overrides the extended commands.
|
|
283
|
+
# A subclass (rather than a runtime-mixed module) keeps the vendor signatures statically
|
|
284
|
+
# visible to type checkers, and is constructed directly (`<Name>.new(source)`) for a
|
|
285
|
+
# matching session — no factory or runtime mix-in.
|
|
286
|
+
VendorModule = Struct.new(:name, :namespace, :parent, :commands, keyword_init: true) do
|
|
287
|
+
def render(indent)
|
|
288
|
+
pad = ' ' * indent
|
|
289
|
+
lines = [
|
|
290
|
+
"#{pad}# @api private",
|
|
291
|
+
"#{pad}# #{namespace}: vendor variant of #{parent}, overriding commands with browser-specific params.",
|
|
292
|
+
"#{pad}# Construct #{name}.new(source) for a matching session; other sessions use #{parent}.",
|
|
293
|
+
"#{pad}class #{name} < #{parent}"
|
|
294
|
+
]
|
|
295
|
+
commands.each_with_index do |cmd, index|
|
|
296
|
+
lines << '' unless index.zero?
|
|
297
|
+
lines.concat(cmd.render_lines(indent + 2))
|
|
298
|
+
end
|
|
299
|
+
lines << "#{pad}end"
|
|
300
|
+
lines.join("\n")
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
225
304
|
# payload_ref is the Protocol-relative class the event's params parse into (nil when
|
|
226
305
|
# non-structured, dispatched raw) — the inbound counterpart to a command's result_ref.
|
|
227
306
|
Event = Struct.new(:wire_name, :event_name, :payload_ref, keyword_init: true) do
|
|
@@ -230,30 +309,53 @@ module BiDiGenerate
|
|
|
230
309
|
end
|
|
231
310
|
|
|
232
311
|
# constant_name is the SCREAMING_SNAKE hash name; pairs are [symbol_key, wire_value] tuples.
|
|
233
|
-
|
|
312
|
+
# spec_href links to the type's definition in the live spec (nil when the schema has none).
|
|
313
|
+
Enum = Struct.new(:constant_name, :pairs, :spec_href, keyword_init: true)
|
|
314
|
+
|
|
315
|
+
# The generated Protocol::ErrorCode module (filename 'error_code'): `codes` is the [wire, class_name]
|
|
316
|
+
# pairs in schema order (the full map); `new_classes` is the subset of class names the classic
|
|
317
|
+
# Error module does not already define (the ones whose RBS this file must declare). Rendered
|
|
318
|
+
# through the same emit/render path as the domain modules.
|
|
319
|
+
ErrorModule = Struct.new(:filename, :codes, :new_classes, keyword_init: true)
|
|
234
320
|
|
|
235
321
|
# ref is the Protocol-relative class path for a nested structured field (nil
|
|
236
322
|
# for a scalar/opaque field); list wraps it in an array. wire_key is the exact
|
|
237
323
|
# JSON payload key (the schema's `wire` name, baked verbatim).
|
|
238
|
-
FieldIR = Struct.new(:ruby_name, :wire_key, :required, :nullable, :ref, :list, :enum, :primitive, :
|
|
239
|
-
keyword_init: true) do
|
|
324
|
+
FieldIR = Struct.new(:ruby_name, :wire_key, :required, :nullable, :ref, :list, :enum, :primitive, :scalar, :const,
|
|
325
|
+
:rbs, keyword_init: true) do
|
|
240
326
|
# A `Serialization::Record.define` spec entry: `name: 'jsonKey'` shorthand, or
|
|
241
327
|
# `name: {wire_key:, …}` when the field carries JSON facts beyond its name.
|
|
242
328
|
# enum carries the allowed-values constant path, validated at construction.
|
|
243
329
|
def spec_entry(indent = 0)
|
|
244
|
-
meta =
|
|
245
|
-
meta << 'required: false' unless required
|
|
246
|
-
meta << 'nullable: true' if nullable
|
|
247
|
-
meta << "ref: '#{ref}'" if ref
|
|
248
|
-
meta << 'list: true' if list
|
|
249
|
-
meta << "enum: '#{enum}'" if enum
|
|
250
|
-
meta << "primitive: '#{primitive}'" if primitive
|
|
330
|
+
meta = value_facts
|
|
251
331
|
return "#{ruby_name}: '#{wire_key}'" if meta.empty?
|
|
252
332
|
|
|
253
333
|
meta.unshift("wire_key: '#{wire_key}'")
|
|
254
334
|
BiDiGenerate.wrap_call("#{ruby_name}: ", meta, indent, open: '{', close: '}')
|
|
255
335
|
end
|
|
256
336
|
|
|
337
|
+
# The JSON facts beyond the field's name, in the order Record.define reads them. A
|
|
338
|
+
# nullable const (`literal / null`) carries `const:` so the runtime rejects a value that
|
|
339
|
+
# is neither the literal nor null; `const.nil?` means the field has no const at all.
|
|
340
|
+
def value_facts
|
|
341
|
+
facts = []
|
|
342
|
+
facts << 'required: false' unless required
|
|
343
|
+
facts << 'nullable: true' if nullable
|
|
344
|
+
facts << "const: #{BiDiGenerate.ruby_literal(const)}" unless const.nil?
|
|
345
|
+
facts << "ref: '#{ref}'" if ref
|
|
346
|
+
facts << 'list: true' if list
|
|
347
|
+
facts << "scalar: #{scalar_literal}" if scalar
|
|
348
|
+
facts << "enum: '#{enum}'" if enum
|
|
349
|
+
facts << "primitive: '#{primitive}'" if primitive
|
|
350
|
+
facts
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# The `scalar` primitive(s) a bare non-object wire value must match at a scalar-tolerant
|
|
354
|
+
# union position: a single primitive string, or an array when the union's scalar arms differ.
|
|
355
|
+
def scalar_literal
|
|
356
|
+
scalar.is_a?(::Array) ? "[#{scalar.map { |s| "'#{s}'" }.join(', ')}]" : "'#{scalar}'"
|
|
357
|
+
end
|
|
358
|
+
|
|
257
359
|
# The `self.new` keyword for this field — a user-supplied input carrying the field's
|
|
258
360
|
# value type. The `?` prefix marks the field omittable; its value type already carries
|
|
259
361
|
# the schema's nullability, so nil is admitted only for a nullable field.
|
|
@@ -270,9 +372,10 @@ module BiDiGenerate
|
|
|
270
372
|
|
|
271
373
|
# A generated immutable value type (a Serialization::Record.define(...) class). discriminator is the
|
|
272
374
|
# baked variant tag {ruby_name:, wire:, value:} or nil; schema_name/synthetic/owner/
|
|
273
|
-
# nested drive owner-nesting (see nest_synthetic).
|
|
274
|
-
|
|
275
|
-
|
|
375
|
+
# nested drive owner-nesting (see nest_synthetic). spec_href links to the type's
|
|
376
|
+
# definition in the live spec (nil when the schema has none, e.g. a synthetic type).
|
|
377
|
+
TypeClass = Struct.new(:ruby_name, :fields, :discriminator, :extensible, :schema_name, :synthetic,
|
|
378
|
+
:owner, :label, :nested, :spec_href, :outbound, :inbound, keyword_init: true) do
|
|
276
379
|
def union? = false
|
|
277
380
|
def nested_types = nested || []
|
|
278
381
|
|
|
@@ -323,7 +426,9 @@ module BiDiGenerate
|
|
|
323
426
|
parts = []
|
|
324
427
|
parts << "?#{discriminator[:ruby_name]}: #{discriminator[:rbs]}" if discriminator
|
|
325
428
|
parts.concat(fields.map(&:rbs_arg))
|
|
326
|
-
|
|
429
|
+
# Match the reader type and the extensible Record impl (which calls `merge!`/`empty?` on it),
|
|
430
|
+
# so a type checker rejects a non-Hash before it crashes at serialization.
|
|
431
|
+
parts << '?extensions: Hash[String, untyped]' if extensible
|
|
327
432
|
parts.join(', ')
|
|
328
433
|
end
|
|
329
434
|
end
|
|
@@ -347,14 +452,54 @@ module BiDiGenerate
|
|
|
347
452
|
end
|
|
348
453
|
|
|
349
454
|
# A generated discriminated union (< Serialization::Union, resolved by lexical scope).
|
|
350
|
-
# nested holds its synthetic variant records (see nest_synthetic).
|
|
351
|
-
|
|
455
|
+
# nested holds its synthetic variant records (see nest_synthetic). spec_href links to
|
|
456
|
+
# the union's definition in the live spec (nil when the schema has none). object_only
|
|
457
|
+
# mirrors the schema's `objectOnly` signal: when true, a non-Hash payload is rejected
|
|
458
|
+
# rather than passed through (every arm is an object, so it can match no variant).
|
|
459
|
+
# scalar_values mirrors the schema's `scalarValues` signal: the exact literals a bare-scalar
|
|
460
|
+
# arm admits (input.Origin's "viewport" / "pointer"), so outbound rejects any other scalar.
|
|
461
|
+
UnionClass = Struct.new(:ruby_name, :discriminator_wire, :variants, :schema_name, :nested, :spec_href,
|
|
462
|
+
:object_only, :scalar_values, :outbound, :inbound, :variant_arg_sigs,
|
|
463
|
+
keyword_init: true) do
|
|
352
464
|
def union? = true
|
|
353
465
|
def value_variants = variants.select { |v| v.mode == :value }
|
|
354
466
|
def presence_variants = variants.select { |v| v.mode == :presence }
|
|
355
467
|
def fallback_variant = variants.find { |v| v.mode == :fallback }
|
|
356
468
|
def nested_types = nested || []
|
|
357
469
|
|
|
470
|
+
# A class-method factory per discriminated variant, so a caller builds the right
|
|
471
|
+
# variant record without naming its class or repeating the discriminator:
|
|
472
|
+
# `ExtensionData.path(path: '/x')` returns `ExtensionPath.new(path: '/x')`. The method
|
|
473
|
+
# name is the variant's discriminator symbol; every value variant's ref is a record, so
|
|
474
|
+
# `.new` is always defined. Presence/fallback arms are omitted (no single tag to name).
|
|
475
|
+
def variant_factories
|
|
476
|
+
value_variants.map do |variant|
|
|
477
|
+
"def self.#{BiDiGenerate.enum_key(variant.value)}(**) = #{variant.ref}.new(**)"
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# RBS for variant_factories: the variant record's own typed `new` signature (threaded in
|
|
482
|
+
# as variant_arg_sigs at build time), so a call is checked against the record's fields
|
|
483
|
+
# rather than an opaque splat; the return type pins the concrete variant.
|
|
484
|
+
def rbs_variant_factories
|
|
485
|
+
value_variants.map do |variant|
|
|
486
|
+
args = (variant_arg_sigs || {})[BiDiGenerate.enum_key(variant.value)] || '**untyped'
|
|
487
|
+
"def self.#{BiDiGenerate.enum_key(variant.value)}: (#{args}) " \
|
|
488
|
+
"-> ::Selenium::WebDriver::BiDi::Protocol::#{variant.ref}"
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# The union's RBS *value* type — the concrete types a value of this union can actually be:
|
|
493
|
+
# each variant record, plus any bare-scalar arm (input.Origin's "viewport"/"pointer"). The
|
|
494
|
+
# union class itself has no instances, so this alias (not the class) is what a field, param,
|
|
495
|
+
# or result of the union is typed to, letting a variant pass where the union is expected.
|
|
496
|
+
def rbs_value_type
|
|
497
|
+
refs = (value_variants + presence_variants + [fallback_variant].compact).map(&:ref).uniq
|
|
498
|
+
parts = refs.map { |ref| "::Selenium::WebDriver::BiDi::Protocol::#{ref}" }
|
|
499
|
+
parts += Array(scalar_values).map { |value| value.is_a?(::String) ? value.inspect : value.to_s }
|
|
500
|
+
parts.empty? ? 'untyped' : parts.join(' | ')
|
|
501
|
+
end
|
|
502
|
+
|
|
358
503
|
# `discriminator 'wire'`, or `discriminator 'wire', {sym: 'token', …}` (wrapped when
|
|
359
504
|
# long) carrying the inbound wire->symbol map for string-tagged variants.
|
|
360
505
|
def discriminator_decl(indent)
|
|
@@ -364,18 +509,45 @@ module BiDiGenerate
|
|
|
364
509
|
|
|
365
510
|
BiDiGenerate.wrap_call("#{head}, ", pairs, indent, open: '{', close: '}')
|
|
366
511
|
end
|
|
512
|
+
|
|
513
|
+
def scalar_values? = !(scalar_values.nil? || scalar_values.empty?)
|
|
514
|
+
|
|
515
|
+
# `scalar_values 'viewport', 'pointer'` — the literals a bare-scalar arm admits.
|
|
516
|
+
def scalar_values_decl
|
|
517
|
+
"scalar_values #{scalar_values.map { |v| BiDiGenerate.ruby_literal(v) }.join(', ')}"
|
|
518
|
+
end
|
|
367
519
|
end
|
|
368
520
|
|
|
369
|
-
|
|
521
|
+
# A prefix-free accessor emitted on the Domain subclass. method_name is the snake_case
|
|
522
|
+
# accessor; type_name is the local class it fronts. Three kinds route rendering: a union
|
|
523
|
+
# accessor returns the class so its variant factories dispatch; a record accessor
|
|
524
|
+
# constructs the instance directly; a vendor accessor returns a sibling vendor domain
|
|
525
|
+
# (`Moz.new(connection)`). rbs_args is the record's typed `new` signature (nil otherwise).
|
|
526
|
+
# See build_accessors / vendor_accessors.
|
|
527
|
+
Accessor = Struct.new(:method_name, :type_name, :union, :vendor, :rbs_args, keyword_init: true) do
|
|
528
|
+
def union? = union
|
|
529
|
+
def vendor? = vendor
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# spec_href links the domain's module section in the live spec (nil when unknown).
|
|
533
|
+
Module = Struct.new(:name, :ruby_class, :filename, :commands, :events, :enums, :types, :accessors,
|
|
534
|
+
:vendor_modules, :spec_href, keyword_init: true)
|
|
370
535
|
|
|
371
536
|
class Schema
|
|
372
537
|
def initialize(schema)
|
|
373
538
|
@types = schema['types']
|
|
374
539
|
@commands = schema['commands']
|
|
375
540
|
@events = schema['events']
|
|
541
|
+
@domains = schema['domains'] || {}
|
|
542
|
+
@vendor = schema['vendor'] || {}
|
|
376
543
|
promote_command_params_records!
|
|
377
544
|
end
|
|
378
545
|
|
|
546
|
+
# The domain's `#module-<domain>` spec link, or nil when the schema has none.
|
|
547
|
+
def domain_href(domain)
|
|
548
|
+
@domains.dig(domain, 'specHref')
|
|
549
|
+
end
|
|
550
|
+
|
|
379
551
|
# A command written in CDDL map form carries its params as an *inline* object (rather
|
|
380
552
|
# than the usual group form referencing a named params type). The projector links the
|
|
381
553
|
# command to those params, but hoists them into a synthetic record owned by the
|
|
@@ -412,6 +584,86 @@ module BiDiGenerate
|
|
|
412
584
|
@commands.select { |c| c['domain'] == domain }
|
|
413
585
|
end
|
|
414
586
|
|
|
587
|
+
# The domain's command param/result wrapper type names — the classes a command
|
|
588
|
+
# constructs (`params`) or parses its result into. They are reachable (so tagged
|
|
589
|
+
# outbound/inbound) but are the message wrappers a command method already builds,
|
|
590
|
+
# not data a caller composes, so they are excluded from the type accessors.
|
|
591
|
+
def command_wrapper_refs(domain)
|
|
592
|
+
commands_for(domain).flat_map { |c| [c.dig('params', 'ref'), c.dig('result', 'ref')] }.compact.to_set
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# Type names reached by at least one non-union-arm reference: used as a record field,
|
|
596
|
+
# list element, map value, or alias target somewhere — not solely as a named union's
|
|
597
|
+
# variant. A type reached only as a union arm is built through its union (a variant
|
|
598
|
+
# factory or the command's flattened dispatch), so a nested one needs no accessor; one
|
|
599
|
+
# reached as a plain field ref (browsingContext.AccessibilityLocator's `value`) does.
|
|
600
|
+
def plainly_reached_types
|
|
601
|
+
@plainly_reached_types ||= @types.each_value.with_object(Set.new) do |node, reached|
|
|
602
|
+
plain_refs(node).each { |ref| reached << ref }
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# The class path to a type relative to its domain class (an accessor body resolves in
|
|
607
|
+
# the Domain subclass scope): "ExtensionData", or "AccessibilityLocator::Value" for a
|
|
608
|
+
# synthetic nested under its owner.
|
|
609
|
+
def domain_relative_path(name)
|
|
610
|
+
prefix = "#{BiDiGenerate.snake_to_class_name(BiDiGenerate.camel_to_snake(name.split('.', 2).first))}::"
|
|
611
|
+
ruby_path(name).sub(/\A#{Regexp.escape(prefix)}/, '')
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# The vendor modules a domain carries, one per namespace (`moz` → module `Moz`). The
|
|
615
|
+
# schema's `vendor` section names, per namespace, which shared type each vendor extends;
|
|
616
|
+
# we map that type back to the command that sends it, so the vendor method mirrors the
|
|
617
|
+
# base command's wire method and result while adding the typed vendor fields. Empty for
|
|
618
|
+
# any domain (or schema) with no vendor extensions, so non-vendor output is unaffected.
|
|
619
|
+
def vendor_modules_for(domain)
|
|
620
|
+
parent = BiDiGenerate.snake_to_class_name(BiDiGenerate.camel_to_snake(domain))
|
|
621
|
+
groups = Hash.new { |h, k| h[k] = [] }
|
|
622
|
+
@vendor.each do |namespace, spec|
|
|
623
|
+
(spec['extends'] || {}).each do |type_name, entry|
|
|
624
|
+
cmd = @commands.find { |c| c.dig('params', 'ref') == type_name }
|
|
625
|
+
next unless cmd && cmd['domain'] == domain
|
|
626
|
+
|
|
627
|
+
groups[namespace] << build_vendor_command(cmd, type_name, entry, namespace)
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
groups.map do |namespace, commands|
|
|
631
|
+
VendorModule.new(name: BiDiGenerate.snake_to_class_name(namespace), namespace: namespace, parent: parent,
|
|
632
|
+
commands: commands)
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
def build_vendor_command(cmd, type_name, entry, namespace)
|
|
637
|
+
shared = record_params(@types[type_name]['fields'])
|
|
638
|
+
taken = shared.map(&:ruby_name)
|
|
639
|
+
VendorCommand.new(
|
|
640
|
+
method_name: BiDiGenerate.safe_method_name(BiDiGenerate.camel_to_snake(cmd['name'])),
|
|
641
|
+
wire_name: cmd['method'],
|
|
642
|
+
result_ref: cmd['result'] && structured_ref(cmd['result']['ref']),
|
|
643
|
+
params_class: BiDiGenerate.type_class_name(type_name),
|
|
644
|
+
shared_params: shared,
|
|
645
|
+
vendor_params: entry['fields'].map { |field| vendor_param(field, namespace, taken) },
|
|
646
|
+
spec_href: cmd['specHref']
|
|
647
|
+
)
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# A vendor field's ruby name drops its namespace prefix (`moz:permanent` → permanent): the
|
|
651
|
+
# module already scopes it, so re-encoding the namespace in every identifier is redundant. The
|
|
652
|
+
# wire key is untouched. Falls back to the prefixed name only if stripping would collide with a
|
|
653
|
+
# shared param on the same command.
|
|
654
|
+
def vendor_param(field, namespace, taken)
|
|
655
|
+
stripped = field['name'].sub(/\A#{Regexp.escape(namespace)}:/, '')
|
|
656
|
+
ruby_name = BiDiGenerate.safe_field_name(BiDiGenerate.camel_to_snake(stripped))
|
|
657
|
+
ruby_name = BiDiGenerate.safe_field_name(BiDiGenerate.camel_to_snake(field['name'])) if taken.include?(ruby_name)
|
|
658
|
+
Param.new(
|
|
659
|
+
ruby_name: ruby_name,
|
|
660
|
+
wire_name: field['wire'],
|
|
661
|
+
required: field['required'],
|
|
662
|
+
enum: enum_const(field['type']),
|
|
663
|
+
rbs: rbs_type(field['type'])
|
|
664
|
+
)
|
|
665
|
+
end
|
|
666
|
+
|
|
415
667
|
def type_kind(ref)
|
|
416
668
|
@types[ref]&.fetch('kind', nil)
|
|
417
669
|
end
|
|
@@ -443,10 +695,17 @@ module BiDiGenerate
|
|
|
443
695
|
next unless name.start_with?("#{domain}.")
|
|
444
696
|
|
|
445
697
|
pairs = type['values'].map { |v| [BiDiGenerate.enum_key(v), v.to_s] }
|
|
446
|
-
Enum.new(constant_name: BiDiGenerate.screaming_snake(name.sub("#{domain}.", '')), pairs: pairs
|
|
698
|
+
Enum.new(constant_name: BiDiGenerate.screaming_snake(name.sub("#{domain}.", '')), pairs: pairs,
|
|
699
|
+
spec_href: type['specHref'])
|
|
447
700
|
end
|
|
448
701
|
end
|
|
449
702
|
|
|
703
|
+
# The protocol-root ErrorCode enum's wire values (e.g. "no such frame"), in schema order.
|
|
704
|
+
# Used to generate the BiDi-specific Error subclasses. [] when the schema has no ErrorCode.
|
|
705
|
+
def error_codes
|
|
706
|
+
@types.dig('ErrorCode', 'values') || []
|
|
707
|
+
end
|
|
708
|
+
|
|
450
709
|
# Structured value classes (records + discriminated unions) declared under
|
|
451
710
|
# "<domain>." Empty records are projector artifacts with nothing to carry, so
|
|
452
711
|
# they stay opaque hashes; only non-empty records and unions become classes.
|
|
@@ -494,12 +753,43 @@ module BiDiGenerate
|
|
|
494
753
|
resolved[:list] ? nil : resolved[:ref]
|
|
495
754
|
end
|
|
496
755
|
|
|
756
|
+
# Public ruby-path resolver (`Owner::Label` for a synthetic), matching how a variant's
|
|
757
|
+
# ref is emitted — so a caller can map a variant ref back to its emitted record.
|
|
758
|
+
def ruby_path_for(name) = ruby_path(name)
|
|
759
|
+
|
|
497
760
|
private
|
|
498
761
|
|
|
499
762
|
def domain_path(name)
|
|
500
763
|
name.include?('.') ? ruby_path(name) : nil
|
|
501
764
|
end
|
|
502
765
|
|
|
766
|
+
# The refs a node exposes through a NON-arm position: a record's fields and map value,
|
|
767
|
+
# or an alias's target. A named union contributes none — its variants are arm positions
|
|
768
|
+
# (built through the union), so they do not count toward plainly_reached_types.
|
|
769
|
+
def plain_refs(node)
|
|
770
|
+
case node['kind']
|
|
771
|
+
when 'record'
|
|
772
|
+
refs = node['fields'].flat_map { |f| refs_in_type(f['type']) }
|
|
773
|
+
node['map'] ? refs + refs_in_type(node['map']) : refs
|
|
774
|
+
when 'alias' then refs_in_type(node['type'])
|
|
775
|
+
else []
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
# Every type name a *type expression* references (mirrors the projector's refsInType),
|
|
780
|
+
# descending list element, map value, inline union arms, and inline record fields. An
|
|
781
|
+
# inline union arm inside a field is a plain position — the field is filled with it.
|
|
782
|
+
def refs_in_type(node)
|
|
783
|
+
return [] unless node
|
|
784
|
+
return [node['ref']] if node['ref']
|
|
785
|
+
return refs_in_type(node['list']) if node['list']
|
|
786
|
+
return refs_in_type(node['map']) if node['map']
|
|
787
|
+
return node['union'].flat_map { |arm| refs_in_type(arm) } if node['union']
|
|
788
|
+
return node['record'].flat_map { |f| refs_in_type(f['type']) } if node['record']
|
|
789
|
+
|
|
790
|
+
[]
|
|
791
|
+
end
|
|
792
|
+
|
|
503
793
|
# Class path, nesting a synthetic type under its owner as `Owner::Label` so a ref
|
|
504
794
|
# resolves to the same nested constant the type is emitted as.
|
|
505
795
|
def ruby_path(name)
|
|
@@ -520,29 +810,33 @@ module BiDiGenerate
|
|
|
520
810
|
nullable = node['nullable'] ? true : false
|
|
521
811
|
if node.key?('list')
|
|
522
812
|
element = resolve(node['list'])
|
|
523
|
-
return {ref: element[:ref], list: true, nullable: nullable,
|
|
813
|
+
return {ref: element[:ref], list: true, nullable: nullable, scalar: element[:scalar],
|
|
814
|
+
rbs: nilable("Array[#{element[:rbs]}]", nullable)}
|
|
524
815
|
end
|
|
525
816
|
if node.key?('ref')
|
|
526
817
|
named = resolve_named(node['ref'])
|
|
527
|
-
return {ref: named[:ref], list: named[:list], nullable: nullable,
|
|
818
|
+
return {ref: named[:ref], list: named[:list], nullable: nullable, scalar: named[:scalar],
|
|
819
|
+
rbs: nilable(named[:rbs], nullable)}
|
|
528
820
|
end
|
|
529
821
|
return resolve_union(node, nullable) if node.key?('union')
|
|
530
822
|
|
|
531
|
-
{ref: nil, list: false, nullable: nullable,
|
|
532
|
-
rbs: nilable(scalar_rbs(node), nullable)}
|
|
823
|
+
{ref: nil, list: false, nullable: nullable, rbs: nilable(scalar_rbs(node), nullable)}
|
|
533
824
|
end
|
|
534
825
|
|
|
535
826
|
# An inline union of one union-typed arm plus scalars (e.g. a MappingRemoteValue entry,
|
|
536
|
-
# RemoteValue / string)
|
|
537
|
-
#
|
|
538
|
-
#
|
|
827
|
+
# RemoteValue / string) is carried as that union ref so nested entries are typed. Because
|
|
828
|
+
# the union is object_only, a bare-scalar sibling would raise there — so the projector's
|
|
829
|
+
# `scalar` signal (a bare-scalar arm is present) is forwarded, and the runtime passes a
|
|
830
|
+
# non-object leaf through instead (the map's string keys). Any other shape (a record arm,
|
|
831
|
+
# multiple structured arms, all scalars) stays opaque.
|
|
539
832
|
def resolve_union(node, nullable)
|
|
540
833
|
refs = node['union'].select { |arm| arm.key?('ref') }
|
|
541
834
|
opaque = {ref: nil, list: false, nullable: nullable, rbs: nilable('untyped', nullable)}
|
|
542
835
|
return opaque unless refs.one? && union_ref?(refs.first['ref'])
|
|
543
836
|
|
|
544
837
|
named = resolve_named(refs.first['ref'])
|
|
545
|
-
{ref: named[:ref], list: named[:list], nullable: nullable,
|
|
838
|
+
{ref: named[:ref], list: named[:list], nullable: nullable, scalar: node['scalar'],
|
|
839
|
+
rbs: nilable('untyped', nullable)}
|
|
546
840
|
end
|
|
547
841
|
|
|
548
842
|
# True when a ref (following aliases) is a union — the only arm whose from_json tolerates a
|
|
@@ -569,7 +863,7 @@ module BiDiGenerate
|
|
|
569
863
|
|
|
570
864
|
case type['kind']
|
|
571
865
|
when 'record' then type['fields'].empty? ? OPAQUE : named_type(name)
|
|
572
|
-
when 'union' then
|
|
866
|
+
when 'union' then named_union(name)
|
|
573
867
|
when 'enum' then {ref: nil, list: false, rbs: 'Symbol'}
|
|
574
868
|
when 'alias' then resolve_named_alias(name, type['type'], seen)
|
|
575
869
|
else OPAQUE
|
|
@@ -583,13 +877,29 @@ module BiDiGenerate
|
|
|
583
877
|
{ref: domain_path(name), list: false, rbs: rbs_abs(ruby_path(name))}
|
|
584
878
|
end
|
|
585
879
|
|
|
880
|
+
# Like named_type, but a union is typed to its value alias (variant | variant | …), not
|
|
881
|
+
# its class — the class has no instances, so a variant must be assignable where the union
|
|
882
|
+
# is expected. The serialization ref is unchanged (still the union that dispatches inbound).
|
|
883
|
+
def named_union(name)
|
|
884
|
+
{ref: domain_path(name), list: false, rbs: union_alias_path(name)}
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
# Absolute RBS path of a union's value alias: its class path with the last segment
|
|
888
|
+
# snake-cased (WebExtension::ExtensionData -> ...::WebExtension::extension_data), matching
|
|
889
|
+
# the `type` alias emitted alongside the class.
|
|
890
|
+
def union_alias_path(name)
|
|
891
|
+
segments = ruby_path(name).split('::')
|
|
892
|
+
segments[-1] = BiDiGenerate.camel_to_snake(segments[-1])
|
|
893
|
+
rbs_abs(segments.join('::'))
|
|
894
|
+
end
|
|
895
|
+
|
|
586
896
|
def resolve_named_alias(name, inner, seen)
|
|
587
|
-
return
|
|
897
|
+
return named_union(name) if inner.key?('union')
|
|
588
898
|
return resolve_named(inner['ref'], seen) if inner.key?('ref')
|
|
589
899
|
|
|
590
900
|
if inner.key?('list')
|
|
591
901
|
element = resolve(inner['list'])
|
|
592
|
-
return {ref: element[:ref], list: true, rbs: "Array[#{element[:rbs]}]"}
|
|
902
|
+
return {ref: element[:ref], list: true, scalar: element[:scalar], rbs: "Array[#{element[:rbs]}]"}
|
|
593
903
|
end
|
|
594
904
|
|
|
595
905
|
{ref: nil, list: false, rbs: scalar_rbs(inner)}
|
|
@@ -599,16 +909,27 @@ module BiDiGenerate
|
|
|
599
909
|
flag ? BiDiGenerate.rbs_nilable(type) : type
|
|
600
910
|
end
|
|
601
911
|
|
|
912
|
+
# The type's send/receive tags (schema `outbound`/`inbound`) as constructor kwargs,
|
|
913
|
+
# coerced to plain booleans — shared by every structured-type builder.
|
|
914
|
+
def directionality(name)
|
|
915
|
+
node = @types[name]
|
|
916
|
+
{outbound: node['outbound'] ? true : false, inbound: node['inbound'] ? true : false}
|
|
917
|
+
end
|
|
918
|
+
|
|
602
919
|
def record_class(name, type)
|
|
603
920
|
const = type['fields'].find { |f| baked_discriminator?(f) }
|
|
604
921
|
discriminator = const && {ruby_name: BiDiGenerate.safe_field_name(BiDiGenerate.camel_to_snake(const['name'])),
|
|
605
922
|
wire: const['wire'], value: const['type']['const'],
|
|
606
923
|
rbs: rbs_const(const['type']['const'])}
|
|
607
924
|
fields = type['fields'].reject { |f| baked_discriminator?(f) }.map { |f| field_ir(f) }
|
|
925
|
+
# Every extensible type gets the extensions store: an undeclared wire key is preserved
|
|
926
|
+
# and echoed back on any type the spec marks extensible, whether or not it is re-sendable.
|
|
927
|
+
# Extensibility alone is the signal; send-reachability does not enter into it.
|
|
608
928
|
TypeClass.new(ruby_name: BiDiGenerate.type_class_name(name), fields: fields,
|
|
609
929
|
discriminator: discriminator, extensible: type['extensible'] ? true : false,
|
|
610
930
|
schema_name: name, synthetic: type['synthetic'] ? true : false,
|
|
611
|
-
owner: type['owner'], label: type['label']
|
|
931
|
+
owner: type['owner'], label: type['label'], spec_href: type['specHref'],
|
|
932
|
+
**directionality(name))
|
|
612
933
|
end
|
|
613
934
|
|
|
614
935
|
# A const field is a baked discriminator tag, unless it is also nullable: the spec's
|
|
@@ -625,7 +946,41 @@ module BiDiGenerate
|
|
|
625
946
|
FieldIR.new(ruby_name: ruby_name, wire_key: field['wire'],
|
|
626
947
|
required: field['required'], nullable: resolved[:nullable],
|
|
627
948
|
ref: resolved[:ref], list: resolved[:list], enum: enum_const(field['type']),
|
|
628
|
-
primitive:
|
|
949
|
+
primitive: leaf_primitive(field['type']), scalar: resolved[:scalar],
|
|
950
|
+
const: leaf_const(field['type']), rbs: resolved[:rbs])
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
# The literal value of a const field, following alias chains, so the runtime can reject a
|
|
954
|
+
# value that is neither the literal nor null (a `literal / null` param such as
|
|
955
|
+
# emulation.setScriptingEnabled's `enabled`). Nil for any non-const node — const literals are
|
|
956
|
+
# never nil, so nil unambiguously means "no const" (a null value is carried by `nullable`).
|
|
957
|
+
def leaf_const(node, seen = {})
|
|
958
|
+
return node['const'] if node.key?('const')
|
|
959
|
+
return nil unless node.key?('ref')
|
|
960
|
+
|
|
961
|
+
name = node['ref']
|
|
962
|
+
type = @types[name]
|
|
963
|
+
return nil if seen[name] || type.nil? || type['kind'] != 'alias'
|
|
964
|
+
|
|
965
|
+
seen[name] = true
|
|
966
|
+
leaf_const(type['type'], seen)
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
# The runtime-checkable scalar primitive of a field, following alias chains so a
|
|
970
|
+
# scalar hidden behind a named alias (js-uint -> integer, browsingContext.BrowsingContext
|
|
971
|
+
# -> string) is typed rather than opaque. The projector carries the primitive on the
|
|
972
|
+
# alias node; this surfaces it onto the field. Nil for a list (its elements are not
|
|
973
|
+
# scalar-checked), a record/union ref, an enum, a const, or an opaque value.
|
|
974
|
+
def leaf_primitive(node, seen = {})
|
|
975
|
+
return node['primitive'] if node.key?('primitive') && CHECKABLE_PRIMITIVES.include?(node['primitive'])
|
|
976
|
+
return nil unless node.key?('ref')
|
|
977
|
+
|
|
978
|
+
name = node['ref']
|
|
979
|
+
type = @types[name]
|
|
980
|
+
return nil if seen[name] || type.nil? || type['kind'] != 'alias'
|
|
981
|
+
|
|
982
|
+
seen[name] = true
|
|
983
|
+
leaf_primitive(type['type'], seen)
|
|
629
984
|
end
|
|
630
985
|
|
|
631
986
|
def union_class(name)
|
|
@@ -635,7 +990,15 @@ module BiDiGenerate
|
|
|
635
990
|
# order); consume it rather than re-deriving and silently depending on emit
|
|
636
991
|
# order. An alias-to-union (only input.Origin) has no selector — its const-string
|
|
637
992
|
# arms aren't first-class types — so it keeps the structural re-derivation.
|
|
638
|
-
type['kind'] == 'union' ? union_from_selector(name, type['selector']) : union_from_alias(name)
|
|
993
|
+
klass = type['kind'] == 'union' ? union_from_selector(name, type['selector']) : union_from_alias(name)
|
|
994
|
+
# A non-object_only union has a bare-scalar arm; only const-literal arms (scalar_values) are
|
|
995
|
+
# modeled, so the runtime can validate an outbound scalar. A non-object_only union without them
|
|
996
|
+
# is a shape the generator doesn't yet handle — fail here, at generation, not at a caller's runtime.
|
|
997
|
+
if !klass.object_only && !klass.scalar_values?
|
|
998
|
+
raise "non-object_only union #{name} has no scalar_values to validate its bare-scalar arm"
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
klass
|
|
639
1002
|
end
|
|
640
1003
|
|
|
641
1004
|
# Map a union `selector` to dispatch variants the template renders:
|
|
@@ -658,7 +1021,9 @@ module BiDiGenerate
|
|
|
658
1021
|
raise "union #{name} selector yielded no dispatch variants" if variants.empty?
|
|
659
1022
|
|
|
660
1023
|
UnionClass.new(ruby_name: BiDiGenerate.type_class_name(name),
|
|
661
|
-
discriminator_wire: selector['by'], variants: variants, schema_name: name
|
|
1024
|
+
discriminator_wire: selector['by'], variants: variants, schema_name: name,
|
|
1025
|
+
spec_href: @types[name]['specHref'], object_only: @types[name]['objectOnly'] ? true : false,
|
|
1026
|
+
**directionality(name))
|
|
662
1027
|
end
|
|
663
1028
|
|
|
664
1029
|
def discriminated_variants(selector)
|
|
@@ -682,7 +1047,8 @@ module BiDiGenerate
|
|
|
682
1047
|
# discriminator; the bare-string arms need no dispatch (Union.from_json returns a
|
|
683
1048
|
# non-Hash payload unchanged). So dispatch the ref arms by their const tag.
|
|
684
1049
|
def union_from_alias(name)
|
|
685
|
-
|
|
1050
|
+
spec = @types[name]
|
|
1051
|
+
consts = spec['type']['union'].filter_map { |arm| arm['ref'] }.to_h do |ref|
|
|
686
1052
|
const = @types[ref]['fields'].find { |f| f['type'].key?('const') }
|
|
687
1053
|
const || raise("alias-union #{name} arm #{ref} has no const discriminator to dispatch on")
|
|
688
1054
|
[ref, const]
|
|
@@ -690,8 +1056,13 @@ module BiDiGenerate
|
|
|
690
1056
|
variants = consts.map do |ref, const|
|
|
691
1057
|
VariantIR.new(mode: :value, value: const['type']['const'], ref: ruby_path(ref), requires: nil)
|
|
692
1058
|
end
|
|
1059
|
+
# An alias-union carries bare-scalar arms (input.Origin's "viewport"/"pointer"), so it
|
|
1060
|
+
# is never object_only — those arms must still pass a non-Hash payload through, but only
|
|
1061
|
+
# a value the schema pins in scalarValues (so a stray "banana" is still rejected outbound).
|
|
693
1062
|
UnionClass.new(ruby_name: BiDiGenerate.type_class_name(name),
|
|
694
|
-
discriminator_wire: consts.values.first['wire'], variants: variants, schema_name: name
|
|
1063
|
+
discriminator_wire: consts.values.first['wire'], variants: variants, schema_name: name,
|
|
1064
|
+
spec_href: spec['specHref'], object_only: spec['objectOnly'] ? true : false,
|
|
1065
|
+
scalar_values: spec['type']['scalarValues'], **directionality(name))
|
|
695
1066
|
end
|
|
696
1067
|
|
|
697
1068
|
def record_params(fields)
|
|
@@ -723,10 +1094,6 @@ module BiDiGenerate
|
|
|
723
1094
|
# check fails open rather than a wrong strict default rejecting valid data.
|
|
724
1095
|
CHECKABLE_PRIMITIVES = %w[string number integer boolean].freeze
|
|
725
1096
|
|
|
726
|
-
def checkable_primitive(node)
|
|
727
|
-
node['primitive'] if node.key?('primitive') && CHECKABLE_PRIMITIVES.include?(node['primitive'])
|
|
728
|
-
end
|
|
729
|
-
|
|
730
1097
|
# The leaf of +resolve+: the bare scalar type, before any nullable wrap. An alias's
|
|
731
1098
|
# own nullable is intentionally left off — only the referencing node's is applied.
|
|
732
1099
|
def scalar_rbs(node)
|
|
@@ -835,15 +1202,95 @@ module BiDiGenerate
|
|
|
835
1202
|
|
|
836
1203
|
def self.build_ir(schema)
|
|
837
1204
|
schema.domains.map do |domain|
|
|
838
|
-
|
|
1205
|
+
types = schema.types_for(domain)
|
|
1206
|
+
thread_variant_arg_sigs(schema, types)
|
|
1207
|
+
vendor_modules = schema.vendor_modules_for(domain)
|
|
1208
|
+
mod = Module.new(
|
|
839
1209
|
name: domain,
|
|
840
1210
|
ruby_class: snake_to_class_name(camel_to_snake(domain)),
|
|
841
1211
|
filename: camel_to_snake(domain),
|
|
842
1212
|
commands: schema.commands_for(domain).map { |cmd| build_command(schema, cmd) },
|
|
843
1213
|
events: schema.events_for(domain).map { |ev| build_event(schema, ev) },
|
|
844
1214
|
enums: schema.enums_for(domain),
|
|
845
|
-
|
|
1215
|
+
accessors: build_accessors(schema, domain, types) + vendor_accessors(vendor_modules),
|
|
1216
|
+
types: nest_synthetic(types),
|
|
1217
|
+
vendor_modules: vendor_modules,
|
|
1218
|
+
spec_href: schema.domain_href(domain)
|
|
846
1219
|
)
|
|
1220
|
+
check_accessor_collisions!(mod)
|
|
1221
|
+
mod
|
|
1222
|
+
end
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
# An accessor per vendor variant, returning a sibling vendor domain over the same connection
|
|
1226
|
+
# (`web_extension.moz` -> `Moz.new(connection)`). Named after the vendor namespace.
|
|
1227
|
+
def self.vendor_accessors(vendor_modules)
|
|
1228
|
+
vendor_modules.map do |vendor_module|
|
|
1229
|
+
Accessor.new(method_name: safe_method_name(vendor_module.namespace), type_name: vendor_module.name,
|
|
1230
|
+
union: false, vendor: true)
|
|
1231
|
+
end
|
|
1232
|
+
end
|
|
1233
|
+
|
|
1234
|
+
# Give each union its variants' typed `new` signatures, keyed by factory method name, so
|
|
1235
|
+
# rbs_variant_factories can emit a checked signature instead of a splat. Keyed by ruby
|
|
1236
|
+
# path (the form a variant ref carries); a cross-module variant not in this list falls
|
|
1237
|
+
# back to `**untyped`.
|
|
1238
|
+
def self.thread_variant_arg_sigs(schema, types)
|
|
1239
|
+
record_sigs = types.reject(&:union?).to_h { |t| [schema.ruby_path_for(t.schema_name), t.rbs_new_args] }
|
|
1240
|
+
types.select(&:union?).each do |union|
|
|
1241
|
+
union.variant_arg_sigs = union.value_variants.to_h do |variant|
|
|
1242
|
+
[BiDiGenerate.enum_key(variant.value), record_sigs[variant.ref]]
|
|
1243
|
+
end
|
|
1244
|
+
end
|
|
1245
|
+
end
|
|
1246
|
+
|
|
1247
|
+
# Outbound-scoped domain accessors: for every emitted type a caller constructs to send,
|
|
1248
|
+
# a prefix-free constructor on the Domain subclass. Built from the pre-nesting type list
|
|
1249
|
+
# so a nested synthetic (referenced by a Ruby-relative `Owner::Label` path) is reachable.
|
|
1250
|
+
def self.build_accessors(schema, domain, types)
|
|
1251
|
+
wrappers = schema.command_wrapper_refs(domain)
|
|
1252
|
+
plainly_reached = schema.plainly_reached_types
|
|
1253
|
+
types.select { |t| accessor?(t, wrappers, plainly_reached) }.map do |t|
|
|
1254
|
+
Accessor.new(method_name: safe_method_name(camel_to_snake(type_class_name(t.schema_name))),
|
|
1255
|
+
type_name: schema.domain_relative_path(t.schema_name), union: t.union?,
|
|
1256
|
+
rbs_args: t.union? ? nil : t.rbs_new_args)
|
|
1257
|
+
end
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
# A type earns a send-side accessor when it is outbound and not a command param/result
|
|
1261
|
+
# wrapper (a command method already builds those). A nested-away synthetic reached only
|
|
1262
|
+
# as a union arm is excluded — it is built through its union (a variant factory or the
|
|
1263
|
+
# command's flattened dispatch), never standalone. A top-level union variant record keeps
|
|
1264
|
+
# its accessor (the plan constructs it directly, e.g. extension_path), as does a synthetic
|
|
1265
|
+
# reached by a plain field ref (browsingContext.AccessibilityLocator's `value`).
|
|
1266
|
+
def self.accessor?(type, wrappers, plainly_reached)
|
|
1267
|
+
return false unless type.outbound
|
|
1268
|
+
return false if wrappers.include?(type.schema_name)
|
|
1269
|
+
|
|
1270
|
+
nested_synthetic = !type.union? && type.synthetic
|
|
1271
|
+
!nested_synthetic || plainly_reached.include?(type.schema_name)
|
|
1272
|
+
end
|
|
1273
|
+
|
|
1274
|
+
# Public instance methods every accessor would shadow if it reused their name:
|
|
1275
|
+
# Domain's own (`execute`/`initialize`) plus everything Object/Kernel expose. The
|
|
1276
|
+
# collision guard fails generation before a schema-driven shadow can ship.
|
|
1277
|
+
INHERITED_INSTANCE_METHODS = (%w[execute initialize].to_set + Object.instance_methods.to_set(&:to_s)).freeze
|
|
1278
|
+
|
|
1279
|
+
# Fail generation if an accessor name would collide with a command method, an
|
|
1280
|
+
# inherited method, or another accessor — turning a future shadow into a build error
|
|
1281
|
+
# rather than a silently overridden method.
|
|
1282
|
+
def self.check_accessor_collisions!(mod)
|
|
1283
|
+
commands = mod.commands.to_set(&:method_name)
|
|
1284
|
+
seen = {}
|
|
1285
|
+
mod.accessors.each do |accessor|
|
|
1286
|
+
name = accessor.method_name
|
|
1287
|
+
clash = if commands.include?(name) then 'a command method'
|
|
1288
|
+
elsif INHERITED_INSTANCE_METHODS.include?(name) then 'an inherited method'
|
|
1289
|
+
elsif seen[name] then "the accessor for #{seen[name]}"
|
|
1290
|
+
end
|
|
1291
|
+
raise "accessor #{mod.ruby_class}##{name} collides with #{clash}" if clash
|
|
1292
|
+
|
|
1293
|
+
seen[name] = accessor.type_name
|
|
847
1294
|
end
|
|
848
1295
|
end
|
|
849
1296
|
|
|
@@ -864,7 +1311,8 @@ module BiDiGenerate
|
|
|
864
1311
|
params: params,
|
|
865
1312
|
result_ref: cmd['result'] && schema.structured_ref(cmd['result']['ref']),
|
|
866
1313
|
params_class: params_class,
|
|
867
|
-
union_params: params_kind == 'union'
|
|
1314
|
+
union_params: params_kind == 'union',
|
|
1315
|
+
spec_href: cmd['specHref']
|
|
868
1316
|
)
|
|
869
1317
|
end
|
|
870
1318
|
|
|
@@ -891,6 +1339,8 @@ module BiDiGenerate
|
|
|
891
1339
|
end
|
|
892
1340
|
|
|
893
1341
|
def self.render(mod, template_path)
|
|
1342
|
+
generated_note = GeneratedNote.render('#', 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb',
|
|
1343
|
+
'bazel run //rb/lib/selenium/webdriver:bidi-generate')
|
|
894
1344
|
ERB.new(File.read(template_path), trim_mode: '-').result(binding)
|
|
895
1345
|
end
|
|
896
1346
|
|
|
@@ -901,6 +1351,40 @@ module BiDiGenerate
|
|
|
901
1351
|
|
|
902
1352
|
emit(modules, output_dir, 'module.rb.erb', 'rb')
|
|
903
1353
|
emit(modules, sig_dir(output_dir), 'module.rbs.erb', 'rbs')
|
|
1354
|
+
emit_error_module(schema, output_dir)
|
|
1355
|
+
end
|
|
1356
|
+
|
|
1357
|
+
# The ErrorCode wire values mapped to their Ruby exception class names (schema order), e.g.
|
|
1358
|
+
# "no such node" => "NoSuchNodeError". This is the schema->Ruby translation: the generated file
|
|
1359
|
+
# carries the Ruby names, and a hand-written pass turns them into WebDriverError subclasses under
|
|
1360
|
+
# the shared Error namespace. Self-contained — no reference to the classic error module.
|
|
1361
|
+
def self.error_code_map(schema)
|
|
1362
|
+
schema.error_codes.map { |code| [code, error_class_name(code)] }
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
# WebDriver error-code string -> exception class name, matching Error.for_error's convention
|
|
1366
|
+
# ("no such node" -> NoSuchNodeError). The Error suffix is normalized (not doubled) for a code
|
|
1367
|
+
# already ending in "error" ("unknown error" -> UnknownError).
|
|
1368
|
+
def self.error_class_name(code)
|
|
1369
|
+
"#{code.split.map(&:capitalize).join.sub(/Error$/, '')}Error"
|
|
1370
|
+
end
|
|
1371
|
+
|
|
1372
|
+
# Writes protocol/error_code.rb (+ its .rbs), the Protocol::ErrorCode map, into the same protocol
|
|
1373
|
+
# dir as the generated domain files.
|
|
1374
|
+
def self.emit_error_module(schema, output_dir)
|
|
1375
|
+
codes = error_code_map(schema)
|
|
1376
|
+
mod = ErrorModule.new(filename: 'error_code', codes: codes, new_classes: bidi_only_classes(codes))
|
|
1377
|
+
emit([mod], output_dir, 'error_code.rb.erb', 'rb')
|
|
1378
|
+
emit([mod], sig_dir(output_dir), 'error_code.rbs.erb', 'rbs')
|
|
1379
|
+
end
|
|
1380
|
+
|
|
1381
|
+
# Class names among `codes` the classic Error module does not already define — the BiDi-only codes
|
|
1382
|
+
# bidi/error.rb registers and whose RBS this file must declare. Shared codes already have RBS in
|
|
1383
|
+
# common/error.rbs, so re-declaring them would duplicate the classic signatures. Only the RBS needs
|
|
1384
|
+
# this split; the emitted map (error_code.rb) stays the full self-contained set.
|
|
1385
|
+
def self.bidi_only_classes(codes)
|
|
1386
|
+
require_relative '../../common/error'
|
|
1387
|
+
codes.filter_map { |_wire, name| name unless ::Selenium::WebDriver::Error.const_defined?(name, false) }
|
|
904
1388
|
end
|
|
905
1389
|
|
|
906
1390
|
# Renders every module through one template and writes the result into target,
|