puppeteer-ruby 0.0.27 → 0.31.1

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.
@@ -314,33 +314,23 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
314
314
  end
315
315
  end
316
316
 
317
- # `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
317
+ private def query_handler_manager
318
+ Puppeteer::QueryHandlerManager.instance
319
+ end
320
+
321
+ # `$()` in JavaScript.
318
322
  # @param selector [String]
319
- def S(selector)
320
- handle = evaluate_handle(
321
- '(element, selector) => element.querySelector(selector)',
322
- selector,
323
- )
324
- element = handle.as_element
325
-
326
- if element
327
- return element
328
- end
329
- handle.dispose
330
- nil
323
+ def query_selector(selector)
324
+ query_handler_manager.detect_query_handler(selector).query_one(self)
331
325
  end
326
+ alias_method :S, :query_selector
332
327
 
333
- # `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
328
+ # `$$()` in JavaScript.
334
329
  # @param selector [String]
335
- def SS(selector)
336
- handles = evaluate_handle(
337
- '(element, selector) => element.querySelectorAll(selector)',
338
- selector,
339
- )
340
- properties = handles.properties
341
- handles.dispose
342
- properties.values.map(&:as_element).compact
330
+ def query_selector_all(selector)
331
+ query_handler_manager.detect_query_handler(selector).query_all(self)
343
332
  end
333
+ alias_method :SS, :query_selector_all
344
334
 
345
335
  class ElementNotFoundError < StandardError
346
336
  def initialize(selector)
@@ -348,12 +338,12 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
348
338
  end
349
339
  end
350
340
 
351
- # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
341
+ # `$eval()` in JavaScript.
352
342
  # @param selector [String]
353
343
  # @param page_function [String]
354
344
  # @return [Object]
355
- def Seval(selector, page_function, *args)
356
- element_handle = S(selector)
345
+ def eval_on_selector(selector, page_function, *args)
346
+ element_handle = query_selector(selector)
357
347
  unless element_handle
358
348
  raise ElementNotFoundError.new(selector)
359
349
  end
@@ -362,25 +352,24 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
362
352
 
363
353
  result
364
354
  end
355
+ alias_method :Seval, :eval_on_selector
365
356
 
366
- define_async_method :async_Seval
357
+ define_async_method :async_eval_on_selector
367
358
 
368
- # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
359
+ # `$$eval()` in JavaScript.
369
360
  # @param selector [String]
370
361
  # @param page_function [String]
371
362
  # @return [Object]
372
- def SSeval(selector, page_function, *args)
373
- handles = evaluate_handle(
374
- '(element, selector) => Array.from(element.querySelectorAll(selector))',
375
- selector,
376
- )
363
+ def eval_on_selector_all(selector, page_function, *args)
364
+ handles = query_handler_manager.detect_query_handler(selector).query_all_array(self)
377
365
  result = handles.evaluate(page_function, *args)
378
366
  handles.dispose
379
367
 
380
368
  result
381
369
  end
370
+ alias_method :SSeval, :eval_on_selector_all
382
371
 
383
- define_async_method :async_SSeval
372
+ define_async_method :async_eval_on_selector_all
384
373
 
385
374
  # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
386
375
  # @param expression [String]
@@ -430,4 +419,10 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
430
419
  # https://en.wikipedia.org/wiki/Polygon#Simple_polygons
431
420
  quad.zip(quad.rotate).map { |p1, p2| (p1.x * p2.y - p2.x * p1.y) / 2 }.reduce(:+).abs
432
421
  end
422
+
423
+ # used in AriaQueryHandler
424
+ def query_ax_tree(accessible_name: nil, role: nil)
425
+ @remote_object.query_ax_tree(@client,
426
+ accessible_name: accessible_name, role: role)
427
+ end
433
428
  end
data/lib/puppeteer/env.rb CHANGED
@@ -14,10 +14,14 @@ class Puppeteer::Env
14
14
  def darwin?
15
15
  RUBY_PLATFORM.include?('darwin')
16
16
  end
17
+
18
+ def windows?
19
+ RUBY_PLATFORM =~ /mswin|mingw|cygwin/
20
+ end
17
21
  end
18
22
 
19
- class Puppeteer
20
- def self.env
21
- Puppeteer::Env.new
23
+ module Puppeteer
24
+ module_function def env
25
+ ::Puppeteer::Env.new
22
26
  end
23
27
  end
@@ -12,10 +12,21 @@ class Puppeteer::ExecutionContext
12
12
  @client = client
13
13
  @world = world
14
14
  @context_id = context_payload['id']
15
+ @context_name = context_payload['name']
15
16
  end
16
17
 
17
18
  attr_reader :client, :world
18
19
 
20
+ # only used in DOMWorld
21
+ private def _context_id
22
+ @context_id
23
+ end
24
+
25
+ # only used in DOMWorld::BindingFunction#add_binding_to_context
26
+ private def _context_name
27
+ @context_name
28
+ end
29
+
19
30
  # @return [Puppeteer::Frame]
20
31
  def frame
21
32
  if_present(@world) do |world|
@@ -223,6 +234,7 @@ class Puppeteer::ExecutionContext
223
234
  remote_object: Puppeteer::RemoteObject.new(response["object"]),
224
235
  )
225
236
  end
237
+ private define_async_method :async_adopt_backend_node_id
226
238
 
227
239
  # @param element_handle [Puppeteer::ElementHandle]
228
240
  # @return [Puppeteer::ElementHandle]
@@ -61,14 +61,15 @@ class Puppeteer::Frame
61
61
 
62
62
  define_async_method :async_evaluate
63
63
 
64
- # `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
64
+ # `$()` in JavaScript.
65
65
  # @param {string} selector
66
66
  # @return {!Promise<?Puppeteer.ElementHandle>}
67
- def S(selector)
68
- @main_world.S(selector)
67
+ def query_selector(selector)
68
+ @main_world.query_selector(selector)
69
69
  end
70
+ alias_method :S, :query_selector
70
71
 
71
- define_async_method :async_S
72
+ define_async_method :async_query_selector
72
73
 
73
74
  # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
74
75
  # @param {string} expression
@@ -79,36 +80,39 @@ class Puppeteer::Frame
79
80
 
80
81
  define_async_method :async_Sx
81
82
 
82
- # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
83
+ # `$eval()` in JavaScript.
83
84
  # @param {string} selector
84
85
  # @param {Function|string} pageFunction
85
86
  # @param {!Array<*>} args
86
87
  # @return {!Promise<(!Object|undefined)>}
87
- def Seval(selector, page_function, *args)
88
- @main_world.Seval(selector, page_function, *args)
88
+ def eval_on_selector(selector, page_function, *args)
89
+ @main_world.eval_on_selector(selector, page_function, *args)
89
90
  end
91
+ alias_method :Seval, :eval_on_selector
90
92
 
91
- define_async_method :async_Seval
93
+ define_async_method :async_eval_on_selector
92
94
 
93
- # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
95
+ # `$$eval()` in JavaScript.
94
96
  # @param {string} selector
95
97
  # @param {Function|string} pageFunction
96
98
  # @param {!Array<*>} args
97
99
  # @return {!Promise<(!Object|undefined)>}
98
- def SSeval(selector, page_function, *args)
99
- @main_world.SSeval(selector, page_function, *args)
100
+ def eval_on_selector_all(selector, page_function, *args)
101
+ @main_world.eval_on_selector_all(selector, page_function, *args)
100
102
  end
103
+ alias_method :SSeval, :eval_on_selector_all
101
104
 
102
- define_async_method :async_SSeval
105
+ define_async_method :async_eval_on_selector_all
103
106
 
104
- # `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
107
+ # `$$()` in JavaScript.
105
108
  # @param {string} selector
106
109
  # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
107
- def SS(selector)
108
- @main_world.SS(selector)
110
+ def query_selector_all(selector)
111
+ @main_world.query_selector_all(selector)
109
112
  end
113
+ alias_method :SS, :query_selector_all
110
114
 
111
- define_async_method :async_SS
115
+ define_async_method :async_query_selector_all
112
116
 
113
117
  # @return [String]
114
118
  def content
@@ -149,16 +153,19 @@ class Puppeteer::Frame
149
153
  @detached
150
154
  end
151
155
 
152
- # @param style_tag [Puppeteer::Page::ScriptTag]
153
- # @return {!Promise<!ElementHandle>}
154
- def add_script_tag(script_tag)
155
- @main_world.add_script_tag(script_tag)
156
+ # @param url [String?]
157
+ # @param path [String?]
158
+ # @param content [String?]
159
+ # @param type [String?]
160
+ def add_script_tag(url: nil, path: nil, content: nil, type: nil)
161
+ @main_world.add_script_tag(url: url, path: path, content: content, type: type)
156
162
  end
157
163
 
158
- # @param style_tag [Puppeteer::Page::StyleTag]
159
- # @return {!Promise<!ElementHandle>}
160
- def add_style_tag(style_tag)
161
- @main_world.add_style_tag(style_tag)
164
+ # @param url [String?]
165
+ # @param path [String?]
166
+ # @param content [String?]
167
+ def add_style_tag(url: nil, path: nil, content: nil)
168
+ @main_world.add_style_tag(url: url, path: path, content: content)
162
169
  end
163
170
 
164
171
  # @param selector [String]
@@ -32,6 +32,14 @@ module Puppeteer::Launcher
32
32
  when Firefox
33
33
  '/Applications/Firefox Nightly.app/Contents/MacOS/firefox'
34
34
  end
35
+ elsif Puppeteer.env.windows?
36
+ case self
37
+ when Chrome
38
+ 'C:\Program Files\Google\Chrome\Application\chrome.exe'
39
+ # 'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
40
+ when Firefox
41
+ 'C:\Program Files\Firefox Nightly\firefox.exe'
42
+ end
35
43
  else
36
44
  case self
37
45
  when Chrome
@@ -92,7 +92,7 @@ module Puppeteer::Launcher
92
92
  '--disable-default-apps',
93
93
  '--disable-dev-shm-usage',
94
94
  '--disable-extensions',
95
- '--disable-features=TranslateUI',
95
+ '--disable-features=Translate',
96
96
  '--disable-hang-monitor',
97
97
  '--disable-ipc-flooding-protection',
98
98
  '--disable-popup-blocking',
@@ -105,6 +105,9 @@ module Puppeteer::Launcher
105
105
  '--enable-automation',
106
106
  '--password-store=basic',
107
107
  '--use-mock-keychain',
108
+ # TODO(sadym): remove '--enable-blink-features=IdleDetection'
109
+ # once IdleDetection is turned on by default.
110
+ '--enable-blink-features=IdleDetection',
108
111
  ]
109
112
 
110
113
  if chrome_arg_options.user_data_dir
@@ -1,4 +1,5 @@
1
1
  require 'base64'
2
+ require 'json'
2
3
  require "stringio"
3
4
 
4
5
  require_relative './page/pdf_options'
@@ -98,7 +99,9 @@ class Puppeteer::Page
98
99
  @client.on_event('Page.loadEventFired') do |event|
99
100
  emit_event(PageEmittedEvents::Load)
100
101
  end
101
- # client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
102
+ @client.on('Runtime.consoleAPICalled') do |event|
103
+ handle_console_api(event)
104
+ end
102
105
  # client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));
103
106
  @client.on_event('Page.javascriptDialogOpening') do |event|
104
107
  handle_dialog_opening(event)
@@ -199,6 +202,7 @@ class Puppeteer::Page
199
202
  end
200
203
 
201
204
  attr_reader :javascript_enabled, :target
205
+ alias_method :javascript_enabled?, :javascript_enabled
202
206
 
203
207
  def browser
204
208
  @target.browser
@@ -276,23 +280,25 @@ class Puppeteer::Page
276
280
  @timeout_settings.default_timeout = timeout
277
281
  end
278
282
 
279
- # `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
283
+ # `$()` in JavaScript.
280
284
  # @param {string} selector
281
285
  # @return {!Promise<?Puppeteer.ElementHandle>}
282
- def S(selector)
283
- main_frame.S(selector)
286
+ def query_selector(selector)
287
+ main_frame.query_selector(selector)
284
288
  end
289
+ alias_method :S, :query_selector
285
290
 
286
- define_async_method :async_S
291
+ define_async_method :async_query_selector
287
292
 
288
- # `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
293
+ # `$$()` in JavaScript.
289
294
  # @param {string} selector
290
295
  # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
291
- def SS(selector)
292
- main_frame.SS(selector)
296
+ def query_selector_all(selector)
297
+ main_frame.query_selector_all(selector)
293
298
  end
299
+ alias_method :SS, :query_selector_all
294
300
 
295
- define_async_method :async_SS
301
+ define_async_method :async_query_selector_all
296
302
 
297
303
  # @param {Function|string} pageFunction
298
304
  # @param {!Array<*>} args
@@ -311,25 +317,27 @@ class Puppeteer::Page
311
317
  context.query_objects(prototype_handle)
312
318
  end
313
319
 
314
- # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
320
+ # `$eval()` in JavaScript.
315
321
  # @param selector [String]
316
322
  # @param page_function [String]
317
323
  # @return [Object]
318
- def Seval(selector, page_function, *args)
319
- main_frame.Seval(selector, page_function, *args)
324
+ def eval_on_selector(selector, page_function, *args)
325
+ main_frame.eval_on_selector(selector, page_function, *args)
320
326
  end
327
+ alias_method :Seval, :eval_on_selector
321
328
 
322
- define_async_method :async_Seval
329
+ define_async_method :async_eval_on_selector
323
330
 
324
- # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
331
+ # `$$eval()` in JavaScript.
325
332
  # @param selector [String]
326
333
  # @param page_function [String]
327
334
  # @return [Object]
328
- def SSeval(selector, page_function, *args)
329
- main_frame.SSeval(selector, page_function, *args)
335
+ def eval_on_selector_all(selector, page_function, *args)
336
+ main_frame.eval_on_selector_all(selector, page_function, *args)
330
337
  end
338
+ alias_method :SSeval, :eval_on_selector_all
331
339
 
332
- define_async_method :async_SSeval
340
+ define_async_method :async_eval_on_selector_all
333
341
 
334
342
  # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
335
343
  # @param {string} expression
@@ -369,37 +377,19 @@ class Puppeteer::Page
369
377
  end
370
378
  end
371
379
 
372
- class ScriptTag
373
- # @param {!{content?: string, path?: string, type?: string, url?: string}} options
374
- def initialize(content: nil, path: nil, type: nil, url: nil)
375
- @content = content
376
- @path = path
377
- @type = type
378
- @url = url
379
- end
380
- attr_reader :content, :path, :type, :url
381
- end
382
-
383
- # @param style_tag [Puppeteer::Page::ScriptTag]
384
- # @return {!Promise<!ElementHandle>}
385
- def add_script_tag(script_tag)
386
- main_frame.add_script_tag(script_tag)
387
- end
388
-
389
- class StyleTag
390
- # @param {!{content?: string, path?: string, url?: string}} options
391
- def initialize(content: nil, path: nil, url: nil)
392
- @content = content
393
- @path = path
394
- @url = url
395
- end
396
- attr_reader :content, :path, :url
380
+ # @param url [String?]
381
+ # @param path [String?]
382
+ # @param content [String?]
383
+ # @param type [String?]
384
+ def add_script_tag(url: nil, path: nil, content: nil, type: nil)
385
+ main_frame.add_script_tag(url: url, path: path, content: content, type: type)
397
386
  end
398
387
 
399
- # @param style_tag [Puppeteer::Page::StyleTag]
400
- # @return {!Promise<!ElementHandle>}
401
- def add_style_tag(style_tag)
402
- main_frame.add_style_tag(style_tag)
388
+ # @param url [String?]
389
+ # @param path [String?]
390
+ # @param content [String?]
391
+ def add_style_tag(url: nil, path: nil, content: nil)
392
+ main_frame.add_style_tag(url: url, path: path, content: content)
403
393
  end
404
394
 
405
395
  # /**
@@ -490,30 +480,31 @@ class Puppeteer::Page
490
480
  emit_event(PageEmittedEvents::PageError, err)
491
481
  end
492
482
 
493
- # /**
494
- # * @param {!Protocol.Runtime.consoleAPICalledPayload} event
495
- # */
496
- # async _onConsoleAPI(event) {
497
- # if (event.executionContextId === 0) {
498
- # // DevTools protocol stores the last 1000 console messages. These
499
- # // messages are always reported even for removed execution contexts. In
500
- # // this case, they are marked with executionContextId = 0 and are
501
- # // reported upon enabling Runtime agent.
502
- # //
503
- # // Ignore these messages since:
504
- # // - there's no execution context we can use to operate with message
505
- # // arguments
506
- # // - these messages are reported before Puppeteer clients can subscribe
507
- # // to the 'console'
508
- # // page event.
509
- # //
510
- # // @see https://github.com/puppeteer/puppeteer/issues/3865
511
- # return;
512
- # }
513
- # const context = this._frameManager.executionContextById(event.executionContextId);
514
- # const values = event.args.map(arg => createJSHandle(context, arg));
515
- # this._addConsoleMessage(event.type, values, event.stackTrace);
516
- # }
483
+ private def handle_console_api(event)
484
+ if event['executionContextId'] == 0
485
+ # DevTools protocol stores the last 1000 console messages. These
486
+ # messages are always reported even for removed execution contexts. In
487
+ # this case, they are marked with executionContextId = 0 and are
488
+ # reported upon enabling Runtime agent.
489
+ #
490
+ # Ignore these messages since:
491
+ # - there's no execution context we can use to operate with message
492
+ # arguments
493
+ # - these messages are reported before Puppeteer clients can subscribe
494
+ # to the 'console'
495
+ # page event.
496
+ #
497
+ # @see https://github.com/puppeteer/puppeteer/issues/3865
498
+ return
499
+ end
500
+
501
+ context = @frame_manager.execution_context_by_id(event['executionContextId'])
502
+ values = event['args'].map do |arg|
503
+ remote_object = Puppeteer::RemoteObject.new(arg)
504
+ Puppeteer::JSHandle.create(context: context, remote_object: remote_object)
505
+ end
506
+ add_console_message(event['type'], values, event['stackTrace'])
507
+ end
517
508
 
518
509
  # /**
519
510
  # * @param {!Protocol.Runtime.bindingCalledPayload} event
@@ -566,32 +557,23 @@ class Puppeteer::Page
566
557
  # }
567
558
  # }
568
559
 
569
- # /**
570
- # * @param {string} type
571
- # * @param {!Array<!Puppeteer.JSHandle>} args
572
- # * @param {Protocol.Runtime.StackTrace=} stackTrace
573
- # */
574
- # _addConsoleMessage(type, args, stackTrace) {
575
- # if (!this.listenerCount(PageEmittedEvents::Console)) {
576
- # args.forEach(arg => arg.dispose());
577
- # return;
578
- # }
579
- # const textTokens = [];
580
- # for (const arg of args) {
581
- # const remoteObject = arg._remoteObject;
582
- # if (remoteObject.objectId)
583
- # textTokens.push(arg.toString());
584
- # else
585
- # textTokens.push(helper.valueFromRemoteObject(remoteObject));
586
- # }
587
- # const location = stackTrace && stackTrace.callFrames.length ? {
588
- # url: stackTrace.callFrames[0].url,
589
- # lineNumber: stackTrace.callFrames[0].lineNumber,
590
- # columnNumber: stackTrace.callFrames[0].columnNumber,
591
- # } : {};
592
- # const message = new ConsoleMessage(type, textTokens.join(' '), args, location);
593
- # this.emit(PageEmittedEvents::Console, message);
594
- # }
560
+ private def add_console_message(type, args, stack_trace)
561
+ text_tokens = args.map { |arg| arg.remote_object.value }
562
+
563
+ call_frame = stack_trace['callFrames']&.first
564
+ location =
565
+ if call_frame
566
+ Puppeteer::ConsoleMessage::Location.new(
567
+ url: call_frame['url'],
568
+ line_number: call_frame['lineNumber'],
569
+ column_number: call_frame['columnNumber'],
570
+ )
571
+ else
572
+ nil
573
+ end
574
+ console_message = Puppeteer::ConsoleMessage.new(type, text_tokens.join(' '), args, location)
575
+ emit_event(PageEmittedEvents::Console, console_message)
576
+ end
595
577
 
596
578
  private def handle_dialog_opening(event)
597
579
  dialog_type = event['type']
@@ -829,6 +811,21 @@ class Puppeteer::Page
829
811
  end
830
812
  end
831
813
 
814
+ # @param is_user_active [Boolean]
815
+ # @param is_screen_unlocked [Boolean]
816
+ def emulate_idle_state(is_user_active: nil, is_screen_unlocked: nil)
817
+ overrides = {
818
+ isUserActive: is_user_active,
819
+ isScreenUnlocked: is_screen_unlocked,
820
+ }.compact
821
+
822
+ if overrides.empty?
823
+ @client.send_message('Emulation.clearIdleOverride')
824
+ else
825
+ @client.send_message('Emulation.setIdleOverride', overrides)
826
+ end
827
+ end
828
+
832
829
  # @param viewport [Viewport]
833
830
  def viewport=(viewport)
834
831
  needs_reload = @emulation_manager.emulate_viewport(viewport)
@@ -847,14 +844,41 @@ class Puppeteer::Page
847
844
 
848
845
  define_async_method :async_evaluate
849
846
 
850
- # /**
851
- # * @param {Function|string} pageFunction
852
- # * @param {!Array<*>} args
853
- # */
854
- # async evaluateOnNewDocument(pageFunction, ...args) {
855
- # const source = helper.evaluationString(pageFunction, ...args);
856
- # await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source });
857
- # }
847
+ class JavaScriptFunction
848
+ def initialize(expression, args)
849
+ @expression = expression
850
+ @args = args
851
+ end
852
+
853
+ def source
854
+ "(#{@expression})(#{arguments})"
855
+ end
856
+
857
+ private def arguments
858
+ @args.map { |arg| arg.nil? ? nil : JSON.dump(arg) }.join(", ")
859
+ end
860
+ end
861
+
862
+ class JavaScriptExpression
863
+ def initialize(expression)
864
+ @expression = expression
865
+ end
866
+
867
+ def source
868
+ @expression
869
+ end
870
+ end
871
+
872
+ def evaluate_on_new_document(page_function, *args)
873
+ source =
874
+ if ['=>', 'async', 'function'].any? { |keyword| page_function.include?(keyword) }
875
+ JavaScriptFunction.new(page_function, args).source
876
+ else
877
+ JavaScriptExpression.new(page_function).source
878
+ end
879
+
880
+ @client.send_message('Page.addScriptToEvaluateOnNewDocument', source: source)
881
+ end
858
882
 
859
883
  # @param {boolean} enabled
860
884
  def cache_enabled=(enabled)
@@ -965,7 +989,7 @@ class Puppeteer::Page
965
989
 
966
990
  def read
967
991
  out = StringIO.new
968
- File.open(@path, 'w') do |file|
992
+ File.open(@path, 'wb') do |file|
969
993
  eof = false
970
994
  until eof
971
995
  response = @client.send_message('IO.read', handle: @handle)