opal-browser 0.2.0.beta1 → 0.3.2
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 +5 -5
- data/.github/workflows/build.yml +95 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +17 -3
- data/LICENSE +2 -1
- data/README.md +183 -52
- data/Rakefile +29 -1
- data/config.ru +20 -3
- data/docs/polyfills.md +24 -0
- data/examples/2048/Gemfile +6 -0
- data/examples/2048/README.md +13 -0
- data/examples/2048/app/application.rb +169 -0
- data/examples/2048/config.ru +9 -0
- data/examples/canvas/Gemfile +6 -0
- data/examples/canvas/README.md +9 -0
- data/examples/canvas/app/application.rb +55 -0
- data/examples/canvas/config.ru +9 -0
- data/examples/component/Gemfile +6 -0
- data/examples/component/README.md +10 -0
- data/examples/component/app/application.rb +66 -0
- data/examples/component/config.ru +9 -0
- data/examples/integrations/README.md +24 -0
- data/examples/integrations/dynamic-rack-opal-sprockets-server/Gemfile +6 -0
- data/examples/integrations/dynamic-rack-opal-sprockets-server/README.md +16 -0
- data/examples/integrations/dynamic-rack-opal-sprockets-server/app/application.rb +6 -0
- data/examples/integrations/dynamic-rack-opal-sprockets-server/config.ru +9 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/.gitignore +1 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/Gemfile +7 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/README.md +22 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/Rakefile +4 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/app/application.rb +6 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/app.rb +32 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/config.ru +3 -0
- data/examples/integrations/dynamic-roda-tilt/.gitignore +1 -0
- data/examples/integrations/dynamic-roda-tilt/Gemfile +8 -0
- data/examples/integrations/dynamic-roda-tilt/README.md +17 -0
- data/examples/integrations/dynamic-roda-tilt/Rakefile +6 -0
- data/examples/integrations/dynamic-roda-tilt/app/application.rb +6 -0
- data/examples/integrations/dynamic-roda-tilt/app.rb +50 -0
- data/examples/integrations/dynamic-roda-tilt/config.ru +3 -0
- data/examples/integrations/dynamic-sinatra-opal-sprockets-server/Gemfile +7 -0
- data/examples/integrations/dynamic-sinatra-opal-sprockets-server/README.md +16 -0
- data/examples/integrations/dynamic-sinatra-opal-sprockets-server/app/application.rb +6 -0
- data/examples/integrations/dynamic-sinatra-opal-sprockets-server/config.ru +29 -0
- data/examples/integrations/static-bash/.gitignore +2 -0
- data/examples/integrations/static-bash/Gemfile +3 -0
- data/examples/integrations/static-bash/README.md +8 -0
- data/examples/integrations/static-bash/app/application.rb +6 -0
- data/examples/integrations/static-bash/build.sh +4 -0
- data/examples/integrations/static-bash/index.html +10 -0
- data/examples/integrations/static-bash-opal-parser/.gitignore +3 -0
- data/examples/integrations/static-bash-opal-parser/Gemfile +3 -0
- data/examples/integrations/static-bash-opal-parser/README.md +10 -0
- data/examples/integrations/static-bash-opal-parser/build.sh +4 -0
- data/examples/integrations/static-bash-opal-parser/index.html +19 -0
- data/examples/integrations/static-rake/.gitignore +1 -0
- data/examples/integrations/static-rake/Gemfile +4 -0
- data/examples/integrations/static-rake/README.md +7 -0
- data/examples/integrations/static-rake/Rakefile +10 -0
- data/examples/integrations/static-rake/app/application.rb +6 -0
- data/examples/integrations/static-rake/index.html +9 -0
- data/examples/integrations/static-rake-guard/.gitignore +1 -0
- data/examples/integrations/static-rake-guard/Gemfile +6 -0
- data/examples/integrations/static-rake-guard/Guardfile +3 -0
- data/examples/integrations/static-rake-guard/README.md +10 -0
- data/examples/integrations/static-rake-guard/Rakefile +10 -0
- data/examples/integrations/static-rake-guard/app/application.rb +6 -0
- data/examples/integrations/static-rake-guard/index.html +9 -0
- data/examples/svg/.gitignore +1 -0
- data/examples/svg/Gemfile +4 -0
- data/examples/svg/README.md +7 -0
- data/examples/svg/Rakefile +10 -0
- data/examples/svg/app/application.rb +11 -0
- data/examples/svg/index.html +17 -0
- data/examples/svg/index.svg +6 -0
- data/index.html.erb +8 -6
- data/lib/opal-browser.rb +1 -0
- data/opal/browser/animation_frame.rb +26 -1
- data/opal/browser/audio/node.rb +121 -0
- data/opal/browser/audio/param_schedule.rb +43 -0
- data/opal/browser/audio.rb +66 -0
- data/opal/browser/blob.rb +94 -0
- data/opal/browser/canvas/data.rb +1 -11
- data/opal/browser/canvas/gradient.rb +1 -11
- data/opal/browser/canvas/style.rb +3 -11
- data/opal/browser/canvas/text.rb +1 -11
- data/opal/browser/canvas.rb +17 -13
- data/opal/browser/console.rb +3 -1
- data/opal/browser/cookies.rb +78 -42
- data/opal/browser/crypto.rb +79 -0
- data/opal/browser/css/declaration.rb +1 -1
- data/opal/browser/css/rule.rb +1 -1
- data/opal/browser/css/style_sheet.rb +2 -2
- data/opal/browser/css.rb +23 -7
- data/opal/browser/database/sql.rb +193 -0
- data/opal/browser/delay.rb +41 -7
- data/opal/browser/dom/attribute.rb +13 -12
- data/opal/browser/dom/builder.rb +31 -17
- data/opal/browser/dom/document.rb +174 -42
- data/opal/browser/dom/document_fragment.rb +18 -0
- data/opal/browser/dom/document_or_shadow_root.rb +19 -0
- data/opal/browser/dom/element/attributes.rb +111 -0
- data/opal/browser/dom/element/button.rb +31 -0
- data/opal/browser/dom/element/custom.rb +177 -0
- data/opal/browser/dom/element/data.rb +82 -0
- data/opal/browser/dom/element/editable.rb +47 -0
- data/opal/browser/dom/element/form.rb +38 -0
- data/opal/browser/dom/element/iframe.rb +37 -0
- data/opal/browser/dom/element/image.rb +2 -0
- data/opal/browser/dom/element/input.rb +48 -1
- data/opal/browser/dom/element/media.rb +17 -0
- data/opal/browser/dom/element/offset.rb +5 -0
- data/opal/browser/dom/element/position.rb +11 -2
- data/opal/browser/dom/element/scroll.rb +123 -24
- data/opal/browser/dom/element/select.rb +42 -0
- data/opal/browser/dom/element/size.rb +17 -0
- data/opal/browser/dom/element/template.rb +11 -0
- data/opal/browser/dom/element/textarea.rb +26 -0
- data/opal/browser/dom/element.rb +468 -238
- data/opal/browser/dom/mutation_observer.rb +4 -4
- data/opal/browser/dom/node.rb +142 -60
- data/opal/browser/dom/node_set.rb +73 -44
- data/opal/browser/dom/shadow_root.rb +12 -0
- data/opal/browser/dom/text.rb +2 -2
- data/opal/browser/dom.rb +40 -16
- data/opal/browser/effects.rb +180 -3
- data/opal/browser/event/all.rb +26 -0
- data/opal/browser/{dom/event → event}/animation.rb +4 -2
- data/opal/browser/{dom/event → event}/audio_processing.rb +4 -2
- data/opal/browser/{dom/event → event}/base.rb +98 -9
- data/opal/browser/{dom/event → event}/before_unload.rb +4 -2
- data/opal/browser/{dom/event → event}/clipboard.rb +11 -2
- data/opal/browser/{dom/event → event}/close.rb +4 -2
- data/opal/browser/{dom/event → event}/composition.rb +4 -2
- data/opal/browser/{dom/event → event}/custom.rb +3 -3
- data/opal/browser/event/data_transfer.rb +95 -0
- data/opal/browser/{dom/event → event}/device_light.rb +4 -2
- data/opal/browser/{dom/event → event}/device_motion.rb +4 -2
- data/opal/browser/{dom/event → event}/device_orientation.rb +4 -2
- data/opal/browser/{dom/event → event}/device_proximity.rb +4 -2
- data/opal/browser/{dom/event → event}/drag.rb +11 -7
- data/opal/browser/{dom/event → event}/focus.rb +4 -2
- data/opal/browser/{dom/event → event}/gamepad.rb +5 -3
- data/opal/browser/{dom/event → event}/hash_change.rb +4 -2
- data/opal/browser/{dom/event → event}/keyboard.rb +16 -3
- data/opal/browser/{dom/event → event}/message.rb +4 -2
- data/opal/browser/{dom/event → event}/mouse.rb +12 -8
- data/opal/browser/{dom/event → event}/page_transition.rb +4 -2
- data/opal/browser/{dom/event → event}/pop_state.rb +4 -2
- data/opal/browser/{dom/event → event}/progress.rb +4 -2
- data/opal/browser/{dom/event → event}/sensor.rb +4 -2
- data/opal/browser/{dom/event → event}/storage.rb +4 -2
- data/opal/browser/{dom/event → event}/touch.rb +4 -2
- data/opal/browser/{dom/event → event}/ui.rb +2 -2
- data/opal/browser/{dom/event → event}/wheel.rb +4 -2
- data/opal/browser/event.rb +163 -0
- data/opal/browser/event_source.rb +2 -2
- data/opal/browser/form_data.rb +225 -0
- data/opal/browser/history.rb +4 -8
- data/opal/browser/http/binary.rb +1 -0
- data/opal/browser/http/headers.rb +16 -2
- data/opal/browser/http/request.rb +46 -48
- data/opal/browser/http/response.rb +5 -1
- data/opal/browser/http.rb +25 -2
- data/opal/browser/immediate.rb +9 -5
- data/opal/browser/interval.rb +34 -11
- data/opal/browser/location.rb +7 -1
- data/opal/browser/navigator.rb +127 -7
- data/opal/browser/polyfill/visual_viewport.rb +216 -0
- data/opal/browser/screen.rb +3 -3
- data/opal/browser/setup/base.rb +6 -0
- data/opal/browser/setup/full.rb +13 -0
- data/opal/browser/setup/large.rb +17 -0
- data/opal/browser/setup/mini.rb +8 -0
- data/opal/browser/setup/traditional.rb +10 -0
- data/opal/browser/socket.rb +8 -4
- data/opal/browser/storage.rb +53 -35
- data/opal/browser/support.rb +72 -5
- data/opal/browser/utils.rb +94 -14
- data/opal/browser/version.rb +1 -1
- data/opal/browser/visual_viewport.rb +39 -0
- data/opal/browser/window/size.rb +31 -3
- data/opal/browser/window/view.rb +15 -0
- data/opal/browser/window.rb +46 -25
- data/opal/browser.rb +1 -10
- data/opal/opal-browser.rb +1 -0
- data/opal-browser.gemspec +3 -3
- data/spec/database/sql_spec.rb +139 -0
- data/spec/delay_spec.rb +41 -0
- data/spec/dom/attribute_spec.rb +49 -0
- data/spec/dom/builder_spec.rb +25 -8
- data/spec/dom/document_spec.rb +22 -0
- data/spec/dom/element/attributes_spec.rb +52 -0
- data/spec/dom/element/custom_spec.rb +106 -0
- data/spec/dom/element/subclass_spec.rb +144 -0
- data/spec/dom/element_spec.rb +181 -4
- data/spec/dom/mutation_observer_spec.rb +12 -8
- data/spec/dom/node_set_spec.rb +44 -0
- data/spec/dom/node_spec.rb +48 -0
- data/spec/dom_spec.rb +8 -0
- data/spec/event_source_spec.rb +15 -12
- data/spec/{dom/event_spec.rb → event_spec.rb} +44 -15
- data/spec/history_spec.rb +23 -19
- data/spec/http_spec.rb +19 -31
- data/spec/immediate_spec.rb +5 -4
- data/spec/interval_spec.rb +59 -0
- data/spec/native_cached_wrapper_spec.rb +46 -0
- data/spec/runner.rb +62 -69
- data/spec/socket_spec.rb +16 -12
- data/spec/spec_helper.rb +2 -5
- data/spec/spec_helper_promise.rb.erb +25 -0
- data/spec/storage_spec.rb +1 -1
- metadata +172 -50
- data/.travis.yml +0 -60
- data/opal/browser/dom/event.rb +0 -253
- data/opal/browser/http/parameters.rb +0 -8
- data/opal/browser/window/scroll.rb +0 -59
data/opal/browser/dom/element.rb
CHANGED
|
@@ -1,40 +1,189 @@
|
|
|
1
|
-
|
|
2
|
-
require 'browser/dom/element/offset'
|
|
3
|
-
require 'browser/dom/element/scroll'
|
|
4
|
-
require 'browser/dom/element/size'
|
|
5
|
-
|
|
6
|
-
require 'browser/dom/element/input'
|
|
7
|
-
require 'browser/dom/element/image'
|
|
1
|
+
# Requires are moved to the bottom of this file.
|
|
8
2
|
|
|
9
3
|
module Browser; module DOM
|
|
10
4
|
|
|
11
5
|
class Element < Node
|
|
12
|
-
def self.create(*args)
|
|
13
|
-
|
|
6
|
+
def self.create(*args, &block)
|
|
7
|
+
if Document === args.first
|
|
8
|
+
document = args.shift
|
|
9
|
+
else
|
|
10
|
+
document = $document
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if self == Element
|
|
14
|
+
document.create_element(*args, &block)
|
|
15
|
+
elsif @tag_name
|
|
16
|
+
document.create_element(@tag_name, *args, &block)
|
|
17
|
+
elsif @selector
|
|
18
|
+
# That's crude, but should cover the most basic cases.
|
|
19
|
+
# Just in case, you can override it safely. To reiterate:
|
|
20
|
+
# .create is not to be used inside libraries, those are
|
|
21
|
+
# expected to use the Document#create_element API.
|
|
22
|
+
kwargs = {}
|
|
23
|
+
kwargs = args.pop if Hash === args.last
|
|
24
|
+
custom_attrs, custom_id, custom_classes = nil, nil, nil
|
|
25
|
+
tag_name = (@selector.scan(/^[\w-]+/).first || "div").upcase
|
|
26
|
+
classes = @selector.scan(/\.([\w-]+)/).flatten
|
|
27
|
+
classes |= custom_classes if custom_classes = kwargs.delete(:classes)
|
|
28
|
+
id = @selector.scan(/#([\w-]+)/).flatten.first
|
|
29
|
+
id = custom_id if custom_id = kwargs.delete(:id)
|
|
30
|
+
attrs = @selector.scan(/\[([\w-]+)=((["'])(.*?)\3|[\w_-]*)\]/).map { |a,b,_,d| [a,d||b] }.to_h
|
|
31
|
+
attrs = attrs.merge(custom_attrs) if custom_attrs = kwargs.delete(:attrs)
|
|
32
|
+
document.create_element(tag_name, *args, classes: classes, id: id, attrs: attrs, **kwargs, &block)
|
|
33
|
+
else
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.subclasses
|
|
39
|
+
@subclasses ||= []
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Define a selector for subclass dispatch
|
|
43
|
+
#
|
|
44
|
+
# Example:
|
|
45
|
+
# ```
|
|
46
|
+
# class CustomElement < Browser::DOM::Element
|
|
47
|
+
# def_selector "div.hello-world"
|
|
48
|
+
# end
|
|
49
|
+
# ```
|
|
50
|
+
def self.def_selector(selector)
|
|
51
|
+
Element.subclasses << self
|
|
52
|
+
|
|
53
|
+
@selector = selector
|
|
54
|
+
|
|
55
|
+
# A special case to speedup dispatch
|
|
56
|
+
@tag_name = selector.upcase unless selector =~ /[^\w-]/
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.selector
|
|
60
|
+
@selector
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.tag_name
|
|
64
|
+
@tag_name
|
|
14
65
|
end
|
|
15
66
|
|
|
16
|
-
def self.new(
|
|
67
|
+
def self.new(*args, &block)
|
|
68
|
+
if args.length == 1 && !block_given? && Opal.native?(args[0])
|
|
69
|
+
# Use `.new` as a wrapping method.
|
|
70
|
+
node = args[0]
|
|
71
|
+
else
|
|
72
|
+
# Use `.new` as an alias for `.create`.
|
|
73
|
+
return create(*args, &block)
|
|
74
|
+
end
|
|
75
|
+
|
|
17
76
|
if self == Element
|
|
18
|
-
|
|
77
|
+
subclass = Element.subclasses.select do |subclass|
|
|
78
|
+
Element.native_is?(node, subclass)
|
|
79
|
+
end.last
|
|
19
80
|
|
|
20
|
-
if
|
|
21
|
-
|
|
81
|
+
if subclass
|
|
82
|
+
subclass.new(node)
|
|
22
83
|
else
|
|
23
|
-
super
|
|
84
|
+
super(node)
|
|
24
85
|
end
|
|
25
86
|
else
|
|
26
|
-
super
|
|
87
|
+
super(node)
|
|
27
88
|
end
|
|
28
89
|
end
|
|
29
90
|
|
|
30
91
|
include Event::Target
|
|
31
92
|
|
|
32
93
|
target {|value|
|
|
33
|
-
|
|
94
|
+
begin
|
|
95
|
+
DOM(value)
|
|
96
|
+
rescue StandardError, JS::Error
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
34
99
|
}
|
|
35
100
|
|
|
36
|
-
|
|
101
|
+
def self.native_is? (native, klass)
|
|
102
|
+
if tag_name = klass.tag_name
|
|
103
|
+
is = `(#{native}.getAttribute("is") || "")`
|
|
104
|
+
`#{tag_name} === #{is}.toUpperCase() || #{tag_name} === #{native}.nodeName`
|
|
105
|
+
else
|
|
106
|
+
Element.native_matches?(native, klass.selector)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
37
109
|
|
|
110
|
+
if Browser.supports? 'Element.matches'
|
|
111
|
+
def self.native_matches? (native, selector)
|
|
112
|
+
`#{native}.matches(#{selector})`
|
|
113
|
+
end
|
|
114
|
+
elsif Browser.supports? 'Element.matches (Opera)'
|
|
115
|
+
def self.native_matches? (native, selector)
|
|
116
|
+
`#{native}.oMatchesSelector(#{selector})`
|
|
117
|
+
end
|
|
118
|
+
elsif Browser.supports? 'Element.matches (Internet Explorer)'
|
|
119
|
+
def self.native_matches? (native, selector)
|
|
120
|
+
`#{native}.msMatchesSelector(#{selector})`
|
|
121
|
+
end
|
|
122
|
+
elsif Browser.supports? 'Element.matches (Firefox)'
|
|
123
|
+
def self.native_matches? (native, selector)
|
|
124
|
+
`#{native}.mozMatchesSelector(#{selector})`
|
|
125
|
+
end
|
|
126
|
+
elsif Browser.supports? 'Element.matches (Chrome)'
|
|
127
|
+
def self.native_matches? (native, selector)
|
|
128
|
+
`#{native}.webkitMatchesSelector(#{selector})`
|
|
129
|
+
end
|
|
130
|
+
elsif Browser.loaded? 'Sizzle'
|
|
131
|
+
def self.native_matches? (native, selector)
|
|
132
|
+
`Sizzle.matchesSelector(#{native}, #{selector})`
|
|
133
|
+
end
|
|
134
|
+
else
|
|
135
|
+
def self.native_matches? (native, selector)
|
|
136
|
+
raise NotImplementedError, 'selector matching unsupported'
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Check whether the element matches the given selector.
|
|
141
|
+
#
|
|
142
|
+
# @param selector [String] the CSS selector
|
|
143
|
+
def =~(selector)
|
|
144
|
+
Element.native_matches?(@native, selector)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Allow for case expressions
|
|
148
|
+
alias === =~
|
|
149
|
+
|
|
150
|
+
# Query for children with the given XPpaths.
|
|
151
|
+
#
|
|
152
|
+
# @param paths [Array<String>] the XPaths to look for
|
|
153
|
+
#
|
|
154
|
+
# @return [NodeSet]
|
|
155
|
+
def /(*paths)
|
|
156
|
+
NodeSet[paths.map { |path| xpath(path) }]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Get the attribute with the given name.
|
|
160
|
+
#
|
|
161
|
+
# @param name [String] the attribute name
|
|
162
|
+
# @param options [Hash] options for the attribute
|
|
163
|
+
#
|
|
164
|
+
# @option options [String] :namespace the namespace for the attribute
|
|
165
|
+
#
|
|
166
|
+
# @return [String?]
|
|
167
|
+
def [](name, options = {})
|
|
168
|
+
attributes.get(name, options)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Set the attribute with the given name and value.
|
|
172
|
+
#
|
|
173
|
+
# @param name [String] the attribute name
|
|
174
|
+
# @param value [Object] the attribute value
|
|
175
|
+
# @param options [Hash] the options for the attribute
|
|
176
|
+
#
|
|
177
|
+
# @option options [String] :namespace the namespace for the attribute
|
|
178
|
+
def []=(name, value, options = {})
|
|
179
|
+
attributes.set(name, value, options)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Add class names to the element.
|
|
183
|
+
#
|
|
184
|
+
# @param names [Array<String>] class names to add
|
|
185
|
+
#
|
|
186
|
+
# @return [self]
|
|
38
187
|
def add_class(*names)
|
|
39
188
|
classes = class_names + names
|
|
40
189
|
|
|
@@ -45,99 +194,205 @@ class Element < Node
|
|
|
45
194
|
self
|
|
46
195
|
end
|
|
47
196
|
|
|
48
|
-
|
|
49
|
-
|
|
197
|
+
# Get the first node that matches the given CSS selector or XPath.
|
|
198
|
+
#
|
|
199
|
+
# @param path_or_selector [String] an XPath or CSS selector
|
|
200
|
+
#
|
|
201
|
+
# @return [Node?]
|
|
202
|
+
def at(path_or_selector)
|
|
203
|
+
xpath(path_or_selector).first || css(path_or_selector).first
|
|
204
|
+
end
|
|
50
205
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
206
|
+
# Get the first node matching the given CSS selectors.
|
|
207
|
+
#
|
|
208
|
+
# @param rules [Array<String>] the CSS selectors to match with
|
|
209
|
+
#
|
|
210
|
+
# @return [Node?]
|
|
211
|
+
def at_css(*rules)
|
|
212
|
+
result = nil
|
|
56
213
|
|
|
57
|
-
|
|
214
|
+
rules.each {|rule|
|
|
215
|
+
if result = css(rule).first
|
|
216
|
+
break
|
|
217
|
+
end
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
result
|
|
58
221
|
end
|
|
59
222
|
|
|
60
|
-
|
|
223
|
+
# Get the first node matching the given XPath.
|
|
224
|
+
#
|
|
225
|
+
# @param paths [Array<String>] the XPath to match with
|
|
226
|
+
#
|
|
227
|
+
# @return [Node?]
|
|
228
|
+
def at_xpath(*paths)
|
|
229
|
+
result = nil
|
|
61
230
|
|
|
62
|
-
|
|
63
|
-
|
|
231
|
+
paths.each {|path|
|
|
232
|
+
if result = xpath(path).first
|
|
233
|
+
break
|
|
234
|
+
end
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
result
|
|
64
238
|
end
|
|
65
239
|
|
|
66
|
-
alias
|
|
240
|
+
alias attr []
|
|
67
241
|
|
|
68
|
-
|
|
69
|
-
Native::Array.new(`#@native.attributes`, get: :item) { |e| DOM(e) }
|
|
70
|
-
end
|
|
242
|
+
alias attribute []
|
|
71
243
|
|
|
244
|
+
# @!attribute [r] attributes
|
|
245
|
+
# @return [Attributes] the attributes for the element
|
|
72
246
|
def attributes(options = {})
|
|
73
247
|
Attributes.new(self, options)
|
|
74
248
|
end
|
|
75
249
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
250
|
+
# @!attribute [r] attribute_nodes
|
|
251
|
+
# @return [NodeSet] the attribute nodes for the element
|
|
252
|
+
def attribute_nodes
|
|
253
|
+
NodeSet[Native::Array.new(`#@native.attributes`, get: :item)]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# @!attribute [r] class_name
|
|
257
|
+
# @return [String] all the element class names
|
|
258
|
+
alias_native :class_name, :className
|
|
259
|
+
|
|
260
|
+
# @!attribute [r] class_names
|
|
261
|
+
# @return [Array<String>] all the element class names
|
|
262
|
+
def class_names
|
|
263
|
+
`#@native.className`.split(/\s+/).reject(&:empty?)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
if Browser.supports? 'Query.css'
|
|
267
|
+
def css(path)
|
|
268
|
+
NodeSet[Native::Array.new(`#@native.querySelectorAll(path)`)]
|
|
269
|
+
rescue StandardError, JS::Error
|
|
270
|
+
NodeSet[]
|
|
271
|
+
end
|
|
272
|
+
elsif Browser.loaded? 'Sizzle'
|
|
273
|
+
def css(path)
|
|
274
|
+
NodeSet[`Sizzle(path, #@native)`]
|
|
275
|
+
rescue StandardError, JS::Error
|
|
276
|
+
NodeSet[]
|
|
277
|
+
end
|
|
278
|
+
else
|
|
279
|
+
# Query for children matching the given CSS selector.
|
|
280
|
+
#
|
|
281
|
+
# @param selector [String] the CSS selector
|
|
282
|
+
#
|
|
283
|
+
# @return [NodeSet]
|
|
284
|
+
def css(selector)
|
|
285
|
+
raise NotImplementedError, 'query by CSS selector unsupported'
|
|
81
286
|
end
|
|
82
287
|
end
|
|
83
288
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
289
|
+
# Click the element. it fires the element's click event.
|
|
290
|
+
def click
|
|
291
|
+
`#@native.click()`
|
|
292
|
+
self
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# @overload data()
|
|
296
|
+
#
|
|
297
|
+
# Return the data for the element.
|
|
298
|
+
#
|
|
299
|
+
# @return [Data]
|
|
300
|
+
#
|
|
301
|
+
# @overload data(hash)
|
|
302
|
+
#
|
|
303
|
+
# Set data on the element.
|
|
304
|
+
#
|
|
305
|
+
# @param hash [Hash] the data to set
|
|
306
|
+
#
|
|
307
|
+
# @return [self]
|
|
308
|
+
def data(value = nil)
|
|
309
|
+
data = Data.new(self)
|
|
310
|
+
|
|
311
|
+
return data unless value
|
|
312
|
+
|
|
313
|
+
if Hash === value
|
|
314
|
+
data.assign(value)
|
|
87
315
|
else
|
|
88
|
-
|
|
316
|
+
raise ArgumentError, 'unknown data type'
|
|
89
317
|
end
|
|
90
|
-
end
|
|
91
318
|
|
|
92
|
-
|
|
93
|
-
|
|
319
|
+
self
|
|
320
|
+
end
|
|
94
321
|
|
|
95
|
-
alias
|
|
96
|
-
alias attribute get
|
|
322
|
+
alias get_attribute []
|
|
97
323
|
|
|
98
|
-
alias
|
|
99
|
-
alias set_attribute set
|
|
324
|
+
alias get []
|
|
100
325
|
|
|
101
|
-
|
|
102
|
-
|
|
326
|
+
# @!attribute height
|
|
327
|
+
# @return [Integer] the height of the element
|
|
328
|
+
def height
|
|
329
|
+
size.height
|
|
103
330
|
end
|
|
104
331
|
|
|
105
|
-
def
|
|
106
|
-
|
|
332
|
+
def height=(value)
|
|
333
|
+
size.height = value
|
|
107
334
|
end
|
|
108
335
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
336
|
+
# @!attribute id
|
|
337
|
+
# @return [String?] the ID of the element
|
|
338
|
+
def id
|
|
339
|
+
%x{
|
|
340
|
+
var id = #@native.id;
|
|
112
341
|
|
|
113
|
-
|
|
114
|
-
|
|
342
|
+
if (id === "") {
|
|
343
|
+
return nil;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
return id;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
115
349
|
end
|
|
116
350
|
|
|
117
|
-
def
|
|
118
|
-
|
|
351
|
+
def id=(value)
|
|
352
|
+
`#@native.id = #{value.to_s}`
|
|
119
353
|
end
|
|
120
354
|
|
|
121
|
-
|
|
122
|
-
|
|
355
|
+
# Set the inner DOM of the element using the {Builder}.
|
|
356
|
+
def inner_dom(builder=nil, &block)
|
|
357
|
+
self.inner_dom = Builder.new(document, builder, &block).to_a
|
|
358
|
+
self
|
|
123
359
|
end
|
|
124
360
|
|
|
125
|
-
|
|
126
|
-
|
|
361
|
+
# Set the inner DOM with the given node.
|
|
362
|
+
#
|
|
363
|
+
# (see #append_child)
|
|
364
|
+
def inner_dom=(node)
|
|
365
|
+
clear
|
|
366
|
+
|
|
367
|
+
self << node
|
|
127
368
|
end
|
|
128
369
|
|
|
129
|
-
|
|
130
|
-
|
|
370
|
+
# @!attribute inner_html
|
|
371
|
+
# @return [String] the inner HTML of the element
|
|
372
|
+
def inner_html
|
|
373
|
+
`#@native.innerHTML`
|
|
131
374
|
end
|
|
132
375
|
|
|
133
|
-
def
|
|
134
|
-
|
|
376
|
+
def inner_html=(value)
|
|
377
|
+
`#@native.innerHTML = #{value}`
|
|
135
378
|
end
|
|
136
379
|
|
|
137
|
-
def
|
|
138
|
-
|
|
380
|
+
def inspect
|
|
381
|
+
inspect = name.downcase
|
|
382
|
+
|
|
383
|
+
if id
|
|
384
|
+
inspect += '.' + id + '!'
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
unless class_names.empty?
|
|
388
|
+
inspect += '.' + class_names.join('.')
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
"#<#{self.class.name.gsub("Browser::","")}: #{inspect}>"
|
|
139
392
|
end
|
|
140
393
|
|
|
394
|
+
# @!attribute offset
|
|
395
|
+
# @return [Offset] the offset of the element
|
|
141
396
|
def offset(*values)
|
|
142
397
|
off = Offset.new(self)
|
|
143
398
|
|
|
@@ -152,116 +407,92 @@ class Element < Node
|
|
|
152
407
|
offset.set(*value)
|
|
153
408
|
end
|
|
154
409
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def inner_dom(&block)
|
|
160
|
-
# FIXME: when block passing is fixed
|
|
161
|
-
doc = document
|
|
162
|
-
clear; Builder.new(doc, self, &block)
|
|
163
|
-
|
|
164
|
-
self
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def inner_dom=(node)
|
|
168
|
-
clear; self << node
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def /(*paths)
|
|
172
|
-
paths.map { |path| xpath(path) }.flatten.uniq
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def at(path)
|
|
176
|
-
xpath(path).first || css(path).first
|
|
410
|
+
# @!attribute outer_html
|
|
411
|
+
# @return [String] the outer HTML of the element
|
|
412
|
+
def outer_html
|
|
413
|
+
`#@native.outerHTML`
|
|
177
414
|
end
|
|
178
415
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return found if found
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
nil
|
|
416
|
+
# @!attribute [r] position
|
|
417
|
+
# @return [Position] the position of the element
|
|
418
|
+
def position
|
|
419
|
+
@position ||= Position.new(self)
|
|
187
420
|
end
|
|
188
421
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return found if found
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
nil
|
|
422
|
+
# @!attribute [r] scroll
|
|
423
|
+
# @return [Scroll] the scrolling for the element
|
|
424
|
+
def scroll
|
|
425
|
+
@scroll ||= Scroll.new(self)
|
|
197
426
|
end
|
|
198
427
|
|
|
428
|
+
# Search for all the children matching the given XPaths or CSS selectors.
|
|
429
|
+
#
|
|
430
|
+
# @param selectors [Array<String>] mixed list of XPaths and CSS selectors
|
|
431
|
+
#
|
|
432
|
+
# @return [NodeSet]
|
|
199
433
|
def search(*selectors)
|
|
200
|
-
NodeSet.new
|
|
434
|
+
NodeSet.new selectors.map {|selector|
|
|
201
435
|
xpath(selector).to_a.concat(css(selector).to_a)
|
|
202
436
|
}.flatten.uniq
|
|
203
437
|
end
|
|
204
438
|
|
|
205
|
-
|
|
206
|
-
def css(path)
|
|
207
|
-
%x{
|
|
208
|
-
try {
|
|
209
|
-
var result = #@native.querySelectorAll(path);
|
|
210
|
-
|
|
211
|
-
return #{NodeSet.new(document,
|
|
212
|
-
Native::Array.new(`result`))};
|
|
213
|
-
}
|
|
214
|
-
catch(e) {
|
|
215
|
-
return #{NodeSet.new(document)};
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
end
|
|
219
|
-
elsif Browser.loaded? 'Sizzle'
|
|
220
|
-
def css(path)
|
|
221
|
-
NodeSet.new(document, `Sizzle(#{path}, #@native)`)
|
|
222
|
-
end
|
|
223
|
-
else
|
|
224
|
-
def css(selector)
|
|
225
|
-
raise NotImplementedError, 'query by CSS selector unsupported'
|
|
226
|
-
end
|
|
227
|
-
end
|
|
439
|
+
alias set []=
|
|
228
440
|
|
|
229
|
-
|
|
230
|
-
if Browser.loaded? 'wicked-good-xpath'
|
|
231
|
-
`wgxpath.install()`
|
|
232
|
-
end
|
|
441
|
+
alias set_attribute []=
|
|
233
442
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
end
|
|
248
|
-
else
|
|
249
|
-
def xpath(path)
|
|
250
|
-
raise NotImplementedError, 'query by XPath unsupported'
|
|
443
|
+
# Creates or accesses the shadow root of this element
|
|
444
|
+
#
|
|
445
|
+
# @param open [Boolean] set to false if you want to create a closed
|
|
446
|
+
# shadow root
|
|
447
|
+
#
|
|
448
|
+
# @return [ShadowRoot]
|
|
449
|
+
def shadow (open = true)
|
|
450
|
+
if root = `#@native.shadowRoot`
|
|
451
|
+
DOM(root)
|
|
452
|
+
else
|
|
453
|
+
DOM(`#@native.attachShadow({mode: #{open ? "open" : "closed"}})`)
|
|
251
454
|
end
|
|
252
455
|
end
|
|
253
456
|
|
|
457
|
+
# Checks for a presence of a shadow root of this element
|
|
458
|
+
#
|
|
459
|
+
# @return [Boolean]
|
|
460
|
+
def shadow?
|
|
461
|
+
`!!#@native.shadowRoot`
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# @overload style()
|
|
465
|
+
#
|
|
466
|
+
# Return the style for the element.
|
|
467
|
+
#
|
|
468
|
+
# @return [CSS::Declaration]
|
|
469
|
+
#
|
|
470
|
+
# @overload style(data)
|
|
471
|
+
#
|
|
472
|
+
# Set the CSS style as string or set of values.
|
|
473
|
+
#
|
|
474
|
+
# @param data [String, Hash] the new style
|
|
475
|
+
#
|
|
476
|
+
# @return [self]
|
|
477
|
+
#
|
|
478
|
+
# @overload style(&block)
|
|
479
|
+
#
|
|
480
|
+
# Set the CSS style from a CSS builder DSL.
|
|
481
|
+
#
|
|
482
|
+
# @return [self]
|
|
254
483
|
def style(data = nil, &block)
|
|
255
484
|
style = CSS::Declaration.new(`#@native.style`)
|
|
256
485
|
|
|
257
486
|
return style unless data || block
|
|
258
487
|
|
|
259
|
-
if data
|
|
488
|
+
if String === data
|
|
260
489
|
style.replace(data)
|
|
261
|
-
elsif data
|
|
490
|
+
elsif Hash === data
|
|
262
491
|
style.assign(data)
|
|
263
492
|
elsif block
|
|
264
493
|
style.apply(&block)
|
|
494
|
+
else
|
|
495
|
+
raise ArgumentError, 'unknown data type'
|
|
265
496
|
end
|
|
266
497
|
|
|
267
498
|
self
|
|
@@ -276,113 +507,112 @@ class Element < Node
|
|
|
276
507
|
CSS::Declaration.new(`#@native.currentStyle`)
|
|
277
508
|
end
|
|
278
509
|
else
|
|
510
|
+
# @!attribute [r] style!
|
|
511
|
+
# @return [CSS::Declaration] get the computed style for the element
|
|
279
512
|
def style!
|
|
280
513
|
raise NotImplementedError, 'computed style unsupported'
|
|
281
514
|
end
|
|
282
515
|
end
|
|
283
516
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
what.each {|name, value|
|
|
291
|
-
`#@native.$data[name] = value`
|
|
292
|
-
}
|
|
293
|
-
else
|
|
294
|
-
return self["data-#{what}"] if self["data-#{what}"]
|
|
295
|
-
|
|
296
|
-
return unless defined?(`#@native.$data`)
|
|
297
|
-
|
|
298
|
-
%x{
|
|
299
|
-
var value = #@native.$data[what];
|
|
300
|
-
|
|
301
|
-
if (value === undefined) {
|
|
302
|
-
return nil;
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
return value;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
end
|
|
517
|
+
# Remove an attribute from the element.
|
|
518
|
+
#
|
|
519
|
+
# @param name [String] the attribute name
|
|
520
|
+
def remove_attribute(name)
|
|
521
|
+
`#@native.removeAttribute(name)`
|
|
309
522
|
end
|
|
310
523
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
elsif Browser.supports? 'Element.matches (Firefox)'
|
|
324
|
-
def matches?(selector)
|
|
325
|
-
`#@native.mozMatchesSelector(#{selector})`
|
|
326
|
-
end
|
|
327
|
-
elsif Browser.supports? 'Element.matches (Chrome)'
|
|
328
|
-
def matches?(selector)
|
|
329
|
-
`#@native.webkitMatchesSelector(#{selector})`
|
|
330
|
-
end
|
|
331
|
-
elsif Browser.loaded? 'Sizzle'
|
|
332
|
-
def matches?(selector)
|
|
333
|
-
`Sizzle.matchesSelector(#@native, #{selector})`
|
|
334
|
-
end
|
|
335
|
-
else
|
|
336
|
-
def matches?(selector)
|
|
337
|
-
raise NotImplementedError, 'selector matching unsupported'
|
|
524
|
+
# Remove class names from the element.
|
|
525
|
+
#
|
|
526
|
+
# @param names [Array<String>] class names to remove
|
|
527
|
+
#
|
|
528
|
+
# @return [self]
|
|
529
|
+
def remove_class(*names)
|
|
530
|
+
classes = class_names - names
|
|
531
|
+
|
|
532
|
+
if classes.empty?
|
|
533
|
+
`#@native.removeAttribute('class')`
|
|
534
|
+
else
|
|
535
|
+
`#@native.className = #{classes.join ' '}`
|
|
338
536
|
end
|
|
339
|
-
end
|
|
340
537
|
|
|
341
|
-
|
|
342
|
-
def window
|
|
343
|
-
document.window
|
|
538
|
+
self
|
|
344
539
|
end
|
|
345
540
|
|
|
346
|
-
|
|
347
|
-
|
|
541
|
+
# @!attribute [r] size
|
|
542
|
+
# @return [Size] the size of the element
|
|
543
|
+
def size(*inc)
|
|
544
|
+
Size.new(self, *inc)
|
|
348
545
|
end
|
|
349
546
|
|
|
350
|
-
class
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
547
|
+
# Toggle class names of the element.
|
|
548
|
+
#
|
|
549
|
+
# @param names [Array<String>] class names to toggle
|
|
550
|
+
#
|
|
551
|
+
# @return [self]
|
|
552
|
+
def toggle_class(*names)
|
|
553
|
+
to_remove, to_add = names.partition { |name| class_names.include? name }
|
|
354
554
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
end
|
|
555
|
+
add_class(*to_add)
|
|
556
|
+
remove_class(*to_remove)
|
|
557
|
+
end
|
|
359
558
|
|
|
360
|
-
|
|
361
|
-
|
|
559
|
+
# @!attribute width
|
|
560
|
+
# @return [Integer] the width of the element
|
|
561
|
+
def width
|
|
562
|
+
size.width
|
|
563
|
+
end
|
|
362
564
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
565
|
+
def width=(value)
|
|
566
|
+
size.width = value
|
|
567
|
+
end
|
|
366
568
|
|
|
367
|
-
|
|
368
|
-
|
|
569
|
+
# @!attribute [r] window
|
|
570
|
+
# @return [Window] the window for the element
|
|
571
|
+
def window
|
|
572
|
+
document.window
|
|
573
|
+
end
|
|
369
574
|
|
|
370
|
-
|
|
371
|
-
|
|
575
|
+
if Browser.supports?('Query.xpath') || Browser.loaded?('wicked-good-xpath')
|
|
576
|
+
if Browser.loaded? 'wicked-good-xpath'
|
|
577
|
+
`wgxpath.install()`
|
|
372
578
|
end
|
|
373
579
|
|
|
374
|
-
def
|
|
375
|
-
|
|
580
|
+
def xpath(path)
|
|
581
|
+
NodeSet[Native::Array.new(
|
|
582
|
+
`(#@native.ownerDocument || #@native).evaluate(path,
|
|
583
|
+
#@native, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)`,
|
|
584
|
+
get: :snapshotItem,
|
|
585
|
+
length: :snapshotLength)]
|
|
586
|
+
rescue StandardError, JS::Error
|
|
587
|
+
NodeSet[]
|
|
376
588
|
end
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
589
|
+
else
|
|
590
|
+
# Query for children matching the given XPath.
|
|
591
|
+
#
|
|
592
|
+
# @param path [String] the XPath
|
|
593
|
+
#
|
|
594
|
+
# @return [NodeSet]
|
|
595
|
+
def xpath(path)
|
|
596
|
+
raise NotImplementedError, 'query by XPath unsupported'
|
|
384
597
|
end
|
|
385
598
|
end
|
|
386
599
|
end
|
|
387
600
|
|
|
388
601
|
end; end
|
|
602
|
+
|
|
603
|
+
require 'browser/dom/element/attributes'
|
|
604
|
+
require 'browser/dom/element/data'
|
|
605
|
+
require 'browser/dom/element/position'
|
|
606
|
+
require 'browser/dom/element/offset'
|
|
607
|
+
require 'browser/dom/element/scroll'
|
|
608
|
+
require 'browser/dom/element/size'
|
|
609
|
+
|
|
610
|
+
require 'browser/dom/element/button'
|
|
611
|
+
require 'browser/dom/element/image'
|
|
612
|
+
require 'browser/dom/element/form'
|
|
613
|
+
require 'browser/dom/element/input'
|
|
614
|
+
require 'browser/dom/element/select'
|
|
615
|
+
require 'browser/dom/element/template'
|
|
616
|
+
require 'browser/dom/element/textarea'
|
|
617
|
+
require 'browser/dom/element/iframe'
|
|
618
|
+
require 'browser/dom/element/media'
|