dommy 0.16.0 → 0.17.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dommy/animation.rb +1 -0
  3. data/lib/dommy/backend.rb +3 -0
  4. data/lib/dommy/blob.rb +6 -0
  5. data/lib/dommy/browser.rb +12 -4
  6. data/lib/dommy/character_data.rb +1 -13
  7. data/lib/dommy/cookie_store.rb +2 -1
  8. data/lib/dommy/document.rb +67 -30
  9. data/lib/dommy/element.rb +11 -0
  10. data/lib/dommy/element_internals.rb +12 -0
  11. data/lib/dommy/encodings/legacy_multibyte.rb +800 -0
  12. data/lib/dommy/encodings/multibyte_overrides.rb +76 -0
  13. data/lib/dommy/encodings/prescan.rb +234 -0
  14. data/lib/dommy/encodings.rb +121 -57
  15. data/lib/dommy/event.rb +107 -54
  16. data/lib/dommy/event_source.rb +1 -0
  17. data/lib/dommy/fetch.rb +244 -53
  18. data/lib/dommy/file_reader.rb +1 -0
  19. data/lib/dommy/form_data.rb +22 -9
  20. data/lib/dommy/form_entry_list.rb +3 -3
  21. data/lib/dommy/html_canvas_element.rb +85 -12
  22. data/lib/dommy/html_elements/base.rb +1 -0
  23. data/lib/dommy/html_elements/forms/input.rb +3 -0
  24. data/lib/dommy/html_elements/interactive.rb +56 -48
  25. data/lib/dommy/html_elements/metadata.rb +23 -0
  26. data/lib/dommy/interaction/form_submission.rb +24 -42
  27. data/lib/dommy/internal/accessibility_tree.rb +149 -162
  28. data/lib/dommy/internal/accessibility_visibility.rb +16 -17
  29. data/lib/dommy/internal/accessible_name.rb +231 -246
  30. data/lib/dommy/internal/aria_role.rb +182 -195
  31. data/lib/dommy/internal/aria_snapshot.rb +35 -38
  32. data/lib/dommy/internal/aria_state.rb +102 -118
  33. data/lib/dommy/internal/autofill.rb +75 -75
  34. data/lib/dommy/internal/backend_prefilter.rb +127 -130
  35. data/lib/dommy/internal/child_navigable.rb +86 -83
  36. data/lib/dommy/internal/close_watcher_manager.rb +1 -0
  37. data/lib/dommy/internal/css/ancestor_filter.rb +64 -64
  38. data/lib/dommy/internal/css/calc.rb +90 -71
  39. data/lib/dommy/internal/css/cascade.rb +69 -67
  40. data/lib/dommy/internal/css/color.rb +249 -187
  41. data/lib/dommy/internal/css/computed_style_builder.rb +34 -0
  42. data/lib/dommy/internal/css/computed_style_declaration.rb +50 -11
  43. data/lib/dommy/internal/css/counters.rb +67 -67
  44. data/lib/dommy/internal/css/custom_properties.rb +114 -114
  45. data/lib/dommy/internal/css/parser.rb +277 -239
  46. data/lib/dommy/internal/css/property_registry.rb +321 -302
  47. data/lib/dommy/internal/css/renderability.rb +37 -35
  48. data/lib/dommy/internal/css/rule_index.rb +2 -2
  49. data/lib/dommy/internal/css/rule_index_inputs.rb +2 -2
  50. data/lib/dommy/internal/css/supports.rb +115 -113
  51. data/lib/dommy/internal/css/ua_stylesheet.rb +55 -53
  52. data/lib/dommy/internal/css_rule_text.rb +121 -119
  53. data/lib/dommy/internal/css_source.rb +1 -3
  54. data/lib/dommy/internal/css_syntax.rb +157 -92
  55. data/lib/dommy/internal/declarative_shadow_roots.rb +1 -0
  56. data/lib/dommy/internal/directionality.rb +120 -123
  57. data/lib/dommy/internal/document_live_ranges.rb +26 -1
  58. data/lib/dommy/internal/dom_matching.rb +185 -184
  59. data/lib/dommy/internal/element_geometry.rb +8 -1
  60. data/lib/dommy/internal/element_matching.rb +71 -69
  61. data/lib/dommy/internal/element_shadow.rb +1 -13
  62. data/lib/dommy/internal/element_state.rb +255 -265
  63. data/lib/dommy/internal/event_handlers.rb +66 -64
  64. data/lib/dommy/internal/exception_report.rb +88 -86
  65. data/lib/dommy/internal/focusability.rb +205 -205
  66. data/lib/dommy/internal/global_functions.rb +49 -47
  67. data/lib/dommy/internal/idl_reflection.rb +99 -97
  68. data/lib/dommy/internal/js_number.rb +35 -33
  69. data/lib/dommy/internal/light_dismiss.rb +130 -132
  70. data/lib/dommy/internal/literal_lookup.rb +75 -75
  71. data/lib/dommy/internal/mime_type.rb +54 -49
  72. data/lib/dommy/internal/mutation_coordinator.rb +46 -0
  73. data/lib/dommy/internal/mutation_observer_microtask.rb +60 -0
  74. data/lib/dommy/internal/namespaces.rb +51 -49
  75. data/lib/dommy/internal/node_adopter.rb +25 -1
  76. data/lib/dommy/internal/node_equality.rb +61 -59
  77. data/lib/dommy/internal/post_insertion_steps.rb +1 -0
  78. data/lib/dommy/internal/referrer.rb +144 -0
  79. data/lib/dommy/internal/rendered_text/collector.rb +22 -3
  80. data/lib/dommy/internal/rendered_text/computed_box.rb +3 -2
  81. data/lib/dommy/internal/rendered_text/fragment.rb +37 -38
  82. data/lib/dommy/internal/rendered_text/items.rb +7 -5
  83. data/lib/dommy/internal/selector_matcher.rb +361 -359
  84. data/lib/dommy/internal/selector_parser.rb +74 -62
  85. data/lib/dommy/internal/sequential_focus_navigation.rb +150 -151
  86. data/lib/dommy/internal/shadow_root_registry.rb +7 -0
  87. data/lib/dommy/internal/slot_assignment.rb +117 -0
  88. data/lib/dommy/internal/slot_signals.rb +125 -0
  89. data/lib/dommy/internal/unsafe_html.rb +79 -77
  90. data/lib/dommy/internal/url_parser.rb +5 -29
  91. data/lib/dommy/internal/web_idl.rb +158 -153
  92. data/lib/dommy/internal/window_bound.rb +14 -0
  93. data/lib/dommy/internal/window_constructors.rb +3 -0
  94. data/lib/dommy/internal/xml_serialization.rb +298 -296
  95. data/lib/dommy/js/dom_interfaces.rb +13 -1
  96. data/lib/dommy/js/host_bridge.rb +23 -1
  97. data/lib/dommy/js/host_callbacks.rb +7 -0
  98. data/lib/dommy/js/host_runtime.js +625 -61
  99. data/lib/dommy/js/import_map.rb +1 -1
  100. data/lib/dommy/js/observable_runtime.js +295 -128
  101. data/lib/dommy/js/webidl_members.js +1 -1
  102. data/lib/dommy/js/webidl_signatures.js +35 -6
  103. data/lib/dommy/media_query_list.rb +1 -0
  104. data/lib/dommy/message_channel.rb +2 -0
  105. data/lib/dommy/mutation_observer.rb +25 -29
  106. data/lib/dommy/navigator.rb +4 -1
  107. data/lib/dommy/node.rb +4 -0
  108. data/lib/dommy/notification.rb +1 -0
  109. data/lib/dommy/promise.rb +12 -0
  110. data/lib/dommy/range.rb +20 -3
  111. data/lib/dommy/shadow_root.rb +25 -1
  112. data/lib/dommy/streams.rb +18 -0
  113. data/lib/dommy/style_declaration.rb +15 -4
  114. data/lib/dommy/text_codec.rb +7 -5
  115. data/lib/dommy/tree_walker.rb +68 -61
  116. data/lib/dommy/version.rb +1 -1
  117. data/lib/dommy/web_socket.rb +1 -0
  118. data/lib/dommy/window.rb +22 -4
  119. data/lib/dommy/worker.rb +1 -0
  120. data/lib/dommy/xml_http_request.rb +12 -2
  121. data/lib/dommy.rb +17 -2
  122. metadata +11 -3
data/lib/dommy/event.rb CHANGED
@@ -344,27 +344,23 @@ module Dommy
344
344
  end
345
345
 
346
346
  event.__internal_set_current_target__(entry.invocation_target)
347
- # The legacy `window.event` global is set for the duration of a listener,
348
- # then restored — but NOT for a listener inside a shadow tree, which would
349
- # otherwise hand an encapsulated event to the outer global.
350
- window = window_of(entry.invocation_target)
351
- previous_event = window&.__internal_current_event__
352
- window.__internal_set_current_event__(event) if window && !entry.in_shadow_tree
353
- begin
354
- entry.invocation_target.__internal_deliver_event__(event, phase)
355
- ensure
356
- window&.__internal_set_current_event__(previous_event)
357
- end
347
+ entry.invocation_target.__internal_deliver_event__(event, phase, in_shadow_tree: entry.in_shadow_tree)
358
348
  throw :stop_propagation if event.propagation_stopped?
359
349
  end
360
350
 
361
- # The Window whose `event` global covers this node, or nil for a node with
362
- # no browsing context.
351
+ # The Window whose `event` global covers this target, or nil for a node
352
+ # with no browsing context. A target that is not a node (an
353
+ # XMLHttpRequest, a WebSocket, a MessagePort) is covered by the window it
354
+ # was made for (Internal::WindowBound); one made for none
355
+ # (`new EventTarget()`, an AbortSignal) has none.
363
356
  def window_of(node)
364
357
  return node if node.is_a?(Window)
365
358
 
366
359
  document = node.is_a?(Document) ? node : (node.document if node.respond_to?(:document))
367
- document.default_view if document.respond_to?(:default_view)
360
+ return document.default_view if document.respond_to?(:default_view)
361
+
362
+ window = node.__internal_relevant_window__ if node.respond_to?(:__internal_relevant_window__)
363
+ window if window.is_a?(Window)
368
364
  end
369
365
 
370
366
  # WHATWG dispatch steps 5-12: walk from the target outward, recording one
@@ -468,16 +464,9 @@ module Dommy
468
464
  # `assignedSlot` deliberately reports null for a closed tree, but the event
469
465
  # path still has to route through the slot.
470
466
  def assigned_slot_of(node)
471
- return nil unless node.respond_to?(:parent_node)
472
-
473
- host = node.parent_node
474
- return nil unless host.respond_to?(:__internal_shadow_root__)
467
+ return nil unless node.is_a?(Node) && (document = node.owner_document) && (backend = node.__dommy_backend_node__)
475
468
 
476
- root = host.__internal_shadow_root__
477
- return nil unless root
478
-
479
- name = node.respond_to?(:__internal_attribute_value__) ? node.__internal_attribute_value__("slot").to_s : ""
480
- root.query_selector_all("slot").find { |slot| (slot.respond_to?(:name) ? slot.name.to_s : "") == name }
469
+ Internal::SlotAssignment.find_slot(document, backend)
481
470
  end
482
471
 
483
472
  def closed_shadow_root?(node)
@@ -493,7 +482,9 @@ module Dommy
493
482
 
494
483
  # `phase` is :capture (capture listeners), :bubble (non-capture), or :both
495
484
  # (at the target). stopImmediatePropagation ends delivery within this node.
496
- def __internal_deliver_event__(event, phase = :both)
485
+ # `in_shadow_tree` is DOM's invocationTargetInShadowTree, which keeps
486
+ # `window.event` from showing an encapsulated event.
487
+ def __internal_deliver_event__(event, phase = :both, in_shadow_tree: false)
497
488
  listeners = listeners_for(event.type).dup
498
489
  listeners.each do |entry|
499
490
  next if entry.removed?
@@ -513,17 +504,19 @@ module Dommy
513
504
  end
514
505
  end
515
506
 
516
- if entry.event_handler?
517
- # The event handler processing algorithm: the handler's CURRENT value
518
- # runs, and its return value is processed — onerror on a global
519
- # cancels on `true`, every other handler on `false`
520
- # (`onsubmit="return false"`).
521
- invoked, result = invoke_event_handler(entry, event)
522
- __internal_process_event_handler_return__(event, result) if invoked
523
- elsif entry.passive?
524
- event.__internal_run_passive__ { invoke_listener_isolated(entry.listener, event, self) }
525
- else
526
- invoke_listener_isolated(entry.listener, event, self)
507
+ with_current_event(listener_global(entry.listener), event, in_shadow_tree) do
508
+ if entry.event_handler?
509
+ # The event handler processing algorithm: the handler's CURRENT value
510
+ # runs, and its return value is processed — onerror on a global
511
+ # cancels on `true`, every other handler on `false`
512
+ # (`onsubmit="return false"`).
513
+ invoked, result = invoke_event_handler(entry, event)
514
+ __internal_process_event_handler_return__(event, result) if invoked
515
+ elsif entry.passive?
516
+ event.__internal_run_passive__ { invoke_listener_isolated(entry.listener, event, self) }
517
+ else
518
+ invoke_listener_isolated(entry.listener, event, self)
519
+ end
527
520
  end
528
521
 
529
522
  break if event.immediate_propagation_stopped?
@@ -532,6 +525,31 @@ module Dommy
532
525
  nil
533
526
  end
534
527
 
528
+ # DOM "inner invoke" steps 8-11: the legacy `window.event` of the listener's
529
+ # global is the event while the listener runs (unless the invocation target
530
+ # is in a shadow tree), and what it was before afterwards. A global that is
531
+ # not a Window has none.
532
+ def with_current_event(window, event, in_shadow_tree)
533
+ return yield unless window.is_a?(Window)
534
+
535
+ previous_event = window.__internal_current_event__
536
+ window.__internal_set_current_event__(event) unless in_shadow_tree
537
+ begin
538
+ yield
539
+ ensure
540
+ window.__internal_set_current_event__(previous_event)
541
+ end
542
+ end
543
+
544
+ # The global of a listener's callback's realm: a script function's
545
+ # window. A Ruby listener (or an event handler, which Dommy compiles in
546
+ # the target's window) has no realm of its own; the target's window
547
+ # stands for it.
548
+ def listener_global(listener)
549
+ window = listener.__internal_window__ if listener.respond_to?(:__internal_window__)
550
+ window || window_of(self)
551
+ end
552
+
535
553
  # WHATWG event handler processing algorithm, step "process return value":
536
554
  # the special error handler (onerror on a Window) cancels the event when the
537
555
  # handler returns true; onbeforeunload has its own returnValue handling
@@ -859,8 +877,20 @@ module Dommy
859
877
  @in_passive_listener = previous
860
878
  end
861
879
 
880
+ # Dispatch's targetOverride: the target itself, or the document a Window
881
+ # event carries under the legacy target override. The override holds for
882
+ # one dispatch only, so a re-dispatch starts from its own target rather
883
+ # than from where the previous dispatch left `target`.
862
884
  def __internal_prepare_for_dispatch__(target)
863
- @target ||= target
885
+ @target = @legacy_target_override || target
886
+ @legacy_target_override = nil
887
+ end
888
+
889
+ # HTML's "legacy target override flag": a Window's load, pageshow,
890
+ # pagehide and unload are targeted at its document.
891
+ def __internal_set_legacy_target_override__(document)
892
+ @legacy_target_override = document
893
+ self
864
894
  end
865
895
 
866
896
  # The shadow-adjusted target for the node currently being visited. The
@@ -957,8 +987,9 @@ module Dommy
957
987
  @propagation_stopped = true if value
958
988
  when "returnValue"
959
989
  # Legacy alias: returnValue = false cancels the event (like
960
- # preventDefault); a truthy value does not un-cancel.
961
- @default_prevented = true if !value && @cancelable
990
+ # preventDefault, so not from a passive listener); a truthy value does
991
+ # not un-cancel.
992
+ @default_prevented = true if !Internal::WebIDL.boolean(value) && @cancelable && !@in_passive_listener
962
993
  else
963
994
  return Bridge::UNHANDLED
964
995
  end
@@ -2061,8 +2092,8 @@ module Dommy
2061
2092
  @key = nullable_string(args[3])
2062
2093
  @old_value = nullable_string(args[4])
2063
2094
  @new_value = nullable_string(args[5])
2064
- @url = (args[6] || "").to_s
2065
- @storage_area = args[7]
2095
+ @url = optional_url(args[6])
2096
+ @storage_area = args[7].equal?(Bridge::UNDEFINED) ? nil : args[7]
2066
2097
  end
2067
2098
  nil
2068
2099
  else
@@ -2072,6 +2103,12 @@ module Dommy
2072
2103
 
2073
2104
  private
2074
2105
 
2106
+ # initStorageEvent's `optional USVString url = ""`: an undefined (or
2107
+ # omitted) argument takes the default.
2108
+ def optional_url(value)
2109
+ value.nil? || value.equal?(Bridge::UNDEFINED) ? "" : value.to_s
2110
+ end
2111
+
2075
2112
  # The IDL's nullable DOMString: a present value stringifies, null/undefined
2076
2113
  # stay null rather than becoming "".
2077
2114
  def nullable_string(value)
@@ -2374,6 +2411,19 @@ module Dommy
2374
2411
  nil
2375
2412
  end
2376
2413
 
2414
+ # DOM "remove an algorithm from a signal": for a caller whose algorithm
2415
+ # has nothing left to abort, so a long-lived signal does not keep it.
2416
+ def __internal_remove_abort_algorithm__(callback)
2417
+ @abort_algorithms.delete(callback)
2418
+ nil
2419
+ end
2420
+
2421
+ # Drop the algorithms the block says have nothing left to abort.
2422
+ def __internal_remove_abort_algorithms_if__(&block)
2423
+ @abort_algorithms.reject!(&block)
2424
+ nil
2425
+ end
2426
+
2377
2427
  # Sentinel meaning "no reason argument was supplied" — distinct from an
2378
2428
  # explicit `null` reason (`abort(null)` keeps reason null, but `abort()` /
2379
2429
  # `abort(undefined)` default to a fresh AbortError).
@@ -2398,8 +2448,12 @@ module Dommy
2398
2448
  dependents_to_abort << dependent
2399
2449
  end
2400
2450
 
2401
- __internal_run_abort_steps__
2402
- dependents_to_abort.each(&:__internal_run_abort_steps__)
2451
+ error = __internal_run_abort_steps__
2452
+ dependents_to_abort.each do |dependent|
2453
+ dependent_error = dependent.__internal_run_abort_steps__
2454
+ error ||= dependent_error
2455
+ end
2456
+ raise error if error
2403
2457
  end
2404
2458
 
2405
2459
  # ----- dependent-signal plumbing (package-private, used by .any) -----
@@ -2433,22 +2487,21 @@ module Dommy
2433
2487
 
2434
2488
  # The spec's "abort steps": run the registered abort algorithms first
2435
2489
  # (synchronously, ahead of any listener), then fire the trusted "abort" event.
2490
+ # What a JS algorithm throws is returned (the first one) for `abort()` to
2491
+ # throw to its caller once every step has run.
2436
2492
  def __internal_run_abort_steps__
2437
2493
  algorithms = @abort_algorithms
2438
2494
  @abort_algorithms = []
2439
- algorithms.each { |algo| invoke_abort_algorithm(algo) }
2495
+ error = nil
2496
+ algorithms.each do |algo|
2497
+ CallableInvoker.invoke_raising(algo)
2498
+ rescue Bridge::ThrowValue => e
2499
+ error ||= e
2500
+ rescue StandardError
2501
+ nil
2502
+ end
2440
2503
  dispatch_event(Event.new("abort", "bubbles" => false, "cancelable" => false).__internal_mark_trusted__)
2441
- end
2442
-
2443
- # Run one abort algorithm (a JS callback over the bridge, or a Ruby proc).
2444
- # A throw is swallowed deliberately: WHATWG runs every abort algorithm and
2445
- # then fires the "abort" event regardless, so one algorithm's failure must
2446
- # not abort the run. (The sole caller — the Observable polyfill — already
2447
- # catches inside its own teardown, so this is a defensive backstop.)
2448
- def invoke_abort_algorithm(algo)
2449
- CallableInvoker.invoke(algo)
2450
- rescue StandardError
2451
- nil
2504
+ error
2452
2505
  end
2453
2506
  end
2454
2507
 
@@ -14,6 +14,7 @@ module Dommy
14
14
  # Spec: https://html.spec.whatwg.org/multipage/server-sent-events.html
15
15
  class EventSource
16
16
  include EventTarget
17
+ include Internal::WindowBound
17
18
  include Internal::EventHandlers::IdlAttributeBridge
18
19
 
19
20
  CONNECTING = 0