selenium-webdriver 4.49.0 → 4.51.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.
Files changed (219) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +20 -0
  3. data/Gemfile +0 -0
  4. data/LICENSE +0 -0
  5. data/NOTICE +0 -0
  6. data/README.md +0 -0
  7. data/bin/linux-arm64/selenium-manager +0 -0
  8. data/bin/linux-x86_64/selenium-manager +0 -0
  9. data/bin/macos/selenium-manager +0 -0
  10. data/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +77 -46
  11. data/bin/selenium-manager.cdx.json +475 -475
  12. data/bin/windows/selenium-manager.exe +0 -0
  13. data/lib/selenium/server.rb +0 -0
  14. data/lib/selenium/webdriver/atoms/findElements.js +19 -4
  15. data/lib/selenium/webdriver/atoms/getAttribute.js +5 -2
  16. data/lib/selenium/webdriver/atoms/isDisplayed.js +5 -2
  17. data/lib/selenium/webdriver/atoms.rb +0 -0
  18. data/lib/selenium/webdriver/bidi/browser.rb +0 -0
  19. data/lib/selenium/webdriver/bidi/browsing_context.rb +0 -0
  20. data/lib/selenium/webdriver/bidi/error.rb +0 -0
  21. data/lib/selenium/webdriver/bidi/log_handler.rb +0 -0
  22. data/lib/selenium/webdriver/bidi/network/cookies.rb +0 -0
  23. data/lib/selenium/webdriver/bidi/network/credentials.rb +0 -0
  24. data/lib/selenium/webdriver/bidi/network/headers.rb +0 -0
  25. data/lib/selenium/webdriver/bidi/network/intercepted_auth.rb +0 -0
  26. data/lib/selenium/webdriver/bidi/network/intercepted_item.rb +0 -0
  27. data/lib/selenium/webdriver/bidi/network/intercepted_request.rb +0 -0
  28. data/lib/selenium/webdriver/bidi/network/intercepted_response.rb +0 -0
  29. data/lib/selenium/webdriver/bidi/network/url_pattern.rb +0 -0
  30. data/lib/selenium/webdriver/bidi/network.rb +0 -0
  31. data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +27 -23
  32. data/lib/selenium/webdriver/bidi/protocol/browser.rb +0 -0
  33. data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +58 -4
  34. data/lib/selenium/webdriver/bidi/protocol/digital_credentials.rb +69 -0
  35. data/lib/selenium/webdriver/bidi/protocol/domain.rb +0 -0
  36. data/lib/selenium/webdriver/bidi/protocol/emulation.rb +31 -0
  37. data/lib/selenium/webdriver/bidi/protocol/error_code.rb +0 -0
  38. data/lib/selenium/webdriver/bidi/protocol/input.rb +0 -0
  39. data/lib/selenium/webdriver/bidi/protocol/log.rb +0 -0
  40. data/lib/selenium/webdriver/bidi/protocol/moz_debugging.rb +334 -0
  41. data/lib/selenium/webdriver/bidi/protocol/moz_profiler.rb +108 -0
  42. data/lib/selenium/webdriver/bidi/protocol/network.rb +0 -0
  43. data/lib/selenium/webdriver/bidi/protocol/permissions.rb +0 -0
  44. data/lib/selenium/webdriver/bidi/protocol/script.rb +1 -1
  45. data/lib/selenium/webdriver/bidi/protocol/session.rb +21 -20
  46. data/lib/selenium/webdriver/bidi/protocol/speculation.rb +0 -0
  47. data/lib/selenium/webdriver/bidi/protocol/storage.rb +0 -0
  48. data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +0 -0
  49. data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +41 -5
  50. data/lib/selenium/webdriver/bidi/protocol.rb +3 -0
  51. data/lib/selenium/webdriver/bidi/serialization/record.rb +35 -2
  52. data/lib/selenium/webdriver/bidi/serialization/union.rb +0 -0
  53. data/lib/selenium/webdriver/bidi/serialization.rb +0 -0
  54. data/lib/selenium/webdriver/bidi/session.rb +0 -0
  55. data/lib/selenium/webdriver/bidi/struct.rb +0 -0
  56. data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +178 -42
  57. data/lib/selenium/webdriver/bidi/support/check_generated.rb +0 -0
  58. data/lib/selenium/webdriver/bidi/transport.rb +0 -0
  59. data/lib/selenium/webdriver/bidi.rb +0 -0
  60. data/lib/selenium/webdriver/chrome/driver.rb +0 -0
  61. data/lib/selenium/webdriver/chrome/features.rb +0 -0
  62. data/lib/selenium/webdriver/chrome/options.rb +0 -0
  63. data/lib/selenium/webdriver/chrome/profile.rb +0 -0
  64. data/lib/selenium/webdriver/chrome/service.rb +0 -0
  65. data/lib/selenium/webdriver/chrome.rb +0 -0
  66. data/lib/selenium/webdriver/chromium/driver.rb +0 -0
  67. data/lib/selenium/webdriver/chromium/features.rb +0 -0
  68. data/lib/selenium/webdriver/chromium/options.rb +0 -0
  69. data/lib/selenium/webdriver/chromium/profile.rb +0 -0
  70. data/lib/selenium/webdriver/chromium.rb +0 -0
  71. data/lib/selenium/webdriver/common/action_builder.rb +0 -0
  72. data/lib/selenium/webdriver/common/alert.rb +0 -0
  73. data/lib/selenium/webdriver/common/child_process.rb +0 -0
  74. data/lib/selenium/webdriver/common/client_config.rb +0 -0
  75. data/lib/selenium/webdriver/common/driver.rb +24 -0
  76. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -0
  77. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +0 -0
  78. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +2 -0
  79. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +0 -0
  80. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +0 -0
  81. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +0 -0
  82. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +0 -0
  83. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +0 -0
  84. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -0
  85. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -0
  86. data/lib/selenium/webdriver/common/driver_extensions/has_fedcm_dialog.rb +0 -0
  87. data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +0 -0
  88. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +0 -0
  89. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +0 -0
  90. data/lib/selenium/webdriver/common/driver_extensions/has_logs.rb +0 -0
  91. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +0 -0
  92. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +0 -0
  93. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +0 -0
  94. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +0 -0
  95. data/lib/selenium/webdriver/common/driver_extensions/has_session_events.rb +0 -0
  96. data/lib/selenium/webdriver/common/driver_extensions/has_session_id.rb +0 -0
  97. data/lib/selenium/webdriver/common/driver_extensions/prints_page.rb +0 -0
  98. data/lib/selenium/webdriver/common/driver_extensions/uploads_files.rb +0 -0
  99. data/lib/selenium/webdriver/common/driver_finder.rb +0 -0
  100. data/lib/selenium/webdriver/common/element.rb +0 -0
  101. data/lib/selenium/webdriver/common/error.rb +0 -0
  102. data/lib/selenium/webdriver/common/fedcm/account.rb +0 -0
  103. data/lib/selenium/webdriver/common/fedcm/dialog.rb +0 -0
  104. data/lib/selenium/webdriver/common/fedcm.rb +0 -0
  105. data/lib/selenium/webdriver/common/file_reaper.rb +0 -0
  106. data/lib/selenium/webdriver/common/interactions/input_device.rb +0 -0
  107. data/lib/selenium/webdriver/common/interactions/interaction.rb +0 -0
  108. data/lib/selenium/webdriver/common/interactions/interactions.rb +0 -0
  109. data/lib/selenium/webdriver/common/interactions/key_actions.rb +0 -0
  110. data/lib/selenium/webdriver/common/interactions/key_input.rb +0 -0
  111. data/lib/selenium/webdriver/common/interactions/none_input.rb +0 -0
  112. data/lib/selenium/webdriver/common/interactions/pause.rb +0 -0
  113. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +0 -0
  114. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +0 -0
  115. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +0 -0
  116. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +0 -0
  117. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +0 -0
  118. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +0 -0
  119. data/lib/selenium/webdriver/common/interactions/scroll.rb +0 -0
  120. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +0 -0
  121. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +0 -0
  122. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +0 -0
  123. data/lib/selenium/webdriver/common/interactions/wheel_input.rb +0 -0
  124. data/lib/selenium/webdriver/common/keys.rb +0 -0
  125. data/lib/selenium/webdriver/common/local_driver.rb +0 -0
  126. data/lib/selenium/webdriver/common/log_entry.rb +0 -0
  127. data/lib/selenium/webdriver/common/logger.rb +0 -0
  128. data/lib/selenium/webdriver/common/logs.rb +0 -0
  129. data/lib/selenium/webdriver/common/manager.rb +0 -0
  130. data/lib/selenium/webdriver/common/navigation.rb +0 -0
  131. data/lib/selenium/webdriver/common/network.rb +0 -0
  132. data/lib/selenium/webdriver/common/options.rb +0 -0
  133. data/lib/selenium/webdriver/common/platform.rb +0 -0
  134. data/lib/selenium/webdriver/common/port_prober.rb +0 -0
  135. data/lib/selenium/webdriver/common/print_options.rb +0 -0
  136. data/lib/selenium/webdriver/common/profile_helper.rb +0 -0
  137. data/lib/selenium/webdriver/common/proxy.rb +0 -0
  138. data/lib/selenium/webdriver/common/script.rb +0 -0
  139. data/lib/selenium/webdriver/common/search_context.rb +0 -0
  140. data/lib/selenium/webdriver/common/selenium_manager.rb +0 -0
  141. data/lib/selenium/webdriver/common/service.rb +0 -0
  142. data/lib/selenium/webdriver/common/service_manager.rb +0 -0
  143. data/lib/selenium/webdriver/common/shadow_root.rb +0 -0
  144. data/lib/selenium/webdriver/common/socket_lock.rb +0 -0
  145. data/lib/selenium/webdriver/common/socket_poller.rb +0 -0
  146. data/lib/selenium/webdriver/common/takes_screenshot.rb +0 -0
  147. data/lib/selenium/webdriver/common/target_locator.rb +0 -0
  148. data/lib/selenium/webdriver/common/timeouts.rb +0 -0
  149. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +0 -0
  150. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +0 -0
  151. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +0 -0
  152. data/lib/selenium/webdriver/common/wait.rb +0 -0
  153. data/lib/selenium/webdriver/common/web_extension.rb +42 -0
  154. data/lib/selenium/webdriver/common/websocket_connection.rb +2 -2
  155. data/lib/selenium/webdriver/common/window.rb +0 -0
  156. data/lib/selenium/webdriver/common/zipper.rb +15 -11
  157. data/lib/selenium/webdriver/common.rb +1 -0
  158. data/lib/selenium/webdriver/devtools/console_event.rb +0 -0
  159. data/lib/selenium/webdriver/devtools/exception_event.rb +0 -0
  160. data/lib/selenium/webdriver/devtools/mutation_event.rb +0 -0
  161. data/lib/selenium/webdriver/devtools/network_interceptor.rb +0 -0
  162. data/lib/selenium/webdriver/devtools/pinned_script.rb +0 -0
  163. data/lib/selenium/webdriver/devtools/request.rb +0 -0
  164. data/lib/selenium/webdriver/devtools/response.rb +0 -0
  165. data/lib/selenium/webdriver/devtools.rb +0 -0
  166. data/lib/selenium/webdriver/edge/driver.rb +0 -0
  167. data/lib/selenium/webdriver/edge/features.rb +0 -0
  168. data/lib/selenium/webdriver/edge/options.rb +0 -0
  169. data/lib/selenium/webdriver/edge/profile.rb +0 -0
  170. data/lib/selenium/webdriver/edge/service.rb +0 -0
  171. data/lib/selenium/webdriver/edge.rb +0 -0
  172. data/lib/selenium/webdriver/firefox/driver.rb +0 -0
  173. data/lib/selenium/webdriver/firefox/features.rb +18 -7
  174. data/lib/selenium/webdriver/firefox/options.rb +0 -0
  175. data/lib/selenium/webdriver/firefox/profile.rb +0 -37
  176. data/lib/selenium/webdriver/firefox/profiles_ini.rb +0 -0
  177. data/lib/selenium/webdriver/firefox/service.rb +0 -0
  178. data/lib/selenium/webdriver/firefox/util.rb +0 -0
  179. data/lib/selenium/webdriver/firefox.rb +0 -2
  180. data/lib/selenium/webdriver/ie/driver.rb +0 -0
  181. data/lib/selenium/webdriver/ie/features.rb +0 -0
  182. data/lib/selenium/webdriver/ie/options.rb +0 -0
  183. data/lib/selenium/webdriver/ie/service.rb +0 -0
  184. data/lib/selenium/webdriver/ie.rb +0 -0
  185. data/lib/selenium/webdriver/remote/bidi_bridge.rb +23 -0
  186. data/lib/selenium/webdriver/remote/bridge/commands.rb +0 -0
  187. data/lib/selenium/webdriver/remote/bridge/locator_converter.rb +0 -0
  188. data/lib/selenium/webdriver/remote/bridge.rb +20 -6
  189. data/lib/selenium/webdriver/remote/capabilities.rb +0 -0
  190. data/lib/selenium/webdriver/remote/driver.rb +0 -0
  191. data/lib/selenium/webdriver/remote/features.rb +9 -8
  192. data/lib/selenium/webdriver/remote/http/common.rb +0 -0
  193. data/lib/selenium/webdriver/remote/http/curb.rb +0 -0
  194. data/lib/selenium/webdriver/remote/http/default.rb +0 -0
  195. data/lib/selenium/webdriver/remote/response.rb +0 -0
  196. data/lib/selenium/webdriver/remote/server_error.rb +0 -0
  197. data/lib/selenium/webdriver/remote.rb +0 -0
  198. data/lib/selenium/webdriver/safari/driver.rb +0 -0
  199. data/lib/selenium/webdriver/safari/features.rb +0 -0
  200. data/lib/selenium/webdriver/safari/options.rb +0 -0
  201. data/lib/selenium/webdriver/safari/service.rb +0 -0
  202. data/lib/selenium/webdriver/safari.rb +0 -0
  203. data/lib/selenium/webdriver/support/abstract_event_listener.rb +0 -0
  204. data/lib/selenium/webdriver/support/block_event_listener.rb +0 -0
  205. data/lib/selenium/webdriver/support/color.rb +0 -0
  206. data/lib/selenium/webdriver/support/escaper.rb +0 -0
  207. data/lib/selenium/webdriver/support/event_firing_bridge.rb +0 -0
  208. data/lib/selenium/webdriver/support/guards/guard.rb +0 -0
  209. data/lib/selenium/webdriver/support/guards/guard_condition.rb +0 -0
  210. data/lib/selenium/webdriver/support/guards.rb +0 -0
  211. data/lib/selenium/webdriver/support/relative_locator.rb +0 -0
  212. data/lib/selenium/webdriver/support/select.rb +0 -0
  213. data/lib/selenium/webdriver/support.rb +0 -0
  214. data/lib/selenium/webdriver/version.rb +1 -1
  215. data/lib/selenium/webdriver.rb +0 -0
  216. data/lib/selenium-webdriver.rb +0 -0
  217. data/selenium-webdriver.gemspec +0 -1
  218. metadata +7 -27
  219. data/lib/selenium/webdriver/firefox/extension.rb +0 -103
@@ -30,7 +30,7 @@ module Selenium
30
30
  class Record < ::Data
31
31
  # Named Field, not Member, to avoid colliding with +::Data#members+.
32
32
  Field = ::Data.define(:name, :wire_key, :nullable, :ref, :list, :fixed, :enum, :required, :primitive,
33
- :scalar, :const)
33
+ :scalar, :const, :map)
34
34
 
35
35
  def self.define(**spec)
36
36
  extensible = spec.delete(:extensible) || false
@@ -61,7 +61,7 @@ module Selenium
61
61
  nullable: meta[:nullable] || false, ref: meta[:ref],
62
62
  list: meta[:list] || false, fixed: meta.fetch(:fixed, UNSET), enum: meta[:enum],
63
63
  required: meta.fetch(:required, true), primitive: meta[:primitive],
64
- scalar: meta[:scalar], const: meta.fetch(:const, UNSET))
64
+ scalar: meta[:scalar], const: meta.fetch(:const, UNSET), map: meta[:map])
65
65
  end
66
66
  private_class_method :field
67
67
 
@@ -127,6 +127,7 @@ module Selenium
127
127
  validate_const(field, value)
128
128
  check_outbound_shape(field, value)
129
129
  check_outbound_primitive(field, value) unless field.list
130
+ check_outbound_map(field, value) if field.map
130
131
  validate_ref(field, value) if field.ref
131
132
  Serialization.validate!("#{name}##{field.name}", value, Protocol.const_get(field.enum)) if field.enum
132
133
  end
@@ -210,6 +211,13 @@ module Selenium
210
211
  raise ::ArgumentError, "#{name}##{field.name} expected #{field.primitive}, got #{value.inspect}"
211
212
  end
212
213
 
214
+ # Outbound mirror of check_map: a map-typed arg must be a Hash whose values are the
215
+ # declared primitive, so a caller mistake is a local ArgumentError.
216
+ def check_outbound_map(field, value)
217
+ mismatch = map_mismatch(field, value)
218
+ raise ::ArgumentError, "#{name}##{field.name}#{mismatch}" if mismatch
219
+ end
220
+
213
221
  def fixed?(field)
214
222
  !UNSET.equal?(field.fixed)
215
223
  end
@@ -236,6 +244,7 @@ module Selenium
236
244
 
237
245
  if field.ref.nil?
238
246
  return raw if field.list
247
+ return check_map(field, raw) if field.map
239
248
 
240
249
  check_primitive(field, raw)
241
250
  # A whole number is exact in both types, so the declared type is held with nothing lost.
@@ -285,6 +294,30 @@ module Selenium
285
294
  raise Error::SerializationError, "#{name}##{field.name} expected #{field.primitive}, got #{raw.inspect}"
286
295
  end
287
296
 
297
+ # A `map` field (`{*text => <primitive>}`, e.g. script.NodeProperties.attributes) must arrive
298
+ # as an object whose every value matches the declared primitive. Keys are JSON object keys,
299
+ # so they are strings by construction. The Hash passes through unchanged once validated.
300
+ def check_map(field, raw)
301
+ mismatch = map_mismatch(field, raw)
302
+ raise Error::SerializationError, "#{name}##{field.name}#{mismatch}" if mismatch
303
+
304
+ raw
305
+ end
306
+
307
+ # The suffix of the error a map value earns, or nil when it conforms — shared by the inbound
308
+ # (SerializationError) and outbound (ArgumentError) checks so the two cannot drift apart. An
309
+ # unrecognized value primitive (none in PRIMITIVE_CHECKS) checks only the shape, matching the
310
+ # lenient default elsewhere.
311
+ def map_mismatch(field, value)
312
+ return " expected a map, got #{value.inspect}" unless value.is_a?(::Hash)
313
+
314
+ check = PRIMITIVE_CHECKS[field.map]
315
+ return if check.nil?
316
+
317
+ entry = value.find { |_, v| !check.call(v) }
318
+ "[#{entry[0].inspect}] expected #{field.map}, got #{entry[1].inspect}" if entry
319
+ end
320
+
288
321
  def enum_hash(field)
289
322
  (@enums ||= {})[field.name] ||= Protocol.const_get(field.enum)
290
323
  end
File without changes
File without changes
File without changes
File without changes
@@ -48,8 +48,10 @@ module BiDiGenerate
48
48
  RUBY_RESERVED = %w[begin end rescue ensure raise return yield if unless while until for do
49
49
  case when then class module def].freeze
50
50
 
51
+ # A vendor namespace separator (`moz:debugging`) reads as a word boundary.
51
52
  def self.camel_to_snake(str)
52
53
  str
54
+ .tr(':', '_')
53
55
  .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
54
56
  .gsub(/([a-z\d])([A-Z])/, '\1_\2')
55
57
  .downcase
@@ -72,6 +74,11 @@ module BiDiGenerate
72
74
  "#{snake_to_class_name(camel_to_snake(domain))}::#{type_class_name(type_name)}"
73
75
  end
74
76
 
77
+ # Source literal for a list of wire keys, in the form RuboCop expects at each size.
78
+ def self.word_array(words)
79
+ words.size > 1 ? "%w[#{words.join(' ')}]" : "[#{words.map { |w| "'#{w}'" }.join(', ')}]"
80
+ end
81
+
75
82
  # Source literal for a discriminator/const value (string, boolean, or number).
76
83
  def self.ruby_literal(value)
77
84
  return 'nil' if value.nil?
@@ -100,15 +107,17 @@ module BiDiGenerate
100
107
  RESERVED_FIELD_NAMES = (RUBY_RESERVED + %w[method hash class send dup clone freeze inspect
101
108
  to_h to_s members with deconstruct deconstruct_keys
102
109
  object_id tap itself then display
103
- extensible extensions]).freeze
110
+ extensible]).freeze
104
111
 
105
112
  # Append underscore to a field name that would shadow a core method; the wire
106
113
  # name is unaffected, only the Ruby reader is renamed.
107
- def self.safe_field_name(name)
114
+ def self.safe_field_name(name, extensible: true)
108
115
  # A vendor-prefixed wire name carries a colon (moz:allowPrivateBrowsing); swap it
109
116
  # for an underscore so the Ruby reader is a legal identifier. The wire key is kept.
110
117
  name = name.tr(':', '_')
111
- RESERVED_FIELD_NAMES.include?(name) ? "#{name}_" : name
118
+ # Only an extensible record holds its undeclared fields in an `extensions` member.
119
+ reserved = RESERVED_FIELD_NAMES.include?(name) || (extensible && name == 'extensions')
120
+ reserved ? "#{name}_" : name
112
121
  end
113
122
 
114
123
  # SCREAMING_SNAKE constant name for an enum, matching the EVENTS map style
@@ -226,6 +235,20 @@ module BiDiGenerate
226
235
  args << "result: #{result_ref}" if result_ref
227
236
  BiDiGenerate.wrap_call('execute', args, indent)
228
237
  end
238
+
239
+ # The full `def … end` block, shared by the module template and a vendor variant that adds
240
+ # the command (VendorModule#render), so both render a command identically.
241
+ def render_lines(indent)
242
+ pad = ' ' * indent
243
+ body = ' ' * (indent + 2)
244
+ lines = ["#{pad}# @api private", "#{pad}# @see #{BiDiGenerate::BIDI_DOC_URL}"]
245
+ lines << "#{pad}# @see #{spec_href}" if spec_href
246
+ lines << "#{pad}#{def_header(indent)}"
247
+ lines.concat(enum_checks(indent + 2).map { |check| "#{body}#{check}" })
248
+ assignment = params_assignment(indent + 2)
249
+ lines << "#{body}#{assignment}" if assignment
250
+ lines + ["#{body}#{execute_call(indent + 2)}", "#{pad}end"]
251
+ end
229
252
  end
230
253
 
231
254
  # A browser-specific extension to a command, kept out of the shared class so a
@@ -280,8 +303,9 @@ module BiDiGenerate
280
303
  end
281
304
  end
282
305
 
283
- # A namespaced group of browser-specific command extensions (e.g. Firefox's `moz:`
284
- # fields), emitted as a subclass of the domain that overrides the extended commands.
306
+ # A namespaced group of browser-specific commands (e.g. Firefox's), emitted as a subclass of
307
+ # the domain: it overrides the commands a vendor extends with `moz:` fields and adds the
308
+ # commands a vendor defines in the domain (`webExtension.moz:listExtensions`).
285
309
  # A subclass (rather than a runtime-mixed module) keeps the vendor signatures statically
286
310
  # visible to type checkers, and is constructed directly (`<Name>.new(source)`) for a
287
311
  # matching session — no factory or runtime mix-in.
@@ -290,7 +314,7 @@ module BiDiGenerate
290
314
  pad = ' ' * indent
291
315
  lines = [
292
316
  "#{pad}# @api private",
293
- "#{pad}# #{namespace}: vendor variant of #{parent}, overriding commands with browser-specific params.",
317
+ "#{pad}# #{namespace}: vendor variant of #{parent}, with browser-specific commands and params.",
294
318
  "#{pad}# Construct #{name}.new(source) for a matching session; other sessions use #{parent}.",
295
319
  "#{pad}class #{name} < #{parent}"
296
320
  ]
@@ -331,7 +355,7 @@ module BiDiGenerate
331
355
  # for a scalar/opaque field); list wraps it in an array. wire_key is the exact
332
356
  # JSON payload key (the schema's `wire` name, baked verbatim).
333
357
  FieldIR = Struct.new(:ruby_name, :wire_key, :required, :nullable, :ref, :list, :enum, :primitive, :scalar, :const,
334
- :rbs, keyword_init: true) do
358
+ :map_of, :rbs, keyword_init: true) do
335
359
  # A `Serialization::Record.define` spec entry: `name: 'jsonKey'` shorthand, or
336
360
  # `name: {wire_key:, …}` when the field carries JSON facts beyond its name.
337
361
  # enum carries the allowed-values constant path, validated at construction.
@@ -351,11 +375,19 @@ module BiDiGenerate
351
375
  facts << 'required: false' unless required
352
376
  facts << 'nullable: true' if nullable
353
377
  facts << "const: #{BiDiGenerate.ruby_literal(const)}" unless const.nil?
378
+ facts + type_facts
379
+ end
380
+
381
+ # The facts describing the value's type and shape: what the runtime reads it as and checks
382
+ # it against. `map_of` is the value primitive of a string-keyed map (`{*text => text}`).
383
+ def type_facts
384
+ facts = []
354
385
  facts << "ref: '#{ref}'" if ref
355
386
  facts << 'list: true' if list
356
387
  facts << "scalar: #{scalar_literal}" if scalar
357
388
  facts << "enum: '#{enum}'" if enum
358
389
  facts << "primitive: '#{primitive}'" if primitive
390
+ facts << "map: '#{map_of}'" if map_of
359
391
  facts
360
392
  end
361
393
 
@@ -543,15 +575,44 @@ module BiDiGenerate
543
575
  :vendor_modules, :spec_href, keyword_init: true)
544
576
 
545
577
  class Schema
578
+ # Vendor content (schema `vendor.<namespace>`) is folded in beside the spec's: a vendor
579
+ # module's commands and events become a domain of their own, and its types resolve like
580
+ # spec types and are emitted in that module (see owned_types).
546
581
  def initialize(schema)
547
- @types = schema['types']
548
- @commands = schema['commands']
549
- @events = schema['events']
550
- @domains = schema['domains'] || {}
551
582
  @vendor = schema['vendor'] || {}
583
+ @vendor_types = @vendor.values.map { |section| section['types'] || {} }.reduce({}, :merge)
584
+ @types = schema['types'].merge(@vendor_types)
585
+ @commands = schema['commands'] + vendor_messages('commands').select { |c| vendor_domain?(c['domain']) }
586
+ @events = schema['events'] + vendor_events
587
+ @domains = schema['domains'] || {}
552
588
  promote_command_params_records!
553
589
  end
554
590
 
591
+ # A vendor module's domain keeps its wire prefix (`moz:debugging`); a spec domain has none.
592
+ def vendor_domain?(domain) = domain.include?(':')
593
+
594
+ def vendor_messages(kind)
595
+ @vendor.values.flat_map { |section| section[kind] || [] }
596
+ end
597
+
598
+ # A vendor event in a spec domain would need the same vendor-variant routing as a command;
599
+ # none exists yet, so fail generation rather than leak it into the shared class.
600
+ def vendor_events
601
+ events = vendor_messages('events')
602
+ stray = events.reject { |e| vendor_domain?(e['domain']) }
603
+ raise "vendor event #{stray.first['method']} in spec domain #{stray.first['domain']} is unsupported" if stray.any?
604
+
605
+ events
606
+ end
607
+
608
+ # The commands a vendor defines in a spec domain (`webExtension.moz:listExtensions`). They are
609
+ # emitted on that domain's vendor variant, never on the shared class (see vendor_modules_for).
610
+ def vendor_added_commands(domain, namespace)
611
+ return [] if vendor_domain?(domain)
612
+
613
+ (@vendor.dig(namespace, 'commands') || []).select { |c| c['domain'] == domain }
614
+ end
615
+
555
616
  # The domain's `#module-<domain>` spec link, or nil when the schema has none.
556
617
  def domain_href(domain)
557
618
  @domains.dig(domain, 'specHref')
@@ -627,23 +688,36 @@ module BiDiGenerate
627
688
  # any domain (or schema) with no vendor extensions, so non-vendor output is unaffected.
628
689
  def vendor_modules_for(domain)
629
690
  parent = BiDiGenerate.snake_to_class_name(BiDiGenerate.camel_to_snake(domain))
630
- groups = Hash.new { |h, k| h[k] = [] }
631
- @vendor.each do |namespace, spec|
632
- (spec['extends'] || {}).each do |type_name, entry|
691
+ @vendor.filter_map do |namespace, spec|
692
+ overrides = (spec['extends'] || {}).filter_map do |type_name, entry|
633
693
  cmd = @commands.find { |c| c.dig('params', 'ref') == type_name }
634
- next unless cmd && cmd['domain'] == domain
635
-
636
- groups[namespace] << build_vendor_command(cmd, type_name, entry, namespace)
694
+ build_vendor_command(cmd, type_name, entry, namespace) if cmd && cmd['domain'] == domain
637
695
  end
638
- end
639
- groups.map do |namespace, commands|
696
+ added = vendor_added_commands(domain, namespace).map { |cmd| build_added_command(cmd, namespace, domain) }
697
+ commands = overrides + added
698
+ next if commands.empty?
699
+
640
700
  VendorModule.new(name: BiDiGenerate.snake_to_class_name(namespace), namespace: namespace, parent: parent,
641
701
  commands: commands)
642
702
  end
643
703
  end
644
704
 
705
+ # The vendor variant already scopes the command, so its name drops the namespace (`moz:listExtensions`
706
+ # → list_extensions); a name that would silently override a spec command fails generation instead.
707
+ def build_added_command(cmd, namespace, domain)
708
+ command = BiDiGenerate.build_command(self, cmd.merge('name' => cmd['name'].delete_prefix("#{namespace}:")))
709
+ spec_names = commands_for(domain).map do |c|
710
+ BiDiGenerate.safe_method_name(BiDiGenerate.camel_to_snake(c['name']))
711
+ end
712
+ if spec_names.include?(command.method_name)
713
+ raise "vendor command #{cmd['method']} would override spec command #{domain}.#{command.method_name}"
714
+ end
715
+
716
+ command
717
+ end
718
+
645
719
  def build_vendor_command(cmd, type_name, entry, namespace)
646
- shared = record_params(@types[type_name]['fields'])
720
+ shared = record_params(@types[type_name]['fields'], extensible_record?(type_name))
647
721
  taken = shared.map(&:ruby_name)
648
722
  VendorCommand.new(
649
723
  method_name: BiDiGenerate.safe_method_name(BiDiGenerate.camel_to_snake(cmd['name'])),
@@ -692,23 +766,61 @@ module BiDiGenerate
692
766
  return nil unless type
693
767
 
694
768
  case type['kind']
695
- when 'record' then record_params(type['fields'])
769
+ when 'record' then record_params(type['fields'], extensible_record?(params_ref['ref']))
696
770
  when 'union' then union_params(type, params_ref['ref'])
697
771
  end
698
772
  end
699
773
 
700
774
  # Enum types declared under "<domain>." become nested constant modules.
701
775
  def enums_for(domain)
702
- @types.filter_map do |name, type|
776
+ owned_types(domain).filter_map do |name, type|
703
777
  next unless type['kind'] == 'enum'
704
- next unless name.start_with?("#{domain}.")
705
778
 
706
779
  pairs = type['values'].map { |v| [BiDiGenerate.enum_key(v), v] }
707
- Enum.new(constant_name: BiDiGenerate.screaming_snake(name.sub("#{domain}.", '')), pairs: pairs,
780
+ Enum.new(constant_name: BiDiGenerate.screaming_snake(name.split('.', 2).last), pairs: pairs,
708
781
  primitive: type['primitive'], spec_href: type['specHref'])
709
782
  end
710
783
  end
711
784
 
785
+ # The types a domain's module declares. A spec domain's are the "<domain>." types the spec
786
+ # defines; a vendor's helper types under that prefix are not emitted until a generated
787
+ # command needs them. A vendor module (`moz:debugging`) owns the vendor types its commands
788
+ # and events name (Mozilla prefixes them `mozDebugging.`), read off their params and result refs.
789
+ def owned_types(domain)
790
+ vendor = vendor_domain?(domain)
791
+ prefixes = type_prefixes(domain).map { |prefix| "#{prefix}." }
792
+ added = vendor ? Set.new : vendor_types_reached(domain)
793
+ @types.select do |name, _|
794
+ next false unless prefixes.any? { |p| name.start_with?(p) }
795
+
796
+ @vendor_types.key?(name) ? vendor || added.include?(name) : !vendor
797
+ end
798
+ end
799
+
800
+ # The vendor types a vendor-added command in this spec domain reaches through its params and
801
+ # result, so they are emitted in the domain's module beside the command that returns them.
802
+ def vendor_types_reached(domain)
803
+ commands = @vendor.keys.flat_map { |namespace| vendor_added_commands(domain, namespace) }
804
+ pending = commands.flat_map { |c| [c.dig('params', 'ref'), c.dig('result', 'ref')] }.compact
805
+ reached = Set.new
806
+ until pending.empty?
807
+ name = pending.pop
808
+ next if reached.include?(name) || !@vendor_types.key?(name)
809
+
810
+ reached << name
811
+ pending.concat(plain_refs(@types[name]) + (@types[name]['variants'] || []))
812
+ end
813
+ reached
814
+ end
815
+
816
+ def type_prefixes(domain)
817
+ return [domain] unless vendor_domain?(domain)
818
+
819
+ messages = commands_for(domain) + events_for(domain)
820
+ refs = messages.flat_map { |m| [m.dig('params', 'ref'), m.dig('result', 'ref')] }
821
+ refs.compact.select { |ref| ref.include?('.') }.map { |ref| ref.split('.', 2).first }.uniq
822
+ end
823
+
712
824
  # The protocol-root ErrorCode enum's wire values (e.g. "no such frame"), in schema order.
713
825
  # Used to generate the BiDi-specific Error subclasses. [] when the schema has no ErrorCode.
714
826
  def error_codes
@@ -721,10 +833,7 @@ module BiDiGenerate
721
833
  # Command/event message envelopes (the `{method, params}` wire wrapper) are
722
834
  # skipped — Transport forms that envelope, so nothing references them.
723
835
  def types_for(domain)
724
- prefix = "#{domain}."
725
- @types.filter_map do |name, type|
726
- next unless name.start_with?(prefix)
727
-
836
+ owned_types(domain).filter_map do |name, type|
728
837
  case type['kind']
729
838
  when 'record' then record_class(name, type) unless type['fields'].empty? || suppressed_record?(type)
730
839
  when 'union' then union_class(name)
@@ -825,7 +934,7 @@ module BiDiGenerate
825
934
  if node.key?('ref')
826
935
  named = resolve_named(node['ref'])
827
936
  return {ref: named[:ref], list: named[:list], nullable: nullable, scalar: named[:scalar],
828
- rbs: nilable(named[:rbs], nullable)}
937
+ map: named[:map], rbs: nilable(named[:rbs], nullable)}
829
938
  end
830
939
  return resolve_union(node, nullable) if node.key?('union')
831
940
 
@@ -871,7 +980,7 @@ module BiDiGenerate
871
980
  return OPAQUE unless type
872
981
 
873
982
  case type['kind']
874
- when 'record' then type['fields'].empty? ? OPAQUE : named_type(name)
983
+ when 'record' then type['fields'].empty? ? map_record(type) : named_type(name)
875
984
  when 'union' then named_union(name)
876
985
  when 'enum' then {ref: nil, list: false, rbs: 'Symbol'}
877
986
  when 'alias' then resolve_named_alias(name, type['type'], seen)
@@ -879,6 +988,18 @@ module BiDiGenerate
879
988
  end
880
989
  end
881
990
 
991
+ # A field-less record is either a genuinely empty one (EmptyParams, Extensible — opaque)
992
+ # or the projector's stand-in for an inline map (`{*text => text}` is hoisted to a record
993
+ # with no fields and the value type under `map`, e.g. script.NodePropertiesAttributes).
994
+ # A primitive-valued map becomes a `map` descriptor the runtime checks and a typed Hash;
995
+ # a ref- or union-valued one (the Mozilla vendor types) stays opaque for now.
996
+ def map_record(type)
997
+ primitive = type.dig('map', 'primitive')
998
+ return OPAQUE unless primitive && CHECKABLE_PRIMITIVES.include?(primitive)
999
+
1000
+ {ref: nil, list: false, map: primitive, rbs: "Hash[String, #{PRIMITIVE_RBS.fetch(primitive)}]"}
1001
+ end
1002
+
882
1003
  # A named structured type's serialization ref (nil for a dotless/global type, never
883
1004
  # emitted as a class) and its absolute RBS class path, derived independently so each
884
1005
  # output keeps its own treatment of dotless names.
@@ -926,21 +1047,36 @@ module BiDiGenerate
926
1047
  end
927
1048
 
928
1049
  def record_class(name, type)
1050
+ extensible = extensible_record?(name)
929
1051
  const = type['fields'].find { |f| baked_discriminator?(f) }
930
- discriminator = const && {ruby_name: BiDiGenerate.safe_field_name(BiDiGenerate.camel_to_snake(const['name'])),
1052
+ discriminator = const && {ruby_name: field_name(const, extensible),
931
1053
  wire: const['wire'], value: const['type']['const'],
932
1054
  rbs: rbs_const(const['type']['const'])}
933
- fields = type['fields'].reject { |f| baked_discriminator?(f) }.map { |f| field_ir(f) }
1055
+ fields = type['fields'].reject { |f| baked_discriminator?(f) }.map { |f| field_ir(f, extensible) }
934
1056
  # Every extensible type gets the extensions store: an undeclared wire key is preserved
935
1057
  # and echoed back on any type the spec marks extensible, whether or not it is re-sendable.
936
- # Extensibility alone is the signal; send-reachability does not enter into it.
1058
+ # Extensibility alone is the signal; send-reachability does not enter into it. A type a
1059
+ # vendor extends is open too: its vendor variant composes the vendor fields through that
1060
+ # store (see VendorCommand), which a closed record would reject.
937
1061
  TypeClass.new(ruby_name: BiDiGenerate.type_class_name(name), fields: fields,
938
- discriminator: discriminator, extensible: type['extensible'] ? true : false,
1062
+ discriminator: discriminator, extensible: extensible,
939
1063
  schema_name: name, synthetic: type['synthetic'] ? true : false,
940
1064
  owner: type['owner'], label: type['label'], spec_href: type['specHref'],
941
1065
  **directionality(name))
942
1066
  end
943
1067
 
1068
+ def vendor_extended?(name)
1069
+ @vendor.values.any? { |spec| (spec['extends'] || {}).key?(name) }
1070
+ end
1071
+
1072
+ def extensible_record?(name)
1073
+ @types.dig(name, 'extensible') || vendor_extended?(name) ? true : false
1074
+ end
1075
+
1076
+ def field_name(field, extensible)
1077
+ BiDiGenerate.safe_field_name(BiDiGenerate.camel_to_snake(field['name']), extensible: extensible)
1078
+ end
1079
+
944
1080
  # A const field is a baked discriminator tag, unless it is also nullable: the spec's
945
1081
  # `literal | null` (browsingContext.setBypassCSP, emulation.setScriptingEnabled) is a
946
1082
  # settable value (the literal to set, null to clear), so it stays a normal field that
@@ -949,14 +1085,14 @@ module BiDiGenerate
949
1085
  field['type'].key?('const') && !field['type']['nullable']
950
1086
  end
951
1087
 
952
- def field_ir(field)
1088
+ def field_ir(field, extensible)
953
1089
  resolved = resolve(field['type'])
954
- ruby_name = BiDiGenerate.safe_field_name(BiDiGenerate.camel_to_snake(field['name']))
1090
+ ruby_name = field_name(field, extensible)
955
1091
  FieldIR.new(ruby_name: ruby_name, wire_key: field['wire'],
956
1092
  required: field['required'], nullable: resolved[:nullable],
957
1093
  ref: resolved[:ref], list: resolved[:list], enum: enum_const(field['type']),
958
1094
  primitive: leaf_primitive(field['type']), scalar: resolved[:scalar],
959
- const: leaf_const(field['type']), rbs: resolved[:rbs])
1095
+ const: leaf_const(field['type']), map_of: resolved[:map], rbs: resolved[:rbs])
960
1096
  end
961
1097
 
962
1098
  # The literal value of a const field, following alias chains, so the runtime can reject a
@@ -1074,10 +1210,10 @@ module BiDiGenerate
1074
1210
  scalar_values: spec['type']['scalarValues'], **directionality(name))
1075
1211
  end
1076
1212
 
1077
- def record_params(fields)
1213
+ def record_params(fields, extensible)
1078
1214
  fields.map do |field|
1079
1215
  Param.new(
1080
- ruby_name: BiDiGenerate.safe_field_name(BiDiGenerate.camel_to_snake(field['name'])),
1216
+ ruby_name: field_name(field, extensible),
1081
1217
  wire_name: field['wire'],
1082
1218
  required: field['required'],
1083
1219
  enum: enum_const(field['type']),
@@ -1146,7 +1282,7 @@ module BiDiGenerate
1146
1282
 
1147
1283
  selector = type['selector']
1148
1284
  guard_union_dispatch_keys_simple!(selector, ref)
1149
- params = merged_params(variants.map { |v| v['fields'] })
1285
+ params = merged_params(variants.map { |v| v['fields'] }, type['variants'].any? { |v| extensible_record?(v) })
1150
1286
  annotate_discriminator_enum!(params, selector)
1151
1287
  params
1152
1288
  end
@@ -1180,12 +1316,12 @@ module BiDiGenerate
1180
1316
 
1181
1317
  # Merge variant field lists into one flat param superset. A field is required only
1182
1318
  # when every variant declares it required; variant-specific fields become optional.
1183
- def merged_params(variant_fields)
1319
+ def merged_params(variant_fields, extensible)
1184
1320
  all_fields = variant_fields.flatten
1185
1321
  all_fields.map { |f| f['wire'] }.uniq.map do |wire|
1186
1322
  field = all_fields.find { |f| f['wire'] == wire }
1187
1323
  required = variant_fields.all? { |fields| fields.any? { |f| f['wire'] == wire && f['required'] } }
1188
- Param.new(ruby_name: BiDiGenerate.safe_field_name(BiDiGenerate.camel_to_snake(field['name'])),
1324
+ Param.new(ruby_name: field_name(field, extensible),
1189
1325
  wire_name: wire, required: required, rbs: rbs_type(field['type']))
1190
1326
  end
1191
1327
  end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -277,6 +277,30 @@ module Selenium
277
277
  @network ||= WebDriver::Network.new(bridge)
278
278
  end
279
279
 
280
+ #
281
+ # Installs a browser extension from an unpacked directory, a packed extension (.xpi/.crx/.zip),
282
+ # or base64-encoded bytes; works with remote (Grid) sessions.
283
+ #
284
+ # @note Chromium requires a BiDi session and installs only unpacked directories
285
+ # (SeleniumHQ/selenium#16541); Firefox falls back to the classic endpoint without BiDi.
286
+ # @param [String] path directory, packed extension, or base64-encoded bytes
287
+ # @return [WebExtension] the installed extension
288
+ #
289
+
290
+ def install_web_extension(...)
291
+ bridge.install_web_extension(...)
292
+ end
293
+
294
+ #
295
+ # Uninstalls a browser extension installed with {#install_web_extension}.
296
+ #
297
+ # @param [WebExtension] extension the extension returned by {#install_web_extension}
298
+ #
299
+
300
+ def uninstall_web_extension(extension)
301
+ bridge.uninstall_web_extension(extension.id)
302
+ end
303
+
280
304
  #-------------------------------- sugar --------------------------------
281
305
 
282
306
  #
@@ -30,6 +30,7 @@ module Selenium
30
30
  #
31
31
 
32
32
  def install_addon(path, temporary = nil)
33
+ WebDriver.logger.deprecate('#install_addon', '#install_web_extension', id: :install_addon)
33
34
  @bridge.install_addon(path, temporary)
34
35
  end
35
36
 
@@ -40,6 +41,7 @@ module Selenium
40
41
  #
41
42
 
42
43
  def uninstall_addon(id)
44
+ WebDriver.logger.deprecate('#uninstall_addon', '#uninstall_web_extension', id: :uninstall_addon)
43
45
  @bridge.uninstall_addon(id)
44
46
  end
45
47
  end # HasAddons