puppeteer-ruby 0.0.13 → 0.0.18

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +2 -14
  3. data/.github/workflows/docs.yml +45 -0
  4. data/.github/workflows/reviewdog.yml +15 -0
  5. data/.rubocop.yml +39 -3
  6. data/README.md +54 -1
  7. data/lib/puppeteer.rb +10 -2
  8. data/lib/puppeteer/browser.rb +53 -34
  9. data/lib/puppeteer/browser_context.rb +35 -5
  10. data/lib/puppeteer/cdp_session.rb +3 -19
  11. data/lib/puppeteer/concurrent_ruby_utils.rb +6 -0
  12. data/lib/puppeteer/connection.rb +9 -16
  13. data/lib/puppeteer/debug_print.rb +1 -1
  14. data/lib/puppeteer/define_async_method.rb +23 -0
  15. data/lib/puppeteer/dom_world.rb +84 -73
  16. data/lib/puppeteer/element_handle.rb +46 -63
  17. data/lib/puppeteer/emulation_manager.rb +2 -6
  18. data/lib/puppeteer/env.rb +18 -0
  19. data/lib/puppeteer/execution_context.rb +1 -6
  20. data/lib/puppeteer/frame.rb +46 -46
  21. data/lib/puppeteer/frame_manager.rb +7 -25
  22. data/lib/puppeteer/js_handle.rb +39 -38
  23. data/lib/puppeteer/keyboard.rb +9 -29
  24. data/lib/puppeteer/mouse.rb +20 -24
  25. data/lib/puppeteer/network_manager.rb +163 -5
  26. data/lib/puppeteer/page.rb +221 -181
  27. data/lib/puppeteer/page/pdf_options.rb +166 -0
  28. data/lib/puppeteer/remote_object.rb +18 -5
  29. data/lib/puppeteer/request.rb +330 -0
  30. data/lib/puppeteer/response.rb +113 -0
  31. data/lib/puppeteer/touch_screen.rb +2 -7
  32. data/lib/puppeteer/version.rb +1 -1
  33. data/lib/puppeteer/wait_task.rb +3 -5
  34. data/lib/puppeteer/web_socket.rb +7 -0
  35. data/puppeteer-ruby.gemspec +2 -1
  36. metadata +25 -103
  37. data/docs/Puppeteer.html +0 -2338
  38. data/docs/Puppeteer/AsyncAwaitBehavior.html +0 -105
  39. data/docs/Puppeteer/Browser.html +0 -2258
  40. data/docs/Puppeteer/BrowserContext.html +0 -809
  41. data/docs/Puppeteer/BrowserFetcher.html +0 -214
  42. data/docs/Puppeteer/BrowserRunner.html +0 -914
  43. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +0 -477
  44. data/docs/Puppeteer/CDPSession.html +0 -813
  45. data/docs/Puppeteer/CDPSession/Error.html +0 -124
  46. data/docs/Puppeteer/ConcurrentRubyUtils.html +0 -438
  47. data/docs/Puppeteer/Connection.html +0 -964
  48. data/docs/Puppeteer/Connection/MessageCallback.html +0 -434
  49. data/docs/Puppeteer/Connection/ProtocolError.html +0 -216
  50. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +0 -217
  51. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +0 -244
  52. data/docs/Puppeteer/ConsoleMessage.html +0 -565
  53. data/docs/Puppeteer/ConsoleMessage/Location.html +0 -433
  54. data/docs/Puppeteer/DOMWorld.html +0 -2293
  55. data/docs/Puppeteer/DOMWorld/DetachedError.html +0 -124
  56. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +0 -124
  57. data/docs/Puppeteer/DebugPrint.html +0 -233
  58. data/docs/Puppeteer/Device.html +0 -470
  59. data/docs/Puppeteer/Devices.html +0 -139
  60. data/docs/Puppeteer/ElementHandle.html +0 -2542
  61. data/docs/Puppeteer/ElementHandle/BoundingBox.html +0 -507
  62. data/docs/Puppeteer/ElementHandle/BoxModel.html +0 -404
  63. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +0 -206
  64. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +0 -206
  65. data/docs/Puppeteer/ElementHandle/Point.html +0 -492
  66. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +0 -124
  67. data/docs/Puppeteer/EmulationManager.html +0 -454
  68. data/docs/Puppeteer/EventCallbackable.html +0 -499
  69. data/docs/Puppeteer/EventCallbackable/EventListeners.html +0 -435
  70. data/docs/Puppeteer/ExecutionContext.html +0 -998
  71. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +0 -124
  72. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +0 -357
  73. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +0 -389
  74. data/docs/Puppeteer/FileChooser.html +0 -455
  75. data/docs/Puppeteer/Frame.html +0 -3835
  76. data/docs/Puppeteer/FrameManager.html +0 -2410
  77. data/docs/Puppeteer/FrameManager/NavigationError.html +0 -124
  78. data/docs/Puppeteer/IfPresent.html +0 -222
  79. data/docs/Puppeteer/JSHandle.html +0 -1352
  80. data/docs/Puppeteer/Keyboard.html +0 -1557
  81. data/docs/Puppeteer/Keyboard/KeyDefinition.html +0 -831
  82. data/docs/Puppeteer/Keyboard/KeyDescription.html +0 -603
  83. data/docs/Puppeteer/Launcher.html +0 -237
  84. data/docs/Puppeteer/Launcher/Base.html +0 -385
  85. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +0 -124
  86. data/docs/Puppeteer/Launcher/BrowserOptions.html +0 -441
  87. data/docs/Puppeteer/Launcher/Chrome.html +0 -674
  88. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +0 -382
  89. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +0 -531
  90. data/docs/Puppeteer/Launcher/LaunchOptions.html +0 -893
  91. data/docs/Puppeteer/LifecycleWatcher.html +0 -834
  92. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +0 -363
  93. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +0 -206
  94. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +0 -124
  95. data/docs/Puppeteer/Mouse.html +0 -1095
  96. data/docs/Puppeteer/Mouse/Button.html +0 -136
  97. data/docs/Puppeteer/NetworkManager.html +0 -901
  98. data/docs/Puppeteer/NetworkManager/Credentials.html +0 -385
  99. data/docs/Puppeteer/Page.html +0 -6173
  100. data/docs/Puppeteer/Page/FileChooserTimeoutError.html +0 -206
  101. data/docs/Puppeteer/Page/ScreenshotOptions.html +0 -845
  102. data/docs/Puppeteer/Page/ScriptTag.html +0 -555
  103. data/docs/Puppeteer/Page/StyleTag.html +0 -448
  104. data/docs/Puppeteer/Page/TargetCrashedError.html +0 -124
  105. data/docs/Puppeteer/RemoteObject.html +0 -1087
  106. data/docs/Puppeteer/Target.html +0 -1336
  107. data/docs/Puppeteer/Target/InitializeFailure.html +0 -124
  108. data/docs/Puppeteer/Target/TargetInfo.html +0 -729
  109. data/docs/Puppeteer/TimeoutError.html +0 -135
  110. data/docs/Puppeteer/TimeoutSettings.html +0 -496
  111. data/docs/Puppeteer/TouchScreen.html +0 -464
  112. data/docs/Puppeteer/Viewport.html +0 -837
  113. data/docs/Puppeteer/WaitTask.html +0 -637
  114. data/docs/Puppeteer/WaitTask/TerminatedError.html +0 -124
  115. data/docs/Puppeteer/WaitTask/TimeoutError.html +0 -206
  116. data/docs/Puppeteer/WebSocket.html +0 -673
  117. data/docs/Puppeteer/WebSocket/DriverImpl.html +0 -412
  118. data/docs/Puppeteer/WebSocket/TransportError.html +0 -124
  119. data/docs/Puppeteer/WebSocketTransport.html +0 -600
  120. data/docs/Puppeteer/WebSocktTransportError.html +0 -124
  121. data/docs/_index.html +0 -816
  122. data/docs/class_list.html +0 -51
  123. data/docs/css/common.css +0 -1
  124. data/docs/css/full_list.css +0 -58
  125. data/docs/css/style.css +0 -496
  126. data/docs/file.README.html +0 -125
  127. data/docs/file_list.html +0 -56
  128. data/docs/frames.html +0 -17
  129. data/docs/index.html +0 -125
  130. data/docs/js/app.js +0 -314
  131. data/docs/js/full_list.js +0 -216
  132. data/docs/js/jquery.js +0 -4
  133. data/docs/method_list.html +0 -4123
  134. data/docs/top-level-namespace.html +0 -126
  135. data/lib/puppeteer/async_await_behavior.rb +0 -38
@@ -4,7 +4,7 @@ class Puppeteer::FrameManager
4
4
  include Puppeteer::DebugPrint
5
5
  include Puppeteer::IfPresent
6
6
  include Puppeteer::EventCallbackable
7
- using Puppeteer::AsyncAwaitBehavior
7
+ using Puppeteer::DefineAsyncMethod
8
8
 
9
9
  UTILITY_WORLD_NAME = '__puppeteer_utility_world__'
10
10
 
@@ -23,7 +23,6 @@ class Puppeteer::FrameManager
23
23
 
24
24
  # @type {!Map<number, !ExecutionContext>}
25
25
  @context_id_to_context = {}
26
- @context_id_created = {}
27
26
 
28
27
  # @type {!Set<string>}
29
28
  @isolated_worlds = Set.new
@@ -74,9 +73,7 @@ class Puppeteer::FrameManager
74
73
  @network_manager.init
75
74
  end
76
75
 
77
- async def async_init
78
- init
79
- end
76
+ define_async_method :async_init
80
77
 
81
78
  attr_reader :network_manager
82
79
 
@@ -281,7 +278,7 @@ class Puppeteer::FrameManager
281
278
  # @param url [String]
282
279
  def handle_frame_navigated_within_document(frame_id, url)
283
280
  frame = @frames[frame_id]
284
- return if !frame
281
+ return unless frame
285
282
  frame.navigated_within_document(url)
286
283
  emit_event 'Events.FrameManager.FrameNavigatedWithinDocument', frame
287
284
  emit_event 'Events.FrameManager.FrameNavigated', frame
@@ -320,44 +317,29 @@ class Puppeteer::FrameManager
320
317
  world.context = context
321
318
  end
322
319
  @context_id_to_context[context_payload['id']] = context
323
- @context_id_created[context_payload['id']] = Time.now
324
320
  end
325
321
 
326
322
  # @param {number} executionContextId
327
323
  def handle_execution_context_destroyed(execution_context_id)
328
324
  context = @context_id_to_context[execution_context_id]
329
- return if !context
325
+ return unless context
330
326
  @context_id_to_context.delete(execution_context_id)
331
- @context_id_created.delete(execution_context_id)
332
327
  if context.world
333
328
  context.world.delete_context(execution_context_id)
334
329
  end
335
330
  end
336
331
 
337
332
  def handle_execution_contexts_cleared
338
- # executionContextsCleared is often notified after executionContextCreated.
339
- # D, [2020-04-06T01:47:03.101227 #13823] DEBUG -- : RECV << {"method"=>"Runtime.executionContextCreated", "params"=>{"context"=>{"id"=>5, "origin"=>"https://github.com", "name"=>"", "auxData"=>{"isDefault"=>true, "type"=>"default", "frameId"=>"71C347B70848B89DDDEFAA8AB5B0BC92"}}}, "sessionId"=>"53F088EED260C28001D26A019F95D9E3"}
340
- # D, [2020-04-06T01:47:03.101439 #13823] DEBUG -- : RECV << {"method"=>"Page.frameNavigated", "params"=>{"frame"=>{"id"=>"71C347B70848B89DDDEFAA8AB5B0BC92", "loaderId"=>"80338225D035AC96BAE8F6D4E81C7D51", "url"=>"https://github.com/search?q=puppeteer", "securityOrigin"=>"https://github.com", "mimeType"=>"text/html"}}, "sessionId"=>"53F088EED260C28001D26A019F95D9E3"}
341
- # D, [2020-04-06T01:47:03.101325 #13823] DEBUG -- : RECV << {"method"=>"Target.targetInfoChanged", "params"=>{"targetInfo"=>{"targetId"=>"71C347B70848B89DDDEFAA8AB5B0BC92", "type"=>"page", "title"=>"https://github.com/search?q=puppeteer", "url"=>"https://github.com/search?q=puppeteer", "attached"=>true, "browserContextId"=>"AF37BC660284CE1552B4ECB147BE9305"}}}
342
- # D, [2020-04-06T01:47:03.101269 #13823] DEBUG -- : RECV << {"method"=>"Runtime.executionContextsCleared", "params"=>{}, "sessionId"=>"53F088EED260C28001D26A019F95D9E3"}
343
- # it unexpectedly clears the created execution context.
344
- # To avoid the problem, just skip recent created ids.
345
- now = Time.now
346
- context_ids_to_skip = @context_id_created.select { |k, v| now - v < 1 }.keys
347
- @context_id_to_context.reject { |k, v| context_ids_to_skip.include?(k) }.each do |execution_context_id, context|
333
+ @context_id_to_context.each do |execution_context_id, context|
348
334
  if context.world
349
335
  context.world.delete_context(execution_context_id)
350
336
  end
351
337
  end
352
- @context_id_to_context.select! { |k, v| context_ids_to_skip.include?(k) }
338
+ @context_id_to_context.clear
353
339
  end
354
340
 
355
341
  def execution_context_by_id(context_id)
356
- context = @context_id_to_context[context_id]
357
- if !context
358
- raise "INTERNAL ERROR: missing context with id = #{context_id}"
359
- end
360
- context
342
+ @context_id_to_context[context_id] or raise "INTERNAL ERROR: missing context with id = #{context_id}"
361
343
  end
362
344
 
363
345
  # @param {!Frame} frame
@@ -1,5 +1,6 @@
1
1
  class Puppeteer::JSHandle
2
- using Puppeteer::AsyncAwaitBehavior
2
+ using Puppeteer::DefineAsyncMethod
3
+ include Puppeteer::IfPresent
3
4
 
4
5
  # @param context [Puppeteer::ExecutionContext]
5
6
  # @param remote_object [Puppeteer::RemoteObject]
@@ -46,11 +47,7 @@ class Puppeteer::JSHandle
46
47
  execution_context.evaluate(page_function, self, *args)
47
48
  end
48
49
 
49
- # @param page_function [String]
50
- # @return [Future<Object>]
51
- async def async_evaluate(page_function, *args)
52
- evaluate(page_function, *args)
53
- end
50
+ define_async_method :async_evaluate
54
51
 
55
52
  # @param page_function [String]
56
53
  # @param args {Array<*>}
@@ -59,28 +56,31 @@ class Puppeteer::JSHandle
59
56
  execution_context.evaluate_handle(page_function, self, *args)
60
57
  end
61
58
 
62
- # @param page_function [String]
63
- # @param args {Array<*>}
64
- # @return [Future<Puppeteer::JSHandle>]
65
- async def async_evaluate_handle(page_function, *args)
66
- evaluate_handle(page_function, *args)
59
+ define_async_method :async_evaluate_handle
60
+
61
+ # getProperty(propertyName) in JavaScript
62
+ # @param name [String]
63
+ # @return [Puppeteer::JSHandle]
64
+ def property(name)
65
+ js = <<~JAVASCRIPT
66
+ (object, propertyName) => {
67
+ const result = {__proto__: null};
68
+ result[propertyName] = object[propertyName];
69
+ return result;
70
+ }
71
+ JAVASCRIPT
72
+ object_handle = evaluate_handle(js, name)
73
+ properties = object_handle.properties
74
+ result = properties[name]
75
+ object_handle.dispose
76
+ result
67
77
  end
68
78
 
69
- # /**
70
- # * @param {string} propertyName
71
- # * @return {!Promise<?JSHandle>}
72
- # */
73
- # async getProperty(propertyName) {
74
- # const objectHandle = await this.evaluateHandle((object, propertyName) => {
75
- # const result = {__proto__: null};
76
- # result[propertyName] = object[propertyName];
77
- # return result;
78
- # }, propertyName);
79
- # const properties = await objectHandle.getProperties();
80
- # const result = properties.get(propertyName) || null;
81
- # await objectHandle.dispose();
82
- # return result;
83
- # }
79
+ # @param name [String]
80
+ # @return [Puppeteer::JSHandle]
81
+ def [](name)
82
+ property(name)
83
+ end
84
84
 
85
85
  # getProperties in JavaScript.
86
86
  # @return [Hash<String, JSHandle>]
@@ -110,7 +110,7 @@ class Puppeteer::JSHandle
110
110
  #
111
111
  # However it would be better that RemoteObject is responsible for
112
112
  # the logic `if (this._remoteObject.objectId) { ... }`.
113
- @remote_object.evaluate_self(@client) || @remote_object.value
113
+ @remote_object.evaluate_self(@client)&.value || @remote_object.value
114
114
  end
115
115
 
116
116
  def as_element
@@ -128,15 +128,16 @@ class Puppeteer::JSHandle
128
128
  @disposed
129
129
  end
130
130
 
131
- # /**
132
- # * @override
133
- # * @return {string}
134
- # */
135
- # toString() {
136
- # if (this._remoteObject.objectId) {
137
- # const type = this._remoteObject.subtype || this._remoteObject.type;
138
- # return 'JSHandle@' + type;
139
- # }
140
- # return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject);
141
- # }
131
+ def to_s
132
+ # original logic was:
133
+ # if (this._remoteObject.objectId) {
134
+ # const type = this._remoteObject.subtype || this._remoteObject.type;
135
+ # return 'JSHandle@' + type;
136
+ # }
137
+ # return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject);
138
+ #
139
+ # However it would be better that RemoteObject is responsible for
140
+ # the logic `if (this._remoteObject.objectId) { ... }`.
141
+ if_present(@remote_object.type_str) { |type_str| "JSHandle@#{type_str}" } || "JSHandle:#{@remote_object.value || 'undefined'}"
142
+ end
142
143
  end
@@ -2,7 +2,7 @@ require_relative './keyboard/key_description'
2
2
  require_relative './keyboard/us_keyboard_layout'
3
3
 
4
4
  class Puppeteer::Keyboard
5
- using Puppeteer::AsyncAwaitBehavior
5
+ using Puppeteer::DefineAsyncMethod
6
6
 
7
7
  # @param {!Puppeteer.CDPSession} client
8
8
  def initialize(client)
@@ -38,12 +38,7 @@ class Puppeteer::Keyboard
38
38
  @client.send_message('Input.dispatchKeyEvent', params)
39
39
  end
40
40
 
41
- # @param key [String]
42
- # @param text [String]
43
- # @return [Future]
44
- async def async_down(key, text: nil)
45
- down(key, text)
46
- end
41
+ define_async_method :async_down
47
42
 
48
43
  # @param {string} key
49
44
  # @return {number}
@@ -127,22 +122,14 @@ class Puppeteer::Keyboard
127
122
  )
128
123
  end
129
124
 
130
- # @param key [String]
131
- # @return [Future]
132
- async def async_up(key)
133
- up(key)
134
- end
125
+ define_async_method :async_up
135
126
 
136
127
  # @param char [string]
137
128
  def send_character(char)
138
129
  @client.send_message('Input.insertText', text: char)
139
130
  end
140
131
 
141
- # @param char [string]
142
- # @return [Future]
143
- async def async_send_character(char)
144
- send_character(char)
145
- end
132
+ define_async_method :async_send_character
146
133
 
147
134
  # @param text [String]
148
135
  # @return [Future]
@@ -159,25 +146,18 @@ class Puppeteer::Keyboard
159
146
  end
160
147
  end
161
148
 
162
- # @param text [String]
163
- # @return [Future]
164
- async def async_type_text(text, delay: nil)
165
- type_text(text, delay)
166
- end
149
+ define_async_method :async_type_text
167
150
 
168
151
  # @param key [String]
152
+ # @param text [String]
169
153
  # @return [Future]
170
- def press(key, delay: nil)
171
- down(key)
154
+ def press(key, delay: nil, text: nil)
155
+ down(key, text: text)
172
156
  if delay
173
157
  sleep(delay.to_i / 1000.0)
174
158
  end
175
159
  up(key)
176
160
  end
177
161
 
178
- # @param key [String]
179
- # @return [Future]
180
- async def async_press(key, delay: nil)
181
- press(key, delay: delay)
182
- end
162
+ define_async_method :async_press
183
163
  end
@@ -1,5 +1,5 @@
1
1
  class Puppeteer::Mouse
2
- using Puppeteer::AsyncAwaitBehavior
2
+ using Puppeteer::DefineAsyncMethod
3
3
 
4
4
  module Button
5
5
  NONE = 'none'
@@ -44,13 +44,7 @@ class Puppeteer::Mouse
44
44
  end
45
45
  end
46
46
 
47
- # @param x [number]
48
- # @param y [number]
49
- # @param steps [number]
50
- # @return [Future]
51
- async def async_move(x, y, steps: nil)
52
- move(x, y, steps: steps)
53
- end
47
+ define_async_method :async_move
54
48
 
55
49
  # @param x [number]
56
50
  # @param y [number]
@@ -70,13 +64,7 @@ class Puppeteer::Mouse
70
64
  up(button: button, click_count: click_count)
71
65
  end
72
66
 
73
- # @param x [number]
74
- # @param y [number]
75
- # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
76
- # @return [Future]
77
- async def async_click(x, y, delay: nil, button: nil, click_count: nil)
78
- click(x, y, delay: delay, button: button, click_count: click_count)
79
- end
67
+ define_async_method :async_click
80
68
 
81
69
  # @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
82
70
  def down(button: nil, click_count: nil)
@@ -91,11 +79,7 @@ class Puppeteer::Mouse
91
79
  )
92
80
  end
93
81
 
94
- # @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
95
- # @return [Future]
96
- async def async_down(button: nil, click_count: nil)
97
- down(button: button, click_count: click_count)
98
- end
82
+ define_async_method :async_down
99
83
 
100
84
  # @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
101
85
  def up(button: nil, click_count: nil)
@@ -110,9 +94,21 @@ class Puppeteer::Mouse
110
94
  )
111
95
  end
112
96
 
113
- # @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
114
- # @return [Future]
115
- async def async_up(button: nil, click_count: nil)
116
- up(button: button, click_count: click_count)
97
+ # Dispatches a `mousewheel` event.
98
+ #
99
+ # @param delta_x [Integer]
100
+ # @param delta_y [Integer]
101
+ def wheel(delta_x: 0, delta_y: 0)
102
+ @client.send_message('Input.dispatchMouseEvent',
103
+ type: 'mouseWheel',
104
+ x: @x,
105
+ y: @y,
106
+ deltaX: delta_x,
107
+ deltaY: delta_y,
108
+ modifiers: @keyboard.modifiers,
109
+ pointerType: 'mouse',
110
+ )
117
111
  end
112
+
113
+ define_async_method :async_up
118
114
  end
@@ -1,5 +1,7 @@
1
1
  class Puppeteer::NetworkManager
2
+ include Puppeteer::DebugPrint
2
3
  include Puppeteer::EventCallbackable
4
+ include Puppeteer::IfPresent
3
5
 
4
6
  class Credentials
5
7
  # @param username [String|NilClass]
@@ -23,19 +25,39 @@ class Puppeteer::NetworkManager
23
25
  @request_id_to_request = {}
24
26
 
25
27
  # @type {!Map<string, !Protocol.Network.requestWillBeSentPayload>}
26
- @request_id_to_request_with_be_sent_event
28
+ @request_id_to_request_with_be_sent_event = {}
27
29
 
28
30
  @extra_http_headers = {}
29
31
 
30
32
  @offline = false
31
33
 
32
- # /** @type {!Set<string>} */
33
- # this._attemptedAuthentications = new Set();
34
+ @attempted_authentications = Set.new
34
35
  @user_request_interception_enabled = false
35
36
  @protocol_request_interception_enabled = false
36
37
  @user_cache_disabled = false
37
- # /** @type {!Map<string, string>} */
38
- # this._requestIdToInterceptionId = new Map();
38
+ @request_id_to_interception_id = {}
39
+
40
+ @client.on_event('Fetch.requestPaused') do |event|
41
+ handle_request_paused(event)
42
+ end
43
+ @client.on_event('Fetch.authRequired') do |event|
44
+ handle_auth_required(event)
45
+ end
46
+ @client.on_event('Network.requestWillBeSent') do |event|
47
+ handle_request_will_be_sent(event)
48
+ end
49
+ @client.on_event('Network.requestServedFromCache') do |event|
50
+ handle_request_served_from_cache(event)
51
+ end
52
+ @client.on_event('Network.responseReceived') do |event|
53
+ handle_response_received(event)
54
+ end
55
+ @client.on_event('Network.loadingFinished') do |event|
56
+ handle_loading_finished(event)
57
+ end
58
+ @client.on_event('Network.loadingFailed') do |event|
59
+ handle_loading_failed(event)
60
+ end
39
61
  end
40
62
 
41
63
  def init
@@ -119,4 +141,140 @@ class Puppeteer::NetworkManager
119
141
  cache_disabled = @user_cache_disabled || @protocol_request_interception_enabled
120
142
  @client.send_message('Network.setCacheDisabled', cacheDisabled: cache_disabled)
121
143
  end
144
+
145
+ private def handle_request_will_be_sent(event)
146
+ # Request interception doesn't happen for data URLs with Network Service.
147
+ if @protocol_request_interception_enabled && !event['request']['url'].start_with?('data:')
148
+ request_id = event['requestId']
149
+ interception_id = @request_id_to_interception_id.delete(request_id)
150
+ if interception_id
151
+ handle_request(event, interception_id)
152
+ else
153
+ @request_id_to_request_with_be_sent_event[request_id] = event
154
+ end
155
+ return
156
+ end
157
+ handle_request(event, nil)
158
+ end
159
+
160
+ private def handle_auth_required(event)
161
+ response = 'Default'
162
+ if @attempted_authentications.include?(event['requestId'])
163
+ response = 'CancelAuth'
164
+ elsif @credentials
165
+ response = 'ProvideCredentials'
166
+ @attempted_authentications << event['requestId']
167
+ end
168
+
169
+ username = @credentials&.username
170
+ password = @credentials&.password
171
+
172
+ begin
173
+ @client.send_message('Fetch.continueWithAuth',
174
+ requestId: event['requestId'],
175
+ authChallengeResponse: {
176
+ response: response,
177
+ username: username,
178
+ password: password,
179
+ },
180
+ )
181
+ rescue => err
182
+ debug_puts(err)
183
+ end
184
+ end
185
+
186
+ private def handle_request_paused(event)
187
+ if !@user_request_interception_enabled && @protocol_request_interception_enabled
188
+ begin
189
+ @client.send_message('Fetch.continueRequest', requestId: event['requestId'])
190
+ rescue => err
191
+ debug_puts(err)
192
+ end
193
+ end
194
+
195
+ request_id = event['networkId']
196
+ interception_id = event['requestId']
197
+ if request_id && (request_will_be_sent_event = @request_id_to_request_with_be_sent_event.delete(request_id))
198
+ handle_request(request_will_be_sent_event, interception_id)
199
+ else
200
+ @request_id_to_interception_id[request_id] = interception_id
201
+ end
202
+ end
203
+
204
+ private def handle_request(event, interception_id)
205
+ redirect_chain = []
206
+ if event['redirectResponse']
207
+ if_present(@request_id_to_request[event['requestId']]) do |request|
208
+ handle_request_redirect(request, event['redirectResponse'])
209
+ redirect_chain = request.internal.redirect_chain
210
+ end
211
+ end
212
+ frame = if_present(event['frameId']) { |frame_id| @frame_manager.frame(frame_id) }
213
+ request = Puppeteer::Request.new(@client, frame, interception_id, @user_request_interception_enabled, event, redirect_chain)
214
+ @request_id_to_request[event['requestId']] = request
215
+ emit_event 'Events.NetworkManager.Request', request
216
+ end
217
+
218
+ private def handle_request_served_from_cache(event)
219
+ if_present(@request_id_to_request[event['requestId']]) do |request|
220
+ request.internal.from_memory_cache = true
221
+ end
222
+ end
223
+
224
+ # @param request [Puppeteer::Request]
225
+ # @param response_payload [Hash]
226
+ private def handle_request_redirect(request, response_payload)
227
+ response = Puppeteer::Response.new(@client, request, response_payload)
228
+ request.internal.response = response
229
+ request.internal.redirect_chain << request
230
+ response.internal.body_loaded_promise.reject(Puppeteer::Response::Redirected.new)
231
+ @request_id_to_request.delete(request.internal.request_id)
232
+ @attempted_authentications.delete(request.internal.interception_id)
233
+ emit_event 'Events.NetworkManager.Response', response
234
+ emit_event 'Events.NetworkManager.RequestFinished', request
235
+ end
236
+
237
+ # @param event [Hash]
238
+ private def handle_response_received(event)
239
+ request = @request_id_to_request[event['requestId']]
240
+ # FileUpload sends a response without a matching request.
241
+ return unless request
242
+
243
+ response = Puppeteer::Response.new(@client, request, event['response'])
244
+ request.internal.response = response
245
+ emit_event 'Events.NetworkManager.Response', response
246
+ end
247
+
248
+ private def handle_loading_finished(event)
249
+ request = @request_id_to_request[event['requestId']]
250
+ # For certain requestIds we never receive requestWillBeSent event.
251
+ # @see https://crbug.com/750469
252
+ return unless request
253
+
254
+
255
+ # Under certain conditions we never get the Network.responseReceived
256
+ # event from protocol. @see https://crbug.com/883475
257
+ if_present(request.response) do |response|
258
+ response.internal.body_loaded_promise.fulfill(nil)
259
+ end
260
+
261
+ @request_id_to_request.delete(request.internal.request_id)
262
+ @attempted_authentications.delete(request.internal.interception_id)
263
+ emit_event 'Events.NetworkManager.RequestFinished', request
264
+ end
265
+
266
+ private def handle_loading_failed(event)
267
+ request = @request_id_to_request[event['requestId']]
268
+ # For certain requestIds we never receive requestWillBeSent event.
269
+ # @see https://crbug.com/750469
270
+ return unless request
271
+
272
+ request.internal.failure_text = event['errorText']
273
+ if_present(request.response) do |response|
274
+ response.internal.body_loaded_promise.fulfill(nil)
275
+ end
276
+ @request_id_to_request.delete(request.internal.request_id)
277
+ @attempted_authentications.delete(request.internal.interception_id)
278
+ emit_event 'Events.NetworkManager.RequestFailed', request
279
+ end
122
280
  end