dommy 0.15.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.
- checksums.yaml +4 -4
- data/lib/dommy/animation.rb +1 -0
- data/lib/dommy/backend.rb +19 -15
- data/lib/dommy/blob.rb +23 -0
- data/lib/dommy/bridge/values.rb +29 -2
- data/lib/dommy/bridge/wire_tags.rb +8 -4
- data/lib/dommy/browser.rb +335 -52
- data/lib/dommy/character_data.rb +21 -40
- data/lib/dommy/class_list.rb +24 -9
- data/lib/dommy/cookie_jar.rb +408 -0
- data/lib/dommy/cookie_store.rb +105 -49
- data/lib/dommy/css.rb +34 -8
- data/lib/dommy/custom_elements.rb +362 -114
- data/lib/dommy/data_transfer.rb +22 -2
- data/lib/dommy/document.rb +1114 -206
- data/lib/dommy/dom_exception.rb +6 -0
- data/lib/dommy/dom_parser.rb +46 -14
- data/lib/dommy/dom_string_list.rb +36 -0
- data/lib/dommy/element.rb +228 -102
- data/lib/dommy/element_internals.rb +227 -0
- data/lib/dommy/encodings/legacy_multibyte.rb +800 -0
- data/lib/dommy/encodings/multibyte_overrides.rb +76 -0
- data/lib/dommy/encodings/prescan.rb +234 -0
- data/lib/dommy/encodings.rb +126 -52
- data/lib/dommy/event.rb +472 -163
- data/lib/dommy/event_source.rb +5 -30
- data/lib/dommy/fetch.rb +244 -53
- data/lib/dommy/file_reader.rb +6 -29
- data/lib/dommy/form_data.rb +62 -15
- data/lib/dommy/form_entry_list.rb +108 -139
- data/lib/dommy/fragment.rb +11 -10
- data/lib/dommy/history.rb +247 -90
- data/lib/dommy/html_canvas_element.rb +95 -42
- data/lib/dommy/html_collection.rb +49 -30
- data/lib/dommy/html_elements/base.rb +112 -69
- data/lib/dommy/html_elements/embedded.rb +349 -105
- data/lib/dommy/html_elements/forms/form.rb +407 -368
- data/lib/dommy/html_elements/forms/input.rb +336 -174
- data/lib/dommy/html_elements/forms/select.rb +90 -74
- data/lib/dommy/html_elements/interactive.rb +329 -111
- data/lib/dommy/html_elements/media.rb +71 -30
- data/lib/dommy/html_elements/metadata.rb +238 -71
- data/lib/dommy/html_elements/miscellaneous.rb +58 -14
- data/lib/dommy/html_elements/tables.rb +7 -9
- data/lib/dommy/html_elements.rb +23 -1
- data/lib/dommy/hyperlink_utils.rb +15 -4
- data/lib/dommy/interaction/drag_and_drop.rb +187 -0
- data/lib/dommy/interaction/event_synthesis.rb +182 -24
- data/lib/dommy/interaction/field_interactor.rb +17 -5
- data/lib/dommy/interaction/form_submission.rb +31 -43
- data/lib/dommy/interaction/key_sender.rb +101 -16
- data/lib/dommy/interaction.rb +1 -0
- data/lib/dommy/internal/accessibility_tree.rb +149 -150
- data/lib/dommy/internal/accessibility_visibility.rb +16 -17
- data/lib/dommy/internal/accessible_description.rb +1 -1
- data/lib/dommy/internal/accessible_name.rb +231 -253
- data/lib/dommy/internal/aria_role.rb +182 -195
- data/lib/dommy/internal/aria_snapshot.rb +35 -38
- data/lib/dommy/internal/aria_state.rb +102 -118
- data/lib/dommy/internal/autofill.rb +75 -75
- data/lib/dommy/internal/backend_prefilter.rb +126 -133
- data/lib/dommy/internal/ce_reactions.rb +285 -0
- data/lib/dommy/internal/ce_reactions_table.rb +296 -0
- data/lib/dommy/internal/child_list.rb +7 -43
- data/lib/dommy/internal/child_navigable.rb +158 -0
- data/lib/dommy/internal/child_node.rb +44 -41
- data/lib/dommy/internal/close_watcher_manager.rb +239 -0
- data/lib/dommy/internal/complex_match.rb +3 -1
- data/lib/dommy/internal/constraint_validation.rb +130 -0
- data/lib/dommy/internal/css/ancestor_filter.rb +107 -0
- data/lib/dommy/internal/css/calc.rb +90 -71
- data/lib/dommy/internal/css/cascade.rb +72 -70
- data/lib/dommy/internal/css/cascaded_declarations.rb +4 -1
- data/lib/dommy/internal/css/color.rb +249 -187
- data/lib/dommy/internal/css/computed_style_builder.rb +43 -3
- data/lib/dommy/internal/css/computed_style_declaration.rb +50 -11
- data/lib/dommy/internal/css/counters.rb +67 -67
- data/lib/dommy/internal/css/custom_properties.rb +115 -103
- data/lib/dommy/internal/css/parser.rb +273 -257
- data/lib/dommy/internal/css/property_registry.rb +321 -302
- data/lib/dommy/internal/css/renderability.rb +39 -31
- data/lib/dommy/internal/css/rule_index.rb +151 -100
- data/lib/dommy/internal/css/rule_index_inputs.rb +135 -0
- data/lib/dommy/internal/css/selector_dependencies.rb +8 -2
- data/lib/dommy/internal/css/style_cache.rb +7 -4
- data/lib/dommy/internal/css/supports.rb +115 -113
- data/lib/dommy/internal/css/ua_stylesheet.rb +60 -24
- data/lib/dommy/internal/css_rule_text.rb +121 -119
- data/lib/dommy/internal/css_source.rb +1 -3
- data/lib/dommy/internal/css_syntax.rb +157 -92
- data/lib/dommy/internal/declarative_shadow_roots.rb +183 -0
- data/lib/dommy/internal/directionality.rb +120 -123
- data/lib/dommy/internal/document_generations.rb +55 -2
- data/lib/dommy/internal/document_interaction_state.rb +293 -9
- data/lib/dommy/internal/document_live_ranges.rb +26 -1
- data/lib/dommy/internal/dom_matching.rb +185 -184
- data/lib/dommy/internal/element_aria.rb +25 -19
- data/lib/dommy/internal/element_geometry.rb +8 -1
- data/lib/dommy/internal/element_matching.rb +71 -69
- data/lib/dommy/internal/element_popover.rb +226 -25
- data/lib/dommy/internal/element_shadow.rb +98 -39
- data/lib/dommy/internal/element_state.rb +264 -181
- data/lib/dommy/internal/element_top_layer.rb +15 -1
- data/lib/dommy/internal/enumerated_keyword_sets.rb +40 -0
- data/lib/dommy/internal/event_handler_tables.rb +241 -0
- data/lib/dommy/internal/event_handlers.rb +151 -0
- data/lib/dommy/internal/exception_report.rb +89 -87
- data/lib/dommy/internal/focusability.rb +267 -0
- data/lib/dommy/internal/form_associated_custom_elements.rb +127 -0
- data/lib/dommy/internal/global_functions.rb +49 -47
- data/lib/dommy/internal/html_or_svg_or_mathml_element.rb +34 -23
- data/lib/dommy/internal/html_serialization.rb +292 -0
- data/lib/dommy/internal/idl_reflection.rb +204 -0
- data/lib/dommy/internal/idl_reflection_table.rb +589 -0
- data/lib/dommy/internal/infra.rb +9 -0
- data/lib/dommy/internal/input_type.rb +76 -18
- data/lib/dommy/internal/insertion_point.rb +27 -3
- data/lib/dommy/internal/js_number.rb +62 -0
- data/lib/dommy/internal/light_dismiss.rb +176 -0
- data/lib/dommy/internal/literal_lookup.rb +75 -75
- data/lib/dommy/internal/mime_type.rb +55 -42
- data/lib/dommy/internal/mutation_coordinator.rb +140 -62
- data/lib/dommy/internal/mutation_observer_microtask.rb +60 -0
- data/lib/dommy/internal/namespaces.rb +51 -49
- data/lib/dommy/internal/node_adopter.rb +77 -5
- data/lib/dommy/internal/node_equality.rb +61 -59
- data/lib/dommy/internal/node_factory.rb +111 -7
- data/lib/dommy/internal/node_identity.rb +1 -1
- data/lib/dommy/internal/node_traversal.rb +22 -4
- data/lib/dommy/internal/node_wrapper_cache.rb +6 -38
- data/lib/dommy/internal/observer_options.rb +24 -14
- data/lib/dommy/internal/origin.rb +86 -0
- data/lib/dommy/internal/parent_node.rb +10 -8
- data/lib/dommy/internal/popover_invoker_element.rb +67 -0
- data/lib/dommy/internal/popover_stack.rb +120 -0
- data/lib/dommy/internal/post_insertion_steps.rb +141 -71
- data/lib/dommy/internal/referrer.rb +144 -0
- data/lib/dommy/internal/reflected_attributes.rb +128 -18
- data/lib/dommy/internal/rendered_text/collector.rb +23 -8
- data/lib/dommy/internal/rendered_text/computed_box.rb +3 -2
- data/lib/dommy/internal/rendered_text/fragment.rb +37 -34
- data/lib/dommy/internal/rendered_text/items.rb +7 -5
- data/lib/dommy/internal/selector_arguments.rb +2 -1
- data/lib/dommy/internal/selector_match.rb +1 -1
- data/lib/dommy/internal/selector_matcher.rb +361 -354
- data/lib/dommy/internal/selector_parser.rb +88 -63
- data/lib/dommy/internal/sequential_focus_navigation.rb +209 -0
- data/lib/dommy/internal/shadow_root_registry.rb +14 -0
- data/lib/dommy/internal/slot_assignment.rb +117 -0
- data/lib/dommy/internal/slot_signals.rb +125 -0
- data/lib/dommy/internal/template_content_registry.rb +7 -1
- data/lib/dommy/internal/text_selection.rb +185 -32
- data/lib/dommy/internal/toggle_events.rb +6 -4
- data/lib/dommy/internal/unsafe_html.rb +109 -0
- data/lib/dommy/internal/url_parser.rb +89 -37
- data/lib/dommy/internal/user_activation.rb +192 -0
- data/lib/dommy/internal/web_idl.rb +176 -38
- data/lib/dommy/internal/window_bound.rb +14 -0
- data/lib/dommy/internal/window_constructors.rb +29 -6
- data/lib/dommy/internal/xml_serialization.rb +298 -296
- data/lib/dommy/js/constructor_resolver.rb +18 -0
- data/lib/dommy/js/custom_element_bridge.rb +57 -82
- data/lib/dommy/js/dom_interfaces.rb +63 -16
- data/lib/dommy/js/host_bridge.rb +212 -41
- data/lib/dommy/js/host_callbacks.rb +7 -0
- data/lib/dommy/js/host_runtime.js +2494 -448
- data/lib/dommy/js/import_map.rb +1 -1
- data/lib/dommy/js/marshaller.rb +13 -4
- data/lib/dommy/js/module_loader.rb +37 -6
- data/lib/dommy/js/module_preload.rb +107 -29
- data/lib/dommy/js/observable_runtime.js +295 -128
- data/lib/dommy/js/script_boot.rb +197 -115
- data/lib/dommy/js/serialized_record.rb +32 -0
- data/lib/dommy/js/webidl_event_handlers.js +45 -0
- data/lib/dommy/js/webidl_members.js +196 -0
- data/lib/dommy/js/webidl_signatures.js +2084 -0
- data/lib/dommy/js/webidl_tables.js +47 -100
- data/lib/dommy/js/window_wiring.rb +9 -5
- data/lib/dommy/location.rb +129 -76
- data/lib/dommy/media_query_list.rb +5 -13
- data/lib/dommy/message_channel.rb +139 -64
- data/lib/dommy/mutation_observer.rb +27 -29
- data/lib/dommy/navigation.rb +59 -9
- data/lib/dommy/navigator.rb +135 -6
- data/lib/dommy/node.rb +23 -11
- data/lib/dommy/notification.rb +10 -1
- data/lib/dommy/promise.rb +20 -0
- data/lib/dommy/range.rb +25 -37
- data/lib/dommy/resources.rb +38 -3
- data/lib/dommy/scheduler.rb +176 -48
- data/lib/dommy/shadow_root.rb +141 -19
- data/lib/dommy/storage.rb +135 -25
- data/lib/dommy/streams.rb +18 -0
- data/lib/dommy/structured_data.rb +45 -0
- data/lib/dommy/style_declaration.rb +31 -4
- data/lib/dommy/text_codec.rb +9 -19
- data/lib/dommy/tree_walker.rb +78 -79
- data/lib/dommy/url.rb +11 -4
- data/lib/dommy/validity_state.rb +109 -107
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +6 -28
- data/lib/dommy/window.rb +833 -103
- data/lib/dommy/worker.rb +5 -28
- data/lib/dommy/xml_http_request.rb +43 -46
- data/lib/dommy.rb +52 -3
- metadata +45 -6
- data/lib/dommy/internal/cookie_jar.rb +0 -29
data/lib/dommy/browser.rb
CHANGED
|
@@ -58,8 +58,11 @@ module Dommy
|
|
|
58
58
|
browser
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
# `encoding` is the encoding the page's bytes were decoded with
|
|
62
|
+
# (Encodings.decode_document), when it came from bytes; HTML given as a
|
|
63
|
+
# string makes a UTF-8 document.
|
|
61
64
|
def initialize(html, url: "http://localhost/", resources: nil, execute_scripts: true, strict: true, settle: true,
|
|
62
|
-
wasm_memory_shim: false, backend: nil, navigable: false, same_origin: false)
|
|
65
|
+
wasm_memory_shim: false, backend: nil, navigable: false, same_origin: false, encoding: nil)
|
|
63
66
|
@resources = resources
|
|
64
67
|
@same_origin = same_origin
|
|
65
68
|
@backend = backend
|
|
@@ -71,21 +74,50 @@ module Dommy
|
|
|
71
74
|
@console = []
|
|
72
75
|
@disposed = false
|
|
73
76
|
@pending_navigations = []
|
|
77
|
+
@popups = []
|
|
74
78
|
@runtime = nil
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
# One browsing session: every window it shows (and their frames) shares
|
|
80
|
+
# localStorage per origin and sessionStorage per origin.
|
|
81
|
+
@storage_provider = StorageProvider.new
|
|
82
|
+
# One cookie store for the session: document.cookie, cookieStore, and
|
|
83
|
+
# the Cookie / Set-Cookie of every request it sends through resources.
|
|
84
|
+
@cookie_jar = CookieJar.new
|
|
85
|
+
@before_unload_handler = nil
|
|
86
|
+
|
|
87
|
+
@window = Dommy.parse(html, encoding: encoding)
|
|
77
88
|
@window.location.__internal_set_url__(url) if url
|
|
78
|
-
|
|
89
|
+
@window.storage_provider = @storage_provider
|
|
90
|
+
@window.cookie_jar = @cookie_jar
|
|
79
91
|
|
|
80
92
|
if navigable
|
|
81
|
-
@fetcher =
|
|
93
|
+
@fetcher = navigation_fetcher
|
|
82
94
|
@history = Navigation::JointHistory.new
|
|
83
95
|
@window.navigation_delegate = self
|
|
84
96
|
@history.push(current_url, window: @window, windex: @window.history.__internal_index__)
|
|
97
|
+
install_history_sync(@window)
|
|
85
98
|
end
|
|
99
|
+
install_runtime(@window)
|
|
86
100
|
check_js_errors!
|
|
87
101
|
end
|
|
88
102
|
|
|
103
|
+
# The provider of this browser's Web Storage areas (shared by every window
|
|
104
|
+
# it shows); see Dommy::StorageProvider.
|
|
105
|
+
attr_reader :storage_provider
|
|
106
|
+
|
|
107
|
+
# This browser's cookies (a Dommy::CookieJar): the one store its pages'
|
|
108
|
+
# document.cookie and cookieStore use and its requests send and fill.
|
|
109
|
+
attr_reader :cookie_jar
|
|
110
|
+
|
|
111
|
+
# Install a handler for a page that asks to confirm leaving it (a canceled
|
|
112
|
+
# `beforeunload`, or one whose returnValue is set): called with the window
|
|
113
|
+
# and the BeforeUnloadEvent, its truthy answer lets the navigation proceed.
|
|
114
|
+
# Without one the navigation always proceeds — a headless page has no sticky
|
|
115
|
+
# user activation, so a browser would not prompt either.
|
|
116
|
+
def on_before_unload(&block)
|
|
117
|
+
@before_unload_handler = block
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
89
121
|
def document = @window.document
|
|
90
122
|
|
|
91
123
|
# Current document HTML (serialized).
|
|
@@ -117,8 +149,8 @@ module Dommy
|
|
|
117
149
|
# Move back / forward one entry in the joint history. A same-document target
|
|
118
150
|
# (the entry's window is still live) traverses in place (popstate); a
|
|
119
151
|
# document-boundary target is re-fetched (no bfcache — D2).
|
|
120
|
-
def back =
|
|
121
|
-
def forward =
|
|
152
|
+
def back = traverse_now(-1)
|
|
153
|
+
def forward = traverse_now(1)
|
|
122
154
|
|
|
123
155
|
# --- NavigationDelegate port (see Dommy::Navigation) ---
|
|
124
156
|
|
|
@@ -138,7 +170,7 @@ module Dommy
|
|
|
138
170
|
navigate_frame(frame,
|
|
139
171
|
{method: method, url: url, params: params, body: body, enctype: enctype, headers: headers},
|
|
140
172
|
resolve_against_current(url.to_s))
|
|
141
|
-
@window.scheduler.queue_microtask(proc { frame.
|
|
173
|
+
@window.scheduler.queue_microtask(proc { frame.__internal_run_iframe_load_event_steps__ })
|
|
142
174
|
return nil
|
|
143
175
|
end
|
|
144
176
|
|
|
@@ -164,6 +196,109 @@ module Dommy
|
|
|
164
196
|
end
|
|
165
197
|
|
|
166
198
|
def traverse(_delta) = nil
|
|
199
|
+
|
|
200
|
+
def history_length = @browser.history_length
|
|
201
|
+
|
|
202
|
+
# The frames of the document loaded into this frame load the same way.
|
|
203
|
+
def load_frame(frame, **nav) = @browser.load_frame(frame, **nav)
|
|
204
|
+
|
|
205
|
+
# A popup opened from inside the frame is the browser's too.
|
|
206
|
+
def open_window(**opts) = @browser.open_window(**opts)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# The navigation delegate of an auxiliary window this browser opened
|
|
210
|
+
# (window.open): its navigations load into it, keeping the Window the
|
|
211
|
+
# opener holds (Dommy has no WindowProxy, so the document is swapped
|
|
212
|
+
# under it), and its close() closes it.
|
|
213
|
+
class PopupDelegate
|
|
214
|
+
def initialize(browser, popup)
|
|
215
|
+
@browser = browser
|
|
216
|
+
@popup = popup
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def navigate(url:, source:, method: "GET", body: nil, params: nil, enctype: nil, target: nil, headers: {}, replace: false)
|
|
220
|
+
@browser.__internal_load_popup__(@popup, {url: url, method: method, body: body, params: params,
|
|
221
|
+
enctype: enctype, headers: headers})
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def traverse(_delta) = nil
|
|
225
|
+
|
|
226
|
+
def close_window = @browser.__internal_close_popup__(@popup)
|
|
227
|
+
|
|
228
|
+
def find_window(name) = @browser.find_window(name)
|
|
229
|
+
|
|
230
|
+
def load_frame(frame, **nav) = @browser.load_frame(frame, **nav)
|
|
231
|
+
|
|
232
|
+
def open_window(**opts) = @browser.open_window(**opts)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# The auxiliary windows the page opened with window.open and has not
|
|
236
|
+
# closed, oldest first.
|
|
237
|
+
def popups = @popups.dup
|
|
238
|
+
|
|
239
|
+
# NavigationDelegate `open_window`: window.open asked for a new top-level
|
|
240
|
+
# browsing context. It is created at once with the initial about:blank
|
|
241
|
+
# (whose origin is the opener's) and navigated to `url` from a task; it
|
|
242
|
+
# shares this browser's cookies and localStorage, and starts its
|
|
243
|
+
# sessionStorage as a copy of this one's. Window#window_open sets its
|
|
244
|
+
# opener and name.
|
|
245
|
+
def open_window(url:, target:, features: "")
|
|
246
|
+
return nil if @disposed
|
|
247
|
+
|
|
248
|
+
popup = Dommy.parse(Internal::ChildNavigable::BLANK_HTML)
|
|
249
|
+
popup.location.__internal_set_url__("about:blank")
|
|
250
|
+
popup.__internal_initial_about_blank__ = true
|
|
251
|
+
popup.storage_provider = @storage_provider.new_session
|
|
252
|
+
popup.cookie_jar = @cookie_jar
|
|
253
|
+
popup.navigation_delegate = PopupDelegate.new(self, popup)
|
|
254
|
+
@popups << popup
|
|
255
|
+
unless Internal::ChildNavigable.matches_about_blank?(url)
|
|
256
|
+
@window.scheduler.set_timeout(proc { __internal_load_popup__(popup, {url: url, method: "GET"}) }, 0)
|
|
257
|
+
end
|
|
258
|
+
popup
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Load a navigation into a popup: fetch it, give the popup's Window the
|
|
262
|
+
# document, and fire its load. Nothing happens when nothing serves it.
|
|
263
|
+
def __internal_load_popup__(popup, nav)
|
|
264
|
+
return nil if @disposed || @resources.nil? || !@popups.include?(popup)
|
|
265
|
+
|
|
266
|
+
url = nav[:url].to_s
|
|
267
|
+
@fetcher ||= navigation_fetcher
|
|
268
|
+
response, final_url = begin
|
|
269
|
+
@fetcher.request(method: nav[:method] || "GET", url: url, params: nav[:params],
|
|
270
|
+
body: nav[:body], enctype: nav[:enctype], headers: nav[:headers] || {})
|
|
271
|
+
rescue StandardError
|
|
272
|
+
[nil, url]
|
|
273
|
+
end
|
|
274
|
+
return nil unless response && document_response?(response)
|
|
275
|
+
|
|
276
|
+
loaded = Internal::ChildNavigable.window_for_response(response.body, response_header(response, "content-type"), final_url)
|
|
277
|
+
popup.__internal_adopt_document_of__(loaded)
|
|
278
|
+
popup.navigation_delegate = PopupDelegate.new(self, popup)
|
|
279
|
+
document = popup.document
|
|
280
|
+
document.__internal_update_readiness__("loading")
|
|
281
|
+
document.__internal_finish_loading__
|
|
282
|
+
nil
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# NavigationDelegate `find_window`: the top-level window (this browser's
|
|
286
|
+
# page or a popup) or a frame in one, whose target name is `name`.
|
|
287
|
+
def find_window(name)
|
|
288
|
+
([@window] + @popups).each do |top|
|
|
289
|
+
return top if top.name == name
|
|
290
|
+
|
|
291
|
+
found = top.__internal_find_descendant_window__(name)
|
|
292
|
+
return found if found
|
|
293
|
+
end
|
|
294
|
+
nil
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# A popup's close(): it leaves the browser, closed.
|
|
298
|
+
def __internal_close_popup__(popup)
|
|
299
|
+
@popups.delete(popup)
|
|
300
|
+
popup.__internal_discard__
|
|
301
|
+
nil
|
|
167
302
|
end
|
|
168
303
|
|
|
169
304
|
def frame_navigation_delegate(frame) = FrameNavigationDelegate.new(self, frame)
|
|
@@ -179,26 +314,49 @@ module Dommy
|
|
|
179
314
|
nav[:url].to_s
|
|
180
315
|
end
|
|
181
316
|
navigate_frame(frame, nav, resolved)
|
|
182
|
-
frame.
|
|
317
|
+
frame.__internal_run_iframe_load_event_steps__
|
|
183
318
|
nil
|
|
184
319
|
end
|
|
185
320
|
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
321
|
+
# NavigationDelegate `load_frame`: an iframe's child navigable is navigating
|
|
322
|
+
# to `url` (its `src`, or a navigation from inside it) — fetch it through
|
|
323
|
+
# this browser's resources and answer the document's Window, or nil when
|
|
324
|
+
# nothing serves it (the frame then keeps its document). The frame installs
|
|
325
|
+
# it and fires its own `load`.
|
|
326
|
+
def load_frame(frame, url:, method: "GET", body: nil, params: nil, enctype: nil, headers: {}, **)
|
|
327
|
+
return nil if @disposed || @resources.nil?
|
|
328
|
+
|
|
329
|
+
frame_window_for(frame, {url: url, method: method, body: body, params: params, enctype: enctype, headers: headers}, url.to_s)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# A history traversal by `delta` that the PAGE asked for (`history.go(n)`
|
|
333
|
+
# past its own document's entries): like a navigation it is a task, so it
|
|
334
|
+
# is recorded and performed at the next drain boundary.
|
|
189
335
|
def traverse(delta)
|
|
190
|
-
|
|
336
|
+
@pending_navigations << {traverse: delta.to_i} if @navigable
|
|
337
|
+
nil
|
|
338
|
+
end
|
|
191
339
|
|
|
192
|
-
|
|
193
|
-
|
|
340
|
+
# `history.length`: the joint session history's size (nil when this
|
|
341
|
+
# browser keeps none).
|
|
342
|
+
def history_length = (@history.length if @navigable)
|
|
194
343
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
344
|
+
# `window.stop()`: drop the navigations the page asked for and that have not
|
|
345
|
+
# been performed yet.
|
|
346
|
+
def stop
|
|
347
|
+
@pending_navigations.clear
|
|
348
|
+
nil
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# A Ruby-initiated traversal (back / forward) runs immediately: a
|
|
352
|
+
# same-document target (its window is still live) traverses in place
|
|
353
|
+
# (popstate); a document-boundary target is re-fetched.
|
|
354
|
+
def traverse_now(delta)
|
|
355
|
+
return self unless @navigable
|
|
356
|
+
|
|
357
|
+
perform_traversal!(delta)
|
|
358
|
+
@runtime.drain_microtasks
|
|
359
|
+
check_js_errors!
|
|
202
360
|
self
|
|
203
361
|
end
|
|
204
362
|
|
|
@@ -221,6 +379,7 @@ module Dommy
|
|
|
221
379
|
# `setTimeout(300)` — use `advance_time(300)` for debounce/throttle.
|
|
222
380
|
def settle
|
|
223
381
|
@runtime.settle
|
|
382
|
+
drive_popups(0)
|
|
224
383
|
flush_navigation!
|
|
225
384
|
check_js_errors!
|
|
226
385
|
self
|
|
@@ -229,6 +388,7 @@ module Dommy
|
|
|
229
388
|
# Advance virtual time by `ms`, running timers that come due, then settle.
|
|
230
389
|
def advance_time(ms)
|
|
231
390
|
@window.scheduler.advance_time(ms)
|
|
391
|
+
drive_popups(ms)
|
|
232
392
|
@runtime.drain_microtasks
|
|
233
393
|
flush_navigation!
|
|
234
394
|
check_js_errors!
|
|
@@ -240,6 +400,7 @@ module Dommy
|
|
|
240
400
|
# land before the next line, then enforce strict mode.
|
|
241
401
|
def after_interaction
|
|
242
402
|
@runtime.drain_microtasks
|
|
403
|
+
drive_popups(0)
|
|
243
404
|
flush_navigation!
|
|
244
405
|
check_js_errors!
|
|
245
406
|
end
|
|
@@ -319,7 +480,10 @@ module Dommy
|
|
|
319
480
|
# Opt-in WPT scaffolding (common/sab.js derives SharedArrayBuffer through
|
|
320
481
|
# WebAssembly.Memory); off by default so real pages don't see the shim.
|
|
321
482
|
runtime.install_wasm_memory_shim if @wasm_memory_shim && runtime.respond_to?(:install_wasm_memory_shim)
|
|
322
|
-
|
|
483
|
+
if @resources
|
|
484
|
+
window.globals["__fetch_handler__"] =
|
|
485
|
+
Resources::FetchHandler.new(@resources, cookie_jar: @cookie_jar, origin: -> { window.origin })
|
|
486
|
+
end
|
|
323
487
|
@runtime = runtime
|
|
324
488
|
doc = window.document
|
|
325
489
|
# An `on*` attribute that arrived after boot (a cloned template, an
|
|
@@ -327,7 +491,7 @@ module Dommy
|
|
|
327
491
|
# Installed whenever a runtime is attached — an embedder that drives script
|
|
328
492
|
# boot itself (`execute_scripts: false`) still needs inline handlers wired.
|
|
329
493
|
doc.inline_handler_wirer = lambda do
|
|
330
|
-
Js::ScriptBoot.wire_inline_handlers(runtime, on_error: ->(e) { @error_log.record(e) })
|
|
494
|
+
Js::ScriptBoot.wire_inline_handlers(runtime, document: doc, on_error: ->(e) { @error_log.record(e) })
|
|
331
495
|
end
|
|
332
496
|
return unless @execute_scripts
|
|
333
497
|
|
|
@@ -348,6 +512,16 @@ module Dommy
|
|
|
348
512
|
runtime.settle if @settle_after_boot
|
|
349
513
|
end
|
|
350
514
|
|
|
515
|
+
# A popup has no realm of its own; the tasks queued on its window (a
|
|
516
|
+
# close(), its timers) run as the browser's time moves.
|
|
517
|
+
def drive_popups(ms)
|
|
518
|
+
@popups.dup.each { |popup| popup.scheduler.advance_time(ms) }
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def navigation_fetcher
|
|
522
|
+
Navigation::Fetcher.new(@resources, same_origin: @same_origin, cookie_jar: @cookie_jar)
|
|
523
|
+
end
|
|
524
|
+
|
|
351
525
|
# Route an unhandled promise rejection through the page's own
|
|
352
526
|
# `unhandledrejection` handling first. The engine decides WHEN a rejection
|
|
353
527
|
# counts as unhandled (see Window#__internal_report_rejection__).
|
|
@@ -371,22 +545,36 @@ module Dommy
|
|
|
371
545
|
frame = resolve_target_frame(nav[:target])
|
|
372
546
|
return navigate_frame(frame, nav, resolved) if frame
|
|
373
547
|
|
|
548
|
+
# The outgoing page may ask to confirm leaving (beforeunload).
|
|
549
|
+
return unless unloading_allowed?(@window)
|
|
550
|
+
|
|
374
551
|
response, final_url = @fetcher.request(
|
|
375
552
|
method: nav[:method] || "GET", url: resolved, params: nav[:params],
|
|
376
553
|
body: nav[:body], enctype: nav[:enctype], headers: nav[:headers] || {}
|
|
377
554
|
)
|
|
378
555
|
return unless response&.success? && document_response?(response)
|
|
379
556
|
|
|
557
|
+
old_window = @window
|
|
558
|
+
referrer = referrer_for(nav[:source], old_window.location.href, final_url)
|
|
380
559
|
# Fire the outgoing document's unload sequence while its realm is still
|
|
381
560
|
# alive, then surface any of its errors before the realm is torn down.
|
|
382
|
-
fire_unload(
|
|
561
|
+
fire_unload(old_window)
|
|
383
562
|
check_js_errors!
|
|
384
563
|
|
|
385
|
-
|
|
564
|
+
# HTML's <meta charset> prescan is for an HTML document (one that names
|
|
565
|
+
# no type is taken for one); an XML type's encoding is not a <meta>'s.
|
|
566
|
+
content_type = response_header(response, "content-type")
|
|
567
|
+
type = Internal::MimeType.essence(content_type)
|
|
568
|
+
text, encoding = Encodings.decode_document(response.body, content_type, html: type.empty? || type == "text/html")
|
|
569
|
+
new_window = Dommy.parse(text, encoding: encoding)
|
|
386
570
|
new_window.location.__internal_set_url__(final_url)
|
|
571
|
+
new_window.document.__internal_set_last_modified__(response_header(response, "last-modified"))
|
|
572
|
+
new_window.document.__internal_referrer__ = referrer if referrer
|
|
387
573
|
new_window.navigation_delegate = self
|
|
574
|
+
new_window.storage_provider = @storage_provider
|
|
575
|
+
new_window.cookie_jar = @cookie_jar
|
|
576
|
+
old_window.__internal_discard__
|
|
388
577
|
@window = new_window
|
|
389
|
-
install_runtime(new_window)
|
|
390
578
|
|
|
391
579
|
windex = new_window.history.__internal_index__
|
|
392
580
|
if rebind || nav[:replace]
|
|
@@ -394,10 +582,82 @@ module Dommy
|
|
|
394
582
|
else
|
|
395
583
|
@history.push(final_url, window: new_window, windex: windex)
|
|
396
584
|
end
|
|
585
|
+
# The joint history knows the new page before its scripts boot, so a
|
|
586
|
+
# pushState / history.length during boot sees the right session.
|
|
587
|
+
install_history_sync(new_window)
|
|
588
|
+
install_runtime(new_window)
|
|
397
589
|
|
|
398
590
|
follow_meta_refresh!(refresh_depth)
|
|
399
591
|
end
|
|
400
592
|
|
|
593
|
+
# Fire `beforeunload` at the outgoing window; a page that cancels it (or
|
|
594
|
+
# sets returnValue) is asked about through the on_before_unload handler, if
|
|
595
|
+
# one is installed.
|
|
596
|
+
def unloading_allowed?(window)
|
|
597
|
+
event = BeforeUnloadEvent.new("beforeunload", "cancelable" => true).__internal_mark_trusted__
|
|
598
|
+
not_canceled = window.dispatch_event(event)
|
|
599
|
+
return true if not_canceled && event.return_value.to_s.empty?
|
|
600
|
+
return true unless @before_unload_handler
|
|
601
|
+
|
|
602
|
+
@before_unload_handler.call(window, event) ? true : false
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# The `document.referrer` a navigation hands the new document: the page
|
|
606
|
+
# that started it (links, forms, script), under the default
|
|
607
|
+
# strict-origin-when-cross-origin policy — the full URL (without fragment
|
|
608
|
+
# or credentials) within an origin, only the origin across origins, nothing
|
|
609
|
+
# on an https -> http downgrade. Ruby-initiated visits and traversals carry
|
|
610
|
+
# none.
|
|
611
|
+
def referrer_for(source, from_url, to_url)
|
|
612
|
+
return nil unless %i[link form location window_open].include?(source)
|
|
613
|
+
|
|
614
|
+
from = URL.new(from_url.to_s)
|
|
615
|
+
to = URL.new(to_url.to_s)
|
|
616
|
+
return nil unless %w[http: https:].include?(from.protocol)
|
|
617
|
+
return "" if from.protocol == "https:" && to.protocol == "http:"
|
|
618
|
+
return "#{from.origin}/" unless from.origin == to.origin
|
|
619
|
+
|
|
620
|
+
from.hash = ""
|
|
621
|
+
from.username = ""
|
|
622
|
+
from.password = ""
|
|
623
|
+
from.href
|
|
624
|
+
rescue StandardError
|
|
625
|
+
nil
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# A traversal by `delta` of the joint history: within the live window's own
|
|
629
|
+
# entries it moves that window's history (popstate); across a document
|
|
630
|
+
# boundary the target entry's URL is re-fetched (no bfcache).
|
|
631
|
+
def perform_traversal!(delta)
|
|
632
|
+
entry = @history.go(delta)
|
|
633
|
+
return unless entry
|
|
634
|
+
|
|
635
|
+
if entry.window && entry.window.equal?(@window)
|
|
636
|
+
@window.history.__internal_go_to__(entry.windex)
|
|
637
|
+
else
|
|
638
|
+
perform_navigation!({url: entry.url, method: "GET", source: :traverse}, rebind: true)
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# Mirror the page's same-document history changes (pushState, fragment
|
|
643
|
+
# navigations, its own back/forward within the document) into the joint
|
|
644
|
+
# history, so Browser#back and history.length see them. Guarded against a
|
|
645
|
+
# navigated-away window.
|
|
646
|
+
def install_history_sync(window)
|
|
647
|
+
window.history.__internal_on_change__ = lambda do |kind, url|
|
|
648
|
+
next unless window.equal?(@window)
|
|
649
|
+
|
|
650
|
+
case kind
|
|
651
|
+
when :push
|
|
652
|
+
@history.push(url, window: window, windex: window.history.__internal_index__)
|
|
653
|
+
when :replace
|
|
654
|
+
@history.rebind_current(url: url, window: window, windex: window.history.__internal_index__)
|
|
655
|
+
when :traverse
|
|
656
|
+
@history.sync_to(window, window.history.__internal_index__)
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
|
|
401
661
|
# The reserved browsing-context keywords; anything else names an iframe.
|
|
402
662
|
RESERVED_TARGETS = %w[_self _top _parent _blank].freeze
|
|
403
663
|
|
|
@@ -417,32 +677,43 @@ module Dommy
|
|
|
417
677
|
# it, install the response document as the frame's content, and fire the
|
|
418
678
|
# frame's `load`. The top-level window and joint history are left alone.
|
|
419
679
|
def navigate_frame(frame, nav, resolved_url)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return unless response&.success?
|
|
680
|
+
# The frame makes about:, data: and blob: documents itself and asks
|
|
681
|
+
# this browser (#load_frame) for the rest.
|
|
682
|
+
sub_window = frame.__internal_child_document_for__(resolved_url, nil, nav.except(:url))
|
|
683
|
+
return nil unless sub_window
|
|
425
684
|
|
|
426
|
-
sub_window = frame_document_for(response)
|
|
427
|
-
sub_window.location.__internal_set_url__(final_url)
|
|
428
685
|
# A navigation from inside the loaded frame also stays in that frame.
|
|
429
|
-
sub_window.navigation_delegate = frame_navigation_delegate(frame)
|
|
430
|
-
# A nested realm needs the seeded constructors to run the response's
|
|
431
|
-
# scripts; a runtime that cannot expose them simply runs without them.
|
|
432
|
-
@runtime.expose_constructors_on(sub_window) if @runtime.respond_to?(:expose_constructors_on)
|
|
686
|
+
sub_window.navigation_delegate = frame_navigation_delegate(frame) if sub_window.navigation_delegate.is_a?(Navigation::NullDelegate)
|
|
433
687
|
frame.__internal_set_content_document__(sub_window.document)
|
|
434
688
|
nil
|
|
435
689
|
end
|
|
436
690
|
|
|
437
|
-
#
|
|
438
|
-
#
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
691
|
+
# Fetch a frame navigation and build the Window of the document it
|
|
692
|
+
# answers. nil when nothing (successful) serves the URL.
|
|
693
|
+
def frame_window_for(frame, nav, resolved_url)
|
|
694
|
+
@fetcher ||= navigation_fetcher
|
|
695
|
+
response, final_url = begin
|
|
696
|
+
@fetcher.request(
|
|
697
|
+
method: nav[:method] || "GET", url: resolved_url, params: nav[:params],
|
|
698
|
+
body: nav[:body], enctype: nav[:enctype], headers: nav[:headers] || {}
|
|
699
|
+
)
|
|
700
|
+
rescue StandardError
|
|
701
|
+
# A resources adapter that cannot serve the request at all fails the
|
|
702
|
+
# navigation like a network error.
|
|
703
|
+
[nil, resolved_url]
|
|
704
|
+
end
|
|
705
|
+
# Nothing answered: a network error, which shows an error page (and
|
|
706
|
+
# still fires the frame's load). A response of any status is a document.
|
|
707
|
+
return Internal::ChildNavigable.error_window(final_url || resolved_url) unless response
|
|
442
708
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
709
|
+
sub_window = Internal::ChildNavigable.window_for_response(
|
|
710
|
+
response.body, response_header(response, "content-type"), final_url
|
|
711
|
+
)
|
|
712
|
+
sub_window.document.__internal_set_last_modified__(response_header(response, "last-modified"))
|
|
713
|
+
# A nested realm needs the seeded constructors to run the response's
|
|
714
|
+
# scripts; a runtime that cannot expose them simply runs without them.
|
|
715
|
+
@runtime.expose_constructors_on(sub_window) if @runtime.respond_to?(:expose_constructors_on)
|
|
716
|
+
sub_window
|
|
446
717
|
end
|
|
447
718
|
|
|
448
719
|
# If the freshly loaded document asks for an immediate `<meta http-equiv=
|
|
@@ -489,24 +760,36 @@ module Dommy
|
|
|
489
760
|
|
|
490
761
|
navs = @pending_navigations
|
|
491
762
|
@pending_navigations = []
|
|
492
|
-
navs.each
|
|
763
|
+
navs.each do |nav|
|
|
764
|
+
nav.key?(:traverse) ? perform_traversal!(nav[:traverse]) : perform_navigation!(nav)
|
|
765
|
+
end
|
|
493
766
|
end
|
|
494
767
|
|
|
768
|
+
# Unload the outgoing document: `pagehide` (a PageTransitionEvent, persisted
|
|
769
|
+
# false), then `unload` — both trusted and targeted at the document.
|
|
495
770
|
def fire_unload(window)
|
|
496
|
-
window.
|
|
497
|
-
window.dispatch_event(
|
|
771
|
+
document = window.document
|
|
772
|
+
window.dispatch_event(document.__internal_page_transition_event__("pagehide"))
|
|
773
|
+
unload = Dommy::Event.new("unload")
|
|
774
|
+
unload.__internal_set_legacy_target_override__(document)
|
|
775
|
+
window.dispatch_event(unload.__internal_mark_trusted__)
|
|
498
776
|
end
|
|
499
777
|
|
|
500
778
|
# Only HTML/XML responses replace the document; other content types (a JSON
|
|
501
779
|
# API hit, an image) leave the current page. A response with no Content-Type
|
|
502
780
|
# is treated as a document (fixtures commonly omit it).
|
|
503
781
|
def document_response?(response)
|
|
504
|
-
|
|
505
|
-
key = headers.keys.find { |k| k.to_s.casecmp?("content-type") }
|
|
506
|
-
content_type = key ? headers[key].to_s.downcase : ""
|
|
782
|
+
content_type = response_header(response, "content-type").to_s.downcase
|
|
507
783
|
content_type.empty? || content_type.include?("html") || content_type.include?("xml")
|
|
508
784
|
end
|
|
509
785
|
|
|
786
|
+
# A response header's value by case-insensitive name, or nil.
|
|
787
|
+
def response_header(response, name)
|
|
788
|
+
headers = response.headers || {}
|
|
789
|
+
key = headers.keys.find { |k| k.to_s.casecmp?(name) }
|
|
790
|
+
key && headers[key]
|
|
791
|
+
end
|
|
792
|
+
|
|
510
793
|
def submit_button?(button)
|
|
511
794
|
if button.tag_name == "BUTTON"
|
|
512
795
|
button.type == "submit"
|
data/lib/dommy/character_data.rb
CHANGED
|
@@ -89,9 +89,10 @@ module Dommy
|
|
|
89
89
|
@__node__.content
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
# Assigning `data`, `nodeValue` or `textContent` is "replace data" over
|
|
93
|
+
# the whole node, so a live range boundary inside it clamps to the start
|
|
94
|
+
# rather than dangling past the end.
|
|
92
95
|
def data=(value)
|
|
93
|
-
# Assigning `data` is "replace data" over the whole node, so a live range
|
|
94
|
-
# boundary inside it clamps to the start rather than dangling past the end.
|
|
95
96
|
old_length = utf16_length(@__node__.content)
|
|
96
97
|
write_data(value)
|
|
97
98
|
@document.__internal_ranges_replaced_data__(self, 0, old_length, utf16_length(@__node__.content))
|
|
@@ -102,7 +103,7 @@ module Dommy
|
|
|
102
103
|
end
|
|
103
104
|
|
|
104
105
|
def node_value=(value)
|
|
105
|
-
|
|
106
|
+
self.data = value
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
def text_content
|
|
@@ -110,9 +111,16 @@ module Dommy
|
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
def text_content=(value)
|
|
113
|
-
|
|
114
|
+
self.data = value
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Node.isConnected: whether the shadow-including root is a Document.
|
|
118
|
+
def is_connected?
|
|
119
|
+
Internal::NodeTraversal.connected?(@__node__, @document)
|
|
114
120
|
end
|
|
115
121
|
|
|
122
|
+
alias connected? is_connected?
|
|
123
|
+
|
|
116
124
|
def remove
|
|
117
125
|
@document.remove_node_with_notify(@__node__)
|
|
118
126
|
nil
|
|
@@ -178,29 +186,13 @@ module Dommy
|
|
|
178
186
|
def substring_data(offset, count)
|
|
179
187
|
s = @__node__.content
|
|
180
188
|
len = utf16_length(s)
|
|
181
|
-
o =
|
|
189
|
+
o = Internal::WebIDL.unsigned_long(offset)
|
|
182
190
|
raise DOMException::IndexSizeError, "offset out of bounds" if o > len
|
|
183
191
|
|
|
184
|
-
c = [
|
|
192
|
+
c = [Internal::WebIDL.unsigned_long(count), len - o].min
|
|
185
193
|
utf16_slice(s, o, c)
|
|
186
194
|
end
|
|
187
195
|
|
|
188
|
-
# ECMAScript ToUint32 — WebIDL `unsigned long` conversion for a data offset
|
|
189
|
-
# or count: ToNumber (a non-numeric string is NaN), truncate toward zero,
|
|
190
|
-
# then take modulo 2**32 (so -1 wraps to 4294967295, 0x100000000+2 to 2).
|
|
191
|
-
def to_uint32(value)
|
|
192
|
-
num =
|
|
193
|
-
case value
|
|
194
|
-
when Integer then value
|
|
195
|
-
when Numeric then value
|
|
196
|
-
when nil then 0 # JS null -> 0
|
|
197
|
-
else Float(value.to_s) rescue Float::NAN
|
|
198
|
-
end
|
|
199
|
-
return 0 unless num.respond_to?(:finite?) ? num.finite? : true
|
|
200
|
-
|
|
201
|
-
num.to_i % (2**32)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
196
|
def append_data(value)
|
|
205
197
|
write_data(@__node__.content + dom_string(value))
|
|
206
198
|
end
|
|
@@ -222,10 +214,10 @@ module Dommy
|
|
|
222
214
|
def replace_data(offset, count, value)
|
|
223
215
|
s = @__node__.content
|
|
224
216
|
len = utf16_length(s)
|
|
225
|
-
o =
|
|
217
|
+
o = Internal::WebIDL.unsigned_long(offset)
|
|
226
218
|
raise DOMException::IndexSizeError, "offset out of bounds" if o > len
|
|
227
219
|
|
|
228
|
-
c = [
|
|
220
|
+
c = [Internal::WebIDL.unsigned_long(count), len - o].min
|
|
229
221
|
inserted = dom_string(value)
|
|
230
222
|
write_data(utf16_slice(s, 0, o) + inserted + utf16_slice(s, o + c, len - (o + c)))
|
|
231
223
|
# Live ranges whose boundary sits in (or past) the replaced run follow it.
|
|
@@ -256,6 +248,8 @@ module Dommy
|
|
|
256
248
|
next_sibling
|
|
257
249
|
when "previousSibling"
|
|
258
250
|
previous_sibling
|
|
251
|
+
when "isConnected"
|
|
252
|
+
is_connected?
|
|
259
253
|
when "childNodes"
|
|
260
254
|
# CharacterData is a leaf node: childNodes is always an empty (but
|
|
261
255
|
# present and iterable) NodeList, and firstChild/lastChild are null.
|
|
@@ -275,25 +269,13 @@ module Dommy
|
|
|
275
269
|
# no `slot` attribute, so it targets the default (unnamed) slot of its parent
|
|
276
270
|
# element's shadow tree; a closed shadow tree hides the assignment (null).
|
|
277
271
|
def assigned_slot
|
|
278
|
-
|
|
279
|
-
return nil unless parent&.element?
|
|
280
|
-
|
|
281
|
-
host = @document.wrap_node(parent)
|
|
282
|
-
return nil unless host.respond_to?(:shadow_root)
|
|
283
|
-
|
|
284
|
-
sr = host.shadow_root
|
|
285
|
-
return nil unless sr
|
|
286
|
-
return nil if sr.__js_get__("mode") == "closed"
|
|
287
|
-
|
|
288
|
-
sr.query_selector_all("slot").find do |slot|
|
|
289
|
-
(slot.respond_to?(:name) ? slot.name.to_s : "") == ""
|
|
290
|
-
end
|
|
272
|
+
Internal::SlotAssignment.find_slot(@document, @__node__, open: true)
|
|
291
273
|
end
|
|
292
274
|
|
|
293
275
|
def __js_set__(key, value)
|
|
294
276
|
case key
|
|
295
277
|
when "textContent", "data", "nodeValue"
|
|
296
|
-
|
|
278
|
+
self.data = value
|
|
297
279
|
end
|
|
298
280
|
|
|
299
281
|
nil
|
|
@@ -312,8 +294,7 @@ module Dommy
|
|
|
312
294
|
false
|
|
313
295
|
when "contains"
|
|
314
296
|
# A leaf node contains only itself (no descendants).
|
|
315
|
-
args[0].
|
|
316
|
-
args[0].__dommy_backend_node__ == @__node__
|
|
297
|
+
args[0].is_a?(Node) && args[0].__dommy_backend_node__ == @__node__
|
|
317
298
|
when "appendChild"
|
|
318
299
|
append_child(args[0])
|
|
319
300
|
when "insertBefore"
|