dommy 0.7.0 → 0.8.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 +9 -1
- data/lib/dommy/attr.rb +192 -39
- data/lib/dommy/backend/nokogiri_adapter.rb +76 -0
- data/lib/dommy/backend/nokolexbor_adapter.rb +37 -0
- data/lib/dommy/backend.rb +46 -0
- data/lib/dommy/blob.rb +28 -9
- data/lib/dommy/bridge/constructor_registry.rb +28 -0
- data/lib/dommy/bridge/methods.rb +57 -0
- data/lib/dommy/bridge.rb +97 -0
- data/lib/dommy/callable_invoker.rb +36 -0
- data/lib/dommy/cookie_store.rb +3 -1
- data/lib/dommy/crypto.rb +7 -1
- data/lib/dommy/css.rb +46 -0
- data/lib/dommy/custom_elements.rb +27 -3
- data/lib/dommy/data_transfer.rb +4 -0
- data/lib/dommy/document.rb +615 -48
- data/lib/dommy/dom_parser.rb +28 -15
- data/lib/dommy/element.rb +999 -471
- data/lib/dommy/event.rb +260 -96
- data/lib/dommy/event_source.rb +6 -2
- data/lib/dommy/fetch.rb +505 -43
- data/lib/dommy/file_reader.rb +11 -3
- data/lib/dommy/form_data.rb +2 -0
- data/lib/dommy/history.rb +43 -8
- data/lib/dommy/html_collection.rb +55 -2
- data/lib/dommy/html_elements.rb +102 -1519
- data/lib/dommy/internal/css_pseudo_handlers.rb +109 -0
- data/lib/dommy/internal/global_functions.rb +26 -0
- data/lib/dommy/internal/idna.rb +16 -7
- data/lib/dommy/internal/ipv4_parser.rb +22 -7
- data/lib/dommy/internal/mutation_coordinator.rb +11 -2
- data/lib/dommy/internal/namespaces.rb +70 -0
- data/lib/dommy/internal/node_equality.rb +86 -0
- data/lib/dommy/internal/node_wrapper_cache.rb +62 -27
- data/lib/dommy/internal/observable_callback.rb +1 -5
- data/lib/dommy/internal/parent_node.rb +126 -0
- data/lib/dommy/internal/reflected_attributes.rb +103 -13
- data/lib/dommy/internal/selector_parser.rb +664 -0
- data/lib/dommy/internal/url_parser.rb +677 -0
- data/lib/dommy/intersection_observer.rb +2 -0
- data/lib/dommy/location.rb +2 -0
- data/lib/dommy/media_query_list.rb +7 -1
- data/lib/dommy/message_channel.rb +32 -2
- data/lib/dommy/mutation_observer.rb +55 -12
- data/lib/dommy/navigator.rb +26 -12
- data/lib/dommy/node.rb +158 -28
- data/lib/dommy/notification.rb +3 -1
- data/lib/dommy/performance.rb +4 -0
- data/lib/dommy/performance_observer.rb +2 -0
- data/lib/dommy/promise.rb +14 -14
- data/lib/dommy/range.rb +74 -5
- data/lib/dommy/resize_observer.rb +2 -0
- data/lib/dommy/scheduler.rb +34 -13
- data/lib/dommy/shadow_root.rb +23 -54
- data/lib/dommy/storage.rb +2 -0
- data/lib/dommy/streams.rb +18 -27
- data/lib/dommy/svg_elements.rb +204 -3606
- data/lib/dommy/text_codec.rb +174 -21
- data/lib/dommy/tree_walker.rb +255 -66
- data/lib/dommy/url.rb +287 -449
- data/lib/dommy/url_pattern.rb +2 -0
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +37 -7
- data/lib/dommy/window.rb +202 -213
- data/lib/dommy/worker.rb +7 -7
- data/lib/dommy/xml_http_request.rb +15 -5
- data/lib/dommy.rb +7 -0
- metadata +12 -3
|
@@ -67,11 +67,17 @@ module Dommy
|
|
|
67
67
|
remove_event_listener("change", @onchange) if @onchange
|
|
68
68
|
@onchange = value
|
|
69
69
|
add_event_listener("change", value) if value
|
|
70
|
+
else
|
|
71
|
+
return Bridge::UNHANDLED
|
|
70
72
|
end
|
|
71
73
|
|
|
72
74
|
nil
|
|
73
75
|
end
|
|
74
76
|
|
|
77
|
+
include Bridge::Methods
|
|
78
|
+
js_methods %w[
|
|
79
|
+
matches addListener removeListener addEventListener removeEventListener dispatchEvent
|
|
80
|
+
]
|
|
75
81
|
def __js_call__(method, args)
|
|
76
82
|
case method
|
|
77
83
|
when "matches"
|
|
@@ -83,7 +89,7 @@ module Dommy
|
|
|
83
89
|
when "addEventListener"
|
|
84
90
|
add_event_listener(args[0], args[1], args[2])
|
|
85
91
|
when "removeEventListener"
|
|
86
|
-
remove_event_listener(args[0], args[1])
|
|
92
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
87
93
|
when "dispatchEvent"
|
|
88
94
|
dispatch_event(args[0])
|
|
89
95
|
end
|
|
@@ -100,11 +100,15 @@ module Dommy
|
|
|
100
100
|
@inline_message_handler = value
|
|
101
101
|
add_event_listener("message", value) if value
|
|
102
102
|
start if value
|
|
103
|
+
else
|
|
104
|
+
return Bridge::UNHANDLED
|
|
103
105
|
end
|
|
104
106
|
|
|
105
107
|
nil
|
|
106
108
|
end
|
|
107
109
|
|
|
110
|
+
include Bridge::Methods
|
|
111
|
+
js_methods %w[postMessage start close addEventListener removeEventListener dispatchEvent]
|
|
108
112
|
def __js_call__(method, args)
|
|
109
113
|
case method
|
|
110
114
|
when "postMessage"
|
|
@@ -116,7 +120,7 @@ module Dommy
|
|
|
116
120
|
when "addEventListener"
|
|
117
121
|
add_event_listener(args[0], args[1], args[2])
|
|
118
122
|
when "removeEventListener"
|
|
119
|
-
remove_event_listener(args[0], args[1])
|
|
123
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
120
124
|
when "dispatchEvent"
|
|
121
125
|
dispatch_event(args[0])
|
|
122
126
|
end
|
|
@@ -157,6 +161,28 @@ module Dommy
|
|
|
157
161
|
super
|
|
158
162
|
end
|
|
159
163
|
end
|
|
164
|
+
|
|
165
|
+
js_methods %w[initMessageEvent]
|
|
166
|
+
def __js_call__(method, args)
|
|
167
|
+
case method
|
|
168
|
+
when "initMessageEvent"
|
|
169
|
+
# Deprecated initMessageEvent(type, bubbles, cancelable, data, origin,
|
|
170
|
+
# lastEventId, source, ports); a no-op while the event is dispatching.
|
|
171
|
+
raise Bridge::TypeError, "initMessageEvent requires a type argument" if args.empty?
|
|
172
|
+
|
|
173
|
+
unless @dispatch_flag
|
|
174
|
+
init_event(args[0], args[1], args[2])
|
|
175
|
+
@data = args[3]
|
|
176
|
+
@origin = (args[4] || "").to_s
|
|
177
|
+
@last_event_id = (args[5] || "").to_s
|
|
178
|
+
@source = args[6]
|
|
179
|
+
@ports = args[7] || []
|
|
180
|
+
end
|
|
181
|
+
nil
|
|
182
|
+
else
|
|
183
|
+
super
|
|
184
|
+
end
|
|
185
|
+
end
|
|
160
186
|
end
|
|
161
187
|
|
|
162
188
|
# `BroadcastChannel` — same-origin pub/sub. Dommy keeps a per-window
|
|
@@ -222,11 +248,15 @@ module Dommy
|
|
|
222
248
|
remove_event_listener("message", @onmessage) if @onmessage
|
|
223
249
|
@onmessage = value
|
|
224
250
|
add_event_listener("message", value) if value
|
|
251
|
+
else
|
|
252
|
+
return Bridge::UNHANDLED
|
|
225
253
|
end
|
|
226
254
|
|
|
227
255
|
nil
|
|
228
256
|
end
|
|
229
257
|
|
|
258
|
+
include Bridge::Methods
|
|
259
|
+
js_methods %w[postMessage close addEventListener removeEventListener dispatchEvent]
|
|
230
260
|
def __js_call__(method, args)
|
|
231
261
|
case method
|
|
232
262
|
when "postMessage"
|
|
@@ -236,7 +266,7 @@ module Dommy
|
|
|
236
266
|
when "addEventListener"
|
|
237
267
|
add_event_listener(args[0], args[1], args[2])
|
|
238
268
|
when "removeEventListener"
|
|
239
|
-
remove_event_listener(args[0], args[1])
|
|
269
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
240
270
|
when "dispatchEvent"
|
|
241
271
|
dispatch_event(args[0])
|
|
242
272
|
end
|
|
@@ -75,8 +75,11 @@ module Dommy
|
|
|
75
75
|
@observed = []
|
|
76
76
|
@records = []
|
|
77
77
|
@scheduled = false
|
|
78
|
+
@registered_docs = []
|
|
78
79
|
end
|
|
79
80
|
|
|
81
|
+
include Bridge::Methods
|
|
82
|
+
js_methods %w[observe disconnect takeRecords]
|
|
80
83
|
def __js_call__(method, args)
|
|
81
84
|
case method
|
|
82
85
|
when "observe"
|
|
@@ -136,16 +139,29 @@ module Dommy
|
|
|
136
139
|
# is supplied; `characterData: true` is implied if
|
|
137
140
|
# characterDataOldValue is supplied. Matches the spec's option
|
|
138
141
|
# normalization in MutationObserverInit.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
# `attributes`/`characterData` are *implied* true only when their
|
|
143
|
+
# old-value/filter companion is present AND the member itself is omitted.
|
|
144
|
+
# If the member is present but false, that companion is a TypeError.
|
|
145
|
+
attr_present = opts.key?("attributes") || opts.key?(:attributes)
|
|
146
|
+
char_present = opts.key?("characterData") || opts.key?(:characterData)
|
|
147
|
+
attrs_extras = !attribute_filter.nil? || truthy_option(opts, "attributeOldValue")
|
|
148
|
+
char_extras = truthy_option(opts, "characterDataOldValue")
|
|
149
|
+
|
|
150
|
+
if attrs_extras && attr_present && !truthy_option(opts, "attributes")
|
|
151
|
+
raise Bridge::TypeError, "attributeOldValue/attributeFilter requires attributes to be true"
|
|
152
|
+
end
|
|
153
|
+
if char_extras && char_present && !truthy_option(opts, "characterData")
|
|
154
|
+
raise Bridge::TypeError, "characterDataOldValue requires characterData to be true"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
attributes_on = truthy_option(opts, "attributes") || (attrs_extras && !attr_present)
|
|
142
158
|
child_list_on = truthy_option(opts, "childList")
|
|
143
|
-
character_data_on = truthy_option(opts, "characterData") ||
|
|
159
|
+
character_data_on = truthy_option(opts, "characterData") || (char_extras && !char_present)
|
|
144
160
|
|
|
145
161
|
# Per spec, observe() must request at least one of childList,
|
|
146
162
|
# attributes, or characterData; otherwise TypeError.
|
|
147
163
|
unless child_list_on || attributes_on || character_data_on
|
|
148
|
-
raise TypeError, "MutationObserver.observe: at least one of childList, attributes, characterData must be true"
|
|
164
|
+
raise Bridge::TypeError, "MutationObserver.observe: at least one of childList, attributes, characterData must be true"
|
|
149
165
|
end
|
|
150
166
|
|
|
151
167
|
entry = {
|
|
@@ -169,7 +185,14 @@ module Dommy
|
|
|
169
185
|
@observed << entry
|
|
170
186
|
end
|
|
171
187
|
|
|
172
|
-
|
|
188
|
+
# Register on the TARGET's node-document — not just the observer's own
|
|
189
|
+
# document — so an observer watching a node in another document (e.g. a
|
|
190
|
+
# DOMParser-created XML document) is reached by that document's mutations.
|
|
191
|
+
doc = document_for_target(target)
|
|
192
|
+
unless @registered_docs.include?(doc)
|
|
193
|
+
doc.register_observer(self)
|
|
194
|
+
@registered_docs << doc
|
|
195
|
+
end
|
|
173
196
|
nil
|
|
174
197
|
end
|
|
175
198
|
|
|
@@ -177,10 +200,19 @@ module Dommy
|
|
|
177
200
|
@records.clear
|
|
178
201
|
@scheduled = false
|
|
179
202
|
@observed.clear
|
|
180
|
-
@
|
|
203
|
+
@registered_docs.each { |doc| doc.unregister_observer(self) }
|
|
204
|
+
@registered_docs.clear
|
|
181
205
|
nil
|
|
182
206
|
end
|
|
183
207
|
|
|
208
|
+
# The node-document a target belongs to (the target itself when it is a
|
|
209
|
+
# Document).
|
|
210
|
+
def document_for_target(target)
|
|
211
|
+
return target if target.is_a?(Dommy::Document)
|
|
212
|
+
|
|
213
|
+
target.instance_variable_get(:@document) || @document
|
|
214
|
+
end
|
|
215
|
+
|
|
184
216
|
def take_records
|
|
185
217
|
out = @records.dup
|
|
186
218
|
@records.clear
|
|
@@ -194,16 +226,27 @@ module Dommy
|
|
|
194
226
|
|
|
195
227
|
records = @records.dup
|
|
196
228
|
@records.clear
|
|
197
|
-
|
|
198
|
-
|
|
229
|
+
# Per spec the callback receives (mutationRecords, observer) and is invoked
|
|
230
|
+
# with `this` set to the observer.
|
|
231
|
+
if @callback.respond_to?(:__js_call_with_this__)
|
|
232
|
+
@callback.__js_call_with_this__([records, self], self)
|
|
233
|
+
elsif @callback.respond_to?(:__js_call__)
|
|
234
|
+
@callback.__js_call__("call", [records, self])
|
|
199
235
|
elsif @callback.respond_to?(:call)
|
|
200
|
-
@callback.call(records)
|
|
236
|
+
@callback.call(records, self)
|
|
201
237
|
end
|
|
202
238
|
end
|
|
203
239
|
|
|
240
|
+
# A MutationObserverInit member is a WebIDL `boolean`, so its value is
|
|
241
|
+
# converted with JS ToBoolean — any object (e.g. `attributes: ["abc"]`) is
|
|
242
|
+
# truthy; only false / 0 / "" / null / undefined / NaN are falsy.
|
|
204
243
|
def truthy_option(hash, key)
|
|
205
|
-
value = hash[key]
|
|
206
|
-
value ==
|
|
244
|
+
value = hash.key?(key) ? hash[key] : hash[key.to_sym]
|
|
245
|
+
return false if value.nil? || value == false || value == 0 || value == ""
|
|
246
|
+
return false if defined?(Bridge::UNDEFINED) && value.equal?(Bridge::UNDEFINED)
|
|
247
|
+
return false if value.is_a?(Float) && value.nan?
|
|
248
|
+
|
|
249
|
+
true
|
|
207
250
|
end
|
|
208
251
|
end
|
|
209
252
|
end
|
data/lib/dommy/navigator.rb
CHANGED
|
@@ -106,6 +106,8 @@ module Dommy
|
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
+
include Bridge::Methods
|
|
110
|
+
js_methods %w[share canShare vibrate getBattery]
|
|
109
111
|
def __js_call__(method, args)
|
|
110
112
|
case method
|
|
111
113
|
when "share"
|
|
@@ -120,7 +122,7 @@ module Dommy
|
|
|
120
122
|
end
|
|
121
123
|
|
|
122
124
|
def __js_set__(_key, _value)
|
|
123
|
-
|
|
125
|
+
Bridge::UNHANDLED
|
|
124
126
|
end
|
|
125
127
|
end
|
|
126
128
|
|
|
@@ -171,9 +173,11 @@ module Dommy
|
|
|
171
173
|
end
|
|
172
174
|
|
|
173
175
|
def __js_set__(_key, _value)
|
|
174
|
-
|
|
176
|
+
Bridge::UNHANDLED
|
|
175
177
|
end
|
|
176
178
|
|
|
179
|
+
include Bridge::Methods
|
|
180
|
+
js_methods %w[readText writeText read write addEventListener removeEventListener dispatchEvent]
|
|
177
181
|
def __js_call__(method, args)
|
|
178
182
|
case method
|
|
179
183
|
when "readText"
|
|
@@ -187,7 +191,7 @@ module Dommy
|
|
|
187
191
|
when "addEventListener"
|
|
188
192
|
add_event_listener(args[0], args[1], args[2])
|
|
189
193
|
when "removeEventListener"
|
|
190
|
-
remove_event_listener(args[0], args[1])
|
|
194
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
191
195
|
when "dispatchEvent"
|
|
192
196
|
dispatch_event(args[0])
|
|
193
197
|
end
|
|
@@ -259,9 +263,11 @@ module Dommy
|
|
|
259
263
|
end
|
|
260
264
|
|
|
261
265
|
def __js_set__(_key, _value)
|
|
262
|
-
|
|
266
|
+
Bridge::UNHANDLED
|
|
263
267
|
end
|
|
264
268
|
|
|
269
|
+
include Bridge::Methods
|
|
270
|
+
js_methods %w[query]
|
|
265
271
|
def __js_call__(method, args)
|
|
266
272
|
case method
|
|
267
273
|
when "query"
|
|
@@ -311,17 +317,21 @@ module Dommy
|
|
|
311
317
|
remove_event_listener("change", @onchange) if @onchange
|
|
312
318
|
@onchange = value
|
|
313
319
|
add_event_listener("change", value) if value
|
|
320
|
+
else
|
|
321
|
+
return Bridge::UNHANDLED
|
|
314
322
|
end
|
|
315
323
|
|
|
316
324
|
nil
|
|
317
325
|
end
|
|
318
326
|
|
|
327
|
+
include Bridge::Methods
|
|
328
|
+
js_methods %w[addEventListener removeEventListener dispatchEvent]
|
|
319
329
|
def __js_call__(method, args)
|
|
320
330
|
case method
|
|
321
331
|
when "addEventListener"
|
|
322
332
|
add_event_listener(args[0], args[1], args[2])
|
|
323
333
|
when "removeEventListener"
|
|
324
|
-
remove_event_listener(args[0], args[1])
|
|
334
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
325
335
|
when "dispatchEvent"
|
|
326
336
|
dispatch_event(args[0])
|
|
327
337
|
end
|
|
@@ -393,6 +403,8 @@ module Dommy
|
|
|
393
403
|
|
|
394
404
|
alias clearWatch clear_watch
|
|
395
405
|
|
|
406
|
+
include Bridge::Methods
|
|
407
|
+
js_methods %w[getCurrentPosition watchPosition clearWatch]
|
|
396
408
|
def __js_call__(method, args)
|
|
397
409
|
case method
|
|
398
410
|
when "getCurrentPosition"
|
|
@@ -415,13 +427,7 @@ module Dommy
|
|
|
415
427
|
end
|
|
416
428
|
|
|
417
429
|
def invoke(callback, payload)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if callback.respond_to?(:__js_call__)
|
|
421
|
-
callback.__js_call__("call", [payload])
|
|
422
|
-
elsif callback.respond_to?(:call)
|
|
423
|
-
callback.call(payload)
|
|
424
|
-
end
|
|
430
|
+
CallableInvoker.invoke(callback, payload)
|
|
425
431
|
end
|
|
426
432
|
end
|
|
427
433
|
|
|
@@ -439,6 +445,8 @@ module Dommy
|
|
|
439
445
|
PromiseValue.resolve(@window, WakeLockSentinel.new(@window, type.to_s))
|
|
440
446
|
end
|
|
441
447
|
|
|
448
|
+
include Bridge::Methods
|
|
449
|
+
js_methods %w[request]
|
|
442
450
|
def __js_call__(method, args)
|
|
443
451
|
case method
|
|
444
452
|
when "request"
|
|
@@ -479,6 +487,8 @@ module Dommy
|
|
|
479
487
|
end
|
|
480
488
|
end
|
|
481
489
|
|
|
490
|
+
include Bridge::Methods
|
|
491
|
+
js_methods %w[release]
|
|
482
492
|
def __js_call__(method, _args)
|
|
483
493
|
case method
|
|
484
494
|
when "release"
|
|
@@ -561,6 +571,8 @@ module Dommy
|
|
|
561
571
|
PromiseValue.resolve(@window, {"held" => held, "pending" => []})
|
|
562
572
|
end
|
|
563
573
|
|
|
574
|
+
include Bridge::Methods
|
|
575
|
+
js_methods %w[request query]
|
|
564
576
|
def __js_call__(method, args)
|
|
565
577
|
case method
|
|
566
578
|
when "request"
|
|
@@ -617,6 +629,8 @@ module Dommy
|
|
|
617
629
|
PromiseValue.resolve(@window, @persisted)
|
|
618
630
|
end
|
|
619
631
|
|
|
632
|
+
include Bridge::Methods
|
|
633
|
+
js_methods %w[estimate persist persisted]
|
|
620
634
|
def __js_call__(method, _args)
|
|
621
635
|
case method
|
|
622
636
|
when "estimate"
|
data/lib/dommy/node.rb
CHANGED
|
@@ -10,12 +10,6 @@ module Dommy
|
|
|
10
10
|
# snapshots tree state at the time of the query, matching what
|
|
11
11
|
# most happy-dom test patterns expect.
|
|
12
12
|
class NodeList < Array
|
|
13
|
-
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
14
|
-
JS_METHOD_NAMES = %w[item forEach entries keys values].freeze
|
|
15
|
-
def __js_method_names__
|
|
16
|
-
JS_METHOD_NAMES
|
|
17
|
-
end
|
|
18
|
-
|
|
19
13
|
# Spec-compliant: out-of-range returns nil, not raise (Array#[] is
|
|
20
14
|
# close but we make negative indices fail too — DOM `item(-1)` is
|
|
21
15
|
# nil, not Array#[-1]'s last element).
|
|
@@ -60,24 +54,24 @@ module Dommy
|
|
|
60
54
|
when "length"
|
|
61
55
|
length
|
|
62
56
|
else
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
# Indexed getter: out-of-range yields JS `undefined` (item() returns null).
|
|
58
|
+
if key.is_a?(Integer) || key.to_s.match?(/\A-?\d+\z/)
|
|
59
|
+
token = item(key.to_i)
|
|
60
|
+
token.nil? ? Bridge::UNDEFINED : token
|
|
65
61
|
end
|
|
66
62
|
end
|
|
67
63
|
end
|
|
68
64
|
|
|
65
|
+
include Bridge::Methods
|
|
66
|
+
# keys/values/entries/Symbol.iterator are provided JS-side (the array-like
|
|
67
|
+
# prototype) so they return real iterators, not arrays — see host_runtime.js.
|
|
68
|
+
js_methods %w[item forEach]
|
|
69
69
|
def __js_call__(method, args)
|
|
70
70
|
case method
|
|
71
71
|
when "item"
|
|
72
72
|
item(args[0])
|
|
73
73
|
when "forEach"
|
|
74
74
|
for_each(&args[0])
|
|
75
|
-
when "entries"
|
|
76
|
-
entries
|
|
77
|
-
when "keys"
|
|
78
|
-
keys
|
|
79
|
-
when "values"
|
|
80
|
-
values
|
|
81
75
|
end
|
|
82
76
|
end
|
|
83
77
|
end
|
|
@@ -94,12 +88,6 @@ module Dommy
|
|
|
94
88
|
class LiveNodeList
|
|
95
89
|
include Enumerable
|
|
96
90
|
|
|
97
|
-
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
98
|
-
JS_METHOD_NAMES = %w[item forEach entries keys values].freeze
|
|
99
|
-
def __js_method_names__
|
|
100
|
-
JS_METHOD_NAMES
|
|
101
|
-
end
|
|
102
|
-
|
|
103
91
|
def initialize(&block)
|
|
104
92
|
@compute = block
|
|
105
93
|
end
|
|
@@ -175,24 +163,24 @@ module Dommy
|
|
|
175
163
|
when "length"
|
|
176
164
|
length
|
|
177
165
|
else
|
|
178
|
-
|
|
179
|
-
|
|
166
|
+
# Indexed getter: out-of-range yields JS `undefined` (item() returns null).
|
|
167
|
+
if key.is_a?(Integer) || key.to_s.match?(/\A-?\d+\z/)
|
|
168
|
+
token = item(key.to_i)
|
|
169
|
+
token.nil? ? Bridge::UNDEFINED : token
|
|
180
170
|
end
|
|
181
171
|
end
|
|
182
172
|
end
|
|
183
173
|
|
|
174
|
+
include Bridge::Methods
|
|
175
|
+
# keys/values/entries/Symbol.iterator are provided JS-side (the array-like
|
|
176
|
+
# prototype) so they return real iterators, not arrays — see host_runtime.js.
|
|
177
|
+
js_methods %w[item forEach]
|
|
184
178
|
def __js_call__(method, args)
|
|
185
179
|
case method
|
|
186
180
|
when "item"
|
|
187
181
|
item(args[0])
|
|
188
182
|
when "forEach"
|
|
189
183
|
for_each(&args[0])
|
|
190
|
-
when "entries"
|
|
191
|
-
entries
|
|
192
|
-
when "keys"
|
|
193
|
-
keys
|
|
194
|
-
when "values"
|
|
195
|
-
values
|
|
196
184
|
end
|
|
197
185
|
end
|
|
198
186
|
end
|
|
@@ -226,5 +214,147 @@ module Dommy
|
|
|
226
214
|
DOCUMENT_POSITION_CONTAINS = 0x08
|
|
227
215
|
DOCUMENT_POSITION_CONTAINED_BY = 0x10
|
|
228
216
|
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20
|
|
217
|
+
|
|
218
|
+
# WHATWG Node.isEqualNode — deep structural equality (type-specific data
|
|
219
|
+
# plus equal, in-order, recursively-equal children). Available on every node
|
|
220
|
+
# class that includes Node; the bridge routes "isEqualNode" here.
|
|
221
|
+
def is_equal_node(other)
|
|
222
|
+
Internal::NodeEquality.equal?(self, other)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Node.isSameNode — strict reference identity (deprecated alias for `===`).
|
|
226
|
+
def is_same_node(other)
|
|
227
|
+
equal?(other)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Node.compareDocumentPosition(other) — a bitmask describing where `other`
|
|
231
|
+
# sits relative to this node: 0 for the same node, CONTAINS/CONTAINED_BY for
|
|
232
|
+
# ancestor/descendant, PRECEDING/FOLLOWING for tree order, or DISCONNECTED
|
|
233
|
+
# (with a stable IMPLEMENTATION_SPECIFIC|PRECEDING) for unrelated nodes.
|
|
234
|
+
# Generic over any node with a backing Nokogiri node.
|
|
235
|
+
def compare_document_position(other)
|
|
236
|
+
return 0 if equal?(other)
|
|
237
|
+
unless respond_to?(:__dommy_backend_node__) && other.respond_to?(:__dommy_backend_node__)
|
|
238
|
+
return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOCUMENT_POSITION_PRECEDING
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
self_node = __dommy_backend_node__
|
|
242
|
+
other_node = other.__dommy_backend_node__
|
|
243
|
+
self_ancestors = node_ancestor_chain(self_node)
|
|
244
|
+
other_ancestors = node_ancestor_chain(other_node)
|
|
245
|
+
|
|
246
|
+
common = self_ancestors.find { |a| other_ancestors.include?(a) }
|
|
247
|
+
unless common
|
|
248
|
+
return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOCUMENT_POSITION_PRECEDING
|
|
249
|
+
end
|
|
250
|
+
return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING if common == self_node
|
|
251
|
+
return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING if common == other_node
|
|
252
|
+
|
|
253
|
+
self_branch = node_branch_under(common, self_ancestors)
|
|
254
|
+
other_branch = node_branch_under(common, other_ancestors)
|
|
255
|
+
common.children.each do |child|
|
|
256
|
+
return DOCUMENT_POSITION_FOLLOWING if child == self_branch
|
|
257
|
+
return DOCUMENT_POSITION_PRECEDING if child == other_branch
|
|
258
|
+
end
|
|
259
|
+
DOCUMENT_POSITION_DISCONNECTED
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Node.getRootNode — the topmost ancestor of this node (the document, a
|
|
263
|
+
# ShadowRoot, a detached subtree root, or the node itself). Generic default
|
|
264
|
+
# for any node backed by a Nokogiri node; classes with special roots
|
|
265
|
+
# (Element's shadow handling) override it.
|
|
266
|
+
def get_root_node(_options = nil)
|
|
267
|
+
return self unless respond_to?(:__dommy_backend_node__) && instance_variable_defined?(:@document)
|
|
268
|
+
|
|
269
|
+
node = __dommy_backend_node__
|
|
270
|
+
node = node.parent while node.respond_to?(:parent) && node.parent
|
|
271
|
+
# The topmost node of an attached subtree is the Nokogiri document, which
|
|
272
|
+
# has no element wrapper — map it to the Document. A detached node's root is
|
|
273
|
+
# itself.
|
|
274
|
+
return @document if @document && node.equal?(@document.nokogiri_doc)
|
|
275
|
+
|
|
276
|
+
(@document && @document.wrap_node(node)) || self
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"
|
|
280
|
+
|
|
281
|
+
# Node.lookupNamespaceURI(prefix) — the namespace bound to `prefix` (or the
|
|
282
|
+
# default namespace for a null/empty prefix) in this node's scope, walking up
|
|
283
|
+
# the element ancestors' namespace declarations. HTML elements default to the
|
|
284
|
+
# XHTML namespace.
|
|
285
|
+
def lookup_namespace_uri(prefix)
|
|
286
|
+
wanted = namespace_prefix_arg(prefix)
|
|
287
|
+
nk = nearest_namespaceable_node
|
|
288
|
+
while nk.respond_to?(:element?) && nk.element?
|
|
289
|
+
nk.namespace_definitions.each do |d|
|
|
290
|
+
return d.href if normalize_ns_prefix(d.prefix) == wanted
|
|
291
|
+
end
|
|
292
|
+
return nk.namespace ? nk.namespace.href : HTML_NAMESPACE if wanted.nil?
|
|
293
|
+
|
|
294
|
+
nk = nk.parent
|
|
295
|
+
end
|
|
296
|
+
nil
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Node.lookupPrefix(namespace) — a prefix bound to `namespace`, or null for
|
|
300
|
+
# the default namespace.
|
|
301
|
+
def lookup_prefix(namespace)
|
|
302
|
+
ns = namespace.to_s
|
|
303
|
+
return nil if ns.empty?
|
|
304
|
+
|
|
305
|
+
nk = nearest_namespaceable_node
|
|
306
|
+
while nk.respond_to?(:element?) && nk.element?
|
|
307
|
+
nk.namespace_definitions.each do |d|
|
|
308
|
+
return d.prefix if d.href == ns && d.prefix
|
|
309
|
+
end
|
|
310
|
+
nk = nk.parent
|
|
311
|
+
end
|
|
312
|
+
nil
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Node.isDefaultNamespace(namespace) — true if `namespace` (null/"" → null) is
|
|
316
|
+
# the default namespace in this node's scope.
|
|
317
|
+
def is_default_namespace(namespace)
|
|
318
|
+
ns = namespace.nil? ? nil : namespace.to_s
|
|
319
|
+
ns = nil if ns == ""
|
|
320
|
+
lookup_namespace_uri(nil) == ns
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
private
|
|
324
|
+
|
|
325
|
+
def node_ancestor_chain(node)
|
|
326
|
+
chain = [node]
|
|
327
|
+
Internal::NodeTraversal.each_ancestor(node) { |n| chain << n }
|
|
328
|
+
chain
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def node_branch_under(common, chain)
|
|
332
|
+
chain.each_with_index do |node, i|
|
|
333
|
+
return node if i.zero? && node == common
|
|
334
|
+
return node if node.respond_to?(:parent) && node.parent == common
|
|
335
|
+
end
|
|
336
|
+
nil
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def namespace_prefix_arg(prefix)
|
|
340
|
+
return nil if prefix.nil? || prefix.to_s.empty?
|
|
341
|
+
return nil if defined?(Bridge::UNDEFINED) && prefix.equal?(Bridge::UNDEFINED)
|
|
342
|
+
|
|
343
|
+
prefix.to_s
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def normalize_ns_prefix(prefix)
|
|
347
|
+
prefix.nil? || prefix.to_s.empty? ? nil : prefix.to_s
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def nearest_namespaceable_node
|
|
351
|
+
return nil unless respond_to?(:__dommy_backend_node__)
|
|
352
|
+
|
|
353
|
+
nk = __dommy_backend_node__
|
|
354
|
+
nk = nk.parent while nk.respond_to?(:element?) && !nk.element? && nk.respond_to?(:parent) && nk.parent
|
|
355
|
+
nk
|
|
356
|
+
end
|
|
229
357
|
end
|
|
230
358
|
end
|
|
359
|
+
|
|
360
|
+
require_relative "internal/node_equality"
|
data/lib/dommy/notification.rb
CHANGED
|
@@ -69,6 +69,8 @@ module Dommy
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
include Bridge::Methods
|
|
73
|
+
js_methods %w[close addEventListener removeEventListener dispatchEvent]
|
|
72
74
|
def __js_call__(method, args)
|
|
73
75
|
case method
|
|
74
76
|
when "close"
|
|
@@ -76,7 +78,7 @@ module Dommy
|
|
|
76
78
|
when "addEventListener"
|
|
77
79
|
add_event_listener(args[0], args[1], args[2])
|
|
78
80
|
when "removeEventListener"
|
|
79
|
-
remove_event_listener(args[0], args[1])
|
|
81
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
80
82
|
when "dispatchEvent"
|
|
81
83
|
dispatch_event(args[0])
|
|
82
84
|
end
|
data/lib/dommy/performance.rb
CHANGED
data/lib/dommy/promise.rb
CHANGED
|
@@ -51,6 +51,8 @@ module Dommy
|
|
|
51
51
|
@handlers = []
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
include Bridge::Methods
|
|
55
|
+
js_methods %w[then catch]
|
|
54
56
|
def __js_call__(method, args)
|
|
55
57
|
case method
|
|
56
58
|
when "then"
|
|
@@ -133,8 +135,12 @@ module Dommy
|
|
|
133
135
|
other.__js_call__(
|
|
134
136
|
"then",
|
|
135
137
|
[
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
# Return nil, not the result of fulfill/reject: those return `self`
|
|
139
|
+
# (a PromiseValue), and run_handler would treat that as a thenable to
|
|
140
|
+
# chain onto — re-adopting forever. These are adoption sinks; their
|
|
141
|
+
# return value must be ignored.
|
|
142
|
+
proc { |resolved| fulfill(resolved); nil },
|
|
143
|
+
proc { |reason| reject(reason); nil }
|
|
138
144
|
]
|
|
139
145
|
)
|
|
140
146
|
self
|
|
@@ -167,13 +173,15 @@ module Dommy
|
|
|
167
173
|
return
|
|
168
174
|
end
|
|
169
175
|
|
|
170
|
-
result =
|
|
171
|
-
if result.is_a?(PromiseValue)
|
|
176
|
+
result = CallableInvoker.invoke(callback, @value)
|
|
177
|
+
if result.is_a?(PromiseValue) && !result.equal?(handler.child)
|
|
178
|
+
# Adopt the returned thenable. The continuation procs return nil so their
|
|
179
|
+
# `self`-returning fulfill/reject don't get re-adopted (infinite chain).
|
|
172
180
|
result.__js_call__(
|
|
173
181
|
"then",
|
|
174
182
|
[
|
|
175
|
-
proc { |resolved| handler.child.fulfill(resolved) },
|
|
176
|
-
proc { |reason| handler.child.reject(reason) }
|
|
183
|
+
proc { |resolved| handler.child.fulfill(resolved); nil },
|
|
184
|
+
proc { |reason| handler.child.reject(reason); nil }
|
|
177
185
|
]
|
|
178
186
|
)
|
|
179
187
|
else
|
|
@@ -187,13 +195,5 @@ module Dommy
|
|
|
187
195
|
def propagate(child)
|
|
188
196
|
@state == :fulfilled ? child.fulfill(@value) : child.reject(@value)
|
|
189
197
|
end
|
|
190
|
-
|
|
191
|
-
def invoke_callback(callback, value)
|
|
192
|
-
if callback.respond_to?(:__js_call__)
|
|
193
|
-
callback.__js_call__("call", [value])
|
|
194
|
-
else
|
|
195
|
-
callback.call(value)
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
198
|
end
|
|
199
199
|
end
|