opal-browser 0.2.0 → 0.3.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 (201) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/build.yml +95 -0
  3. data/.gitignore +2 -0
  4. data/Gemfile +17 -3
  5. data/LICENSE +2 -1
  6. data/README.md +116 -54
  7. data/Rakefile +29 -1
  8. data/config.ru +20 -3
  9. data/docs/polyfills.md +24 -0
  10. data/examples/2048/Gemfile +7 -0
  11. data/examples/2048/README.md +13 -0
  12. data/examples/2048/app/application.rb +169 -0
  13. data/examples/2048/config.ru +9 -0
  14. data/examples/canvas/Gemfile +7 -0
  15. data/examples/canvas/README.md +9 -0
  16. data/examples/canvas/app/application.rb +55 -0
  17. data/examples/canvas/config.ru +9 -0
  18. data/examples/component/Gemfile +7 -0
  19. data/examples/component/README.md +10 -0
  20. data/examples/component/app/application.rb +66 -0
  21. data/examples/component/config.ru +9 -0
  22. data/examples/integrations/README.md +24 -0
  23. data/examples/integrations/dynamic-rack-opal-sprockets-server/Gemfile +7 -0
  24. data/examples/integrations/dynamic-rack-opal-sprockets-server/README.md +16 -0
  25. data/examples/integrations/dynamic-rack-opal-sprockets-server/app/application.rb +6 -0
  26. data/examples/integrations/dynamic-rack-opal-sprockets-server/config.ru +9 -0
  27. data/examples/integrations/dynamic-roda-roda-sprockets/.gitignore +1 -0
  28. data/examples/integrations/dynamic-roda-roda-sprockets/Gemfile +8 -0
  29. data/examples/integrations/dynamic-roda-roda-sprockets/README.md +22 -0
  30. data/examples/integrations/dynamic-roda-roda-sprockets/Rakefile +4 -0
  31. data/examples/integrations/dynamic-roda-roda-sprockets/app/application.rb +6 -0
  32. data/examples/integrations/dynamic-roda-roda-sprockets/app.rb +32 -0
  33. data/examples/integrations/dynamic-roda-roda-sprockets/config.ru +3 -0
  34. data/examples/integrations/dynamic-roda-tilt/.gitignore +1 -0
  35. data/examples/integrations/dynamic-roda-tilt/Gemfile +9 -0
  36. data/examples/integrations/dynamic-roda-tilt/README.md +17 -0
  37. data/examples/integrations/dynamic-roda-tilt/Rakefile +6 -0
  38. data/examples/integrations/dynamic-roda-tilt/app/application.rb +6 -0
  39. data/examples/integrations/dynamic-roda-tilt/app.rb +50 -0
  40. data/examples/integrations/dynamic-roda-tilt/config.ru +3 -0
  41. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/Gemfile +8 -0
  42. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/README.md +16 -0
  43. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/app/application.rb +6 -0
  44. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/config.ru +29 -0
  45. data/examples/integrations/static-bash/.gitignore +2 -0
  46. data/examples/integrations/static-bash/Gemfile +4 -0
  47. data/examples/integrations/static-bash/README.md +8 -0
  48. data/examples/integrations/static-bash/app/application.rb +6 -0
  49. data/examples/integrations/static-bash/build.sh +4 -0
  50. data/examples/integrations/static-bash/index.html +10 -0
  51. data/examples/integrations/static-bash-opal-parser/.gitignore +3 -0
  52. data/examples/integrations/static-bash-opal-parser/Gemfile +4 -0
  53. data/examples/integrations/static-bash-opal-parser/README.md +10 -0
  54. data/examples/integrations/static-bash-opal-parser/build.sh +4 -0
  55. data/examples/integrations/static-bash-opal-parser/index.html +19 -0
  56. data/examples/integrations/static-rake/.gitignore +1 -0
  57. data/examples/integrations/static-rake/Gemfile +5 -0
  58. data/examples/integrations/static-rake/README.md +7 -0
  59. data/examples/integrations/static-rake/Rakefile +10 -0
  60. data/examples/integrations/static-rake/app/application.rb +6 -0
  61. data/examples/integrations/static-rake/index.html +9 -0
  62. data/examples/integrations/static-rake-guard/.gitignore +1 -0
  63. data/examples/integrations/static-rake-guard/Gemfile +7 -0
  64. data/examples/integrations/static-rake-guard/Guardfile +3 -0
  65. data/examples/integrations/static-rake-guard/README.md +10 -0
  66. data/examples/integrations/static-rake-guard/Rakefile +10 -0
  67. data/examples/integrations/static-rake-guard/app/application.rb +6 -0
  68. data/examples/integrations/static-rake-guard/index.html +9 -0
  69. data/examples/svg/.gitignore +1 -0
  70. data/examples/svg/Gemfile +5 -0
  71. data/examples/svg/README.md +7 -0
  72. data/examples/svg/Rakefile +10 -0
  73. data/examples/svg/app/application.rb +11 -0
  74. data/examples/svg/index.html +17 -0
  75. data/examples/svg/index.svg +6 -0
  76. data/index.html.erb +2 -3
  77. data/opal/browser/audio/node.rb +121 -0
  78. data/opal/browser/audio/param_schedule.rb +43 -0
  79. data/opal/browser/audio.rb +66 -0
  80. data/opal/browser/blob.rb +94 -0
  81. data/opal/browser/canvas/data.rb +1 -1
  82. data/opal/browser/canvas/gradient.rb +1 -1
  83. data/opal/browser/canvas/style.rb +3 -1
  84. data/opal/browser/canvas/text.rb +1 -1
  85. data/opal/browser/canvas.rb +17 -3
  86. data/opal/browser/console.rb +3 -1
  87. data/opal/browser/cookies.rb +16 -7
  88. data/opal/browser/crypto.rb +79 -0
  89. data/opal/browser/css/declaration.rb +1 -1
  90. data/opal/browser/css/rule.rb +1 -1
  91. data/opal/browser/css/style_sheet.rb +2 -2
  92. data/opal/browser/css.rb +23 -7
  93. data/opal/browser/database/sql.rb +7 -8
  94. data/opal/browser/delay.rb +16 -0
  95. data/opal/browser/dom/attribute.rb +1 -1
  96. data/opal/browser/dom/builder.rb +29 -10
  97. data/opal/browser/dom/document.rb +81 -13
  98. data/opal/browser/dom/document_fragment.rb +18 -0
  99. data/opal/browser/dom/document_or_shadow_root.rb +19 -0
  100. data/opal/browser/dom/element/attributes.rb +28 -4
  101. data/opal/browser/dom/element/button.rb +31 -0
  102. data/opal/browser/dom/element/custom.rb +177 -0
  103. data/opal/browser/dom/element/data.rb +17 -2
  104. data/opal/browser/dom/element/editable.rb +47 -0
  105. data/opal/browser/dom/element/form.rb +38 -0
  106. data/opal/browser/dom/element/iframe.rb +37 -0
  107. data/opal/browser/dom/element/image.rb +2 -0
  108. data/opal/browser/dom/element/input.rb +36 -0
  109. data/opal/browser/dom/element/media.rb +17 -0
  110. data/opal/browser/dom/element/scroll.rb +106 -74
  111. data/opal/browser/dom/element/select.rb +6 -0
  112. data/opal/browser/dom/element/size.rb +12 -0
  113. data/opal/browser/dom/element/template.rb +2 -0
  114. data/opal/browser/dom/element/textarea.rb +2 -0
  115. data/opal/browser/dom/element.rb +193 -48
  116. data/opal/browser/dom/mutation_observer.rb +2 -2
  117. data/opal/browser/dom/node.rb +53 -13
  118. data/opal/browser/dom/node_set.rb +11 -2
  119. data/opal/browser/dom/shadow_root.rb +12 -0
  120. data/opal/browser/dom/text.rb +2 -2
  121. data/opal/browser/dom.rb +38 -5
  122. data/opal/browser/effects.rb +170 -4
  123. data/opal/browser/event/all.rb +26 -0
  124. data/opal/browser/event/animation.rb +2 -0
  125. data/opal/browser/event/audio_processing.rb +2 -0
  126. data/opal/browser/event/base.rb +35 -4
  127. data/opal/browser/event/before_unload.rb +2 -0
  128. data/opal/browser/event/clipboard.rb +9 -0
  129. data/opal/browser/event/close.rb +2 -0
  130. data/opal/browser/event/composition.rb +2 -0
  131. data/opal/browser/event/custom.rb +1 -1
  132. data/opal/browser/event/data_transfer.rb +95 -0
  133. data/opal/browser/event/device_light.rb +2 -0
  134. data/opal/browser/event/device_motion.rb +2 -0
  135. data/opal/browser/event/device_orientation.rb +2 -0
  136. data/opal/browser/event/device_proximity.rb +2 -0
  137. data/opal/browser/event/drag.rb +9 -5
  138. data/opal/browser/event/focus.rb +2 -0
  139. data/opal/browser/event/gamepad.rb +3 -1
  140. data/opal/browser/event/hash_change.rb +2 -0
  141. data/opal/browser/event/keyboard.rb +14 -1
  142. data/opal/browser/event/message.rb +2 -0
  143. data/opal/browser/event/mouse.rb +10 -6
  144. data/opal/browser/event/page_transition.rb +2 -0
  145. data/opal/browser/event/pop_state.rb +2 -0
  146. data/opal/browser/event/progress.rb +2 -0
  147. data/opal/browser/event/sensor.rb +2 -0
  148. data/opal/browser/event/storage.rb +2 -0
  149. data/opal/browser/event/touch.rb +2 -0
  150. data/opal/browser/event/wheel.rb +2 -0
  151. data/opal/browser/event.rb +26 -116
  152. data/opal/browser/event_source.rb +1 -1
  153. data/opal/browser/form_data.rb +225 -0
  154. data/opal/browser/history.rb +4 -8
  155. data/opal/browser/http/request.rb +32 -10
  156. data/opal/browser/http/response.rb +5 -1
  157. data/opal/browser/http.rb +0 -2
  158. data/opal/browser/immediate.rb +0 -2
  159. data/opal/browser/location.rb +7 -1
  160. data/opal/browser/navigator.rb +105 -4
  161. data/opal/browser/polyfill/visual_viewport.rb +216 -0
  162. data/opal/browser/screen.rb +2 -2
  163. data/opal/browser/setup/base.rb +6 -0
  164. data/opal/browser/setup/full.rb +13 -0
  165. data/opal/browser/setup/large.rb +17 -0
  166. data/opal/browser/setup/mini.rb +8 -0
  167. data/opal/browser/setup/traditional.rb +10 -0
  168. data/opal/browser/socket.rb +3 -3
  169. data/opal/browser/storage.rb +2 -2
  170. data/opal/browser/support.rb +13 -1
  171. data/opal/browser/utils.rb +94 -14
  172. data/opal/browser/version.rb +1 -1
  173. data/opal/browser/visual_viewport.rb +39 -0
  174. data/opal/browser/window/size.rb +14 -0
  175. data/opal/browser/window/view.rb +15 -0
  176. data/opal/browser/window.rb +29 -16
  177. data/opal/browser.rb +1 -11
  178. data/opal-browser.gemspec +3 -3
  179. data/spec/database/sql_spec.rb +43 -35
  180. data/spec/delay_spec.rb +15 -12
  181. data/spec/dom/document_spec.rb +10 -8
  182. data/spec/dom/element/custom_spec.rb +106 -0
  183. data/spec/dom/element/subclass_spec.rb +144 -0
  184. data/spec/dom/element_spec.rb +42 -0
  185. data/spec/dom/mutation_observer_spec.rb +12 -8
  186. data/spec/dom/node_spec.rb +48 -0
  187. data/spec/dom_spec.rb +8 -0
  188. data/spec/event_source_spec.rb +15 -12
  189. data/spec/{dom/event_spec.rb → event_spec.rb} +44 -15
  190. data/spec/history_spec.rb +23 -19
  191. data/spec/http_spec.rb +19 -31
  192. data/spec/immediate_spec.rb +5 -4
  193. data/spec/interval_spec.rb +18 -9
  194. data/spec/native_cached_wrapper_spec.rb +46 -0
  195. data/spec/runner.rb +37 -62
  196. data/spec/socket_spec.rb +15 -12
  197. data/spec/spec_helper.rb +2 -1
  198. data/spec/spec_helper_promise.rb.erb +25 -0
  199. metadata +119 -16
  200. data/.travis.yml +0 -74
  201. data/opal/browser/window/scroll.rb +0 -59
@@ -1,7 +1,7 @@
1
1
  module Browser; class Canvas
2
2
 
3
3
  class StyleObject
4
- include Native
4
+ include Native::Wrapper
5
5
 
6
6
  attr_reader :context
7
7
 
@@ -56,10 +56,12 @@ class Style < StyleObject
56
56
 
57
57
  def smooth!
58
58
  `#@native.mozImageSmoothingEnabled = #{@smooth = true}`
59
+ `#@native.imageSmoothingEnabled = #{@smooth = true}`
59
60
  end
60
61
 
61
62
  def no_smooth!
62
63
  `#@native.mozImageSmoothingEnabled = #{@smooth = false}`
64
+ `#@native.imageSmoothingEnabled = #{@smooth = false}`
63
65
  end
64
66
  end
65
67
 
@@ -1,7 +1,7 @@
1
1
  module Browser; class Canvas
2
2
 
3
3
  class Text
4
- include Native
4
+ include Native::Wrapper
5
5
 
6
6
  attr_reader :context
7
7
 
@@ -1,5 +1,3 @@
1
- require 'promise'
2
-
3
1
  require 'browser/canvas/style'
4
2
  require 'browser/canvas/text'
5
3
  require 'browser/canvas/data'
@@ -8,7 +6,7 @@ require 'browser/canvas/gradient'
8
6
  module Browser
9
7
 
10
8
  class Canvas
11
- include Native
9
+ include Native::Wrapper
12
10
 
13
11
  attr_reader :element, :style, :text
14
12
 
@@ -59,6 +57,14 @@ class Canvas
59
57
  @element[:height].to_i
60
58
  end
61
59
 
60
+ def width=(new_width)
61
+ @element[:width] = new_width.to_i
62
+ end
63
+
64
+ def height=(new_height)
65
+ @element[:height] = new_height.to_i
66
+ end
67
+
62
68
  def append_to(parent)
63
69
  @element.append_to(parent)
64
70
  end
@@ -312,6 +318,14 @@ class Canvas
312
318
  def to_data(type = undefined)
313
319
  `#{@element.to_n}.toDataUrl(type)`
314
320
  end
321
+
322
+ def to_dom(*)
323
+ @element
324
+ end
325
+
326
+ def on(*args, &block); @element.on(*args, &block); end
327
+ def one(*args, &block); @element.one(*args, &block); end
328
+ def off(*args, &block); @element.off(*args, &block); end
315
329
  end
316
330
 
317
331
  Browser::DOM::Builder.for Canvas do |b, item|
@@ -1,10 +1,12 @@
1
+ warn "`console' has been moved to Opal's stdlib, please `require 'console'` instead." if RUBY_ENGINE_VERSION.to_f >= 0.9
2
+
1
3
  module Browser
2
4
 
3
5
  # Manipulate the browser console.
4
6
  #
5
7
  # @see https://developer.mozilla.org/en-US/docs/Web/API/console
6
8
  class Console
7
- include Native
9
+ include Browser::NativeCachedWrapper
8
10
 
9
11
  # Clear the console.
10
12
  def clear
@@ -5,10 +5,17 @@ module Browser
5
5
  # Allows manipulation of browser cookies.
6
6
  #
7
7
  # @see https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
8
+ #
9
+ # Usage:
10
+ #
11
+ # cookies = Browser::Cookies.new(`document`)
12
+ # cookies["my-cookie"] = "monster"
13
+ # cookies.delete("my-cookie")
14
+ #
8
15
  class Cookies
9
16
  # Default cookie options.
10
17
  DEFAULT = {
11
- expires: Time.now + 1.day,
18
+ expires: Time.now + 60 * 60 * 24,
12
19
  secure: false
13
20
  }
14
21
 
@@ -30,12 +37,12 @@ class Cookies
30
37
  #
31
38
  # @return [Object]
32
39
  def [](name)
33
- matches = `#@document.cookie`.scan(/#{Regexp.escape(name.encode_uri_component)}=([^;]*)/)
40
+ matches = `#@document.cookie`.scan(/#{Regexp.escape(FormData.encode(name))}=([^;]*)/)
34
41
 
35
42
  return if matches.empty?
36
43
 
37
- result = matches.map {|cookie|
38
- JSON.parse(cookie.match(/^.*?=(.*)$/)[1].decode_uri_component)
44
+ result = matches.flatten.map {|value|
45
+ JSON.parse(FormData.decode(value))
39
46
  }
40
47
 
41
48
  result.length == 1 ? result.first : result
@@ -53,7 +60,9 @@ class Cookies
53
60
  # @option options [String] :domain the domain the cookie is valid on
54
61
  # @option options [Boolean] :secure whether the cookie is secure or not
55
62
  def []=(name, value, options = {})
56
- `#@document.cookie = #{encode name, value.is_a?(String) ? value : JSON.dump(value), @options.merge(options)}`
63
+ string = value.is_a?(String) ? value : JSON.dump(value)
64
+ encoded_value = encode(name, string, @options.merge(options))
65
+ `#@document.cookie = #{encoded_value}`
57
66
  end
58
67
 
59
68
  # Delete a cookie.
@@ -110,10 +119,10 @@ protected
110
119
  def encode(key, value, options = {})
111
120
  io = StringIO.new
112
121
 
113
- io << key.encode_uri_component << ?= << value.encode_uri_component << '; '
122
+ io << FormData.encode(key) << ?= << FormData.encode(value) << '; '
114
123
 
115
124
  io << 'max-age=' << options[:max_age] << '; ' if options[:max_age]
116
- io << 'expires=' << options[:expires].to_utc << '; ' if options[:expires]
125
+ io << 'expires=' << options[:expires].utc << '; ' if options[:expires]
117
126
  io << 'path=' << options[:path] << '; ' if options[:path]
118
127
  io << 'domain=' << options[:domain] << '; ' if options[:domain]
119
128
  io << 'secure' if options[:secure]
@@ -0,0 +1,79 @@
1
+ module Browser
2
+
3
+ # Implements (parts of) the web crypto interface
4
+ #
5
+ # https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API
6
+ class Crypto
7
+ include NativeCachedWrapper
8
+
9
+ class Digest
10
+ def initialize(buf)
11
+ @buffer = Buffer.new(buf)
12
+ end
13
+
14
+ attr_reader :buffer
15
+
16
+ # Convert a digest to a hexadecimal string
17
+ def to_hex
18
+ buffer.to_a.map { |i| "%02x" % i }.join
19
+ end
20
+
21
+ # Convert a digest to a binary string
22
+ def to_s
23
+ buffer.to_a.map { |i| "%c" % i }.join
24
+ end
25
+
26
+ # Convert a digest to a Base64-encoded string
27
+ #
28
+ # You will need to `require "base64"`
29
+ def to_b64
30
+ Base64.strict_encode64(to_s)
31
+ end
32
+
33
+ # Convert a digest to a urlsafe Base64-encoded string
34
+ #
35
+ # You will need to `require "base64"`
36
+ def to_u64(padding: false)
37
+ Base64.urlsafe_encode64(to_s, padding: padding)
38
+ end
39
+ end
40
+
41
+ # Compute a cryptographic digest of data (a Buffer). If block is given,
42
+ # it will call a block, otherwise it will return a Promise that will
43
+ # return once a digest is computed.
44
+ #
45
+ # Allowed values for algo: SHA-1, SHA-256 (default), SHA-368, SHA-512.
46
+ #
47
+ # The block/promise will be given an argument of type {Digest} which can
48
+ # be used to format a digest.
49
+ #
50
+ # Example:
51
+ # ```
52
+ # Browser::Blob.new(['test']).buffer.then { |b|
53
+ # $window.crypto.digest(b)
54
+ # }.then { |d|
55
+ # puts d.to_hex
56
+ # }
57
+ # ```
58
+ def digest data, algo = 'SHA-256', &block
59
+ promise = nil
60
+ unless block_given?
61
+ promise = Promise.new
62
+ block = proc { |i| promise.resolve(i) }
63
+ end
64
+ resblock = proc { |i| block.call(Digest.new(i)) }
65
+
66
+ `#@native.subtle.digest(algo, #{Native.convert(data)}).then(#{resblock.to_n})`
67
+ promise
68
+ end
69
+ end
70
+
71
+ class Window
72
+ # @!attribute [r] crypto
73
+ # @return [Crypto] the crypto interface of this window
74
+ def crypto
75
+ @crypto ||= Crypto.new(`#@native.crypto`)
76
+ end
77
+ end
78
+
79
+ end
@@ -1,7 +1,7 @@
1
1
  module Browser; module CSS
2
2
 
3
3
  class Declaration
4
- include Native
4
+ include Browser::NativeCachedWrapper
5
5
  include Enumerable
6
6
 
7
7
  def rule
@@ -1,7 +1,7 @@
1
1
  module Browser; module CSS
2
2
 
3
3
  class Rule
4
- include Native
4
+ include Browser::NativeCachedWrapper
5
5
 
6
6
  STYLE_RULE = 1
7
7
  CHARSET_RULE = 2
@@ -1,10 +1,10 @@
1
1
  module Browser; module CSS
2
2
 
3
3
  class StyleSheet
4
- include Native
4
+ include Browser::NativeCachedWrapper
5
5
 
6
6
  def initialize(what)
7
- if what.is_a? DOM::Element
7
+ if DOM::Element === what
8
8
  super(`#{what.to_n}.sheet`)
9
9
  else
10
10
  super(what)
data/opal/browser/css.rb CHANGED
@@ -4,19 +4,35 @@ require 'browser/css/rule'
4
4
  require 'browser/css/rule/style'
5
5
 
6
6
  module Kernel
7
- # Create a <style> element from a string or a block using the
8
- # {Browser::CSS::Builder} DSL.
7
+ # @overload CSS(document = $document, &block)
9
8
  #
10
- # @param text [String] the CSS text
11
- # @return [Browser::DOM::Element] the create <style> element
12
- def CSS(text = nil, &block)
13
- style = $document.create_element(:style)
9
+ # Create a `<style>` element from a {Paggio::CSS} DSL.
10
+ #
11
+ # @param document [Browser::DOM::Document] the document instance
12
+ # we intend to use
13
+ #
14
+ # @return [Browser::DOM::Element] the created `<style>` element
15
+ #
16
+ # @overload CSS(string, document = $document)
17
+ #
18
+ # Create a `<style>` element from a string.
19
+ #
20
+ # @param document [Browser::DOM::Document] the document instance
21
+ # we intend to use
22
+ #
23
+ # @return [Browser::DOM::Element] the created `<style>` element
24
+ def CSS(*args, &block)
25
+ document = if args.length > 1 || block_given?
26
+ args.pop
27
+ end || $document
28
+
29
+ style = document.create_element(:style)
14
30
  style[:type] = 'text/css'
15
31
 
16
32
  if block
17
33
  style.inner_text = Paggio.css(&block)
18
34
  else
19
- style.inner_text = text
35
+ style.inner_text = args.join("")
20
36
  end
21
37
 
22
38
  style
@@ -1,4 +1,3 @@
1
- require 'promise'
2
1
  require 'ostruct'
3
2
 
4
3
  module Browser; module Database
@@ -27,7 +26,7 @@ class SQL
27
26
  Timeout = Class.new(self)
28
27
  end
29
28
 
30
- include Native
29
+ include Native::Wrapper
31
30
 
32
31
  # @return [String] the name of the database
33
32
  attr_reader :name
@@ -73,7 +72,7 @@ class SQL
73
72
  return `#@native.version` unless block
74
73
 
75
74
  `#@native.changeVersion(#{from}, #{to},
76
- #{-> t { block.call(Transaction.new(self, t)) }})`
75
+ #{->(t) { block.call(Transaction.new(self, t)) }})`
77
76
  end
78
77
 
79
78
  # Start a transaction on the database.
@@ -82,12 +81,12 @@ class SQL
82
81
  def transaction(&block)
83
82
  raise ArgumentError, 'no block given' unless block
84
83
 
85
- `#@native.transaction(#{-> t { block.call(Transaction.new(self, t)) }})`
84
+ `#@native.transaction(#{->(t) { block.call(Transaction.new(self, t)) }})`
86
85
  end
87
86
 
88
87
  # Allows you to make changes to the database or read data from it.
89
88
  class Transaction
90
- include Native
89
+ include Native::Wrapper
91
90
 
92
91
  # @return [Database] the database the transaction has been created from
93
92
  attr_reader :database
@@ -109,8 +108,8 @@ class SQL
109
108
  promise = Promise.new
110
109
 
111
110
  `#@native.executeSql(#{query}, #{parameters},
112
- #{-> _, r { promise.resolve(Result.new(self, r)) }},
113
- #{-> _, e { promise.reject(Error.new(e)) }})`
111
+ #{->(_, r) { promise.resolve(Result.new(self, r)) }},
112
+ #{->(_, e) { promise.reject(Error.new(e)) }})`
114
113
 
115
114
  promise
116
115
  end
@@ -129,7 +128,7 @@ class SQL
129
128
  end
130
129
 
131
130
  class Result
132
- include Native
131
+ include Native::Wrapper
133
132
 
134
133
  # @return [Transaction] the transaction the result came from
135
134
  attr_reader :transaction
@@ -49,6 +49,17 @@ class Window
49
49
  def after!(time, &block)
50
50
  Delay.new(@native, time, &block)
51
51
  end
52
+
53
+ # Returns a promise that will resolve after the given seconds.
54
+ #
55
+ # @param time [Float] the seconds after it gets called
56
+ #
57
+ # @return [Promise] the promise that will resolve after timeout happens
58
+ def resolve_after(time)
59
+ promise = Promise.new
60
+ Delay.new(@native, time) { promise.resolve }.start
61
+ promise
62
+ end
52
63
  end
53
64
 
54
65
  end
@@ -63,6 +74,11 @@ module Kernel
63
74
  def after!(time, &block)
64
75
  $window.after!(time, &block)
65
76
  end
77
+
78
+ # (see Browser::Window#resolve_after)
79
+ def resolve_after(time)
80
+ $window.resolve_after(time)
81
+ end
66
82
  end
67
83
 
68
84
  class Proc
@@ -2,7 +2,7 @@ module Browser; module DOM
2
2
 
3
3
  # Encapsulates an {Element} attribute.
4
4
  class Attribute
5
- include Native
5
+ include Browser::NativeCachedWrapper
6
6
 
7
7
  # @!attribute [r] name
8
8
  # @return [String] the name of the attribute
@@ -38,10 +38,32 @@ class Builder
38
38
 
39
39
  attr_reader :document, :element
40
40
 
41
- def initialize(document, &block)
41
+ NEW_PAGGIO = (Paggio::HTML.instance_method(:build!) rescue false)
42
+
43
+ def initialize(document, builder=nil, &block)
42
44
  @document = document
43
- @builder = Paggio::HTML.new(&block)
44
- @roots = @builder.each.map { |e| Builder.build(self, e) }
45
+
46
+ # Compatibility issue due to an unreleased Paggio gem.
47
+ # Let's try to support both versions. When Paggio is released,
48
+ # we may remove it.
49
+
50
+ if NEW_PAGGIO
51
+ @builder = Paggio::HTML.new(defer: true, &block)
52
+
53
+ build = proc do
54
+ @builder.build!(force_call: !!builder)
55
+ @roots = @builder.each.map { |e| Builder.build(self, e) }
56
+ end
57
+
58
+ if builder
59
+ builder.extend!(@builder, &build)
60
+ else
61
+ build.()
62
+ end
63
+ else
64
+ @builder = Paggio::HTML.new(&block)
65
+ @roots = @builder.each.map { |e| Builder.build(self, e) }
66
+ end
45
67
  end
46
68
 
47
69
  def to_a
@@ -54,15 +76,12 @@ Builder.for String do |b, item|
54
76
  end
55
77
 
56
78
  Builder.for Paggio::HTML::Element do |b, item|
57
- dom = b.document.create_element(`item.name`)
79
+ options = {}
58
80
 
59
- if Hash === `item.attributes`
60
- dom.attributes.merge!(`item.attributes`)
61
- end
81
+ options[:attrs] = `item.attributes` if Hash === `item.attributes`
82
+ options[:classes] = `item.class_names`
62
83
 
63
- `item.class_names`.each {|value|
64
- dom.add_class value
65
- }
84
+ dom = b.document.create_element(`item.name`, **options)
66
85
 
67
86
  if on = `item.on || nil`
68
87
  on.each {|args, block|
@@ -1,6 +1,8 @@
1
1
  module Browser; module DOM
2
2
 
3
3
  class Document < Element
4
+ include DocumentOrShadowRoot
5
+
4
6
  # Get the first element matching the given ID, CSS selector or XPath.
5
7
  #
6
8
  # @param what [String] ID, CSS selector or XPath
@@ -24,20 +26,67 @@ class Document < Element
24
26
  # @return [Element?] the body element of the document
25
27
  def body
26
28
  DOM(`#@native.body`)
29
+ rescue ArgumentError
30
+ raise '$document.body is not defined; try to wrap your code in $document.ready{}'
27
31
  end
28
32
 
29
33
  # Create a new element for the document.
30
34
  #
31
35
  # @param name [String] the node name
32
- # @param options [Hash] optional `:namespace` name
36
+ # @param builder [Browser::DOM::Builder] optional builder to append element to
37
+ # @param options [String] :namespace optional namespace name
38
+ # @param options [String] :is optional WebComponents is parameter
39
+ # @param options [String] :id optional id to set
40
+ # @param options [Array<String>] :classes optional classes to set
41
+ # @param options [Hash] :attrs optional attributes to set
33
42
  #
34
43
  # @return [Element]
35
- def create_element(name, options = {})
44
+ def create_element(name, builder=nil, **options, &block)
45
+ opts = {}
46
+
47
+ if options[:is] ||= (options.dig(:attrs, :is))
48
+ opts[:is] = options[:is]
49
+ end
50
+
36
51
  if ns = options[:namespace]
37
- DOM(`#@native.createElementNS(#{ns}, #{name})`)
52
+ elem = `#@native.createElementNS(#{ns}, #{name}, #{opts.to_n})`
38
53
  else
39
- DOM(`#@native.createElement(name)`)
54
+ elem = `#@native.createElement(name, #{opts.to_n})`
40
55
  end
56
+
57
+ if options[:classes]
58
+ `#{elem}.className = #{Array(options[:classes]).join(" ")}`
59
+ end
60
+
61
+ if options[:id]
62
+ `#{elem}.id = #{options[:id]}`
63
+ end
64
+
65
+ if options[:attrs]
66
+ options[:attrs].each do |k,v|
67
+ next unless v
68
+ `#{elem}.setAttribute(#{k}, #{v})`
69
+ end
70
+ end
71
+
72
+ dom = DOM(elem)
73
+
74
+ if block_given?
75
+ dom.inner_dom(builder, &block)
76
+ end
77
+
78
+ if builder
79
+ builder << dom
80
+ end
81
+
82
+ dom
83
+ end
84
+
85
+ # Create a new document fragment.
86
+ #
87
+ # @return [DocumentFragment]
88
+ def create_document_fragment
89
+ DOM(`#@native.createDocumentFragment()`)
41
90
  end
42
91
 
43
92
  # Create a new text node for the document.
@@ -49,6 +98,15 @@ class Document < Element
49
98
  DOM(`#@native.createTextNode(#{content})`)
50
99
  end
51
100
 
101
+ # Create a new comment node for the document.
102
+ #
103
+ # @param content [String] the comment content
104
+ #
105
+ # @return [Comment]
106
+ def create_comment(content)
107
+ DOM(`#@native.createComment(#{content})`)
108
+ end
109
+
52
110
  def document
53
111
  self
54
112
  end
@@ -98,7 +156,13 @@ class Document < Element
98
156
 
99
157
  # Check if the document is ready.
100
158
  def ready?
101
- `#@native.readyState === "complete"`
159
+ `#@native.readyState === "complete" || #@native.readyState === "interactive"`
160
+ end
161
+
162
+ # @!attribute referrer
163
+ # @return [String] the referring document, or empty string if direct access
164
+ def referrer
165
+ `#@native.referrer`
102
166
  end
103
167
 
104
168
  # @!attribute root
@@ -111,14 +175,6 @@ class Document < Element
111
175
  `#@native.documentElement = #{Native.convert(element)}`
112
176
  end
113
177
 
114
- # @!attribute [r] style_sheets
115
- # @return [Array<CSS::StyleSheet>] the style sheets for the document
116
- def style_sheets
117
- Native::Array.new(`#@native.styleSheets`) {|e|
118
- CSS::StyleSheet.new(e)
119
- }
120
- end
121
-
122
178
  # @!attribute title
123
179
  # @return [String] the document title
124
180
  def title
@@ -129,6 +185,18 @@ class Document < Element
129
185
  `#@native.title = value`
130
186
  end
131
187
 
188
+ # @!attribute [r] hidden?
189
+ # @return [Boolean] is the page considered hidden?
190
+ def hidden?
191
+ `#@native.hidden`
192
+ end
193
+
194
+ # @!attribute [r] visibility
195
+ # @return [String] the visibility state of the document - prerender, hidden or visible
196
+ def visibility
197
+ `#@native.visibilityState`
198
+ end
199
+
132
200
  if Browser.supports? 'Document.view'
133
201
  def window
134
202
  Window.new(`#@native.defaultView`)
@@ -1,7 +1,25 @@
1
1
  module Browser; module DOM
2
2
 
3
+ # TODO: DocumentFragment is not a subclass of Element, but
4
+ # a subclass of Node. It implements a ParentNode.
5
+ #
6
+ # @see https://github.com/opal/opal-browser/pull/46
3
7
  class DocumentFragment < Element
8
+ def self.new(node)
9
+ if self == DocumentFragment
10
+ if defined? `#{node}.mode`
11
+ ShadowRoot.new(node)
12
+ else
13
+ super
14
+ end
15
+ else
16
+ super
17
+ end
18
+ end
4
19
 
20
+ def self.create
21
+ $document.create_document_fragment
22
+ end
5
23
  end
6
24
 
7
25
  end; end
@@ -0,0 +1,19 @@
1
+ module Browser; module DOM
2
+
3
+ # Document and ShadowRoot have some methods and properties in common.
4
+ # This solution mimics how it's done in DOM.
5
+ #
6
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot
7
+ module DocumentOrShadowRoot
8
+ # @!attribute [r] style_sheets
9
+ # @return [Array<CSS::StyleSheet>] the style sheets for the document
10
+ def style_sheets
11
+ Native::Array.new(`#@native.styleSheets`) {|e|
12
+ CSS::StyleSheet.new(e)
13
+ }
14
+ end
15
+
16
+ alias stylesheets style_sheets
17
+ end
18
+
19
+ end; end