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
@@ -27,8 +27,6 @@ class Storage
27
27
  }]
28
28
  end
29
29
 
30
- include Enumerable
31
-
32
30
  # @!attribute [r] name
33
31
  # @return [String] the name of the storage
34
32
  attr_reader :name
@@ -45,13 +43,7 @@ class Storage
45
43
  @data = {}
46
44
 
47
45
  autosave!
48
- init
49
- end
50
-
51
- # @!attribute [r] encoded_name
52
- # @return [String] the generated name
53
- def encoded_name
54
- "$opal.storage.#@name"
46
+ reload
55
47
  end
56
48
 
57
49
  # Check if autosaving is enabled.
@@ -72,6 +64,8 @@ class Storage
72
64
  @autosave = false
73
65
  end
74
66
 
67
+ include Enumerable
68
+
75
69
  # Iterate over the (key, value) pairs in the storage.
76
70
  #
77
71
  # @yield [key, value]
@@ -83,9 +77,8 @@ class Storage
83
77
  self
84
78
  end
85
79
 
86
- # Get a value in the storage.
87
- def [](key)
88
- @data[key]
80
+ def method_missing(*args, &block)
81
+ @data.__send__(*args, &block)
89
82
  end
90
83
 
91
84
  # Set a value in the storage.
@@ -98,7 +91,7 @@ class Storage
98
91
  # Delete a value from the storage.
99
92
  def delete(key)
100
93
  @data.delete(key).tap {
101
- save if autosave
94
+ save if autosave?
102
95
  }
103
96
  end
104
97
 
@@ -120,57 +113,82 @@ class Storage
120
113
  end
121
114
  end
122
115
 
123
- # @!method init
124
- # @private
116
+ # Call the block between a [#reload] and [#save].
117
+ def commit(&block)
118
+ autosave = @autosave
119
+ @autosave = false
120
+ result = nil
121
+
122
+ reload
123
+
124
+ begin
125
+ result = block.call
126
+ save
127
+ rescue
128
+ reload
129
+ raise
130
+ ensure
131
+ @autosave = autosave
132
+ end
133
+
134
+ result
135
+ end
136
+
137
+ def to_h
138
+ @data
139
+ end
140
+
141
+ # @!method reload
142
+ # Load the storage.
125
143
 
126
144
  # @!method save
127
145
  # Persist the current state to the storage.
128
146
 
129
147
  if Browser.supports? 'Storage.local'
130
- def init
131
- replace `#@window.localStorage[#{encoded_name}] || '{}'`
148
+ def reload
149
+ replace `#@window.localStorage[#@name] || '{}'`
132
150
  end
133
151
 
134
152
  def save
135
- `#@window.localStorage[#{encoded_name}] = #{JSON.dump(self)}`
153
+ `#@window.localStorage[#@name] = #{JSON.dump(self)}`
136
154
  end
137
155
  elsif Browser.supports? 'Storage.global'
138
- def init
139
- replace `#@window.globalStorage[#@window.location.hostname][#{encoded_name}] || '{}'`
156
+ def reload
157
+ replace `#@window.globalStorage[#@window.location.hostname][#@name] || '{}'`
140
158
  end
141
159
 
142
160
  def save
143
- `#@window.globalStorage[#@window.location.hostname][#{encoded_name}] = #{JSON.dump(self)}`
161
+ `#@window.globalStorage[#@window.location.hostname][#@name] = #{JSON.dump(self)}`
144
162
  end
145
163
  elsif Browser.supports? 'Element.addBehavior'
146
- def init
164
+ def reload
147
165
  %x{
148
166
  #@element = #@window.document.createElement('link');
149
167
  #@element.addBehavior('#default#userData');
150
168
 
151
169
  #@window.document.getElementsByTagName('head')[0].appendChild(#@element);
152
170
 
153
- #@element.load(#{encoded_name});
171
+ #@element.load(#@name);
154
172
  }
155
173
 
156
- replace `#@element.getAttribute(#{encoded_name}) || '{}'`
174
+ replace `#@element.getAttribute(#@name) || '{}'`
157
175
  end
158
176
 
159
177
  def save
160
178
  %x{
161
- #@element.setAttribute(#{encoded_name}, #{JSON.dump(self)});
162
- #@element.save(#{encoded_name});
179
+ #@element.setAttribute(#@name, #{JSON.dump(self)});
180
+ #@element.save(#@name);
163
181
  }
164
182
  end
165
183
  else
166
- def init
184
+ def reload
167
185
  $document.cookies.options expires: 60 * 60 * 24 * 365
168
186
 
169
- replace $document.cookies[encoded_name]
187
+ replace $document.cookies[@name]
170
188
  end
171
189
 
172
190
  def save
173
- $document.cookies[encoded_name] = self
191
+ $document.cookies[@name] = JSON.dump(self)
174
192
  end
175
193
  end
176
194
 
@@ -178,12 +196,12 @@ class Storage
178
196
  #
179
197
  # @return [String] the JSON representation
180
198
  def to_json
181
- io = StringIO.new("{")
199
+ io = StringIO.new << "{"
182
200
 
183
201
  io << JSON.create_id.to_json << ":" << self.class.name.to_json << ","
184
202
 
185
- each {|key, value|
186
- io << key.to_json.to_json << ":" << value.to_json << ","
203
+ @data.each {|key, value|
204
+ io << key.to_json.to_s << ":" << value.to_json << ","
187
205
  }
188
206
 
189
207
  io.seek(-1, IO::SEEK_CUR)
@@ -202,12 +220,12 @@ class SessionStorage < Storage
202
220
  Browser.supports? 'Storage.session'
203
221
  end
204
222
 
205
- def init
206
- replace `#@window.sessionStorage[#{encoded_name}] || '{}'`
223
+ def reload
224
+ replace `#@window.sessionStorage[#@name] || '{}'`
207
225
  end
208
226
 
209
227
  def save
210
- `#@window.sessionStorage[#{encoded_name}] = #{JSON.dump(self)}`
228
+ `#@window.sessionStorage[#@name] = #{JSON.dump(self)}`
211
229
  end
212
230
  end
213
231
 
@@ -1,9 +1,14 @@
1
+ # The engine the browser is running on.
2
+ #
3
+ # Keep in mind it uses the user agent to know, so it's not reliable in case of
4
+ # spoofing.
1
5
  BROWSER_ENGINE = `/MSIE|WebKit|Presto|Gecko/.exec(navigator.userAgent)[0]`.downcase rescue :unknown
2
6
 
3
7
  module Browser
4
8
  # @private
5
9
  @support = `{}`
6
10
 
11
+ # Check if the browser supports the given feature.
7
12
  def self.supports?(feature)
8
13
  if defined?(`#@support[#{feature}]`)
9
14
  return `#@support[#{feature}]`
@@ -25,8 +30,11 @@ module Browser
25
30
  when 'ActiveX'
26
31
  defined?(`window.ActiveXObject`)
27
32
 
33
+ when 'WebSQL'
34
+ defined?(`window.openDatabase`)
35
+
28
36
  when 'Query.css'
29
- defined?(`Element.prototype.querySelectorAll`)
37
+ defined?(`document.querySelectorAll`)
30
38
 
31
39
  when 'Query.xpath'
32
40
  defined?(`document.evaluate`)
@@ -62,6 +70,9 @@ module Browser
62
70
  defined?(`document.documentElement.currentStyle`)
63
71
 
64
72
  when 'Window.send'
73
+ defined?(`window.postMessage`)
74
+
75
+ when 'Window.send (Asynchronous)'
65
76
  if defined?(`window.postMessage`) && !defined?(`window.importScripts`)
66
77
  %x{
67
78
  var ok = true,
@@ -70,11 +81,14 @@ module Browser
70
81
  window.onmessage = function() { ok = false; };
71
82
  window.postMessage("", "*")
72
83
  window.onmessage = old;
73
- }
74
84
 
75
- `ok`
85
+ return ok;
86
+ }
76
87
  end
77
88
 
89
+ when 'Window.send (Synchronous)'
90
+ !supports?('Window.send (Asynchronous)')
91
+
78
92
  when 'Window.innerSize'
79
93
  defined?(`window.innerHeight`)
80
94
 
@@ -84,11 +98,54 @@ module Browser
84
98
  when 'Window.scroll'
85
99
  defined?(`document.documentElement.scrollLeft`)
86
100
 
101
+ when 'Window.scrollBy'
102
+ defined?(`document.documentElement.scrollBy`)
103
+
87
104
  when 'Window.pageOffset'
88
105
  defined?(`window.pageXOffset`)
89
106
 
107
+ when 'Attr.isId'
108
+ %x{
109
+ var div = document.createElement('div');
110
+ div.setAttribute('id', 'xxxxxxxxxxxxx');
111
+
112
+ return typeof(div.attributes['id'].isId) !== "undefined";
113
+ }
114
+
90
115
  when 'Element.addBehavior'
91
- defined?(`document.body.addBehavior`)
116
+ defined?(`document.documentElement.addBehavior`)
117
+
118
+ when 'Element.className'
119
+ %x{
120
+ var div = document.createElement("div");
121
+ div.setAttribute('className', 'x');
122
+
123
+ return div.className === 'x';
124
+ }
125
+
126
+ when 'Element.class'
127
+ %x{
128
+ var div = document.createElement("div");
129
+ div.setAttribute('class', 'x');
130
+
131
+ return div.className === 'x';
132
+ }
133
+
134
+ when 'Element.for'
135
+ %x{
136
+ var label = document.createElement("label");
137
+ label.setAttribute('for', 'x');
138
+
139
+ return label.htmlFor === 'x';
140
+ }
141
+
142
+ when 'Element.htmlFor'
143
+ %x{
144
+ var label = document.createElement("label");
145
+ label.setAttribute('htmlFor', 'x');
146
+
147
+ return label.htmlFor === 'x';
148
+ }
92
149
 
93
150
  when 'Element.clientSize'
94
151
  defined?(`document.documentElement.clientHeight`)
@@ -128,7 +185,7 @@ module Browser
128
185
  `new MouseEvent("click")`
129
186
 
130
187
  true
131
- rescue
188
+ rescue StandardError, JS::Error
132
189
  false
133
190
  end
134
191
 
@@ -215,11 +272,21 @@ module Browser
215
272
 
216
273
  when 'Animation.cancelRequest (Chrome)', 'Animation.cancelRequest (Safari)'
217
274
  defined?(`window.webkitCancelRequestAnimationFrame`)
275
+
276
+ when 'Audio'
277
+ defined?(`window.AudioContext`)
278
+
279
+ when 'Audio (Safari)', 'Audio (Chrome)'
280
+ defined?(`window.webkitAudioContext`)
281
+
282
+ when 'Custom Elements'
283
+ defined?(`window.customElements`)
218
284
  end
219
285
 
220
286
  `#@support[#{feature}] = #{support}`
221
287
  end
222
288
 
289
+ # Check if the given polyfill is loaded.
223
290
  def self.loaded?(name)
224
291
  case name
225
292
  when 'Sizzle'
@@ -1,51 +1,133 @@
1
1
  module Browser
2
+ Promise = defined?(PromiseV2) ? PromiseV2 : ::Promise
3
+
2
4
  Size = Struct.new(:width, :height)
3
5
  Position = Struct.new(:x, :y)
6
+
7
+ # {Browser::NativeCachedWrapper} is a special case of {Native::Wrapper}.
8
+ #
9
+ # What this module does is it makes sure that your Ruby objects
10
+ # are mapped 1:1 to your Javascript objects. So that for instance
11
+ # your `$document.at_css('body')` is always the same Ruby object.
12
+ #
13
+ # You can only use it if your final `.new` is of the signature
14
+ # `.new(native)` and your native (probably DOM) object persists and
15
+ # doesn't mind arbitrary properties.
16
+ #
17
+ # The rule of thumb is: if it does overload `#initialize`'s signature
18
+ # and not `.new`'s - it won't work. Use {Native::Wrapper} in this case.
19
+ module NativeCachedWrapper
20
+ def self.included(klass)
21
+ klass.include Native::Wrapper
22
+ klass.extend NativeCachedWrapperClassMethods
23
+ end
24
+
25
+ def restricted?
26
+ !!@restricted
27
+ end
28
+
29
+ # Change a native reference and make sure the change is reflected on JS
30
+ # side as well. This method is used by Node#initialize_copy. Please don't
31
+ # use this method outside of the cloning semantic.
32
+ def set_native_reference(native)
33
+ `#{native}.$$opal_native_cached = #{self}`
34
+ @native = native
35
+ end
36
+ end
37
+
38
+ module NativeCachedWrapperClassMethods
39
+ # Check if we don't have access to arbitrary properties of a (presumably)
40
+ # native object.
41
+ private def restricted?(native)
42
+ %x{
43
+ try {
44
+ typeof(#{native}.$$try_restricted_access);
45
+ } catch (e) {
46
+ if (e.name == 'SecurityError') return true;
47
+ }
48
+ return false;
49
+ }
50
+ end
51
+
52
+ def new(native)
53
+ # We can't access arbitrary properties if an element is restricted
54
+ # i.e. the DOM element is an item we can't fully access due to CORS.
55
+ if restricted?(native)
56
+ # Let's try to bypass any further initializers... may be ugly, but
57
+ # works.
58
+ obj = allocate
59
+ obj.instance_variable_set :@native, native
60
+ obj.instance_variable_set :@restricted, true
61
+ return obj
62
+ end
63
+
64
+ # It's not a native element? Weird, better throw an exception.
65
+ raise ArgumentError if !native?(native)
66
+
67
+ if defined? `#{native}.$$opal_native_cached`
68
+ `#{native}.$$opal_native_cached`
69
+ else
70
+ `#{native}.$$opal_native_cached = #{super(native)}`
71
+ end
72
+ end
73
+ end
4
74
  end
5
75
 
6
76
  class Object
7
77
  # Encode as URI.
8
78
  #
79
+ # @deprecated Please use FormData.encode_uri
9
80
  # @return [String] the {Object#to_s} encoded for usage as URI
10
81
  def encode_uri
11
- to_s.encode_uri
82
+ warn "opal-browser: Object#encode_uri is deprecated. Please use FormData.encode_uri"
83
+ FormData.encode_uri(to_s)
12
84
  end
13
85
 
14
86
  # Encode as URI component.
15
87
  #
88
+ # @deprecated Please use FormData.encode
16
89
  # @return [String] the {Object#to_s} encoded for usage as URI component
17
90
  def encode_uri_component
18
- to_s.encode_uri_component
91
+ warn "opal-browser: Object#encode_uri_component is deprecated. Please use FormData.encode"
92
+ FormData.encode(to_s)
19
93
  end
20
94
  end
21
95
 
22
96
  class String
23
97
  # Encode as URI component.
24
98
  #
99
+ # @deprecated Please use FormData.encode
25
100
  # @return [String] the string encoded for usage as URI component
26
101
  def encode_uri_component
27
- `encodeURIComponent(self)`
102
+ warn "opal-browser: String#encode_uri_component is deprecated. Please use FormData.encode"
103
+ FormData.encode(self)
28
104
  end
29
105
 
30
106
  # Encode as URI.
31
107
  #
108
+ # @deprecated Please use FormData.encode_uri
32
109
  # @return [String] the string encoded as URI
33
110
  def encode_uri
34
- `encodeURI(self)`
111
+ warn "opal-browser: String#encode_uri is deprecated. Please use FormData.encode_uri"
112
+ FormData.encode_uri(self)
35
113
  end
36
114
 
37
115
  # Decode as URI component.
38
116
  #
117
+ # @deprecated Please use FormData.decode
39
118
  # @return [String] the string decoded as URI component
40
119
  def decode_uri_component
41
- `decodeURIComponent(self)`
120
+ warn "opal-browser: String#decode_uri_component is deprecated. Please use FormData.decode"
121
+ FormData.decode(self)
42
122
  end
43
123
 
44
124
  # Decode as URI.
45
125
  #
126
+ # @deprecated Please use FormData.decode_uri
46
127
  # @return [String] the string decoded as URI
47
128
  def decode_uri
48
- `decodeURI(self)`
129
+ warn "opal-browser: String#decode_uri is deprecated. Please use FormData.decode_uri"
130
+ FormData.decode_uri(self)
49
131
  end
50
132
  end
51
133
 
@@ -54,21 +136,19 @@ class Hash
54
136
  #
55
137
  # @param string [String] the URL encoded form
56
138
  #
139
+ # @deprecated Please use FormData.parse_query
57
140
  # @return [Hash]
58
141
  def self.decode_uri(string)
59
- self[string.split(?&).map {|part|
60
- name, value = part.split(?=)
61
-
62
- [name.decode_uri_component, value.decode_uri_component]
63
- }]
142
+ warn "opal-browser: Hash.decode_uri is deprecated. Please use FormData.parse_query"
143
+ FormData.parse_query(string)
64
144
  end
65
145
 
66
146
  # Encode the Hash to an URL form.
67
147
  #
148
+ # @deprecated Please use FormData.build_query
68
149
  # @return [String] the URL encoded form
69
150
  def encode_uri
70
- map {|name, value|
71
- "#{name.to_s.encode_uri_component}=#{value.to_s.encode_uri_component}"
72
- }.join(?&)
151
+ warn "opal-browser: Hash#encode_uri is deprecated. Please use FormData.build_query"
152
+ FormData.build_query(self)
73
153
  end
74
154
  end
@@ -1,3 +1,3 @@
1
1
  module Browser
2
- VERSION = '0.2.0.beta1'
2
+ VERSION = '0.3.2'
3
3
  end
@@ -0,0 +1,39 @@
1
+ # Firefox browsers need either a flag or a polyfill
2
+ require "browser/polyfill/visual_viewport"
3
+
4
+ module Browser
5
+ # The mobile web contains two viewports, the Layout and Visual viewport.
6
+ # The Layout viewport is what a page lays out its elements into and the
7
+ # Visual viewport is what is actually visible on the screen. When the user
8
+ # pinch-zooms into the page, the visual viewport shrinks but the layout viewport
9
+ # is unchanged. UI like the on-screen keyboard (OSK) can also shrink the visual
10
+ # viewport without affecting the layout viewport.
11
+ #
12
+ # https://github.com/WICG/visual-viewport
13
+ # https://developer.mozilla.org/en-US/docs/Web/API/VisualViewport
14
+ class VisualViewport
15
+ include Native::Wrapper
16
+ include Event::Target
17
+
18
+ alias_native :offset_left, :offsetLeft
19
+ alias_native :offset_top, :offsetTop
20
+ alias_native :page_left, :pageLeft
21
+ alias_native :page_top, :pageTop
22
+ alias_native :width, :width
23
+ alias_native :height, :height
24
+ alias_native :scale, :scale
25
+
26
+ attr_accessor :native
27
+ end
28
+
29
+ class Window
30
+ def visual_viewport
31
+ @visual_viewport ||= VisualViewport.new(`#@native.visualViewport`)
32
+
33
+ # Polyfill can take some time to load
34
+ @visual_viewport.native ||= `#@native.visualViewport`
35
+
36
+ @visual_viewport
37
+ end
38
+ end
39
+ end
@@ -1,20 +1,34 @@
1
1
  module Browser; class Window
2
2
 
3
+ # Allows access and manipulation of the {Window} size.
3
4
  class Size
5
+ # @private
4
6
  def initialize(window)
5
7
  @window = window
6
8
  @native = window.to_n
7
9
  end
8
10
 
9
- def set(what)
10
- width = what[:width] || self.width
11
- height = what[:height] || self.height
11
+ def set(*args)
12
+ if Hash === args.first
13
+ width, height = args.first.values_at(:width, :height)
14
+ else
15
+ width, height = args
16
+ end
17
+
18
+ width ||= self.width
19
+ height ||= self.height
12
20
 
13
21
  `#@native.resizeTo(#{width}, #{height})`
14
22
 
15
23
  self
16
24
  end
17
25
 
26
+ # @!attribute width
27
+ # @return [Integer] the width of the window
28
+
29
+ # @!attribute height
30
+ # @return [Integer] the height of the window
31
+
18
32
  if Browser.supports? 'Window.outerSize'
19
33
  def width
20
34
  `#@native.outerWidth`
@@ -40,6 +54,20 @@ class Size
40
54
  def height=(value)
41
55
  set(height: value)
42
56
  end
57
+
58
+ # @!attribute inner_width
59
+ # @return [Integer] the inner width of the window
60
+
61
+ def inner_width
62
+ `#@native.innerWidth`
63
+ end
64
+
65
+ # @!attribute inner_height
66
+ # @return [Integer] the inner height of the window
67
+
68
+ def inner_height
69
+ `#@native.innerHeight`
70
+ end
43
71
  end
44
72
 
45
73
  end; end
@@ -31,6 +31,21 @@ class View
31
31
  raise NotImplementedError, 'window size unsupported'
32
32
  end
33
33
  end
34
+
35
+ # Get a device pixel ratio. Can be used to handle desktop browser
36
+ # zoom, retina devices and custom screen scale for mobile devices.
37
+ # Use $window.visual_viewport.scale to handle mobile zoom.
38
+ def zoom
39
+ `#@native.devicePixelRatio`
40
+ end
41
+
42
+ # Handle #pixel_ratio changes. This will trigger a block on zoom.
43
+ def on_zoom &block
44
+ %x{
45
+ var mqString = "(resolution: " + #@native.devicePixelRatio + "dppx)";
46
+ #@native.matchMedia(mqString).addListener(#{block.to_n});
47
+ }
48
+ end
34
49
  end
35
50
 
36
51
  end; end