dommy 0.11.0 → 0.13.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/backend/makiri_adapter.rb +2 -14
- data/lib/dommy/backend.rb +0 -5
- data/lib/dommy/bridge.rb +16 -3
- data/lib/dommy/browser.rb +43 -47
- data/lib/dommy/callable_invoker.rb +16 -0
- data/lib/dommy/compression_streams.rb +12 -62
- data/lib/dommy/custom_elements.rb +22 -16
- data/lib/dommy/document.rb +241 -47
- data/lib/dommy/element.rb +71 -23
- data/lib/dommy/encodings/tables.rb +751 -0
- data/lib/dommy/encodings.rb +299 -0
- data/lib/dommy/event.rb +10 -8
- data/lib/dommy/fetch.rb +60 -49
- data/lib/dommy/form_data.rb +10 -0
- data/lib/dommy/html_elements.rb +136 -62
- data/lib/dommy/internal/child_node.rb +1 -5
- data/lib/dommy/internal/exception_report.rb +123 -0
- data/lib/dommy/internal/idna.rb +4 -4
- data/lib/dommy/internal/idna_data.rb +513 -100
- data/lib/dommy/internal/leaf_node.rb +2 -8
- data/lib/dommy/internal/mutation_coordinator.rb +46 -19
- data/lib/dommy/internal/node_wrapper_cache.rb +23 -1
- data/lib/dommy/internal/parent_node.rb +14 -74
- data/lib/dommy/internal/range_text_serializer.rb +2 -1
- data/lib/dommy/internal/selector_ast.rb +17 -0
- data/lib/dommy/internal/selector_matcher.rb +20 -21
- data/lib/dommy/internal/selector_parser.rb +104 -32
- data/lib/dommy/internal/shadow_root_registry.rb +12 -1
- data/lib/dommy/internal/url_parser.rb +70 -18
- data/lib/dommy/internal/web_idl.rb +57 -0
- data/lib/dommy/js/custom_element_bridge.rb +10 -0
- data/lib/dommy/js/dom_interfaces.rb +15 -4
- data/lib/dommy/js/error_log.rb +137 -0
- data/lib/dommy/js/handle_table.rb +15 -0
- data/lib/dommy/js/host_bridge.rb +27 -0
- data/lib/dommy/js/host_runtime.js +186 -30
- data/lib/dommy/js/marshaller.rb +22 -2
- data/lib/dommy/js/runtime.rb +8 -1
- data/lib/dommy/js/script_boot.rb +58 -14
- data/lib/dommy/js/wire_tags.rb +8 -0
- data/lib/dommy/location.rb +14 -4
- data/lib/dommy/navigator.rb +3 -1
- data/lib/dommy/node.rb +19 -3
- data/lib/dommy/promise.rb +4 -4
- data/lib/dommy/range.rb +116 -183
- data/lib/dommy/scheduler.rb +35 -6
- data/lib/dommy/selection.rb +422 -0
- data/lib/dommy/shadow_root.rb +8 -3
- data/lib/dommy/static_range.rb +65 -0
- data/lib/dommy/streams.rb +1200 -187
- data/lib/dommy/svg_elements.rb +45 -0
- data/lib/dommy/text_codec.rb +110 -213
- data/lib/dommy/tree_walker.rb +9 -17
- data/lib/dommy/url.rb +34 -32
- data/lib/dommy/url_pattern/canonicalizer.rb +183 -0
- data/lib/dommy/url_pattern/component.rb +64 -0
- data/lib/dommy/url_pattern/constructor_string_parser.rb +281 -0
- data/lib/dommy/url_pattern/init_processor.rb +163 -0
- data/lib/dommy/url_pattern/pattern_parser.rb +343 -0
- data/lib/dommy/url_pattern/regexp_translator.rb +694 -0
- data/lib/dommy/url_pattern/tokenizer.rb +199 -0
- data/lib/dommy/url_pattern.rb +310 -103
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/window.rb +164 -35
- data/lib/dommy/xml_http_request.rb +18 -4
- data/lib/dommy.rb +5 -0
- metadata +17 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64a22d9ba17102a03881baedbda66b5236b8c4499140d779e4c4acba86583241
|
|
4
|
+
data.tar.gz: e111a114c5a7ae9485a3e99af339e70a59dce6b9a83a837639ac6564873a8d4e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67c651bd499d2cd0d6cf1fb862b11a9f0f6b68ba1814abe40732b990e5b66b9b5f0494fd33cb989e4b7a5f64faa3a8a152e8979231f4c082f32187999188030a
|
|
7
|
+
data.tar.gz: e7e8ee9194687b9fe6be28646e9f80d32631f3cf78e5b9fefbab5fd913b9a1d6116ab819bae3a54030f2fefc6e4fa8e478c8ab2667cc086e009c5ebb1a7c9481
|
|
@@ -43,18 +43,6 @@ module Dommy
|
|
|
43
43
|
node.clone_node(deep)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
# Makiri documents have no node-level clone; re-parsing the serialized
|
|
47
|
-
# document reproduces the full tree. Dispatch on the document kind so an XML
|
|
48
|
-
# document round-trips through the XML parser (case/namespaces/CDATA) and an
|
|
49
|
-
# HTML document through the HTML parser.
|
|
50
|
-
def clone_document(doc)
|
|
51
|
-
if doc.is_a?(::Makiri::XML::Document)
|
|
52
|
-
::Makiri::XML::Document.parse(doc.to_xml)
|
|
53
|
-
else
|
|
54
|
-
::Makiri::HTML::Document.parse(doc.to_html)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
46
|
# A fresh, empty HTML-backed document (children dropped so it starts with no
|
|
59
47
|
# documentElement). The backing for a shallow clone of an HTML document.
|
|
60
48
|
def empty_document
|
|
@@ -233,8 +221,8 @@ module Dommy
|
|
|
233
221
|
end
|
|
234
222
|
|
|
235
223
|
# Both Makiri document families (HTML and XML) mint a real PI node and
|
|
236
|
-
# serialize it
|
|
237
|
-
#
|
|
224
|
+
# serialize it as `<?target data?>`, so PIs — unlike CDATA — need no
|
|
225
|
+
# HTML-document fallback.
|
|
238
226
|
def create_processing_instruction(target, data, doc)
|
|
239
227
|
doc.create_processing_instruction(target, data)
|
|
240
228
|
end
|
data/lib/dommy/backend.rb
CHANGED
|
@@ -53,11 +53,6 @@ module Dommy
|
|
|
53
53
|
current.clone_node(node, deep: deep)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
# Deep copy of a whole document (DOM cloneNode on the document).
|
|
57
|
-
def clone_document(doc)
|
|
58
|
-
current.clone_document(doc)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
56
|
# A fresh, empty HTML-backed document.
|
|
62
57
|
def empty_document
|
|
63
58
|
current.empty_document
|
data/lib/dommy/bridge.rb
CHANGED
|
@@ -57,9 +57,20 @@ module Dommy
|
|
|
57
57
|
class JSValue
|
|
58
58
|
attr_reader :ref
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
# `stack_frames` are the value's JS stack, when the tag that crossed
|
|
61
|
+
# carried one. Ruby cannot reach through a ref to read `.stack`, so an
|
|
62
|
+
# error-shaped value brings its frames with it (see host_runtime.js
|
|
63
|
+
# tagValue) and reporting uses them for the source position.
|
|
64
|
+
attr_reader :stack_frames
|
|
65
|
+
|
|
66
|
+
# The value's JS constructor name, when the tag carried one.
|
|
67
|
+
attr_reader :js_name
|
|
68
|
+
|
|
69
|
+
def initialize(ref, label = nil, stack_frames = nil, js_name = nil)
|
|
61
70
|
@ref = ref
|
|
62
71
|
@label = label
|
|
72
|
+
@stack_frames = stack_frames
|
|
73
|
+
@js_name = js_name
|
|
63
74
|
end
|
|
64
75
|
|
|
65
76
|
def to_s = (@label || "[object]").to_s
|
|
@@ -75,9 +86,11 @@ module Dommy
|
|
|
75
86
|
class ThrowValue < RuntimeError
|
|
76
87
|
attr_reader :value
|
|
77
88
|
|
|
78
|
-
|
|
89
|
+
# `message` overrides the value's string form, for a caller that can say
|
|
90
|
+
# more about it than `to_s` can (a JS error's kind, say).
|
|
91
|
+
def initialize(value, message = nil)
|
|
79
92
|
@value = value
|
|
80
|
-
super(value.to_s)
|
|
93
|
+
super(message || value.to_s)
|
|
81
94
|
end
|
|
82
95
|
end
|
|
83
96
|
|
data/lib/dommy/browser.rb
CHANGED
|
@@ -23,24 +23,14 @@ module Dommy
|
|
|
23
23
|
# settle before the next line.
|
|
24
24
|
include Dommy::Interaction::Driver
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
class JsError < StandardError
|
|
28
|
-
attr_reader :causes
|
|
26
|
+
attr_reader :window, :runtime, :console
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def build_message(causes)
|
|
38
|
-
lines = causes.map { |e| " #{e.class}: #{e.message}" }
|
|
39
|
-
"#{causes.length} uncaught JS error(s):\n#{lines.join("\n")}"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
28
|
+
# The ledger of errors the page left unhandled (`Dommy::Js::ErrorLog`);
|
|
29
|
+
# shared with the other hosts so strict mode behaves the same everywhere.
|
|
30
|
+
attr_reader :error_log
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
# The errors themselves, as a browser console's scrollback.
|
|
33
|
+
def js_errors = @error_log.errors
|
|
44
34
|
|
|
45
35
|
# Build a browser and (unless `execute_scripts: false`) boot its scripts. In
|
|
46
36
|
# block form the browser is yielded and disposed afterward, returning the
|
|
@@ -72,16 +62,13 @@ module Dommy
|
|
|
72
62
|
wasm_memory_shim: false, backend: nil, navigable: false, same_origin: false)
|
|
73
63
|
@resources = resources
|
|
74
64
|
@same_origin = same_origin
|
|
75
|
-
@strict = strict
|
|
76
65
|
@backend = backend
|
|
77
66
|
@execute_scripts = execute_scripts
|
|
78
67
|
@settle_after_boot = settle
|
|
79
68
|
@wasm_memory_shim = wasm_memory_shim
|
|
80
69
|
@navigable = navigable
|
|
81
|
-
@
|
|
70
|
+
@error_log = Js::ErrorLog.new(strict: strict)
|
|
82
71
|
@console = []
|
|
83
|
-
@acknowledged = 0
|
|
84
|
-
@allow_errors = false
|
|
85
72
|
@disposed = false
|
|
86
73
|
@pending_navigation = nil
|
|
87
74
|
@runtime = nil
|
|
@@ -238,22 +225,19 @@ module Dommy
|
|
|
238
225
|
|
|
239
226
|
# Suppress strict-mode failure for JS errors raised inside the block (they
|
|
240
227
|
# stay collected in #js_errors for inspection). For tests that expect errors.
|
|
241
|
-
def allow_js_errors
|
|
242
|
-
|
|
243
|
-
@allow_errors = true
|
|
244
|
-
yield
|
|
245
|
-
ensure
|
|
246
|
-
@allow_errors = prev
|
|
247
|
-
@acknowledged = @js_errors.length
|
|
228
|
+
def allow_js_errors(&block)
|
|
229
|
+
@error_log.allow(&block)
|
|
248
230
|
end
|
|
249
231
|
|
|
232
|
+
# Tear the realm down, then fail on anything the page left unhandled and
|
|
233
|
+
# nobody has reported. Disposing first means the failure cannot leave a live
|
|
234
|
+
# VM behind.
|
|
250
235
|
def dispose
|
|
251
236
|
return if @disposed
|
|
252
237
|
|
|
253
238
|
@disposed = true
|
|
254
|
-
pending = unacknowledged
|
|
255
239
|
@runtime&.dispose
|
|
256
|
-
|
|
240
|
+
@error_log.check!(context: current_url)
|
|
257
241
|
end
|
|
258
242
|
|
|
259
243
|
private
|
|
@@ -268,8 +252,19 @@ module Dommy
|
|
|
268
252
|
# The JS engine is pluggable: `@backend` selects a registered runtime
|
|
269
253
|
# (nil → the configured default, QuickJS when dommy-js-quickjs is loaded).
|
|
270
254
|
runtime = Js.build_runtime(@backend)
|
|
271
|
-
|
|
272
|
-
|
|
255
|
+
# Every uncaught error the page produces goes through the window's
|
|
256
|
+
# "report an exception" / "notify about rejected promises" funnel, and only
|
|
257
|
+
# what the page left unhandled lands here. A page that installs its own
|
|
258
|
+
# `window.onerror` / `unhandledrejection` handler and cancels the event
|
|
259
|
+
# suppresses the failure, exactly as it would in a browser.
|
|
260
|
+
window.__internal_on_unhandled_error__ { |err| @error_log.record(err) }
|
|
261
|
+
# A rejection the page handled after we reported it is retracted, so it
|
|
262
|
+
# stops failing anything (WHATWG `rejectionhandled`).
|
|
263
|
+
window.__internal_on_rejection_handled__ { |record| @error_log.retract(record) }
|
|
264
|
+
runtime.on_unhandled_rejection { |err| report_rejection(window, err) }
|
|
265
|
+
if runtime.respond_to?(:on_callback_error)
|
|
266
|
+
runtime.on_callback_error { |err| Internal::ExceptionReport.report_at(window, err) }
|
|
267
|
+
end
|
|
273
268
|
runtime.on_log { |log| @console << log }
|
|
274
269
|
runtime.define_host_object("document", window.document)
|
|
275
270
|
runtime.install_window(window)
|
|
@@ -285,24 +280,35 @@ module Dommy
|
|
|
285
280
|
# Installed whenever a runtime is attached — an embedder that drives script
|
|
286
281
|
# boot itself (`execute_scripts: false`) still needs inline handlers wired.
|
|
287
282
|
doc.inline_handler_wirer = lambda do
|
|
288
|
-
Js::ScriptBoot.wire_inline_handlers(runtime, on_error: ->(e) { @
|
|
283
|
+
Js::ScriptBoot.wire_inline_handlers(runtime, on_error: ->(e) { @error_log.record(e) })
|
|
289
284
|
end
|
|
290
285
|
return unless @execute_scripts
|
|
291
286
|
|
|
287
|
+
# `on_error:` here is only the windowless fallback — a booted document has
|
|
288
|
+
# a window, so a throwing script reports through it (see ScriptBoot).
|
|
289
|
+
|
|
292
290
|
# Dynamically-inserted `<script src>` (webpack/Vite on-demand chunks)
|
|
293
291
|
# fetch + run through the same resources adapter, after boot.
|
|
294
292
|
doc.external_script_runner = lambda do |element, src|
|
|
295
293
|
Js::ScriptBoot.run_external_script(runtime, doc, element, src,
|
|
296
|
-
resources: @resources, on_error: ->(e) { @
|
|
294
|
+
resources: @resources, on_error: ->(e) { @error_log.record(e) })
|
|
297
295
|
end
|
|
298
296
|
Js::ScriptBoot.run_document_scripts(
|
|
299
|
-
runtime, doc, resources: @resources, on_error: ->(e) { @
|
|
297
|
+
runtime, doc, resources: @resources, on_error: ->(e) { @error_log.record(e) }
|
|
300
298
|
)
|
|
301
299
|
# Leave the page in a ready state: run on-load promises, due-now timers,
|
|
302
300
|
# and rAF (not future timers). `settle: false` observes it mid-flight.
|
|
303
301
|
runtime.settle if @settle_after_boot
|
|
304
302
|
end
|
|
305
303
|
|
|
304
|
+
# Route an unhandled promise rejection through the page's own
|
|
305
|
+
# `unhandledrejection` handling first. The engine decides WHEN a rejection
|
|
306
|
+
# counts as unhandled (see Window#__internal_report_rejection__).
|
|
307
|
+
def report_rejection(window, error)
|
|
308
|
+
value = Internal::ExceptionReport.error_value(error)
|
|
309
|
+
window.__internal_report_rejection__(value, host_error: error)
|
|
310
|
+
end
|
|
311
|
+
|
|
306
312
|
# Perform a recorded navigation: fetch the target (following redirects),
|
|
307
313
|
# fire the old document's unload, then replace the Window + JS realm with the
|
|
308
314
|
# freshly parsed document and update the joint history. A network miss or a
|
|
@@ -400,8 +406,6 @@ module Dommy
|
|
|
400
406
|
content_type.empty? || content_type.include?("html") || content_type.include?("xml")
|
|
401
407
|
end
|
|
402
408
|
|
|
403
|
-
def unacknowledged = @js_errors[@acknowledged..] || []
|
|
404
|
-
|
|
405
409
|
def submit_button?(button)
|
|
406
410
|
if button.tag_name == "BUTTON"
|
|
407
411
|
button.type == "submit"
|
|
@@ -410,18 +414,10 @@ module Dommy
|
|
|
410
414
|
end
|
|
411
415
|
end
|
|
412
416
|
|
|
413
|
-
# In strict mode, fail on
|
|
414
|
-
#
|
|
415
|
-
# at most once.
|
|
417
|
+
# In strict mode, fail on anything the page left unhandled since the last
|
|
418
|
+
# checkpoint. The ledger drains itself, so each error is reported once.
|
|
416
419
|
def check_js_errors!
|
|
417
|
-
|
|
418
|
-
return unless @strict
|
|
419
|
-
|
|
420
|
-
pending = unacknowledged
|
|
421
|
-
return if pending.empty?
|
|
422
|
-
|
|
423
|
-
@acknowledged = @js_errors.length
|
|
424
|
-
raise JsError, pending
|
|
420
|
+
@error_log.check!(context: current_url)
|
|
425
421
|
end
|
|
426
422
|
end
|
|
427
423
|
end
|
|
@@ -20,6 +20,22 @@ module Dommy
|
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# Invoke `callback` so a JS throw SURFACES as a Ruby exception instead of
|
|
24
|
+
# being swallowed by the bridge. WHATWG requires a task's exception to be
|
|
25
|
+
# reported at the global ("report an exception") rather than silently
|
|
26
|
+
# dropped, so the entry points that own a task — a timer / rAF callback —
|
|
27
|
+
# invoke this form and report whatever comes out. The plain `invoke` keeps
|
|
28
|
+
# the swallowing behavior for callers that handle their own errors.
|
|
29
|
+
def invoke_raising(callback, *args)
|
|
30
|
+
return if callback.nil?
|
|
31
|
+
|
|
32
|
+
if callback.respond_to?(:__js_invoke__)
|
|
33
|
+
callback.__js_invoke__(args, raising: true)
|
|
34
|
+
else
|
|
35
|
+
invoke(callback, *args)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
23
39
|
# Invoke a DOM event listener per the EventTarget rule: an object with
|
|
24
40
|
# `handle_event`, else a Ruby callable, else a JS-bridged callable (tried in
|
|
25
41
|
# that order). A JS function listener's `this` must be the event's
|
|
@@ -10,43 +10,18 @@ module Dommy
|
|
|
10
10
|
# finalizes and emits the compressed/decompressed bytes downstream.
|
|
11
11
|
#
|
|
12
12
|
# Spec: https://wicg.github.io/compression/
|
|
13
|
-
class CompressionStream
|
|
13
|
+
class CompressionStream < TransformStream
|
|
14
14
|
SUPPORTED = %w[gzip deflate deflate-raw].freeze
|
|
15
15
|
|
|
16
|
-
attr_reader :readable, :writable
|
|
17
|
-
|
|
18
16
|
def initialize(window, format)
|
|
19
17
|
raise ArgumentError, "unsupported format #{format.inspect}" unless SUPPORTED.include?(format.to_s)
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
buffer = +""
|
|
22
20
|
compressor = build_compressor(format.to_s)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@writable = WritableStream.new(
|
|
28
|
-
window,
|
|
29
|
-
{
|
|
30
|
-
"write" => proc { |chunk| @buffer << coerce(chunk) },
|
|
31
|
-
"close" => proc do
|
|
32
|
-
compressed = compressor.call(@buffer)
|
|
33
|
-
controller.enqueue(compressed)
|
|
34
|
-
@readable.__internal_close__
|
|
35
|
-
end,
|
|
36
|
-
"abort" => proc { |r| @readable.__internal_error__(r) }
|
|
37
|
-
}
|
|
38
|
-
)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def __js_get__(key)
|
|
42
|
-
case key
|
|
43
|
-
when "readable"
|
|
44
|
-
@readable
|
|
45
|
-
when "writable"
|
|
46
|
-
@writable
|
|
47
|
-
else
|
|
48
|
-
Bridge::ABSENT
|
|
49
|
-
end
|
|
21
|
+
super(window, {
|
|
22
|
+
"transform" => proc { |chunk, _controller| buffer << coerce(chunk); nil },
|
|
23
|
+
"flush" => proc { |controller| controller.enqueue(compressor.call(buffer)); nil }
|
|
24
|
+
})
|
|
50
25
|
end
|
|
51
26
|
|
|
52
27
|
private
|
|
@@ -79,43 +54,18 @@ module Dommy
|
|
|
79
54
|
end
|
|
80
55
|
end
|
|
81
56
|
|
|
82
|
-
class DecompressionStream
|
|
57
|
+
class DecompressionStream < TransformStream
|
|
83
58
|
SUPPORTED = %w[gzip deflate deflate-raw].freeze
|
|
84
59
|
|
|
85
|
-
attr_reader :readable, :writable
|
|
86
|
-
|
|
87
60
|
def initialize(window, format)
|
|
88
61
|
raise ArgumentError, "unsupported format #{format.inspect}" unless SUPPORTED.include?(format.to_s)
|
|
89
62
|
|
|
90
|
-
|
|
63
|
+
buffer = +""
|
|
91
64
|
decompressor = build_decompressor(format.to_s)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@writable = WritableStream.new(
|
|
97
|
-
window,
|
|
98
|
-
{
|
|
99
|
-
"write" => proc { |chunk| @buffer << coerce(chunk) },
|
|
100
|
-
"close" => proc do
|
|
101
|
-
plain = decompressor.call(@buffer)
|
|
102
|
-
controller.enqueue(plain)
|
|
103
|
-
@readable.__internal_close__
|
|
104
|
-
end,
|
|
105
|
-
"abort" => proc { |r| @readable.__internal_error__(r) }
|
|
106
|
-
}
|
|
107
|
-
)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def __js_get__(key)
|
|
111
|
-
case key
|
|
112
|
-
when "readable"
|
|
113
|
-
@readable
|
|
114
|
-
when "writable"
|
|
115
|
-
@writable
|
|
116
|
-
else
|
|
117
|
-
Bridge::ABSENT
|
|
118
|
-
end
|
|
65
|
+
super(window, {
|
|
66
|
+
"transform" => proc { |chunk, _controller| buffer << coerce(chunk); nil },
|
|
67
|
+
"flush" => proc { |controller| controller.enqueue(decompressor.call(buffer)); nil }
|
|
68
|
+
})
|
|
119
69
|
end
|
|
120
70
|
|
|
121
71
|
private
|
|
@@ -86,19 +86,16 @@ module Dommy
|
|
|
86
86
|
promise
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
# Walk `root`'s subtree and re-wrap any nodes whose tag is now
|
|
89
|
+
# Walk `root`'s shadow-including subtree and re-wrap any nodes whose tag is now
|
|
90
90
|
# registered; fires `connectedCallback` for each upgraded node
|
|
91
91
|
# that's currently attached to a document tree.
|
|
92
92
|
def upgrade(root)
|
|
93
93
|
return nil unless root.respond_to?(:__dommy_backend_node__)
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
next unless nk.element?
|
|
95
|
+
@window.document.__internal_each_shadow_including_element__(root.__dommy_backend_node__) do |nk|
|
|
97
96
|
next unless @definitions.key?(nk.name)
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
@window.document.__internal_reset_wrapper__(nk)
|
|
101
|
-
wrapped = @window.document.wrap_node(nk)
|
|
98
|
+
wrapped = rewrap_for_upgrade(@window.document, nk)
|
|
102
99
|
next unless wrapped
|
|
103
100
|
|
|
104
101
|
replay_observed_attributes(wrapped)
|
|
@@ -137,6 +134,22 @@ module Dommy
|
|
|
137
134
|
list&.each { |p| p.fulfill(klass) }
|
|
138
135
|
end
|
|
139
136
|
|
|
137
|
+
# Re-wrap `nk` as its definition's class, or nil when there is nothing to
|
|
138
|
+
# upgrade. An element that is already an instance of the class is left alone:
|
|
139
|
+
# upgrading an element that is already custom does nothing. The new wrapper
|
|
140
|
+
# is told which one it replaces, because a script may hold the element — a
|
|
141
|
+
# JS-defined element upgrades that reference in place.
|
|
142
|
+
def rewrap_for_upgrade(doc, nk)
|
|
143
|
+
previous = doc.__internal_peek_wrapper__(nk)
|
|
144
|
+
klass = @definitions[nk.name]
|
|
145
|
+
return nil if klass.is_a?(Module) && previous.is_a?(klass)
|
|
146
|
+
|
|
147
|
+
doc.__internal_reset_wrapper__(nk)
|
|
148
|
+
wrapped = doc.wrap_node(nk)
|
|
149
|
+
wrapped.__internal_upgraded_from__(previous) if previous && wrapped.respond_to?(:__internal_upgraded_from__)
|
|
150
|
+
wrapped
|
|
151
|
+
end
|
|
152
|
+
|
|
140
153
|
# When define() lands after the matching element is already in
|
|
141
154
|
# the document, those nodes need upgrading: re-wrap them with the
|
|
142
155
|
# new class and fire connectedCallback.
|
|
@@ -145,11 +158,11 @@ module Dommy
|
|
|
145
158
|
# Match by tag name rather than interpolating `name` into a CSS selector:
|
|
146
159
|
# a spec-valid custom element name may contain "." (a CSS class selector
|
|
147
160
|
# char) or other metacharacters, which would corrupt the query.
|
|
148
|
-
|
|
161
|
+
# Shadow trees included, in shadow-including tree order (define step 18).
|
|
162
|
+
doc.__internal_each_shadow_including_element__(doc.backend_doc) do |nk|
|
|
149
163
|
next unless nk.name == name
|
|
150
164
|
|
|
151
|
-
doc
|
|
152
|
-
wrapped = doc.wrap_node(nk)
|
|
165
|
+
wrapped = rewrap_for_upgrade(doc, nk)
|
|
153
166
|
next unless wrapped
|
|
154
167
|
|
|
155
168
|
replay_observed_attributes(wrapped)
|
|
@@ -178,12 +191,5 @@ module Dommy
|
|
|
178
191
|
)
|
|
179
192
|
end
|
|
180
193
|
end
|
|
181
|
-
|
|
182
|
-
def walk_descendants(node, &blk)
|
|
183
|
-
yield node
|
|
184
|
-
return unless node.respond_to?(:children)
|
|
185
|
-
|
|
186
|
-
node.children.each { |c| walk_descendants(c, &blk) }
|
|
187
|
-
end
|
|
188
194
|
end
|
|
189
195
|
end
|