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.
Files changed (218) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/build.yml +95 -0
  3. data/.gitignore +3 -0
  4. data/CHANGELOG.md +8 -0
  5. data/Gemfile +17 -3
  6. data/LICENSE +2 -1
  7. data/README.md +183 -52
  8. data/Rakefile +29 -1
  9. data/config.ru +20 -3
  10. data/docs/polyfills.md +24 -0
  11. data/examples/2048/Gemfile +6 -0
  12. data/examples/2048/README.md +13 -0
  13. data/examples/2048/app/application.rb +169 -0
  14. data/examples/2048/config.ru +9 -0
  15. data/examples/canvas/Gemfile +6 -0
  16. data/examples/canvas/README.md +9 -0
  17. data/examples/canvas/app/application.rb +55 -0
  18. data/examples/canvas/config.ru +9 -0
  19. data/examples/component/Gemfile +6 -0
  20. data/examples/component/README.md +10 -0
  21. data/examples/component/app/application.rb +66 -0
  22. data/examples/component/config.ru +9 -0
  23. data/examples/integrations/README.md +24 -0
  24. data/examples/integrations/dynamic-rack-opal-sprockets-server/Gemfile +6 -0
  25. data/examples/integrations/dynamic-rack-opal-sprockets-server/README.md +16 -0
  26. data/examples/integrations/dynamic-rack-opal-sprockets-server/app/application.rb +6 -0
  27. data/examples/integrations/dynamic-rack-opal-sprockets-server/config.ru +9 -0
  28. data/examples/integrations/dynamic-roda-roda-sprockets/.gitignore +1 -0
  29. data/examples/integrations/dynamic-roda-roda-sprockets/Gemfile +7 -0
  30. data/examples/integrations/dynamic-roda-roda-sprockets/README.md +22 -0
  31. data/examples/integrations/dynamic-roda-roda-sprockets/Rakefile +4 -0
  32. data/examples/integrations/dynamic-roda-roda-sprockets/app/application.rb +6 -0
  33. data/examples/integrations/dynamic-roda-roda-sprockets/app.rb +32 -0
  34. data/examples/integrations/dynamic-roda-roda-sprockets/config.ru +3 -0
  35. data/examples/integrations/dynamic-roda-tilt/.gitignore +1 -0
  36. data/examples/integrations/dynamic-roda-tilt/Gemfile +8 -0
  37. data/examples/integrations/dynamic-roda-tilt/README.md +17 -0
  38. data/examples/integrations/dynamic-roda-tilt/Rakefile +6 -0
  39. data/examples/integrations/dynamic-roda-tilt/app/application.rb +6 -0
  40. data/examples/integrations/dynamic-roda-tilt/app.rb +50 -0
  41. data/examples/integrations/dynamic-roda-tilt/config.ru +3 -0
  42. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/Gemfile +7 -0
  43. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/README.md +16 -0
  44. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/app/application.rb +6 -0
  45. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/config.ru +29 -0
  46. data/examples/integrations/static-bash/.gitignore +2 -0
  47. data/examples/integrations/static-bash/Gemfile +3 -0
  48. data/examples/integrations/static-bash/README.md +8 -0
  49. data/examples/integrations/static-bash/app/application.rb +6 -0
  50. data/examples/integrations/static-bash/build.sh +4 -0
  51. data/examples/integrations/static-bash/index.html +10 -0
  52. data/examples/integrations/static-bash-opal-parser/.gitignore +3 -0
  53. data/examples/integrations/static-bash-opal-parser/Gemfile +3 -0
  54. data/examples/integrations/static-bash-opal-parser/README.md +10 -0
  55. data/examples/integrations/static-bash-opal-parser/build.sh +4 -0
  56. data/examples/integrations/static-bash-opal-parser/index.html +19 -0
  57. data/examples/integrations/static-rake/.gitignore +1 -0
  58. data/examples/integrations/static-rake/Gemfile +4 -0
  59. data/examples/integrations/static-rake/README.md +7 -0
  60. data/examples/integrations/static-rake/Rakefile +10 -0
  61. data/examples/integrations/static-rake/app/application.rb +6 -0
  62. data/examples/integrations/static-rake/index.html +9 -0
  63. data/examples/integrations/static-rake-guard/.gitignore +1 -0
  64. data/examples/integrations/static-rake-guard/Gemfile +6 -0
  65. data/examples/integrations/static-rake-guard/Guardfile +3 -0
  66. data/examples/integrations/static-rake-guard/README.md +10 -0
  67. data/examples/integrations/static-rake-guard/Rakefile +10 -0
  68. data/examples/integrations/static-rake-guard/app/application.rb +6 -0
  69. data/examples/integrations/static-rake-guard/index.html +9 -0
  70. data/examples/svg/.gitignore +1 -0
  71. data/examples/svg/Gemfile +4 -0
  72. data/examples/svg/README.md +7 -0
  73. data/examples/svg/Rakefile +10 -0
  74. data/examples/svg/app/application.rb +11 -0
  75. data/examples/svg/index.html +17 -0
  76. data/examples/svg/index.svg +6 -0
  77. data/index.html.erb +8 -6
  78. data/lib/opal-browser.rb +1 -0
  79. data/opal/browser/animation_frame.rb +26 -1
  80. data/opal/browser/audio/node.rb +121 -0
  81. data/opal/browser/audio/param_schedule.rb +43 -0
  82. data/opal/browser/audio.rb +66 -0
  83. data/opal/browser/blob.rb +94 -0
  84. data/opal/browser/canvas/data.rb +1 -11
  85. data/opal/browser/canvas/gradient.rb +1 -11
  86. data/opal/browser/canvas/style.rb +3 -11
  87. data/opal/browser/canvas/text.rb +1 -11
  88. data/opal/browser/canvas.rb +17 -13
  89. data/opal/browser/console.rb +3 -1
  90. data/opal/browser/cookies.rb +78 -42
  91. data/opal/browser/crypto.rb +79 -0
  92. data/opal/browser/css/declaration.rb +1 -1
  93. data/opal/browser/css/rule.rb +1 -1
  94. data/opal/browser/css/style_sheet.rb +2 -2
  95. data/opal/browser/css.rb +23 -7
  96. data/opal/browser/database/sql.rb +193 -0
  97. data/opal/browser/delay.rb +41 -7
  98. data/opal/browser/dom/attribute.rb +13 -12
  99. data/opal/browser/dom/builder.rb +31 -17
  100. data/opal/browser/dom/document.rb +174 -42
  101. data/opal/browser/dom/document_fragment.rb +18 -0
  102. data/opal/browser/dom/document_or_shadow_root.rb +19 -0
  103. data/opal/browser/dom/element/attributes.rb +111 -0
  104. data/opal/browser/dom/element/button.rb +31 -0
  105. data/opal/browser/dom/element/custom.rb +177 -0
  106. data/opal/browser/dom/element/data.rb +82 -0
  107. data/opal/browser/dom/element/editable.rb +47 -0
  108. data/opal/browser/dom/element/form.rb +38 -0
  109. data/opal/browser/dom/element/iframe.rb +37 -0
  110. data/opal/browser/dom/element/image.rb +2 -0
  111. data/opal/browser/dom/element/input.rb +48 -1
  112. data/opal/browser/dom/element/media.rb +17 -0
  113. data/opal/browser/dom/element/offset.rb +5 -0
  114. data/opal/browser/dom/element/position.rb +11 -2
  115. data/opal/browser/dom/element/scroll.rb +123 -24
  116. data/opal/browser/dom/element/select.rb +42 -0
  117. data/opal/browser/dom/element/size.rb +17 -0
  118. data/opal/browser/dom/element/template.rb +11 -0
  119. data/opal/browser/dom/element/textarea.rb +26 -0
  120. data/opal/browser/dom/element.rb +468 -238
  121. data/opal/browser/dom/mutation_observer.rb +4 -4
  122. data/opal/browser/dom/node.rb +142 -60
  123. data/opal/browser/dom/node_set.rb +73 -44
  124. data/opal/browser/dom/shadow_root.rb +12 -0
  125. data/opal/browser/dom/text.rb +2 -2
  126. data/opal/browser/dom.rb +40 -16
  127. data/opal/browser/effects.rb +180 -3
  128. data/opal/browser/event/all.rb +26 -0
  129. data/opal/browser/{dom/event → event}/animation.rb +4 -2
  130. data/opal/browser/{dom/event → event}/audio_processing.rb +4 -2
  131. data/opal/browser/{dom/event → event}/base.rb +98 -9
  132. data/opal/browser/{dom/event → event}/before_unload.rb +4 -2
  133. data/opal/browser/{dom/event → event}/clipboard.rb +11 -2
  134. data/opal/browser/{dom/event → event}/close.rb +4 -2
  135. data/opal/browser/{dom/event → event}/composition.rb +4 -2
  136. data/opal/browser/{dom/event → event}/custom.rb +3 -3
  137. data/opal/browser/event/data_transfer.rb +95 -0
  138. data/opal/browser/{dom/event → event}/device_light.rb +4 -2
  139. data/opal/browser/{dom/event → event}/device_motion.rb +4 -2
  140. data/opal/browser/{dom/event → event}/device_orientation.rb +4 -2
  141. data/opal/browser/{dom/event → event}/device_proximity.rb +4 -2
  142. data/opal/browser/{dom/event → event}/drag.rb +11 -7
  143. data/opal/browser/{dom/event → event}/focus.rb +4 -2
  144. data/opal/browser/{dom/event → event}/gamepad.rb +5 -3
  145. data/opal/browser/{dom/event → event}/hash_change.rb +4 -2
  146. data/opal/browser/{dom/event → event}/keyboard.rb +16 -3
  147. data/opal/browser/{dom/event → event}/message.rb +4 -2
  148. data/opal/browser/{dom/event → event}/mouse.rb +12 -8
  149. data/opal/browser/{dom/event → event}/page_transition.rb +4 -2
  150. data/opal/browser/{dom/event → event}/pop_state.rb +4 -2
  151. data/opal/browser/{dom/event → event}/progress.rb +4 -2
  152. data/opal/browser/{dom/event → event}/sensor.rb +4 -2
  153. data/opal/browser/{dom/event → event}/storage.rb +4 -2
  154. data/opal/browser/{dom/event → event}/touch.rb +4 -2
  155. data/opal/browser/{dom/event → event}/ui.rb +2 -2
  156. data/opal/browser/{dom/event → event}/wheel.rb +4 -2
  157. data/opal/browser/event.rb +163 -0
  158. data/opal/browser/event_source.rb +2 -2
  159. data/opal/browser/form_data.rb +225 -0
  160. data/opal/browser/history.rb +4 -8
  161. data/opal/browser/http/binary.rb +1 -0
  162. data/opal/browser/http/headers.rb +16 -2
  163. data/opal/browser/http/request.rb +46 -48
  164. data/opal/browser/http/response.rb +5 -1
  165. data/opal/browser/http.rb +25 -2
  166. data/opal/browser/immediate.rb +9 -5
  167. data/opal/browser/interval.rb +34 -11
  168. data/opal/browser/location.rb +7 -1
  169. data/opal/browser/navigator.rb +127 -7
  170. data/opal/browser/polyfill/visual_viewport.rb +216 -0
  171. data/opal/browser/screen.rb +3 -3
  172. data/opal/browser/setup/base.rb +6 -0
  173. data/opal/browser/setup/full.rb +13 -0
  174. data/opal/browser/setup/large.rb +17 -0
  175. data/opal/browser/setup/mini.rb +8 -0
  176. data/opal/browser/setup/traditional.rb +10 -0
  177. data/opal/browser/socket.rb +8 -4
  178. data/opal/browser/storage.rb +53 -35
  179. data/opal/browser/support.rb +72 -5
  180. data/opal/browser/utils.rb +94 -14
  181. data/opal/browser/version.rb +1 -1
  182. data/opal/browser/visual_viewport.rb +39 -0
  183. data/opal/browser/window/size.rb +31 -3
  184. data/opal/browser/window/view.rb +15 -0
  185. data/opal/browser/window.rb +46 -25
  186. data/opal/browser.rb +1 -10
  187. data/opal/opal-browser.rb +1 -0
  188. data/opal-browser.gemspec +3 -3
  189. data/spec/database/sql_spec.rb +139 -0
  190. data/spec/delay_spec.rb +41 -0
  191. data/spec/dom/attribute_spec.rb +49 -0
  192. data/spec/dom/builder_spec.rb +25 -8
  193. data/spec/dom/document_spec.rb +22 -0
  194. data/spec/dom/element/attributes_spec.rb +52 -0
  195. data/spec/dom/element/custom_spec.rb +106 -0
  196. data/spec/dom/element/subclass_spec.rb +144 -0
  197. data/spec/dom/element_spec.rb +181 -4
  198. data/spec/dom/mutation_observer_spec.rb +12 -8
  199. data/spec/dom/node_set_spec.rb +44 -0
  200. data/spec/dom/node_spec.rb +48 -0
  201. data/spec/dom_spec.rb +8 -0
  202. data/spec/event_source_spec.rb +15 -12
  203. data/spec/{dom/event_spec.rb → event_spec.rb} +44 -15
  204. data/spec/history_spec.rb +23 -19
  205. data/spec/http_spec.rb +19 -31
  206. data/spec/immediate_spec.rb +5 -4
  207. data/spec/interval_spec.rb +59 -0
  208. data/spec/native_cached_wrapper_spec.rb +46 -0
  209. data/spec/runner.rb +62 -69
  210. data/spec/socket_spec.rb +16 -12
  211. data/spec/spec_helper.rb +2 -5
  212. data/spec/spec_helper_promise.rb.erb +25 -0
  213. data/spec/storage_spec.rb +1 -1
  214. metadata +172 -50
  215. data/.travis.yml +0 -60
  216. data/opal/browser/dom/event.rb +0 -253
  217. data/opal/browser/http/parameters.rb +0 -8
  218. data/opal/browser/window/scroll.rb +0 -59
@@ -0,0 +1,225 @@
1
+ module Browser
2
+
3
+ class FormData
4
+ include NativeCachedWrapper
5
+
6
+ module Converter
7
+ # Encode as URI component.
8
+ #
9
+ # @return [String] the string encoded for usage as URI component
10
+ def encode(string)
11
+ `encodeURIComponent(#{string})`
12
+ end
13
+
14
+ # Decode as URI component.
15
+ #
16
+ # @return [String] the string decoded as URI component
17
+ def decode(string)
18
+ `decodeURIComponent(#{string})`
19
+ end
20
+
21
+ # Encode as URI.
22
+ #
23
+ # @return [String] the string encoded as URI
24
+ def encode_uri(string)
25
+ `encodeURI(#{string})`
26
+ end
27
+
28
+ # Decode as URI.
29
+ #
30
+ # @return [String] the string decoded as URI
31
+ def decode_uri(string)
32
+ `decodeURI(#{string})`
33
+ end
34
+
35
+ # Flattens a hash to build a flat array, later to be formatted to
36
+ # produce a nested query.
37
+ #
38
+ # This code should be compatible with what Rack::Utils#build_nested_query [1]
39
+ # does.
40
+ #
41
+ # [1] https://github.com/rack/rack/blob/master/lib/rack/utils.rb
42
+ def flatten(value, key="")
43
+ case value
44
+ when Hash
45
+ out = []
46
+ value.each do |k,v|
47
+ k = "#{key}[#{k}]" if key != ''
48
+ out += flatten(v,k)
49
+ end
50
+ out
51
+ when Array
52
+ out = []
53
+ value.each do |v|
54
+ k = "#{key}[]"
55
+ out += flatten(v,k)
56
+ end
57
+ out
58
+ else
59
+ [[key,value]]
60
+ end
61
+ end
62
+
63
+ # Converts a flat array to a Hash.
64
+ #
65
+ # This code should be compatible with what Rack::Utils#parse_nested_query [1]
66
+ # does.
67
+ #
68
+ # [1] https://github.com/rack/rack/blob/master/lib/rack/utils.rb
69
+ def unflatten(array)
70
+ out = {}
71
+ array.each do |k,v|
72
+ path = [k.split("[").first] + k.scan(/\[(.*?)\]/).flatten
73
+ c = out
74
+
75
+ set = proc { |v,weak| } # Do nothing for the first level
76
+
77
+ path.each do |i|
78
+ case i
79
+ when "" # Array
80
+ set.([], true)
81
+ set = proc do |v,weak|
82
+ c << v
83
+ c = c.last
84
+ end
85
+ else # Hash
86
+ set.({}, true)
87
+ set = proc do |v,weak|
88
+ c[i] ||= v
89
+ c[i] = v if !weak
90
+ c = c[i]
91
+ end
92
+ end
93
+ end
94
+ set.(v, false)
95
+
96
+ end
97
+ out
98
+ end
99
+
100
+ # Checks if a query Hash contains any files.
101
+ def contain_files?(hash)
102
+ flatten(hash).any? { |k,v| [File, Blob].include?(v.class) }
103
+ end
104
+
105
+ # Convert a query Hash to a query string
106
+ #
107
+ # @return [String] the string encoded as URI
108
+ def build_query(hash, sep=?&)
109
+ flatten(hash).map { |k,v| encode(k) + ?= + encode(v.to_s) }.join(sep)
110
+ end
111
+
112
+ # Convert a query Hash to a FormData instance
113
+ #
114
+ # @return [FormData] the instance of FormData
115
+ def build_form_data(hash)
116
+ fd = FormData.create
117
+ flatten(hash).each { |k,v| fd << [k,v] }
118
+ fd
119
+ end
120
+
121
+ # Convert a query string to a query Hash
122
+ #
123
+ # @return [Hash] the query hash
124
+ def parse_query(string, sep=?&)
125
+ unflatten(string.split(sep).map { |s| s.split(?=).map(&method(:decode)) })
126
+ end
127
+
128
+ # Converts a JS native value to a wrapped one if possible.
129
+ #
130
+ # @return [String, File, Blob]
131
+ def from_native(n)
132
+ %x{
133
+ var c = #{n}.constructor;
134
+ if (c === File) {
135
+ #{n = File.new(n)}
136
+ }
137
+ else if (c === Blob) {
138
+ #{n = Blob.new(n)}
139
+ }
140
+ }
141
+ n
142
+ end
143
+ end
144
+
145
+ extend Converter
146
+ include Enumerable
147
+
148
+ # Create a new FormData instance
149
+ def self.create(hash=nil)
150
+ if Hash === hash
151
+ FormData.build_form_data(hash)
152
+ elsif DOM::Element::Form === hash
153
+ new(`new FormData(#{hash.to_n})`)
154
+ else
155
+ new(`new FormData()`)
156
+ end
157
+ end
158
+
159
+ # Append a tuple to this FormData instance
160
+ #
161
+ # @param tuple [Array(String, String), Array(String, Blob), Array(String, File),
162
+ # Array(String, Blob, String), Array(String, File, String)]
163
+ # a tuple of a key, value and possibly a filename
164
+ def <<(tuple)
165
+ key, value, filename = tuple
166
+
167
+ unless filename
168
+ `#@native.append(#{key}, #{Native.convert(value)})`
169
+ else
170
+ `#@native.append(#{key}, #{Native.convert(value)}, #{filename})`
171
+ end
172
+ end
173
+
174
+ # Get a field from this FormData instance with a given name
175
+ def [](key)
176
+ FormData.from_native(`#@native.get(#{key})`)
177
+ end
178
+
179
+ # Set a field in this FormData instance with a given name
180
+ def set(key, value, filename = nil)
181
+ unless filename
182
+ `#@native.set(#{key}, #{Native.convert(value)})`
183
+ else
184
+ `#@native.set(#{key}, #{Native.convert(value)}, #{filename})`
185
+ end
186
+ end
187
+ alias []= set
188
+
189
+ # Convert to hash
190
+ def to_h
191
+ hash = {}
192
+ %x{
193
+ var pair, v, e = #@native.entries();
194
+ while (true) {
195
+ v = e.next();
196
+ if (v.done) break;
197
+ pair = v.value;
198
+ #{hash[`pair[0]`] = FormData.from_native(`pair[1]`)}
199
+ }
200
+ }
201
+ hash
202
+ end
203
+
204
+ # Convert to array
205
+ def to_a
206
+ to_h.to_a
207
+ end
208
+
209
+ # Iterate over all elements of this FormData
210
+ def each(&block)
211
+ to_h.each(&block)
212
+ end
213
+
214
+ # Checks if a field of this name exists in this FormData instance
215
+ def include?(key)
216
+ `#@native.has(#{key})`
217
+ end
218
+
219
+ # Delete a field from this FormData instance
220
+ def delete(key)
221
+ `#@native.delete(#{key})`
222
+ end
223
+ end
224
+
225
+ end
@@ -11,7 +11,7 @@ class History
11
11
  Browser.supports? 'History'
12
12
  end
13
13
 
14
- include Native
14
+ include Browser::NativeCachedWrapper
15
15
 
16
16
  # @!attribute [r] length
17
17
  # @return [Integer] how many items are in the history
@@ -36,9 +36,7 @@ class History
36
36
  # @param item [String] the item to push in the history
37
37
  # @param data [Object] additional state to push
38
38
  def push(item, data = nil)
39
- data = `null` if data.nil?
40
-
41
- `#@native.pushState(data, null, item)`
39
+ `#@native.pushState(#{data.to_n}, null, item)`
42
40
  end
43
41
 
44
42
  # Replace the current history item with another.
@@ -46,15 +44,13 @@ class History
46
44
  # @param item [String] the item to replace with
47
45
  # @param data [Object] additional state to replace
48
46
  def replace(item, data = nil)
49
- data = `null` if data.nil?
50
-
51
- `#@native.replaceState(data, null, item)`
47
+ `#@native.replaceState(#{data.to_n}, null, item)`
52
48
  end
53
49
 
54
50
  # @!attribute [r] current
55
51
  # @return [String] the current item
56
52
  def current
57
- $window.location.path
53
+ $window.location.full_path
58
54
  end
59
55
 
60
56
  # @!attribute [r] state
@@ -24,6 +24,7 @@ class Binary
24
24
  # Iterate over each byte in the binary.
25
25
  #
26
26
  # @yield [byte] the byte
27
+ #
27
28
  # @return [self]
28
29
  def each(&block)
29
30
  return enum_for :each unless block
@@ -14,6 +14,8 @@ class Headers
14
14
  end
15
15
 
16
16
  # Create {Headers} from a hash.
17
+ #
18
+ # @param hash [Hash]
17
19
  def self.[](hash)
18
20
  result = new
19
21
 
@@ -36,9 +38,11 @@ class Headers
36
38
  @hash.clear
37
39
  end
38
40
 
39
- # Iterate over the headers.
41
+ # Enumerate over the headers.
42
+ #
43
+ # @yieldparam name [String] the name of the header
44
+ # @yieldparam value [String] the value of the header
40
45
  #
41
- # @yield [name, value] the header name and value
42
46
  # @return [self]
43
47
  def each(&block)
44
48
  return enum_for :each unless block
@@ -51,10 +55,18 @@ class Headers
51
55
  end
52
56
 
53
57
  # Get the value of a header.
58
+ #
59
+ # @param name [String] the name of the header
60
+ #
61
+ # @return [String] the value of the header
54
62
  def [](name)
55
63
  @hash[name.downcase]
56
64
  end
57
65
 
66
+ # Set a value for the header.
67
+ #
68
+ # @param name [String] the name of the header
69
+ # @param value [String] the value of the header
58
70
  def []=(name, value)
59
71
  header = Header.new(name, value)
60
72
 
@@ -64,6 +76,7 @@ class Headers
64
76
  # Push a header.
65
77
  #
66
78
  # @param header [Header] the header to push
79
+ #
67
80
  # @return [self]
68
81
  def <<(header)
69
82
  @hash[header.name.downcase] = header
@@ -76,6 +89,7 @@ class Headers
76
89
  # Merge in place other headers.
77
90
  #
78
91
  # @param other [Headers, Hash, #each] the headers to merge
92
+ #
79
93
  # @return [self]
80
94
  def merge!(other)
81
95
  other.each {|name, value|
@@ -1,25 +1,18 @@
1
1
  module Browser; module HTTP
2
2
 
3
3
  class Request
4
- include Native
4
+ include Native::Wrapper
5
+ include Event::Target
5
6
 
6
- # Open and send a request.
7
- #
8
- # @param method [Symbol] the HTTP method to use
9
- # @param url [String, #to_s] the URL to request
10
- # @param parameters [String, Hash] the parameters to send
11
- def self.open(method, url, parameters = nil, &block)
12
- request = new(&block)
13
- request.open(method, url)
14
- request.send(*parameters)
15
- end
16
-
17
- DEFAULT_HEADERS = {
7
+ # Default headers.
8
+ HEADERS = {
18
9
  'X-Requested-With' => 'XMLHttpRequest',
19
10
  'X-Opal-Version' => RUBY_ENGINE_VERSION,
20
11
  'Accept' => 'text/javascript, text/html, application/xml, text/xml, */*'
21
12
  }
22
13
 
14
+ STATES = %w[uninitialized loading loaded interactive complete]
15
+
23
16
  # @!attribute [r] headers
24
17
  # @return [Headers] the request headers
25
18
  attr_reader :headers
@@ -45,7 +38,7 @@ class Request
45
38
 
46
39
  @parameters = {}
47
40
  @query = {}
48
- @headers = Headers[DEFAULT_HEADERS]
41
+ @headers = Headers[HEADERS]
49
42
  @method = :get
50
43
  @asynchronous = true
51
44
  @binary = false
@@ -104,21 +97,13 @@ class Request
104
97
  end
105
98
 
106
99
  # Make the request asynchronous.
107
- #
108
- # @return [self]
109
100
  def asynchronous!
110
101
  @asynchronous = true
111
-
112
- self
113
102
  end
114
103
 
115
104
  # Make the request synchronous.
116
- #
117
- # @return [self]
118
105
  def synchronous!
119
106
  @asynchronous = false
120
-
121
- self
122
107
  end
123
108
 
124
109
  # Check the request is binary.
@@ -129,8 +114,6 @@ class Request
129
114
  # Make the request binary.
130
115
  def binary!
131
116
  @binary = true
132
-
133
- self
134
117
  end
135
118
 
136
119
  # Check if the request is cacheable.
@@ -139,12 +122,8 @@ class Request
139
122
  end
140
123
 
141
124
  # Disable caching for this request.
142
- #
143
- # @return [self]
144
125
  def no_cache!
145
126
  @cacheable = false
146
-
147
- self
148
127
  end
149
128
 
150
129
  # Get or set the user used for authentication.
@@ -213,13 +192,14 @@ class Request
213
192
  # Register an event on the request.
214
193
  #
215
194
  # @param what [Symbol, String] the event name
216
- # @yield [response] yields the {Response}
217
195
  #
218
- # @return [self]
219
- def on(what, &block)
220
- @callbacks[what] << block
221
-
222
- self
196
+ # @yieldparam response [Response] the response for the event
197
+ def on(what, *, &block)
198
+ if STATES.include?(what) || %w[success failure].include?(what) || Integer === what
199
+ @callbacks[what] << block
200
+ else
201
+ super
202
+ end
223
203
  end
224
204
 
225
205
  # Open the request.
@@ -242,22 +222,27 @@ class Request
242
222
 
243
223
  url = @url
244
224
 
225
+ # add a dummy random parameter to the query to try circumvent caching
245
226
  unless cacheable?
246
227
  @query[:_] = rand
247
228
  end
248
229
 
230
+ # add the encoded query to the @url, prepending the right character if
231
+ # there was already a query in the defined @url or not
249
232
  unless @query.empty?
250
- if url.include???
233
+ if url.include? ??
251
234
  url += ?&
252
235
  else
253
236
  url += ??
254
237
  end
255
238
 
256
- url += @query.encode_uri
239
+ url += FormData.build_query(@query)
257
240
  end
258
241
 
259
242
  `#@native.open(#{@method.to_s.upcase}, #{url.to_s}, #{@asynchronous}, #{@user.to_n}, #{@password.to_n})`
260
243
 
244
+ # if there are no registered callbacks no point in setting the event
245
+ # handler
261
246
  unless @callbacks.empty?
262
247
  `#@native.onreadystatechange = #{callback}`
263
248
  end
@@ -277,6 +262,8 @@ class Request
277
262
 
278
263
  raise 'the request has already been sent' if sent?
279
264
 
265
+ # try to circumvent caching setting an If-Modified-Since header with a very
266
+ # old date
280
267
  unless cacheable?
281
268
  `#@native.setRequestHeader("If-Modified-Since", "Tue, 11 Sep 2001 12:46:00 GMT")`
282
269
  end
@@ -309,14 +296,29 @@ class Request
309
296
 
310
297
  if String === parameters
311
298
  data = parameters
312
- elsif Hash === parameters && !parameters.empty?
313
- data = parameters.map {|vals|
314
- vals.map(&:encode_uri_component).join(?=)
315
- }.join(?&)
299
+ elsif (Hash === parameters && !parameters.empty?) || FormData === parameters
300
+ data = if Hash === parameters
301
+ if FormData.contain_files?(parameters)
302
+ FormData.build_form_data(parameters)
303
+ else
304
+ FormData.build_query(parameters)
305
+ end
306
+ else #if FormData === parameters
307
+ parameters
308
+ end
316
309
 
317
310
  unless @content_type
318
- `#@native.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')`
311
+ if FormData === data
312
+ # I thought it's done this way, but it isn't. It actually is
313
+ # "multipart/form-data; boundary=-----------.......". Let's miss it
314
+ # purposefully, because it's filled in automatically in this example.
315
+ # `#@native.setRequestHeader('Content-Type', 'multipart/form-data')`
316
+ else
317
+ `#@native.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')`
318
+ end
319
319
  end
320
+
321
+ data = data.to_n
320
322
  else
321
323
  data = `null`
322
324
  end
@@ -327,18 +329,14 @@ class Request
327
329
  end
328
330
 
329
331
  # Abort the request.
330
- #
331
- # @return [self]
332
332
  def abort
333
333
  `#@native.abort()`
334
-
335
- self
336
334
  end
337
335
 
338
336
  private
339
337
  def callback
340
- proc {|event|
341
- state = %w[uninitialized loading loaded interactive complete][`#@native.readyState`]
338
+ -> event {
339
+ state = STATES[`#@native.readyState`]
342
340
  res = response
343
341
 
344
342
  @callbacks[state].each { |b| b.(res) }
@@ -354,7 +352,7 @@ private
354
352
  @callbacks[:failure].each { |b| b.(res) }
355
353
  end
356
354
  end
357
- }.to_n
355
+ }
358
356
  end
359
357
  end
360
358
 
@@ -4,7 +4,7 @@ module Browser; module HTTP
4
4
 
5
5
  # Represents an HTTP response.
6
6
  class Response
7
- include Native
7
+ include Native::Wrapper
8
8
 
9
9
  Status = Struct.new(:code, :text)
10
10
 
@@ -47,6 +47,10 @@ class Response
47
47
  !success?
48
48
  end
49
49
 
50
+ # @!attribute [r] url
51
+ # @return [String] the response URL (after redirects)
52
+ alias_native :url, :responseURL
53
+
50
54
  # @!attribute [r] text
51
55
  # @return [String] the response body as text
52
56
  def text
data/opal/browser/http.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'promise'
2
-
3
1
  require 'browser/http/binary'
4
2
  require 'browser/http/headers'
5
3
  require 'browser/http/request'
@@ -8,6 +6,7 @@ require 'browser/http/response'
8
6
  module Browser
9
7
 
10
8
  module HTTP
9
+ # Check if HTTP requests are supported.
11
10
  def self.supported?
12
11
  Browser.supports?('XHR') || Browser.supports?('ActiveXObject')
13
12
  end
@@ -18,6 +17,8 @@ module HTTP
18
17
  # @param url [String] the URL to request
19
18
  # @param data [String, Hash] the data to send
20
19
  #
20
+ # @yieldparam request [Request] the request to configure
21
+ #
21
22
  # @return [Promise] a promise that will be resolved with the response
22
23
  def self.send(method, url, data = nil, &block)
23
24
  Promise.new.tap {|promise|
@@ -37,6 +38,8 @@ module HTTP
37
38
  #
38
39
  # @param url [String] the URL to request
39
40
  #
41
+ # @yieldparam request [Request] the request to configure
42
+ #
40
43
  # @return [Promise] a promise that will be resolved with the response
41
44
  def self.get(url, &block)
42
45
  send(:get, url, &block)
@@ -46,6 +49,8 @@ module HTTP
46
49
  #
47
50
  # @param url [String] the URL to request
48
51
  #
52
+ # @yieldparam request [Request] the request to configure
53
+ #
49
54
  # @return [Promise] a promise that will be resolved with the response
50
55
  def self.head(url, &block)
51
56
  send(:head, url, &block)
@@ -56,6 +61,8 @@ module HTTP
56
61
  # @param url [String] the URL to request
57
62
  # @param data [String, Hash] the data to send
58
63
  #
64
+ # @yieldparam request [Request] the request to configure
65
+ #
59
66
  # @return [Promise] a promise that will be resolved with the response
60
67
  def self.post(url, data = nil, &block)
61
68
  send(:post, url, data, &block)
@@ -66,6 +73,8 @@ module HTTP
66
73
  # @param url [String] the URL to request
67
74
  # @param data [String, Hash] the data to send
68
75
  #
76
+ # @yieldparam request [Request] the request to configure
77
+ #
69
78
  # @return [Promise] a promise that will be resolved with the response
70
79
  def self.put(url, data = nil, &block)
71
80
  send(:put, url, data, &block)
@@ -76,6 +85,8 @@ module HTTP
76
85
  # @param url [String] the URL to request
77
86
  # @param data [String, Hash] the data to send
78
87
  #
88
+ # @yieldparam request [Request] the request to configure
89
+ #
79
90
  # @return [Promise] a promise that will be resolved with the response
80
91
  def self.delete(url, data = nil, &block)
81
92
  send(:delete, url, data, &block)
@@ -87,6 +98,8 @@ module HTTP
87
98
  # @param url [String] the URL to request
88
99
  # @param data [String, Hash] the data to send
89
100
  #
101
+ # @yieldparam request [Request] the request to configure
102
+ #
90
103
  # @return [Response] the response
91
104
  def self.send!(method, url, data = nil, &block)
92
105
  Request.new(&block).open(method, url, false).send(data)
@@ -96,6 +109,8 @@ module HTTP
96
109
  #
97
110
  # @param url [String] the URL to request
98
111
  #
112
+ # @yieldparam request [Request] the request to configure
113
+ #
99
114
  # @return [Response] the response
100
115
  def self.get!(url, &block)
101
116
  send!(:get, url, &block)
@@ -105,6 +120,8 @@ module HTTP
105
120
  #
106
121
  # @param url [String] the URL to request
107
122
  #
123
+ # @yieldparam request [Request] the request to configure
124
+ #
108
125
  # @return [Response] the response
109
126
  def self.head!(url, &block)
110
127
  send!(:head, url, &block)
@@ -115,6 +132,8 @@ module HTTP
115
132
  # @param url [String] the URL to request
116
133
  # @param data [String, Hash] the data to send
117
134
  #
135
+ # @yieldparam request [Request] the request to configure
136
+ #
118
137
  # @return [Response] the response
119
138
  def self.post!(url, data = nil, &block)
120
139
  send!(:post, url, data, &block)
@@ -125,6 +144,8 @@ module HTTP
125
144
  # @param url [String] the URL to request
126
145
  # @param data [String, Hash] the data to send
127
146
  #
147
+ # @yieldparam request [Request] the request to configure
148
+ #
128
149
  # @return [Response] the response
129
150
  def self.put!(url, data = nil, &block)
130
151
  send!(:put, url, data, &block)
@@ -135,6 +156,8 @@ module HTTP
135
156
  # @param url [String] the URL to request
136
157
  # @param data [String, Hash] the data to send
137
158
  #
159
+ # @yieldparam request [Request] the request to configure
160
+ #
138
161
  # @return [Response] the response
139
162
  def self.delete!(url, data = nil, &block)
140
163
  send!(:delete, url, data, &block)
@@ -1,5 +1,3 @@
1
- require 'promise'
2
-
3
1
  module Browser
4
2
 
5
3
  # Class to easily create and dispatch an immediate call.
@@ -37,7 +35,6 @@ class Immediate
37
35
 
38
36
  # @!method prevent
39
37
  # Prevent the immediate from being called once scheduled.
40
-
41
38
  if Browser.supports? 'Immediate'
42
39
  def dispatch
43
40
  @id = `window.setImmediate(function() {
@@ -58,7 +55,7 @@ class Immediate
58
55
  def prevent
59
56
  `window.msClearImmediate(#@id)`
60
57
  end
61
- elsif Browser.supports? 'Window.send'
58
+ elsif Browser.supports? 'Window.send (Asynchronous)'
62
59
  # @private
63
60
  @@tasks = {}
64
61
 
@@ -77,7 +74,7 @@ class Immediate
77
74
  @id = rand(1_000_000).to_s
78
75
  @@tasks[@id] = [@function, @arguments, @block]
79
76
 
80
- $window.send! "#{@@prefix}#{@id}"
77
+ $window.send "#{@@prefix}#{@id}"
81
78
  end
82
79
 
83
80
  def prevent
@@ -132,6 +129,13 @@ end
132
129
 
133
130
  end
134
131
 
132
+ module Kernel
133
+ # (see Immediate.new)
134
+ def defer(*args, &block)
135
+ Browser::Immediate.new(block, args).tap(&:dispatch)
136
+ end
137
+ end
138
+
135
139
  class Proc
136
140
  # (see Immediate.new)
137
141
  def defer(*args, &block)