opal-browser 0.1.0.beta1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (257) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/build.yml +95 -0
  3. data/.gitignore +3 -0
  4. data/.yardopts +1 -1
  5. data/Gemfile +22 -3
  6. data/LICENSE +20 -0
  7. data/README.md +200 -20
  8. data/Rakefile +29 -1
  9. data/config.ru +20 -2
  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 +24 -0
  78. data/lib/opal-browser.rb +1 -0
  79. data/opal/browser/animation_frame.rb +92 -10
  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 +2 -12
  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 +86 -28
  89. data/opal/browser/console.rb +6 -38
  90. data/opal/browser/cookies.rb +90 -27
  91. data/opal/browser/crypto.rb +79 -0
  92. data/opal/browser/css/declaration.rb +1 -6
  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 +94 -0
  98. data/opal/browser/dom/attribute.rb +16 -9
  99. data/opal/browser/dom/builder.rb +35 -25
  100. data/opal/browser/dom/character_data.rb +43 -7
  101. data/opal/browser/dom/document.rb +171 -37
  102. data/opal/browser/dom/document_fragment.rb +18 -0
  103. data/opal/browser/dom/document_or_shadow_root.rb +19 -0
  104. data/opal/browser/dom/element/attributes.rb +111 -0
  105. data/opal/browser/dom/element/button.rb +31 -0
  106. data/opal/browser/dom/element/custom.rb +177 -0
  107. data/opal/browser/dom/element/data.rb +82 -0
  108. data/opal/browser/dom/element/editable.rb +47 -0
  109. data/opal/browser/dom/element/form.rb +38 -0
  110. data/opal/browser/dom/element/iframe.rb +37 -0
  111. data/opal/browser/dom/element/image.rb +25 -0
  112. data/opal/browser/dom/element/input.rb +48 -1
  113. data/opal/browser/dom/element/media.rb +17 -0
  114. data/opal/browser/dom/element/offset.rb +32 -10
  115. data/opal/browser/dom/element/position.rb +11 -2
  116. data/opal/browser/dom/element/scroll.rb +139 -20
  117. data/opal/browser/dom/element/select.rb +42 -0
  118. data/opal/browser/dom/element/size.rb +46 -0
  119. data/opal/browser/dom/element/template.rb +11 -0
  120. data/opal/browser/dom/element/textarea.rb +26 -0
  121. data/opal/browser/dom/element.rb +496 -168
  122. data/opal/browser/dom/mutation_observer.rb +69 -9
  123. data/opal/browser/dom/node.rb +270 -83
  124. data/opal/browser/dom/node_set.rb +74 -41
  125. data/opal/browser/dom/shadow_root.rb +12 -0
  126. data/opal/browser/dom/text.rb +18 -3
  127. data/opal/browser/dom.rb +40 -18
  128. data/opal/browser/effects.rb +180 -3
  129. data/opal/browser/event/all.rb +26 -0
  130. data/opal/browser/event/animation.rb +40 -0
  131. data/opal/browser/{dom/event → event}/audio_processing.rb +10 -6
  132. data/opal/browser/event/base.rb +461 -0
  133. data/opal/browser/event/before_unload.rb +17 -0
  134. data/opal/browser/event/clipboard.rb +37 -0
  135. data/opal/browser/event/close.rb +49 -0
  136. data/opal/browser/event/composition.rb +52 -0
  137. data/opal/browser/event/custom.rb +65 -0
  138. data/opal/browser/event/data_transfer.rb +95 -0
  139. data/opal/browser/event/device_light.rb +25 -0
  140. data/opal/browser/{dom/event → event}/device_motion.rb +21 -6
  141. data/opal/browser/event/device_orientation.rb +50 -0
  142. data/opal/browser/{dom/event → event}/device_proximity.rb +10 -6
  143. data/opal/browser/event/drag.rb +123 -0
  144. data/opal/browser/event/focus.rb +41 -0
  145. data/opal/browser/event/gamepad.rb +62 -0
  146. data/opal/browser/{dom/event → event}/hash_change.rb +10 -6
  147. data/opal/browser/event/keyboard.rb +128 -0
  148. data/opal/browser/event/message.rb +72 -0
  149. data/opal/browser/{dom/event → event}/mouse.rb +37 -32
  150. data/opal/browser/event/page_transition.rb +25 -0
  151. data/opal/browser/event/pop_state.rb +35 -0
  152. data/opal/browser/event/progress.rb +45 -0
  153. data/opal/browser/event/sensor.rb +17 -0
  154. data/opal/browser/{dom/event → event}/storage.rb +10 -6
  155. data/opal/browser/{dom/event → event}/touch.rb +14 -21
  156. data/opal/browser/event/ui.rb +38 -0
  157. data/opal/browser/{dom/event → event}/wheel.rb +6 -4
  158. data/opal/browser/event.rb +163 -0
  159. data/opal/browser/event_source.rb +7 -4
  160. data/opal/browser/form_data.rb +225 -0
  161. data/opal/browser/history.rb +53 -21
  162. data/opal/browser/http/binary.rb +1 -0
  163. data/opal/browser/http/headers.rb +21 -2
  164. data/opal/browser/http/request.rb +83 -55
  165. data/opal/browser/http/response.rb +5 -1
  166. data/opal/browser/http.rb +47 -9
  167. data/opal/browser/immediate.rb +128 -10
  168. data/opal/browser/interval.rb +41 -23
  169. data/opal/browser/location.rb +20 -4
  170. data/opal/browser/navigator.rb +136 -13
  171. data/opal/browser/polyfill/visual_viewport.rb +216 -0
  172. data/opal/browser/screen.rb +34 -8
  173. data/opal/browser/setup/base.rb +6 -0
  174. data/opal/browser/setup/full.rb +13 -0
  175. data/opal/browser/setup/large.rb +17 -0
  176. data/opal/browser/setup/mini.rb +8 -0
  177. data/opal/browser/setup/traditional.rb +10 -0
  178. data/opal/browser/socket.rb +16 -8
  179. data/opal/browser/storage.rb +155 -52
  180. data/opal/browser/support.rb +299 -0
  181. data/opal/browser/utils.rb +116 -18
  182. data/opal/browser/version.rb +1 -1
  183. data/opal/browser/visual_viewport.rb +39 -0
  184. data/opal/browser/window/size.rb +47 -9
  185. data/opal/browser/window/view.rb +37 -4
  186. data/opal/browser/window.rb +46 -26
  187. data/opal/browser.rb +1 -10
  188. data/opal/opal-browser.rb +1 -0
  189. data/opal-browser.gemspec +10 -12
  190. data/spec/database/sql_spec.rb +139 -0
  191. data/spec/delay_spec.rb +41 -0
  192. data/spec/dom/attribute_spec.rb +49 -0
  193. data/spec/dom/builder_spec.rb +36 -19
  194. data/spec/dom/document_spec.rb +28 -6
  195. data/spec/dom/element/attributes_spec.rb +52 -0
  196. data/spec/dom/element/custom_spec.rb +106 -0
  197. data/spec/dom/element/subclass_spec.rb +144 -0
  198. data/spec/dom/element_spec.rb +184 -7
  199. data/spec/dom/mutation_observer_spec.rb +13 -9
  200. data/spec/dom/node_set_spec.rb +44 -0
  201. data/spec/dom/node_spec.rb +87 -27
  202. data/spec/dom_spec.rb +19 -9
  203. data/spec/event_source_spec.rb +18 -15
  204. data/spec/{dom/event_spec.rb → event_spec.rb} +55 -26
  205. data/spec/history_spec.rb +32 -19
  206. data/spec/http_spec.rb +25 -36
  207. data/spec/immediate_spec.rb +10 -7
  208. data/spec/interval_spec.rb +59 -0
  209. data/spec/native_cached_wrapper_spec.rb +46 -0
  210. data/spec/runner.rb +107 -0
  211. data/spec/socket_spec.rb +18 -14
  212. data/spec/spec_helper.rb +2 -4
  213. data/spec/spec_helper_promise.rb.erb +25 -0
  214. data/spec/storage_spec.rb +7 -7
  215. data/spec/wgxpath.install.js +49 -0
  216. data/spec/window_spec.rb +2 -2
  217. metadata +181 -93
  218. data/opal/browser/compatibility/animation_frame.rb +0 -93
  219. data/opal/browser/compatibility/dom/document/window.rb +0 -15
  220. data/opal/browser/compatibility/dom/element/css.rb +0 -15
  221. data/opal/browser/compatibility/dom/element/matches.rb +0 -31
  222. data/opal/browser/compatibility/dom/element/offset.rb +0 -20
  223. data/opal/browser/compatibility/dom/element/scroll.rb +0 -25
  224. data/opal/browser/compatibility/dom/element/style.rb +0 -15
  225. data/opal/browser/compatibility/dom/mutation_observer.rb +0 -47
  226. data/opal/browser/compatibility/http/request.rb +0 -15
  227. data/opal/browser/compatibility/immediate.rb +0 -107
  228. data/opal/browser/compatibility/window/scroll.rb +0 -27
  229. data/opal/browser/compatibility/window/size.rb +0 -13
  230. data/opal/browser/compatibility/window/view.rb +0 -13
  231. data/opal/browser/compatibility.rb +0 -59
  232. data/opal/browser/dom/compatibility.rb +0 -8
  233. data/opal/browser/dom/event/animation.rb +0 -26
  234. data/opal/browser/dom/event/base.rb +0 -207
  235. data/opal/browser/dom/event/before_unload.rb +0 -13
  236. data/opal/browser/dom/event/clipboard.rb +0 -26
  237. data/opal/browser/dom/event/close.rb +0 -35
  238. data/opal/browser/dom/event/composition.rb +0 -38
  239. data/opal/browser/dom/event/custom.rb +0 -30
  240. data/opal/browser/dom/event/device_light.rb +0 -21
  241. data/opal/browser/dom/event/device_orientation.rb +0 -36
  242. data/opal/browser/dom/event/drag.rb +0 -113
  243. data/opal/browser/dom/event/focus.rb +0 -23
  244. data/opal/browser/dom/event/gamepad.rb +0 -47
  245. data/opal/browser/dom/event/keyboard.rb +0 -93
  246. data/opal/browser/dom/event/message.rb +0 -50
  247. data/opal/browser/dom/event/page_transition.rb +0 -21
  248. data/opal/browser/dom/event/pop_state.rb +0 -21
  249. data/opal/browser/dom/event/progress.rb +0 -31
  250. data/opal/browser/dom/event/sensor.rb +0 -13
  251. data/opal/browser/dom/event/ui.rb +0 -22
  252. data/opal/browser/dom/event.rb +0 -240
  253. data/opal/browser/http/compatibility.rb +0 -1
  254. data/opal/browser/http/parameters.rb +0 -8
  255. data/opal/browser/timeout.rb +0 -60
  256. data/opal/browser/window/compatibility.rb +0 -3
  257. data/opal/browser/window/scroll.rb +0 -49
@@ -1,19 +1,24 @@
1
- #--
2
- # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
- # Version 2, December 2004
4
- #
5
- # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
- # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
- #
8
- # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
- #++
10
-
11
1
  require 'json'
12
2
  require 'stringio'
13
3
 
14
4
  module Browser
15
5
 
16
- class Storage < Hash
6
+ # A {Storage} allows you to store data across page loads and browser
7
+ # restarts.
8
+ #
9
+ # Compatibility
10
+ # -------------
11
+ # The compatibility layer will try various implementations in the following
12
+ # order.
13
+ #
14
+ # + [window.localStorage](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage#localStorage)
15
+ # + [window.globalStorage](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage#globalStorage)
16
+ # + [document.body.addBehavior](http://msdn.microsoft.com/en-us/library/ms531424(VS.85).aspx)
17
+ # + [document.cookie](https://developer.mozilla.org/en-US/docs/Web/API/document.cookie)
18
+ #
19
+ # @see https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage
20
+ # @todo remove method_defined? checks when require order is fixed
21
+ class Storage
17
22
  def self.json_create(data)
18
23
  data.delete(JSON.create_id)
19
24
 
@@ -22,101 +27,181 @@ class Storage < Hash
22
27
  }]
23
28
  end
24
29
 
30
+ # @!attribute [r] name
31
+ # @return [String] the name of the storage
25
32
  attr_reader :name
26
33
 
34
+ # Create a new storage on the given window with the given name.
35
+ #
36
+ # @param window [native] the window to save the storage to
37
+ # @param name [String] the name to use to discern different storages
27
38
  def initialize(window, name)
28
39
  super()
29
40
 
30
41
  @window = window
31
42
  @name = name
43
+ @data = {}
32
44
 
33
45
  autosave!
46
+ reload
47
+ end
48
+
49
+ # Check if autosaving is enabled.
50
+ #
51
+ # When autosaving is enabled the {Storage} is saved every time a change is
52
+ # made, otherwise you'll have to save it manually yourself.
53
+ def autosave?
54
+ @autosave
55
+ end
34
56
 
35
- init if respond_to? :init
57
+ # Enable autosaving.
58
+ def autosave!
59
+ @autosave = true
36
60
  end
37
61
 
38
- def encoded_name
39
- "$opal.storage.#{@name}"
62
+ # Disable autosaving.
63
+ def no_autosave!
64
+ @autosave = false
40
65
  end
41
66
 
42
- def autosave?; @autosave; end
43
- def autosave!; @autosave = true; end
44
- def no_autosave!; @autosave = false; end
67
+ include Enumerable
45
68
 
46
- def replace(what)
47
- if what.is_a?(String)
48
- super JSON.parse(what)
69
+ # Iterate over the (key, value) pairs in the storage.
70
+ #
71
+ # @yield [key, value]
72
+ def each(&block)
73
+ return enum_for :each unless block
74
+
75
+ @data.each(&block)
76
+
77
+ self
78
+ end
79
+
80
+ def method_missing(*args, &block)
81
+ @data.__send__(*args, &block)
82
+ end
83
+
84
+ # Set a value in the storage.
85
+ def []=(key, value)
86
+ @data[key] = value
87
+
88
+ save if autosave?
89
+ end
90
+
91
+ # Delete a value from the storage.
92
+ def delete(key)
93
+ @data.delete(key).tap {
94
+ save if autosave?
95
+ }
96
+ end
97
+
98
+ # Clear the storage.
99
+ def clear
100
+ @data.clear.tap {
101
+ save if autosave?
102
+ }
103
+ end
104
+
105
+ # Replace the current storage with the given one.
106
+ #
107
+ # @param new [Hash, String] if new is a {String} it will be parsed as JSON
108
+ def replace(new)
109
+ if String === new
110
+ @data.replace(JSON.parse(new))
49
111
  else
50
- super
112
+ @data.replace(new)
51
113
  end
52
114
  end
53
115
 
54
- %w([]= delete clear).each {|name|
55
- define_method name do |*args|
56
- # FIXME: remove the application when it's fixed
57
- super(*args).tap {
58
- save if autosave?
59
- }
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
60
132
  end
61
- }
62
133
 
63
- def save; end
134
+ result
135
+ end
136
+
137
+ def to_h
138
+ @data
139
+ end
140
+
141
+ # @!method reload
142
+ # Load the storage.
64
143
 
65
- if `window.localStorage`
66
- def init
67
- replace `#@window.localStorage[#{encoded_name}] || '{}'`
144
+ # @!method save
145
+ # Persist the current state to the storage.
146
+
147
+ if Browser.supports? 'Storage.local'
148
+ def reload
149
+ replace `#@window.localStorage[#@name] || '{}'`
68
150
  end
69
151
 
70
152
  def save
71
- `#@window.localStorage[#{encoded_name}] = #{JSON.dump(self)}`
153
+ `#@window.localStorage[#@name] = #{JSON.dump(self)}`
72
154
  end
73
- elsif `window.globalStorage`
74
- def init
75
- replace `#@window.globalStorage[#@window.location.hostname][#{encoded_name}] || '{}'`
155
+ elsif Browser.supports? 'Storage.global'
156
+ def reload
157
+ replace `#@window.globalStorage[#@window.location.hostname][#@name] || '{}'`
76
158
  end
77
159
 
78
160
  def save
79
- `#@window.globalStorage[#@window.location.hostname][#{encoded_name}] = #{JSON.dump(self)}`
161
+ `#@window.globalStorage[#@window.location.hostname][#@name] = #{JSON.dump(self)}`
80
162
  end
81
- elsif `document.body.addBehavior`
82
- def init
163
+ elsif Browser.supports? 'Element.addBehavior'
164
+ def reload
83
165
  %x{
84
166
  #@element = #@window.document.createElement('link');
85
167
  #@element.addBehavior('#default#userData');
86
168
 
87
169
  #@window.document.getElementsByTagName('head')[0].appendChild(#@element);
88
170
 
89
- #@element.load(#{encoded_name});
171
+ #@element.load(#@name);
90
172
  }
91
173
 
92
- replace `#@element.getAttribute(#{encoded_name}) || '{}'`
174
+ replace `#@element.getAttribute(#@name) || '{}'`
93
175
  end
94
176
 
95
177
  def save
96
178
  %x{
97
- #@element.setAttribute(#{encoded_name}, #{JSON.dump(self)});
98
- #@element.save(#{encoded_name});
179
+ #@element.setAttribute(#@name, #{JSON.dump(self)});
180
+ #@element.save(#@name);
99
181
  }
100
182
  end
101
183
  else
102
- def init
184
+ def reload
103
185
  $document.cookies.options expires: 60 * 60 * 24 * 365
104
186
 
105
- replace $document.cookies[encoded_name]
187
+ replace $document.cookies[@name]
106
188
  end
107
189
 
108
190
  def save
109
- $document.cookies[encoded_name] = self
191
+ $document.cookies[@name] = JSON.dump(self)
110
192
  end
111
193
  end
112
194
 
195
+ # Convert the storage to JSON.
196
+ #
197
+ # @return [String] the JSON representation
113
198
  def to_json
114
- io = StringIO.new("{")
199
+ io = StringIO.new << "{"
115
200
 
116
201
  io << JSON.create_id.to_json << ":" << self.class.name.to_json << ","
117
202
 
118
- each {|key, value|
119
- io << key.to_json.to_json << ":" << value.to_json << ","
203
+ @data.each {|key, value|
204
+ io << key.to_json.to_s << ":" << value.to_json << ","
120
205
  }
121
206
 
122
207
  io.seek(-1, IO::SEEK_CUR)
@@ -126,21 +211,39 @@ class Storage < Hash
126
211
  end
127
212
  end
128
213
 
214
+ # A {SessionStorage} allows you to store data across page reloads, as long as the session
215
+ # is active.
216
+ #
217
+ # @see https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage#sessionStorage
129
218
  class SessionStorage < Storage
130
- def init
131
- replace `#@window.sessionStorage[#{encoded_name}] || '{}'`
219
+ def self.supported?
220
+ Browser.supports? 'Storage.session'
221
+ end
222
+
223
+ def reload
224
+ replace `#@window.sessionStorage[#@name] || '{}'`
132
225
  end
133
226
 
134
227
  def save
135
- `#@window.sessionStorage[#{encoded_name}] = #{JSON.dump(self)}`
228
+ `#@window.sessionStorage[#@name] = #{JSON.dump(self)}`
136
229
  end
137
230
  end
138
231
 
139
232
  class Window
233
+ # Get a storage with the given name.
234
+ #
235
+ # @param name [Symbol] the name of the storage
236
+ #
237
+ # @return [Storage]
140
238
  def storage(name = :default)
141
239
  Storage.new(to_n, name)
142
240
  end
143
241
 
242
+ # Get a session storage with the given name.
243
+ #
244
+ # @param name [Symbol] the name of the storage
245
+ #
246
+ # @return [SessionStorage]
144
247
  def session_storage(name = :default)
145
248
  SessionStorage.new(to_n, name)
146
249
  end
@@ -0,0 +1,299 @@
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.
5
+ BROWSER_ENGINE = `/MSIE|WebKit|Presto|Gecko/.exec(navigator.userAgent)[0]`.downcase rescue :unknown
6
+
7
+ module Browser
8
+ # @private
9
+ @support = `{}`
10
+
11
+ # Check if the browser supports the given feature.
12
+ def self.supports?(feature)
13
+ if defined?(`#@support[#{feature}]`)
14
+ return `#@support[#{feature}]`
15
+ end
16
+
17
+ support = case feature
18
+ when 'MutationObserver'
19
+ defined?(`window.MutationObserver`)
20
+
21
+ when 'WebSocket'
22
+ defined?(`window.WebSocket`)
23
+
24
+ when 'EventSource'
25
+ defined?(`window.EventSource`)
26
+
27
+ when 'XHR'
28
+ defined?(`window.XMLHttpRequest`)
29
+
30
+ when 'ActiveX'
31
+ defined?(`window.ActiveXObject`)
32
+
33
+ when 'WebSQL'
34
+ defined?(`window.openDatabase`)
35
+
36
+ when 'Query.css'
37
+ defined?(`document.querySelectorAll`)
38
+
39
+ when 'Query.xpath'
40
+ defined?(`document.evaluate`)
41
+
42
+ when 'Storage.local'
43
+ defined?(`window.localStorage`)
44
+
45
+ when 'Storage.global'
46
+ defined?(`window.globalStorage`)
47
+
48
+ when 'Storage.session'
49
+ defined?(`window.sessionStorage`)
50
+
51
+ when 'Immediate'
52
+ defined?(`window.setImmediate`)
53
+
54
+ when 'Immediate (Internet Explorer)'
55
+ defined?(`window.msSetImmediate`)
56
+
57
+ when 'Immediate (Firefox)'
58
+ defined?(`window.mozSetImmediate`)
59
+
60
+ when 'Immediate (Opera)'
61
+ defined?(`window.oSetImmediate`)
62
+
63
+ when 'Immediate (Chrome)', 'setImmediate (Safari)'
64
+ defined?(`window.webkitSetImmediate`)
65
+
66
+ when 'CSS.computed'
67
+ defined?(`window.getComputedStyle`)
68
+
69
+ when 'CSS.current'
70
+ defined?(`document.documentElement.currentStyle`)
71
+
72
+ when 'Window.send'
73
+ defined?(`window.postMessage`)
74
+
75
+ when 'Window.send (Asynchronous)'
76
+ if defined?(`window.postMessage`) && !defined?(`window.importScripts`)
77
+ %x{
78
+ var ok = true,
79
+ old = window.onmessage;
80
+
81
+ window.onmessage = function() { ok = false; };
82
+ window.postMessage("", "*")
83
+ window.onmessage = old;
84
+
85
+ return ok;
86
+ }
87
+ end
88
+
89
+ when 'Window.send (Synchronous)'
90
+ !supports?('Window.send (Asynchronous)')
91
+
92
+ when 'Window.innerSize'
93
+ defined?(`window.innerHeight`)
94
+
95
+ when 'Window.outerSize'
96
+ defined?(`window.outerHeight`)
97
+
98
+ when 'Window.scroll'
99
+ defined?(`document.documentElement.scrollLeft`)
100
+
101
+ when 'Window.scrollBy'
102
+ defined?(`document.documentElement.scrollBy`)
103
+
104
+ when 'Window.pageOffset'
105
+ defined?(`window.pageXOffset`)
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
+
115
+ when 'Element.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
+ }
149
+
150
+ when 'Element.clientSize'
151
+ defined?(`document.documentElement.clientHeight`)
152
+
153
+ when 'Element.scroll'
154
+ defined?(`document.documentElement.scrollLeft`)
155
+
156
+ when 'Element.textContent'
157
+ defined?(`document.documentElement.textContent`)
158
+
159
+ when 'Element.innerText'
160
+ defined?(`document.documentElement.innerText`)
161
+
162
+ when 'Element.matches'
163
+ defined?(`document.documentElement.matches`)
164
+
165
+ when 'Element.matches (Internet Explorer)'
166
+ defined?(`document.documentElement.msMatchesSelector`)
167
+
168
+ when 'Element.matches (Firefox)'
169
+ defined?(`document.documentElement.mozMatchesSelector`)
170
+
171
+ when 'Element.matches (Opera)'
172
+ defined?(`document.documentElement.oMatchesSelector`)
173
+
174
+ when 'Element.matches (Chrome)', 'Element.matches (Safari)'
175
+ defined?(`document.documentElement.webkitMatchesSelector`)
176
+
177
+ when 'Element.getBoundingClientRect'
178
+ defined?(`document.documentElement.getBoundingClientRect`)
179
+
180
+ when 'Event.readystatechange'
181
+ `"onreadystatechange" in window.document.createElement("script")`
182
+
183
+ when 'Event.constructor'
184
+ begin
185
+ `new MouseEvent("click")`
186
+
187
+ true
188
+ rescue StandardError, JS::Error
189
+ false
190
+ end
191
+
192
+ when 'Event.create'
193
+ defined?(`document.createEvent`)
194
+
195
+ when 'Event.createObject'
196
+ defined?(`document.createEventObject`)
197
+
198
+ when 'Event.addListener'
199
+ defined?(`document.addEventListener`)
200
+
201
+ when 'Event.attach'
202
+ defined?(`document.attachEvent`)
203
+
204
+ when 'Event.removeListener'
205
+ defined?(`document.removeEventListener`)
206
+
207
+ when 'Event.detach'
208
+ defined?(`document.detachEvent`)
209
+
210
+ when 'Event.dispatch'
211
+ defined?(`document.dispatchEvent`)
212
+
213
+ when 'Event.fire'
214
+ defined?(`document.fireEvent`)
215
+
216
+ when /^Event\.([A-Z].*?)$/
217
+ `(#{$1} + "Event") in window`
218
+
219
+ when 'Document.view'
220
+ defined?(`document.defaultView`)
221
+
222
+ when 'Document.window'
223
+ defined?(`document.parentWindow`)
224
+
225
+ when 'History'
226
+ defined?(`window.history.pushState`)
227
+
228
+ when 'History.state'
229
+ defined?(`window.history.state`)
230
+
231
+ when 'Animation.request'
232
+ defined?(`window.requestAnimationFrame`)
233
+
234
+ when 'Animation.request (Internet Explorer)'
235
+ defined?(`window.msRequestAnimationFrame`)
236
+
237
+ when 'Animation.request (Firefox)'
238
+ defined?(`window.mozRequestAnimationFrame`)
239
+
240
+ when 'Animation.request (Opera)'
241
+ defined?(`window.oRequestAnimationFrame`)
242
+
243
+ when 'Animation.request (Chrome)', 'Animation.request (Safari)'
244
+ defined?(`window.webkitRequestAnimationFrame`)
245
+
246
+ when 'Animation.cancel'
247
+ defined?(`window.cancelAnimationFrame`)
248
+
249
+ when 'Animation.cancel (Internet Explorer)'
250
+ defined?(`window.msCancelAnimationFrame`)
251
+
252
+ when 'Animation.cancel (Firefox)'
253
+ defined?(`window.mozCancelAnimationFrame`)
254
+
255
+ when 'Animation.cancel (Opera)'
256
+ defined?(`window.oCancelAnimationFrame`)
257
+
258
+ when 'Animation.cancel (Chrome)', 'Animation.cancel (Safari)'
259
+ defined?(`window.webkitCancelAnimationFrame`)
260
+
261
+ when 'Animation.cancelRequest'
262
+ defined?(`window.cancelRequestAnimationFrame`)
263
+
264
+ when 'Animation.cancelRequest (Internet Explorer)'
265
+ defined?(`window.msCancelRequestAnimationFrame`)
266
+
267
+ when 'Animation.cancelRequest (Firefox)'
268
+ defined?(`window.mozCancelRequestAnimationFrame`)
269
+
270
+ when 'Animation.cancelRequest (Opera)'
271
+ defined?(`window.oCancelRequestAnimationFrame`)
272
+
273
+ when 'Animation.cancelRequest (Chrome)', 'Animation.cancelRequest (Safari)'
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`)
284
+ end
285
+
286
+ `#@support[#{feature}] = #{support}`
287
+ end
288
+
289
+ # Check if the given polyfill is loaded.
290
+ def self.loaded?(name)
291
+ case name
292
+ when 'Sizzle'
293
+ defined?(`window.Sizzle`)
294
+
295
+ when 'wicked-good-xpath'
296
+ defined?(`window.wgxpath`)
297
+ end
298
+ end
299
+ end