opal-browser 0.2.0.beta1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +22 -8
  3. data/Gemfile +1 -1
  4. data/README.md +59 -5
  5. data/index.html.erb +7 -4
  6. data/lib/opal-browser.rb +1 -0
  7. data/opal-browser.gemspec +1 -1
  8. data/opal/browser.rb +1 -0
  9. data/opal/browser/animation_frame.rb +26 -1
  10. data/opal/browser/canvas.rb +0 -10
  11. data/opal/browser/canvas/data.rb +0 -10
  12. data/opal/browser/canvas/gradient.rb +0 -10
  13. data/opal/browser/canvas/style.rb +0 -10
  14. data/opal/browser/canvas/text.rb +0 -10
  15. data/opal/browser/cookies.rb +6 -8
  16. data/opal/browser/database/sql.rb +194 -0
  17. data/opal/browser/delay.rb +25 -7
  18. data/opal/browser/dom.rb +2 -11
  19. data/opal/browser/dom/attribute.rb +12 -11
  20. data/opal/browser/dom/builder.rb +4 -9
  21. data/opal/browser/dom/document.rb +105 -41
  22. data/opal/browser/dom/element.rb +317 -231
  23. data/opal/browser/dom/element/attributes.rb +87 -0
  24. data/opal/browser/dom/element/data.rb +67 -0
  25. data/opal/browser/dom/element/input.rb +12 -1
  26. data/opal/browser/dom/element/offset.rb +5 -0
  27. data/opal/browser/dom/element/position.rb +11 -2
  28. data/opal/browser/dom/element/scroll.rb +77 -10
  29. data/opal/browser/dom/element/select.rb +36 -0
  30. data/opal/browser/dom/element/size.rb +5 -0
  31. data/opal/browser/dom/element/template.rb +9 -0
  32. data/opal/browser/dom/element/textarea.rb +24 -0
  33. data/opal/browser/dom/mutation_observer.rb +2 -2
  34. data/opal/browser/dom/node.rb +93 -51
  35. data/opal/browser/dom/node_set.rb +66 -48
  36. data/opal/browser/effects.rb +11 -0
  37. data/opal/browser/{dom/event.rb → event.rb} +32 -32
  38. data/opal/browser/{dom/event → event}/animation.rb +2 -2
  39. data/opal/browser/{dom/event → event}/audio_processing.rb +2 -2
  40. data/opal/browser/{dom/event → event}/base.rb +65 -7
  41. data/opal/browser/{dom/event → event}/before_unload.rb +2 -2
  42. data/opal/browser/{dom/event → event}/clipboard.rb +2 -2
  43. data/opal/browser/{dom/event → event}/close.rb +2 -2
  44. data/opal/browser/{dom/event → event}/composition.rb +2 -2
  45. data/opal/browser/{dom/event → event}/custom.rb +2 -2
  46. data/opal/browser/{dom/event → event}/device_light.rb +2 -2
  47. data/opal/browser/{dom/event → event}/device_motion.rb +2 -2
  48. data/opal/browser/{dom/event → event}/device_orientation.rb +2 -2
  49. data/opal/browser/{dom/event → event}/device_proximity.rb +2 -2
  50. data/opal/browser/{dom/event → event}/drag.rb +2 -2
  51. data/opal/browser/{dom/event → event}/focus.rb +2 -2
  52. data/opal/browser/{dom/event → event}/gamepad.rb +2 -2
  53. data/opal/browser/{dom/event → event}/hash_change.rb +2 -2
  54. data/opal/browser/{dom/event → event}/keyboard.rb +2 -2
  55. data/opal/browser/{dom/event → event}/message.rb +2 -2
  56. data/opal/browser/{dom/event → event}/mouse.rb +2 -2
  57. data/opal/browser/{dom/event → event}/page_transition.rb +2 -2
  58. data/opal/browser/{dom/event → event}/pop_state.rb +2 -2
  59. data/opal/browser/{dom/event → event}/progress.rb +2 -2
  60. data/opal/browser/{dom/event → event}/sensor.rb +2 -2
  61. data/opal/browser/{dom/event → event}/storage.rb +2 -2
  62. data/opal/browser/{dom/event → event}/touch.rb +2 -2
  63. data/opal/browser/{dom/event → event}/ui.rb +2 -2
  64. data/opal/browser/{dom/event → event}/wheel.rb +2 -2
  65. data/opal/browser/event_source.rb +1 -1
  66. data/opal/browser/http.rb +25 -0
  67. data/opal/browser/http/binary.rb +1 -0
  68. data/opal/browser/http/headers.rb +16 -2
  69. data/opal/browser/http/request.rb +14 -38
  70. data/opal/browser/immediate.rb +9 -3
  71. data/opal/browser/interval.rb +34 -11
  72. data/opal/browser/navigator.rb +23 -4
  73. data/opal/browser/screen.rb +1 -1
  74. data/opal/browser/socket.rb +5 -1
  75. data/opal/browser/storage.rb +51 -33
  76. data/opal/browser/support.rb +59 -4
  77. data/opal/browser/version.rb +1 -1
  78. data/opal/browser/window.rb +17 -9
  79. data/opal/browser/window/size.rb +17 -3
  80. data/opal/opal-browser.rb +1 -0
  81. data/spec/database/sql_spec.rb +131 -0
  82. data/spec/delay_spec.rb +38 -0
  83. data/spec/dom/attribute_spec.rb +49 -0
  84. data/spec/dom/builder_spec.rb +25 -8
  85. data/spec/dom/document_spec.rb +20 -0
  86. data/spec/dom/element/attributes_spec.rb +52 -0
  87. data/spec/dom/element_spec.rb +139 -4
  88. data/spec/dom/node_set_spec.rb +44 -0
  89. data/spec/interval_spec.rb +50 -0
  90. data/spec/runner.rb +46 -28
  91. data/spec/socket_spec.rb +1 -0
  92. data/spec/spec_helper.rb +0 -4
  93. data/spec/storage_spec.rb +1 -1
  94. metadata +57 -39
  95. data/opal/browser/http/parameters.rb +0 -8
@@ -37,7 +37,6 @@ class Immediate
37
37
 
38
38
  # @!method prevent
39
39
  # Prevent the immediate from being called once scheduled.
40
-
41
40
  if Browser.supports? 'Immediate'
42
41
  def dispatch
43
42
  @id = `window.setImmediate(function() {
@@ -58,7 +57,7 @@ class Immediate
58
57
  def prevent
59
58
  `window.msClearImmediate(#@id)`
60
59
  end
61
- elsif Browser.supports? 'Window.send'
60
+ elsif Browser.supports? 'Window.send (Asynchronous)'
62
61
  # @private
63
62
  @@tasks = {}
64
63
 
@@ -77,7 +76,7 @@ class Immediate
77
76
  @id = rand(1_000_000).to_s
78
77
  @@tasks[@id] = [@function, @arguments, @block]
79
78
 
80
- $window.send! "#{@@prefix}#{@id}"
79
+ $window.send "#{@@prefix}#{@id}"
81
80
  end
82
81
 
83
82
  def prevent
@@ -132,6 +131,13 @@ end
132
131
 
133
132
  end
134
133
 
134
+ module Kernel
135
+ # (see Immediate.new)
136
+ def defer(*args, &block)
137
+ Browser::Immediate.new(block, args).tap(&:dispatch)
138
+ end
139
+ end
140
+
135
141
  class Proc
136
142
  # (see Immediate.new)
137
143
  def defer(*args, &block)
@@ -19,14 +19,11 @@ class Interval
19
19
  @block = block
20
20
 
21
21
  @aborted = false
22
- @stopped = true
23
-
24
- start
25
22
  end
26
23
 
27
24
  # Check if the interval has been stopped.
28
25
  def stopped?
29
- @stopped
26
+ @id.nil?
30
27
  end
31
28
 
32
29
  # Check if the interval has been aborted.
@@ -44,6 +41,8 @@ class Interval
44
41
 
45
42
  # Stop the interval, it will be possible to start it again.
46
43
  def stop
44
+ return if stopped?
45
+
47
46
  `#@window.clearInterval(#@id)`
48
47
 
49
48
  @stopped = true
@@ -53,10 +52,14 @@ class Interval
53
52
  # Start the interval if it has been stopped.
54
53
  def start
55
54
  raise "the interval has been aborted" if aborted?
56
-
57
55
  return unless stopped?
58
56
 
59
- @id = `#@window.setInterval(#{@block.to_n}, #@every * 1000)`
57
+ @id = `#@window.setInterval(#@block, #@every * 1000)`
58
+ end
59
+
60
+ # Call the [Interval] block.
61
+ def call
62
+ @block.call
60
63
  end
61
64
  end
62
65
 
@@ -67,22 +70,42 @@ class Window
67
70
  #
68
71
  # @return [Interval] the object representing the interval
69
72
  def every(time, &block)
73
+ Interval.new(@native, time, &block).tap(&:start)
74
+ end
75
+
76
+ # Execute the block every given seconds, you have to call [#start] on it
77
+ # yourself.
78
+ #
79
+ # @param time [Float] the seconds between every call
80
+ #
81
+ # @return [Interval] the object representing the interval
82
+ def every!(time, &block)
70
83
  Interval.new(@native, time, &block)
71
84
  end
72
85
  end
73
86
 
74
87
  end
75
88
 
89
+ module Kernel
90
+ # (see Browser::Window#every)
91
+ def every(time, &block)
92
+ $window.every(time, &block)
93
+ end
94
+
95
+ # (see Browser::Window#every!)
96
+ def every!(time, &block)
97
+ $window.every!(time, &block)
98
+ end
99
+ end
100
+
76
101
  class Proc
77
102
  # (see Browser::Window#every)
78
103
  def every(time)
79
104
  $window.every(time, &self)
80
105
  end
81
- end
82
106
 
83
- module Kernel
84
- # (see Browser::Window#every)
85
- def every(time, &block)
86
- $window.every(time, &block)
107
+ # (see Browser::Window#every!)
108
+ def every!(time)
109
+ $window.every!(time, &self)
87
110
  end
88
111
  end
@@ -62,6 +62,27 @@ class Navigator
62
62
  alias_native :version
63
63
  end
64
64
 
65
+ # Representation for the arary of plugins.
66
+ #
67
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins
68
+ class Plugins < Native::Array
69
+ def initialize(plugins)
70
+ super plugins do |p|
71
+ Plugin.new(p)
72
+ end
73
+ end
74
+
75
+ # Reload all browser plugins.
76
+ def refresh
77
+ `#@native.refresh(false)`
78
+ end
79
+
80
+ # Reload all browser plugins reloading pages that contain `<embed>`s.
81
+ def refresh!
82
+ `#@native.refresh(true)`
83
+ end
84
+ end
85
+
65
86
  # @!attribute [r] code
66
87
  # @return [String] the browser code name
67
88
  alias_native :code, :appCodeName
@@ -112,11 +133,9 @@ class Navigator
112
133
  alias_native :platform
113
134
 
114
135
  # @!attribute [r] plugins
115
- # @return [Native::Array<Plugin>] the enabled plugins
136
+ # @return [Plugins] the enabled plugins
116
137
  def plugins
117
- Native::Array.new `#@native.plugins` do |p|
118
- Plugin.new(p)
119
- end
138
+ Plugins.new(`#@native.plugins`)
120
139
  end
121
140
 
122
141
  # @!attribute [r] product
@@ -5,7 +5,7 @@ module Browser
5
5
  # @see https://developer.mozilla.org/en-US/docs/Web/API/Window.screen
6
6
  class Screen
7
7
  include Native
8
- include DOM::Event::Target
8
+ include Event::Target
9
9
 
10
10
  target {|value|
11
11
  Screen.new(value) if Native.is_a?(value, `window.Screen`)
@@ -11,7 +11,7 @@ class Socket
11
11
 
12
12
  include Native
13
13
  include IO::Writable
14
- include DOM::Event::Target
14
+ include Event::Target
15
15
 
16
16
  target {|value|
17
17
  Socket.new(value) if Native.is_a?(value, `window.WebSocket`)
@@ -107,6 +107,10 @@ class Socket
107
107
  `#@native.send(#{data.to_n})`
108
108
  end
109
109
 
110
+ alias << write
111
+
112
+ alias send write
113
+
110
114
  # Close the socket.
111
115
  #
112
116
  # @param code [Integer, nil] the error code
@@ -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
 
@@ -182,7 +200,7 @@ class Storage
182
200
 
183
201
  io << JSON.create_id.to_json << ":" << self.class.name.to_json << ","
184
202
 
185
- each {|key, value|
203
+ @data.each {|key, value|
186
204
  io << key.to_json.to_json << ":" << value.to_json << ","
187
205
  }
188
206
 
@@ -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
 
@@ -87,8 +101,48 @@ module Browser
87
101
  when 'Window.pageOffset'
88
102
  defined?(`window.pageXOffset`)
89
103
 
104
+ when 'Attr.isId'
105
+ %x{
106
+ var div = document.createElement('div');
107
+ div.setAttribute('id', 'xxxxxxxxxxxxx');
108
+
109
+ return typeof(div.attributes['id'].isId) !== "undefined";
110
+ }
111
+
90
112
  when 'Element.addBehavior'
91
- defined?(`document.body.addBehavior`)
113
+ defined?(`document.documentElement.addBehavior`)
114
+
115
+ when 'Element.className'
116
+ %x{
117
+ var div = document.createElement("div");
118
+ div.setAttribute('className', 'x');
119
+
120
+ return div.className === 'x';
121
+ }
122
+
123
+ when 'Element.class'
124
+ %x{
125
+ var div = document.createElement("div");
126
+ div.setAttribute('class', 'x');
127
+
128
+ return div.className === 'x';
129
+ }
130
+
131
+ when 'Element.for'
132
+ %x{
133
+ var label = document.createElement("label");
134
+ label.setAttribute('for', 'x');
135
+
136
+ return label.htmlFor === 'x';
137
+ }
138
+
139
+ when 'Element.htmlFor'
140
+ %x{
141
+ var label = document.createElement("label");
142
+ label.setAttribute('htmlFor', 'x');
143
+
144
+ return label.htmlFor === 'x';
145
+ }
92
146
 
93
147
  when 'Element.clientSize'
94
148
  defined?(`document.documentElement.clientHeight`)
@@ -220,6 +274,7 @@ module Browser
220
274
  `#@support[#{feature}] = #{support}`
221
275
  end
222
276
 
277
+ # Check if the given polyfill is loaded.
223
278
  def self.loaded?(name)
224
279
  case name
225
280
  when 'Sizzle'