playwright-ruby-client 0.0.5 → 0.0.6

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/playwright.rb +2 -0
  3. data/lib/playwright/channel_owners/element_handle.rb +75 -0
  4. data/lib/playwright/channel_owners/frame.rb +117 -0
  5. data/lib/playwright/channel_owners/page.rb +68 -11
  6. data/lib/playwright/channel_owners/request.rb +90 -0
  7. data/lib/playwright/input_type.rb +19 -0
  8. data/lib/playwright/input_types/keyboard.rb +32 -0
  9. data/lib/playwright/input_types/mouse.rb +4 -0
  10. data/lib/playwright/input_types/touchscreen.rb +4 -0
  11. data/lib/playwright/javascript/expression.rb +22 -0
  12. data/lib/playwright/javascript/function.rb +22 -0
  13. data/lib/playwright/playwright_api.rb +31 -23
  14. data/lib/playwright/timeout_settings.rb +1 -1
  15. data/lib/playwright/url_matcher.rb +19 -0
  16. data/lib/playwright/version.rb +1 -1
  17. data/lib/playwright_api/accessibility.rb +46 -6
  18. data/lib/playwright_api/binding_call.rb +6 -6
  19. data/lib/playwright_api/browser.rb +76 -16
  20. data/lib/playwright_api/browser_context.rb +284 -30
  21. data/lib/playwright_api/browser_type.rb +54 -9
  22. data/lib/playwright_api/cdp_session.rb +23 -1
  23. data/lib/playwright_api/chromium_browser_context.rb +16 -6
  24. data/lib/playwright_api/console_message.rb +10 -10
  25. data/lib/playwright_api/dialog.rb +42 -0
  26. data/lib/playwright_api/download.rb +19 -4
  27. data/lib/playwright_api/element_handle.rb +174 -21
  28. data/lib/playwright_api/file_chooser.rb +8 -0
  29. data/lib/playwright_api/frame.rb +355 -68
  30. data/lib/playwright_api/js_handle.rb +45 -9
  31. data/lib/playwright_api/keyboard.rb +98 -8
  32. data/lib/playwright_api/mouse.rb +22 -0
  33. data/lib/playwright_api/page.rb +779 -127
  34. data/lib/playwright_api/playwright.rb +98 -19
  35. data/lib/playwright_api/request.rb +70 -23
  36. data/lib/playwright_api/response.rb +6 -6
  37. data/lib/playwright_api/route.rb +48 -0
  38. data/lib/playwright_api/selectors.rb +14 -6
  39. data/lib/playwright_api/video.rb +8 -0
  40. data/lib/playwright_api/web_socket.rb +3 -5
  41. data/lib/playwright_api/worker.rb +12 -0
  42. metadata +7 -2
@@ -0,0 +1,19 @@
1
+ module Playwright
2
+ class InputType
3
+ def initialize(channel)
4
+ @channel = channel
5
+ end
6
+ end
7
+
8
+ # namespace declaration
9
+ module InputTypes ; end
10
+
11
+ def self.define_input_type(class_name, &block)
12
+ klass = Class.new(InputType)
13
+ klass.class_eval(&block) if block
14
+ InputTypes.const_set(class_name, klass)
15
+ end
16
+ end
17
+
18
+ # load subclasses
19
+ Dir[File.join(__dir__, 'input_types', '*.rb')].each { |f| require f }
@@ -0,0 +1,32 @@
1
+ module Playwright
2
+ define_input_type :Keyboard do
3
+ def down(key)
4
+ @channel.send_message_to_server('keyboardDown', key: key)
5
+ nil
6
+ end
7
+
8
+ def up(key)
9
+ @channel.send_message_to_server('keyboardDown', key: key)
10
+ end
11
+
12
+ def insert_text(text)
13
+ @channel.send_message_to_server('keyboardInsertText', text: text)
14
+ end
15
+
16
+ def type_text(text, delay: nil)
17
+ params = {
18
+ text: text,
19
+ delay: delay,
20
+ }.compact
21
+ @channel.send_message_to_server('keyboardType', params)
22
+ end
23
+
24
+ def press(key, delay: nil)
25
+ params = {
26
+ key: key,
27
+ delay: delay,
28
+ }.compact
29
+ @channel.send_message_to_server('keyboardPress', params)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ module Playwright
2
+ define_input_type :Mouse do
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Playwright
2
+ define_input_type :Touchscreen do
3
+ end
4
+ end
@@ -25,6 +25,28 @@ module Playwright
25
25
  )
26
26
  ::Playwright::ChannelOwner.from(resp)
27
27
  end
28
+
29
+ def eval_on_selector(channel, selector)
30
+ value = channel.send_message_to_server(
31
+ 'evalOnSelector',
32
+ selector: selector,
33
+ expression: @expression,
34
+ isFunction: false,
35
+ arg: @serialized_arg,
36
+ )
37
+ ValueParser.new(value).parse
38
+ end
39
+
40
+ def eval_on_selector_all(channel, selector)
41
+ value = channel.send_message_to_server(
42
+ 'evalOnSelectorAll',
43
+ selector: selector,
44
+ expression: @expression,
45
+ isFunction: false,
46
+ arg: @serialized_arg,
47
+ )
48
+ ValueParser.new(value).parse
49
+ end
28
50
  end
29
51
  end
30
52
  end
@@ -25,6 +25,28 @@ module Playwright
25
25
  )
26
26
  ::Playwright::ChannelOwner.from(resp)
27
27
  end
28
+
29
+ def eval_on_selector(channel, selector)
30
+ value = channel.send_message_to_server(
31
+ 'evalOnSelector',
32
+ selector: selector,
33
+ expression: @definition,
34
+ isFunction: true,
35
+ arg: @serialized_arg,
36
+ )
37
+ ValueParser.new(value).parse
38
+ end
39
+
40
+ def eval_on_selector_all(channel, selector)
41
+ value = channel.send_message_to_server(
42
+ 'evalOnSelectorAll',
43
+ selector: selector,
44
+ expression: @definition,
45
+ isFunction: true,
46
+ arg: @serialized_arg,
47
+ )
48
+ ValueParser.new(value).parse
49
+ end
28
50
  end
29
51
  end
30
52
  end
@@ -7,53 +7,58 @@ module Playwright
7
7
  # @param channel_owner [ChannelOwner]
8
8
  # @note Intended for internal use only.
9
9
  def self.from_channel_owner(channel_owner)
10
- Factory.new(channel_owner).create
10
+ Factory.new(channel_owner, 'ChannelOwners').create
11
11
  end
12
12
 
13
13
  class Factory
14
- def initialize(channel_owner)
15
- channel_owner_class_name = channel_owner.class.name
16
- raise "#{channel_owner_class_name} is not a channel owner" unless channel_owner_class_name.include?('::ChannelOwners::')
14
+ def initialize(impl, module_name)
15
+ impl_class_name = impl.class.name
16
+ unless impl_class_name.include?("::#{module_name}::")
17
+ raise "#{impl_class_name} is not #{module_name}"
18
+ end
17
19
 
18
- @channel_owner = channel_owner
20
+ @impl = impl
21
+ @module_name = module_name
19
22
  end
20
23
 
21
24
  def create
22
- api_class = detect_class_for(@channel_owner.class)
25
+ api_class = detect_class_for(@impl.class)
23
26
  if api_class
24
- api_class.new(@channel_owner)
27
+ api_class.new(@impl)
25
28
  else
26
- raise NotImplementedError.new("Playwright::#{expected_class_name_for(@channel_owner.class)} is not implemented")
29
+ raise NotImplementedError.new("Playwright::#{expected_class_name_for(@impl.class)} is not implemented")
27
30
  end
28
31
  end
29
32
 
30
33
  private
31
34
 
32
35
  def expected_class_name_for(klass)
33
- klass.name.split('::ChannelOwners::').last
36
+ klass.name.split("::#{@module_name}::").last
37
+ end
38
+
39
+ def superclass_exist?(klass)
40
+ ![::Playwright::ChannelOwner, ::Playwright::InputType, Object].include?(klass.superclass)
34
41
  end
35
42
 
36
43
  def detect_class_for(klass)
37
44
  class_name = expected_class_name_for(klass)
38
45
  if ::Playwright.const_defined?(class_name)
39
46
  ::Playwright.const_get(class_name)
47
+ elsif superclass_exist?(klass)
48
+ detect_class_for(klass.superclass)
40
49
  else
41
- if [::Playwright::ChannelOwner, Object].include?(klass.superclass)
42
- nil
43
- else
44
- detect_class_for(klass.superclass)
45
- end
50
+ nil
46
51
  end
47
52
  end
48
53
  end
49
54
 
50
- # @param channel_owner [Playwright::ChannelOwner]
51
- def initialize(channel_owner)
52
- @channel_owner = channel_owner
55
+ # @param impl [Playwright::ChannelOwner|Playwright::InputType]
56
+ def initialize(impl)
57
+ @impl = impl
53
58
  end
54
59
 
55
60
  def ==(other)
56
- @channel_owner.to_s == other.instance_variable_get(:'@channel_owner').to_s
61
+ @impl.to_s == other.instance_variable_get(:'@impl').to_s
57
62
  end
58
63
 
59
64
  # @param block [Proc]
@@ -61,16 +66,19 @@ module Playwright
61
66
  return nil unless block.is_a?(Proc)
62
67
 
63
68
  -> (*args) {
64
- wrapped_args = args.map { |arg| wrap_channel_owner(arg) }
69
+ wrapped_args = args.map { |arg| wrap_impl(arg) }
65
70
  block.call(*wrapped_args)
66
71
  }
67
72
  end
68
73
 
69
- private def wrap_channel_owner(object)
70
- if object.is_a?(ChannelOwner)
74
+ private def wrap_impl(object)
75
+ case object
76
+ when ChannelOwner
71
77
  PlaywrightApi.from_channel_owner(object)
72
- elsif object.is_a?(Array)
73
- object.map { |obj| wrap_channel_owner(obj) }
78
+ when InputType
79
+ Factory.new(object, 'InputTypes').create
80
+ when Array
81
+ object.map { |obj| wrap_impl(obj) }
74
82
  else
75
83
  object
76
84
  end
@@ -9,7 +9,7 @@ module Playwright
9
9
  attr_writer :default_timeout, :default_navigation_timeout
10
10
 
11
11
  def navigation_timeout
12
- @default_navigation_timeout || @default_timeout || @parent&.navigation_timeout || EFAULT_TIMEOUT
12
+ @default_navigation_timeout || @default_timeout || @parent&.navigation_timeout || DEFAULT_TIMEOUT
13
13
  end
14
14
 
15
15
  def timeout
@@ -0,0 +1,19 @@
1
+ module Playwright
2
+ class UrlMatcher
3
+ # @param url [String|Regexp]
4
+ def initialize(url)
5
+ @url = url
6
+ end
7
+
8
+ def match?(target_url)
9
+ case @url
10
+ when String
11
+ @url == target_url
12
+ when Regexp
13
+ @url.match?(target_url)
14
+ else
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.6'
5
5
  end
@@ -6,18 +6,18 @@ module Playwright
6
6
  # Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might
7
7
  # have wildly different output.
8
8
  #
9
- # Blink - Chromium's rendering engine - has a concept of "accessibility tree", which is then translated into different
10
- # platform-specific APIs. Accessibility namespace gives users access to the Blink Accessibility Tree.
9
+ # Rendering engines of Chromium, Firefox and Webkit have a concept of "accessibility tree", which is then translated into
10
+ # different platform-specific APIs. Accessibility namespace gives access to this Accessibility Tree.
11
11
  #
12
- # Most of the accessibility tree gets filtered out when converting from Blink AX Tree to Platform-specific AX-Tree or by
13
- # assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing only the
14
- # "interesting" nodes of the tree.
12
+ # Most of the accessibility tree gets filtered out when converting from internal browser AX Tree to Platform-specific
13
+ # AX-Tree or by assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing
14
+ # only the "interesting" nodes of the tree.
15
15
  class Accessibility < PlaywrightApi
16
16
 
17
17
  # Captures the current state of the accessibility tree. The returned object represents the root accessible node of the
18
18
  # page.
19
19
  #
20
- # > **NOTE** The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
20
+ # > NOTE: The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
21
21
  # Playwright will discard them as well for an easier to process tree, unless `interestingOnly` is set to `false`.
22
22
  #
23
23
  # An example of dumping the entire accessibility tree:
@@ -28,6 +28,16 @@ module Playwright
28
28
  # console.log(snapshot);
29
29
  # ```
30
30
  #
31
+ # ```python async
32
+ # snapshot = await page.accessibility.snapshot()
33
+ # print(snapshot)
34
+ # ```
35
+ #
36
+ # ```python sync
37
+ # snapshot = page.accessibility.snapshot()
38
+ # print(snapshot)
39
+ # ```
40
+ #
31
41
  # An example of logging the focused node's name:
32
42
  #
33
43
  #
@@ -46,6 +56,36 @@ module Playwright
46
56
  # return null;
47
57
  # }
48
58
  # ```
59
+ #
60
+ # ```python async
61
+ # def find_focused_node(node):
62
+ # if (node.get("focused"))
63
+ # return node
64
+ # for child in (node.get("children") or []):
65
+ # found_node = find_focused_node(child)
66
+ # return found_node
67
+ # return None
68
+ #
69
+ # snapshot = await page.accessibility.snapshot()
70
+ # node = find_focused_node(snapshot)
71
+ # if node:
72
+ # print(node["name"])
73
+ # ```
74
+ #
75
+ # ```python sync
76
+ # def find_focused_node(node):
77
+ # if (node.get("focused"))
78
+ # return node
79
+ # for child in (node.get("children") or []):
80
+ # found_node = find_focused_node(child)
81
+ # return found_node
82
+ # return None
83
+ #
84
+ # snapshot = page.accessibility.snapshot()
85
+ # node = find_focused_node(snapshot)
86
+ # if node:
87
+ # print(node["name"])
88
+ # ```
49
89
  def snapshot(interestingOnly: nil, root: nil)
50
90
  raise NotImplementedError.new('snapshot is not implemented yet.')
51
91
  end
@@ -4,20 +4,20 @@ module Playwright
4
4
 
5
5
  # -- inherited from EventEmitter --
6
6
  # @nodoc
7
- def off(event, callback)
8
- wrap_channel_owner(@channel_owner.off(event, callback))
7
+ def on(event, callback)
8
+ wrap_impl(@impl.on(event, callback))
9
9
  end
10
10
 
11
11
  # -- inherited from EventEmitter --
12
12
  # @nodoc
13
- def once(event, callback)
14
- wrap_channel_owner(@channel_owner.once(event, callback))
13
+ def off(event, callback)
14
+ wrap_impl(@impl.off(event, callback))
15
15
  end
16
16
 
17
17
  # -- inherited from EventEmitter --
18
18
  # @nodoc
19
- def on(event, callback)
20
- wrap_channel_owner(@channel_owner.on(event, callback))
19
+ def once(event, callback)
20
+ wrap_impl(@impl.once(event, callback))
21
21
  end
22
22
  end
23
23
  end
@@ -1,5 +1,5 @@
1
1
  module Playwright
2
- # - extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
2
+ # - extends: [EventEmitter]
3
3
  #
4
4
  # A Browser is created via [`method: BrowserType.launch`]. An example of using a `Browser` to create a [Page]:
5
5
  #
@@ -15,8 +15,36 @@ module Playwright
15
15
  # })();
16
16
  # ```
17
17
  #
18
- # See `ChromiumBrowser`, [FirefoxBrowser] and [WebKitBrowser] for browser-specific features. Note that
19
- # [`method: BrowserType.launch`] always returns a specific browser instance, based on the browser being launched.
18
+ # ```python async
19
+ # import asyncio
20
+ # from playwright.async_api import async_playwright
21
+ #
22
+ # async def run(playwright):
23
+ # firefox = playwright.firefox
24
+ # browser = await firefox.launch()
25
+ # page = await browser.new_page()
26
+ # await page.goto("https://example.com")
27
+ # await browser.close()
28
+ #
29
+ # async def main():
30
+ # async with async_playwright() as playwright:
31
+ # await run(playwright)
32
+ # asyncio.run(main())
33
+ # ```
34
+ #
35
+ # ```python sync
36
+ # from playwright.sync_api import sync_playwright
37
+ #
38
+ # def run(playwright):
39
+ # firefox = playwright.firefox
40
+ # browser = firefox.launch()
41
+ # page = browser.new_page()
42
+ # page.goto("https://example.com")
43
+ # browser.close()
44
+ #
45
+ # with sync_playwright() as playwright:
46
+ # run(playwright)
47
+ # ```
20
48
  class Browser < PlaywrightApi
21
49
 
22
50
  # In case this browser is obtained using [`method: BrowserType.launch`], closes the browser and all of its pages (if any
@@ -27,7 +55,7 @@ module Playwright
27
55
  #
28
56
  # The `Browser` object itself is considered to be disposed and cannot be used anymore.
29
57
  def close
30
- wrap_channel_owner(@channel_owner.close)
58
+ wrap_impl(@impl.close)
31
59
  end
32
60
 
33
61
  # Returns an array of all open browser contexts. In a newly created browser, this will return zero browser contexts.
@@ -40,13 +68,27 @@ module Playwright
40
68
  # const context = await browser.newContext();
41
69
  # console.log(browser.contexts().length); // prints `1`
42
70
  # ```
71
+ #
72
+ # ```python async
73
+ # browser = await pw.webkit.launch()
74
+ # print(len(browser.contexts())) # prints `0`
75
+ # context = await browser.new_context()
76
+ # print(len(browser.contexts())) # prints `1`
77
+ # ```
78
+ #
79
+ # ```python sync
80
+ # browser = pw.webkit.launch()
81
+ # print(len(browser.contexts())) # prints `0`
82
+ # context = browser.new_context()
83
+ # print(len(browser.contexts())) # prints `1`
84
+ # ```
43
85
  def contexts
44
- wrap_channel_owner(@channel_owner.contexts)
86
+ wrap_impl(@impl.contexts)
45
87
  end
46
88
 
47
89
  # Indicates that the browser is connected.
48
90
  def connected?
49
- wrap_channel_owner(@channel_owner.connected?)
91
+ wrap_impl(@impl.connected?)
50
92
  end
51
93
 
52
94
  # Creates a new browser context. It won't share cookies/cache with other browser contexts.
@@ -62,6 +104,24 @@ module Playwright
62
104
  # await page.goto('https://example.com');
63
105
  # })();
64
106
  # ```
107
+ #
108
+ # ```python async
109
+ # browser = await playwright.firefox.launch() # or "chromium" or "webkit".
110
+ # # create a new incognito browser context.
111
+ # context = await browser.new_context()
112
+ # # create a new page in a pristine context.
113
+ # page = await context.new_page()
114
+ # await page.goto("https://example.com")
115
+ # ```
116
+ #
117
+ # ```python sync
118
+ # browser = playwright.firefox.launch() # or "chromium" or "webkit".
119
+ # # create a new incognito browser context.
120
+ # context = browser.new_context()
121
+ # # create a new page in a pristine context.
122
+ # page = context.new_page()
123
+ # page.goto("https://example.com")
124
+ # ```
65
125
  def new_context(
66
126
  acceptDownloads: nil,
67
127
  bypassCSP: nil,
@@ -87,7 +147,7 @@ module Playwright
87
147
  videoSize: nil,
88
148
  videosPath: nil,
89
149
  viewport: nil)
90
- wrap_channel_owner(@channel_owner.new_context(acceptDownloads: acceptDownloads, bypassCSP: bypassCSP, colorScheme: colorScheme, deviceScaleFactor: deviceScaleFactor, extraHTTPHeaders: extraHTTPHeaders, geolocation: geolocation, hasTouch: hasTouch, httpCredentials: httpCredentials, ignoreHTTPSErrors: ignoreHTTPSErrors, isMobile: isMobile, javaScriptEnabled: javaScriptEnabled, locale: locale, logger: logger, offline: offline, permissions: permissions, proxy: proxy, recordHar: recordHar, recordVideo: recordVideo, storageState: storageState, timezoneId: timezoneId, userAgent: userAgent, videoSize: videoSize, videosPath: videosPath, viewport: viewport))
150
+ wrap_impl(@impl.new_context(acceptDownloads: acceptDownloads, bypassCSP: bypassCSP, colorScheme: colorScheme, deviceScaleFactor: deviceScaleFactor, extraHTTPHeaders: extraHTTPHeaders, geolocation: geolocation, hasTouch: hasTouch, httpCredentials: httpCredentials, ignoreHTTPSErrors: ignoreHTTPSErrors, isMobile: isMobile, javaScriptEnabled: javaScriptEnabled, locale: locale, logger: logger, offline: offline, permissions: permissions, proxy: proxy, recordHar: recordHar, recordVideo: recordVideo, storageState: storageState, timezoneId: timezoneId, userAgent: userAgent, videoSize: videoSize, videosPath: videosPath, viewport: viewport))
91
151
  end
92
152
 
93
153
  # Creates a new page in a new browser context. Closing this page will close the context as well.
@@ -120,35 +180,35 @@ module Playwright
120
180
  videoSize: nil,
121
181
  videosPath: nil,
122
182
  viewport: nil)
123
- wrap_channel_owner(@channel_owner.new_page(acceptDownloads: acceptDownloads, bypassCSP: bypassCSP, colorScheme: colorScheme, deviceScaleFactor: deviceScaleFactor, extraHTTPHeaders: extraHTTPHeaders, geolocation: geolocation, hasTouch: hasTouch, httpCredentials: httpCredentials, ignoreHTTPSErrors: ignoreHTTPSErrors, isMobile: isMobile, javaScriptEnabled: javaScriptEnabled, locale: locale, logger: logger, offline: offline, permissions: permissions, proxy: proxy, recordHar: recordHar, recordVideo: recordVideo, storageState: storageState, timezoneId: timezoneId, userAgent: userAgent, videoSize: videoSize, videosPath: videosPath, viewport: viewport))
183
+ wrap_impl(@impl.new_page(acceptDownloads: acceptDownloads, bypassCSP: bypassCSP, colorScheme: colorScheme, deviceScaleFactor: deviceScaleFactor, extraHTTPHeaders: extraHTTPHeaders, geolocation: geolocation, hasTouch: hasTouch, httpCredentials: httpCredentials, ignoreHTTPSErrors: ignoreHTTPSErrors, isMobile: isMobile, javaScriptEnabled: javaScriptEnabled, locale: locale, logger: logger, offline: offline, permissions: permissions, proxy: proxy, recordHar: recordHar, recordVideo: recordVideo, storageState: storageState, timezoneId: timezoneId, userAgent: userAgent, videoSize: videoSize, videosPath: videosPath, viewport: viewport))
124
184
  end
125
185
 
126
186
  # Returns the browser version.
127
187
  def version
128
- wrap_channel_owner(@channel_owner.version)
188
+ wrap_impl(@impl.version)
129
189
  end
130
190
 
131
191
  # @nodoc
132
192
  def after_initialize
133
- wrap_channel_owner(@channel_owner.after_initialize)
193
+ wrap_impl(@impl.after_initialize)
134
194
  end
135
195
 
136
196
  # -- inherited from EventEmitter --
137
197
  # @nodoc
138
- def off(event, callback)
139
- wrap_channel_owner(@channel_owner.off(event, callback))
198
+ def on(event, callback)
199
+ wrap_impl(@impl.on(event, callback))
140
200
  end
141
201
 
142
202
  # -- inherited from EventEmitter --
143
203
  # @nodoc
144
- def once(event, callback)
145
- wrap_channel_owner(@channel_owner.once(event, callback))
204
+ def off(event, callback)
205
+ wrap_impl(@impl.off(event, callback))
146
206
  end
147
207
 
148
208
  # -- inherited from EventEmitter --
149
209
  # @nodoc
150
- def on(event, callback)
151
- wrap_channel_owner(@channel_owner.on(event, callback))
210
+ def once(event, callback)
211
+ wrap_impl(@impl.once(event, callback))
152
212
  end
153
213
  end
154
214
  end