dommy 0.8.1 → 0.10.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/README.md +3 -3
- data/lib/dommy/animation.rb +4 -0
- data/lib/dommy/attr.rb +57 -13
- data/lib/dommy/backend/makiri_adapter.rb +364 -0
- data/lib/dommy/backend.rb +143 -33
- data/lib/dommy/blob.rb +50 -6
- data/lib/dommy/bridge.rb +11 -0
- data/lib/dommy/browser.rb +420 -0
- data/lib/dommy/callable_invoker.rb +6 -2
- data/lib/dommy/compression_streams.rb +4 -0
- data/lib/dommy/crypto.rb +4 -0
- data/lib/dommy/css.rb +487 -50
- data/lib/dommy/custom_elements.rb +2 -2
- data/lib/dommy/data_transfer.rb +2 -0
- data/lib/dommy/data_uri.rb +35 -0
- data/lib/dommy/deferred_response.rb +59 -0
- data/lib/dommy/document.rb +901 -275
- data/lib/dommy/dom_exception.rb +2 -0
- data/lib/dommy/dom_parser.rb +7 -17
- data/lib/dommy/element.rb +940 -353
- data/lib/dommy/event.rb +529 -17
- data/lib/dommy/fetch.rb +577 -53
- data/lib/dommy/form_data.rb +2 -0
- data/lib/dommy/history.rb +26 -3
- data/lib/dommy/html_canvas_element.rb +230 -0
- data/lib/dommy/html_collection.rb +150 -27
- data/lib/dommy/html_elements.rb +1968 -282
- data/lib/dommy/interaction/debug.rb +35 -0
- data/lib/dommy/interaction/dom_summary.rb +131 -0
- data/lib/dommy/interaction/driver.rb +385 -0
- data/lib/dommy/interaction/event_synthesis.rb +164 -0
- data/lib/dommy/interaction/field_interactor.rb +177 -0
- data/lib/dommy/interaction/form_submission.rb +268 -0
- data/lib/dommy/interaction/locator.rb +158 -0
- data/lib/dommy/interaction/role_query.rb +58 -0
- data/lib/dommy/interaction.rb +32 -0
- data/lib/dommy/internal/accessibility_tree.rb +215 -0
- data/lib/dommy/internal/accessible_description.rb +38 -0
- data/lib/dommy/internal/accessible_name.rb +301 -0
- data/lib/dommy/internal/aria_role.rb +252 -0
- data/lib/dommy/internal/aria_snapshot.rb +64 -0
- data/lib/dommy/internal/aria_state.rb +151 -0
- data/lib/dommy/internal/child_node.rb +199 -0
- data/lib/dommy/internal/css/calc.rb +242 -0
- data/lib/dommy/internal/css/cascade.rb +472 -0
- data/lib/dommy/internal/css/color.rb +381 -0
- data/lib/dommy/internal/css/computed_style_declaration.rb +130 -0
- data/lib/dommy/internal/css/counters.rb +227 -0
- data/lib/dommy/internal/css/custom_properties.rb +183 -0
- data/lib/dommy/internal/css/media_query.rb +302 -0
- data/lib/dommy/internal/css/parser.rb +265 -0
- data/lib/dommy/internal/css/property_registry.rb +512 -0
- data/lib/dommy/internal/css/rule_index.rb +494 -0
- data/lib/dommy/internal/css/supports.rb +158 -0
- data/lib/dommy/internal/css/ua_stylesheet.rb +53 -0
- data/lib/dommy/internal/css_pseudo_handlers.rb +283 -42
- data/lib/dommy/internal/css_rule_text.rb +160 -0
- data/lib/dommy/internal/dom_matching.rb +80 -9
- data/lib/dommy/internal/element_matching.rb +109 -0
- data/lib/dommy/internal/global_functions.rb +55 -9
- data/lib/dommy/internal/mutation_coordinator.rb +122 -11
- data/lib/dommy/internal/namespaces.rb +49 -5
- data/lib/dommy/internal/node_wrapper_cache.rb +217 -38
- data/lib/dommy/internal/observer_manager.rb +6 -0
- data/lib/dommy/internal/observer_matcher.rb +6 -8
- data/lib/dommy/internal/parent_node.rb +98 -57
- data/lib/dommy/internal/selector_ast.rb +124 -0
- data/lib/dommy/internal/selector_index.rb +146 -0
- data/lib/dommy/internal/selector_matcher.rb +845 -0
- data/lib/dommy/internal/selector_parser.rb +321 -132
- data/lib/dommy/internal/shadow_root_registry.rb +9 -2
- data/lib/dommy/internal/template_content_registry.rb +26 -18
- data/lib/dommy/internal/xml_serialization.rb +344 -0
- data/lib/dommy/intersection_observer.rb +2 -0
- data/lib/dommy/js/bridge_conformance.rb +80 -0
- data/lib/dommy/js/constructor_resolver.rb +44 -0
- data/lib/dommy/js/custom_element_bridge.rb +101 -0
- data/lib/dommy/js/dom_interfaces.rb +177 -0
- data/lib/dommy/js/handle_table.rb +60 -0
- data/lib/dommy/js/host_bridge.rb +538 -0
- data/lib/dommy/js/host_runtime.js +2357 -0
- data/lib/dommy/js/import_map.rb +58 -0
- data/lib/dommy/js/marshaller.rb +240 -0
- data/lib/dommy/js/module_loader.rb +99 -0
- data/lib/dommy/js/observable_runtime.js +742 -0
- data/lib/dommy/js/runtime.rb +115 -0
- data/lib/dommy/js/script_boot.rb +301 -0
- data/lib/dommy/js/wire_tags.rb +62 -0
- data/lib/dommy/location.rb +78 -13
- data/lib/dommy/media_query_list.rb +50 -14
- data/lib/dommy/message_channel.rb +22 -6
- data/lib/dommy/minitest/assertions.rb +27 -0
- data/lib/dommy/mutation_observer.rb +89 -5
- data/lib/dommy/navigation.rb +263 -0
- data/lib/dommy/navigator.rb +34 -2
- data/lib/dommy/node.rb +198 -35
- data/lib/dommy/notification.rb +2 -0
- data/lib/dommy/parser.rb +1 -1
- data/lib/dommy/performance.rb +21 -1
- data/lib/dommy/promise.rb +94 -10
- data/lib/dommy/range.rb +187 -33
- data/lib/dommy/resources.rb +178 -0
- data/lib/dommy/rspec/capy_style_matchers.rb +126 -0
- data/lib/dommy/scheduler.rb +165 -13
- data/lib/dommy/screen.rb +91 -0
- data/lib/dommy/shadow_root.rb +109 -13
- data/lib/dommy/storage.rb +63 -11
- data/lib/dommy/streams.rb +6 -0
- data/lib/dommy/text_codec.rb +7 -1
- data/lib/dommy/tree_walker.rb +51 -46
- data/lib/dommy/url.rb +17 -3
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +45 -2
- data/lib/dommy/window.rb +322 -15
- data/lib/dommy/worker.rb +8 -4
- data/lib/dommy/xml_http_request.rb +76 -9
- data/lib/dommy.rb +38 -1
- metadata +82 -14
- data/lib/dommy/backend/nokogiri_adapter.rb +0 -127
- data/lib/dommy/backend/nokolexbor_adapter.rb +0 -117
data/lib/dommy/backend.rb
CHANGED
|
@@ -2,24 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module Dommy
|
|
4
4
|
# `Dommy::Backend` — pluggable HTML parser abstraction. Lets Dommy
|
|
5
|
-
# work with
|
|
6
|
-
# Nokolexbor (faster, HTML5-only). Internally, all DOM library
|
|
5
|
+
# work with Makiri (Lexbor-based, HTML5-only). Internally, all DOM library
|
|
7
6
|
# code goes through this facade rather than referencing the parser
|
|
8
7
|
# directly.
|
|
9
8
|
#
|
|
10
|
-
# Defaults to
|
|
9
|
+
# Defaults to Makiri.
|
|
11
10
|
#
|
|
12
11
|
# Switching backends:
|
|
13
12
|
#
|
|
14
13
|
# require "dommy"
|
|
15
|
-
# Dommy::Backend.use(:
|
|
14
|
+
# Dommy::Backend.use(:makiri)
|
|
16
15
|
#
|
|
17
16
|
# Or set directly:
|
|
18
17
|
#
|
|
19
|
-
# Dommy::Backend.current = Dommy::Backend::
|
|
18
|
+
# Dommy::Backend.current = Dommy::Backend::Makiri
|
|
20
19
|
#
|
|
21
20
|
# All adapters must implement the same interface — see
|
|
22
|
-
# `Backend::
|
|
21
|
+
# `Backend::Makiri` for the canonical reference.
|
|
23
22
|
module Backend
|
|
24
23
|
class BackendNotAvailable < StandardError
|
|
25
24
|
end
|
|
@@ -33,36 +32,133 @@ module Dommy
|
|
|
33
32
|
|
|
34
33
|
def use(name)
|
|
35
34
|
@current = case name.to_sym
|
|
36
|
-
when :
|
|
37
|
-
require_relative "backend/
|
|
38
|
-
|
|
39
|
-
when :nokolexbor
|
|
40
|
-
require_relative "backend/nokolexbor_adapter"
|
|
41
|
-
Nokolexbor
|
|
35
|
+
when :makiri
|
|
36
|
+
require_relative "backend/makiri_adapter"
|
|
37
|
+
Makiri
|
|
42
38
|
else
|
|
43
|
-
raise ArgumentError, "Unknown backend: #{name.inspect}. Use :
|
|
39
|
+
raise ArgumentError, "Unknown backend: #{name.inspect}. Use :makiri."
|
|
44
40
|
end
|
|
45
41
|
end
|
|
46
42
|
|
|
43
|
+
# Stable per-document identity key for a backend node, used to cache DOM
|
|
44
|
+
# wrappers and key per-node side tables. Makiri mints a fresh wrapper per traversal
|
|
45
|
+
# but never frees nodes (arena-owned), so it keys on the stable node pointer.
|
|
46
|
+
def identity_key(node)
|
|
47
|
+
current.identity_key(node)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Deep (or shallow) copy of an element/node, detached and owned by the
|
|
51
|
+
# same document — the backing for DOM cloneNode.
|
|
52
|
+
def clone_node(node, deep:)
|
|
53
|
+
current.clone_node(node, deep: deep)
|
|
54
|
+
end
|
|
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
|
+
# A fresh, empty HTML-backed document.
|
|
62
|
+
def empty_document
|
|
63
|
+
current.empty_document
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# A fresh, empty XML-backed document — for `new Document()` / createDocument,
|
|
67
|
+
# which the DOM defines as XML documents (case-preserving, real CDATA
|
|
68
|
+
# nodeType, namespaces).
|
|
69
|
+
def empty_xml_document
|
|
70
|
+
current.empty_xml_document
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# An empty backing document matching `doc`'s kind (HTML stays HTML, XML stays
|
|
74
|
+
# XML) — for a shallow document clone, whose result keeps the source flavor.
|
|
75
|
+
def empty_document_like(doc)
|
|
76
|
+
current.empty_document_like(doc)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Bring `node` (already detached from its old tree) into `target_doc`,
|
|
80
|
+
# returning the backend node now owned by `target_doc`. Makiri can't move a node
|
|
81
|
+
# between arenas, so it imports a copy — callers must reseat any wrapper
|
|
82
|
+
# onto the returned node.
|
|
83
|
+
def adopt(node, target_doc)
|
|
84
|
+
current.adopt(node, target_doc)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Whether the backend can move a node between documents in place or must adopt a copy first
|
|
88
|
+
# (Lexbor's arenas can't move a node, so inserting a foreign node requires importing
|
|
89
|
+
# it). Lets callers skip a needless — and on an empty target, root-less and
|
|
90
|
+
# therefore crashing — adoption on backends that don't need it.
|
|
91
|
+
def moves_nodes_across_documents?
|
|
92
|
+
current.respond_to?(:moves_nodes_across_documents?) ? current.moves_nodes_across_documents? : true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# CSS query that honors Dommy's custom pseudo-classes
|
|
96
|
+
# (`:disabled`/`:enabled`/`:checked`/`:scope`). Each backend applies its
|
|
97
|
+
# own mechanism (Lexbor native pseudos plus a
|
|
98
|
+
# `:scope` rewrite). `scope_node` binds `:scope` to that element.
|
|
99
|
+
def select_all(node, selector, scope_node: nil)
|
|
100
|
+
current.select_all(node, selector, scope_node: scope_node)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def select_first(node, selector, scope_node: nil)
|
|
104
|
+
current.select_first(node, selector, scope_node: scope_node)
|
|
105
|
+
end
|
|
106
|
+
|
|
47
107
|
# Delegate calls so internal code can use `Backend.parse(...)`.
|
|
48
108
|
def parse(html)
|
|
49
109
|
current.parse(html)
|
|
50
110
|
end
|
|
51
111
|
|
|
52
|
-
# Parse XML input into an XML document.
|
|
53
|
-
#
|
|
112
|
+
# Parse XML input into an XML document.
|
|
113
|
+
# fall back to the HTML parser.
|
|
54
114
|
def parse_xml(xml)
|
|
55
|
-
current.
|
|
115
|
+
current.parse_xml(xml)
|
|
56
116
|
end
|
|
57
117
|
|
|
58
118
|
def fragment(html, owner_doc:)
|
|
59
119
|
current.fragment(html, owner_doc: owner_doc)
|
|
60
120
|
end
|
|
61
121
|
|
|
122
|
+
# Make `node` the sole document element of `doc` (used by
|
|
123
|
+
# DOMImplementation.createDocument). Lexbor-backed documents are seeded with an <html> shell
|
|
124
|
+
# that must be cleared first.
|
|
125
|
+
def set_document_root(doc, node)
|
|
126
|
+
current.set_document_root(doc, node)
|
|
127
|
+
end
|
|
128
|
+
|
|
62
129
|
def create_element(name, doc)
|
|
63
130
|
current.create_element(name, doc)
|
|
64
131
|
end
|
|
65
132
|
|
|
133
|
+
# Create a namespaced element permitting a DOM-valid qualified name that a
|
|
134
|
+
# strict XML backend would reject (an internal invalid char like "f}oo"),
|
|
135
|
+
# preserving case/prefix. Returns nil when the backend has no loose path
|
|
136
|
+
# (fall back to #create_element); raises ArgumentError for a genuinely
|
|
137
|
+
# invalid name (the caller maps it to InvalidCharacterError).
|
|
138
|
+
def create_element_loose(qualified_name, prefix, local, namespace, doc)
|
|
139
|
+
current.create_element_loose(qualified_name, prefix, local, namespace, doc)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# A detached DocumentType node owned by `doc` (for
|
|
143
|
+
# DOMImplementation.createDocumentType). Returns nil when the backend has no
|
|
144
|
+
# doctype factory (the caller falls back to a synthetic DocumentType); raises
|
|
145
|
+
# ArgumentError for a name the factory rejects (the caller then also falls
|
|
146
|
+
# back, since createDocumentType is permissive).
|
|
147
|
+
def create_document_type(name, public_id, system_id, doc)
|
|
148
|
+
current.respond_to?(:create_document_type) ? current.create_document_type(name, public_id, system_id, doc) : nil
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# The parsed document's DocumentType node, or nil when it declares none.
|
|
152
|
+
def internal_subset(doc)
|
|
153
|
+
current.respond_to?(:internal_subset) ? current.internal_subset(doc) : nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# The backend class for a DocumentType node (nil if unsupported), so the
|
|
157
|
+
# wrapper cache can route it to Dommy::DocumentType.
|
|
158
|
+
def document_type_class
|
|
159
|
+
current.respond_to?(:document_type_class) ? current.document_type_class : nil
|
|
160
|
+
end
|
|
161
|
+
|
|
66
162
|
def create_text(content, doc)
|
|
67
163
|
current.create_text(content, doc)
|
|
68
164
|
end
|
|
@@ -81,16 +177,40 @@ module Dommy
|
|
|
81
177
|
current.respond_to?(:cdata_class) ? current.cdata_class : nil
|
|
82
178
|
end
|
|
83
179
|
|
|
180
|
+
# ProcessingInstruction node (`<?target data?>`). Supported by every
|
|
181
|
+
# backend Dommy ships (both Makiri document families), so — unlike CDATA —
|
|
182
|
+
# there is no text-node fallback.
|
|
183
|
+
def create_processing_instruction(target, data, doc)
|
|
184
|
+
current.create_processing_instruction(target, data, doc)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def processing_instruction_class
|
|
188
|
+
current.processing_instruction_class
|
|
189
|
+
end
|
|
190
|
+
|
|
84
191
|
def namespace_of(node)
|
|
85
192
|
current.namespace_of(node)
|
|
86
193
|
end
|
|
87
194
|
|
|
195
|
+
# The element's in-scope namespace declarations (each responds to
|
|
196
|
+
# `prefix`/`href`). Empty on backends without an XML namespace model.
|
|
197
|
+
def namespace_definitions(node)
|
|
198
|
+
current.namespace_definitions(node)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# The content child nodes of a `<template>` element. HTML5 parsers model
|
|
202
|
+
# template contents differently — Lexbor/Makiri in a separate content fragment — so reading them goes
|
|
203
|
+
# through the backend. Used by the template-content registry's migration.
|
|
204
|
+
def template_content_nodes(node)
|
|
205
|
+
current.template_content_nodes(node)
|
|
206
|
+
end
|
|
207
|
+
|
|
88
208
|
def add_namespace_definition(node, prefix, href)
|
|
89
209
|
current.add_namespace_definition(node, prefix, href)
|
|
90
210
|
end
|
|
91
211
|
|
|
92
212
|
# Namespaced attribute access (DOM *AttributeNS). `namespace` is an href
|
|
93
|
-
# String or nil.
|
|
213
|
+
# String or nil.
|
|
94
214
|
def get_attribute_ns(node, namespace, local_name)
|
|
95
215
|
current.get_attribute_ns(node, namespace, local_name)
|
|
96
216
|
end
|
|
@@ -148,25 +268,15 @@ module Dommy
|
|
|
148
268
|
private
|
|
149
269
|
|
|
150
270
|
def detect_default
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
raise(BackendNotAvailable, "Dommy requires either 'nokogiri' or 'nokolexbor' gem to be installed.")
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def try_nokogiri
|
|
157
|
-
require "nokogiri"
|
|
158
|
-
|
|
159
|
-
require_relative "backend/nokogiri_adapter"
|
|
160
|
-
Nokogiri
|
|
161
|
-
rescue LoadError
|
|
162
|
-
nil
|
|
271
|
+
try_makiri ||
|
|
272
|
+
raise(BackendNotAvailable, "Dommy requires either 'makiri' gem to be installed.")
|
|
163
273
|
end
|
|
164
274
|
|
|
165
|
-
def
|
|
166
|
-
require "
|
|
275
|
+
def try_makiri
|
|
276
|
+
require "makiri"
|
|
167
277
|
|
|
168
|
-
require_relative "backend/
|
|
169
|
-
|
|
278
|
+
require_relative "backend/makiri_adapter"
|
|
279
|
+
Makiri
|
|
170
280
|
rescue LoadError
|
|
171
281
|
nil
|
|
172
282
|
end
|
data/lib/dommy/blob.rb
CHANGED
|
@@ -21,19 +21,26 @@ module Dommy
|
|
|
21
21
|
# Promises (they need a scheduler). A window-less Blob falls back to a
|
|
22
22
|
# synchronous result, which `await` still handles.
|
|
23
23
|
def initialize(parts = [], options = {}, window = nil)
|
|
24
|
+
# WebIDL: an omitted / `undefined` blobParts argument defaults to an empty
|
|
25
|
+
# sequence (`new Blob()` / `new Blob(undefined)` is a zero-length Blob), so
|
|
26
|
+
# it must not be coerced to the string "undefined".
|
|
27
|
+
parts = [] if parts.nil? || (defined?(Bridge::UNDEFINED) && parts.equal?(Bridge::UNDEFINED))
|
|
24
28
|
parts = [parts] unless parts.is_a?(Array)
|
|
25
29
|
@data = collect_bytes(parts)
|
|
26
30
|
@size = @data.bytesize
|
|
27
|
-
raw_type = options["type"] || options[:type] || ""
|
|
28
|
-
|
|
31
|
+
raw_type = (options["type"] || options[:type] || "").to_s
|
|
32
|
+
# A type string with any code point outside U+0020..U+007E is discarded
|
|
33
|
+
# (→ ""); otherwise it is ASCII-lowercased (FileAPI "parse a MIME type"
|
|
34
|
+
# gate, applied to both the constructor and slice's contentType).
|
|
35
|
+
@type = raw_type.match?(/[^ -~]/) ? "" : raw_type.downcase
|
|
29
36
|
@window = window
|
|
30
37
|
end
|
|
31
38
|
|
|
32
39
|
# Return a new Blob over a byte range of this one.
|
|
33
40
|
# Negative indices are treated as offsets from the end (per spec).
|
|
34
41
|
def slice(start = 0, last = @size, content_type = "")
|
|
35
|
-
s = clamp_index(start
|
|
36
|
-
e = clamp_index(last
|
|
42
|
+
s = clamp_index(clamp_long_long(start), @size)
|
|
43
|
+
e = clamp_index(clamp_long_long(last), @size)
|
|
37
44
|
e = s if e < s
|
|
38
45
|
Blob.new([@data.byteslice(s, e - s) || ""], {"type" => content_type.to_s}, @window)
|
|
39
46
|
end
|
|
@@ -51,6 +58,13 @@ module Dommy
|
|
|
51
58
|
Bridge::ArrayBuffer.new(@data.bytes)
|
|
52
59
|
end
|
|
53
60
|
|
|
61
|
+
# Read the bytes as a Uint8Array (the spec return type). The DOM spec returns
|
|
62
|
+
# a Promise<Uint8Array>; Dommy is synchronous. `Bridge::Bytes` crosses the JS
|
|
63
|
+
# boundary as a Uint8Array (vs `ArrayBuffer` for #array_buffer).
|
|
64
|
+
def bytes
|
|
65
|
+
Bridge::Bytes.new(@data.bytes)
|
|
66
|
+
end
|
|
67
|
+
|
|
54
68
|
# Raw binary bytes (Ruby ASCII-8BIT string). Used by FormData /
|
|
55
69
|
# fetch when serializing multipart bodies.
|
|
56
70
|
def __dommy_bytes__
|
|
@@ -63,23 +77,38 @@ module Dommy
|
|
|
63
77
|
@size
|
|
64
78
|
when "type"
|
|
65
79
|
@type
|
|
80
|
+
else
|
|
81
|
+
Bridge::ABSENT
|
|
66
82
|
end
|
|
67
83
|
end
|
|
68
84
|
|
|
69
85
|
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
70
86
|
# File < Blob inherits these (it adds only properties).
|
|
71
87
|
include Bridge::Methods
|
|
72
|
-
js_methods %w[slice text arrayBuffer]
|
|
88
|
+
js_methods %w[slice text arrayBuffer bytes]
|
|
73
89
|
def __js_call__(method, args)
|
|
74
90
|
case method
|
|
75
91
|
when "slice"
|
|
76
|
-
|
|
92
|
+
# An omitted / `undefined` start|end uses the default (0 / size); map
|
|
93
|
+
# UNDEFINED to nil so the `|| default` fallbacks apply (a bare UNDEFINED
|
|
94
|
+
# is truthy and has no #to_i). contentType is a plain DOMString: omitted
|
|
95
|
+
# / undefined → "" (default), but an explicit JS null coerces to "null".
|
|
96
|
+
a = args.map { |v| v.equal?(Bridge::UNDEFINED) ? nil : v }
|
|
97
|
+
ctype = if args.length < 3 || args[2].equal?(Bridge::UNDEFINED)
|
|
98
|
+
""
|
|
99
|
+
else
|
|
100
|
+
args[2].nil? ? "null" : args[2].to_s
|
|
101
|
+
end
|
|
102
|
+
slice(a[0] || 0, a[1] || @size, ctype)
|
|
77
103
|
when "text"
|
|
78
104
|
# WHATWG: Blob.text() returns a Promise<string>.
|
|
79
105
|
promise_or_value(text)
|
|
80
106
|
when "arrayBuffer"
|
|
81
107
|
# WHATWG: Blob.arrayBuffer() returns a Promise<ArrayBuffer>.
|
|
82
108
|
promise_or_value(array_buffer)
|
|
109
|
+
when "bytes"
|
|
110
|
+
# WHATWG: Blob.bytes() returns a Promise<Uint8Array>.
|
|
111
|
+
promise_or_value(bytes)
|
|
83
112
|
end
|
|
84
113
|
end
|
|
85
114
|
|
|
@@ -113,6 +142,21 @@ module Dommy
|
|
|
113
142
|
idx = length + idx if idx.negative?
|
|
114
143
|
idx.clamp(0, length)
|
|
115
144
|
end
|
|
145
|
+
|
|
146
|
+
# WebIDL `[Clamp] long long` conversion of a slice bound: a fractional value
|
|
147
|
+
# rounds to the nearest integer, ties to even (banker's rounding) — so
|
|
148
|
+
# `slice(1.5)` starts at 2 and `slice(3.5)` at 4, per the [Clamp] extended
|
|
149
|
+
# attribute on Blob.slice.
|
|
150
|
+
def clamp_long_long(value)
|
|
151
|
+
return value if value.is_a?(Integer)
|
|
152
|
+
|
|
153
|
+
f = value.to_f
|
|
154
|
+
return 0 if f.nan?
|
|
155
|
+
|
|
156
|
+
f.round(half: :even)
|
|
157
|
+
rescue StandardError
|
|
158
|
+
0
|
|
159
|
+
end
|
|
116
160
|
end
|
|
117
161
|
|
|
118
162
|
# `File` — Blob with a filename and an optional last-modified
|
data/lib/dommy/bridge.rb
CHANGED
|
@@ -37,6 +37,17 @@ module Dommy
|
|
|
37
37
|
def UNDEFINED.inspect = "#<Dommy::Bridge::UNDEFINED>"
|
|
38
38
|
UNDEFINED.freeze
|
|
39
39
|
|
|
40
|
+
# The sentinel a `__js_get__` returns for a GENUINELY-ABSENT property (a key
|
|
41
|
+
# the object does not have), as distinct from a present property whose value
|
|
42
|
+
# is `nil`/JS null or `UNDEFINED`/JS undefined. It marshals to JS `undefined`
|
|
43
|
+
# for the value, but the proxy reports `("x" in obj) === false` for it — so
|
|
44
|
+
# feature detection like `isUndefined(window.Vue)` AND `"Vue" in window` are
|
|
45
|
+
# both correct. (UNDEFINED means present-but-undefined → reported present.)
|
|
46
|
+
ABSENT = Object.new
|
|
47
|
+
def ABSENT.to_s = "undefined"
|
|
48
|
+
def ABSENT.inspect = "#<Dommy::Bridge::ABSENT>"
|
|
49
|
+
ABSENT.freeze
|
|
50
|
+
|
|
40
51
|
# An opaque handle to a JS-side value that Ruby only stores and hands back
|
|
41
52
|
# (an AbortSignal's reason, a CustomEvent's detail). A non-plain JS object
|
|
42
53
|
# (Error, class instance, …) crosses as one of these instead of being
|