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
@@ -0,0 +1,166 @@
1
+ require 'mime/types'
2
+
3
+ class Puppeteer::Page
4
+ # /**
5
+ # * @typedef {Object} PDFOptions
6
+ # * @property {number=} scale
7
+ # * @property {boolean=} displayHeaderFooter
8
+ # * @property {string=} headerTemplate
9
+ # * @property {string=} footerTemplate
10
+ # * @property {boolean=} printBackground
11
+ # * @property {boolean=} landscape
12
+ # * @property {string=} pageRanges
13
+ # * @property {string=} format
14
+ # * @property {string|number=} width
15
+ # * @property {string|number=} height
16
+ # * @property {boolean=} preferCSSPageSize
17
+ # * @property {!{top?: string|number, bottom?: string|number, left?: string|number, right?: string|number}=} margin
18
+ # * @property {string=} path
19
+ # */
20
+ class PDFOptions
21
+ # @params options [Hash]
22
+ def initialize(options)
23
+ unless options[:path]
24
+ # Original puppeteer allows path = nil, however nothing to do without path actually.
25
+ # Also in most case, users forget to specify path parameter. So let's raise ArgumentError.
26
+ raise ArgumentError('"path" parameter must be specified.')
27
+ end
28
+
29
+ @scale = options[:scale]
30
+ @display_header_footer = options[:display_header_footer]
31
+ @header_template = options[:header_template]
32
+ @footer_template = options[:footer_template]
33
+ @print_background = options[:print_background]
34
+ @landscape = options[:landscape]
35
+ @page_ranges = options[:page_ranges]
36
+ @format = options[:format]
37
+ @width = options[:width]
38
+ @height = options[:height]
39
+ @prefer_css_page_size = options[:prefer_css_page_size]
40
+ @margin = Margin.new(options[:margin] || {})
41
+ @path = options[:path]
42
+ end
43
+
44
+ attr_reader :path
45
+
46
+ class PaperSize
47
+ def initialize(width:, height:)
48
+ @width = width
49
+ @height = height
50
+ end
51
+ attr_reader :width, :height
52
+ end
53
+
54
+ PAPER_FORMATS = {
55
+ letter: PaperSize.new(width: 8.5, height: 11),
56
+ legal: PaperSize.new(width: 8.5, height: 14),
57
+ tabloid: PaperSize.new(width: 11, height: 17),
58
+ ledger: PaperSize.new(width: 17, height: 11),
59
+ a0: PaperSize.new(width: 33.1, height: 46.8),
60
+ a1: PaperSize.new(width: 23.4, height: 33.1),
61
+ a2: PaperSize.new(width: 16.54, height: 23.4),
62
+ a3: PaperSize.new(width: 11.7, height: 16.54),
63
+ a4: PaperSize.new(width: 8.27, height: 11.7),
64
+ a5: PaperSize.new(width: 5.83, height: 8.27),
65
+ a6: PaperSize.new(width: 4.13, height: 5.83),
66
+ }
67
+
68
+ UNIT_TO_PIXELS = {
69
+ px: 1,
70
+ in: 96,
71
+ cm: 37.8,
72
+ mm: 3.78,
73
+ }
74
+
75
+ # @param parameter [String|Integer|nil]
76
+ private def convert_print_parameter_to_inches(parameter)
77
+ return nil if parameter.nil?
78
+
79
+ pixels =
80
+ if parameter.is_a?(Numeric)
81
+ parameter.to_i
82
+ elsif parameter.is_a?(String)
83
+ unit = parameter[-2..-1].downcase
84
+ value =
85
+ if UNIT_TO_PIXELS.has_key?(unit)
86
+ parameter[0...-2].to_i
87
+ else
88
+ unit = 'px'
89
+ parameter.to_i
90
+ end
91
+
92
+ value * UNIT_TO_PIXELS[unit]
93
+ else
94
+ raise ArgumentError.new("page.pdf() Cannot handle parameter type: #{parameter.class}")
95
+ end
96
+
97
+ pixels / 96
98
+ end
99
+
100
+ private def paper_size
101
+ @paper_size ||= calc_paper_size
102
+ end
103
+
104
+ # @return [PaperSize]
105
+ private def calc_paper_size
106
+ if @format
107
+ PAPER_FORMATS[@format.downcase] or raise ArgumentError.new("Unknown paper format: #{@format}")
108
+ else
109
+ PaperSize.new(
110
+ width: convert_print_parameter_to_inches(@width) || 8.5,
111
+ height: convert_print_parameter_to_inches(@height) || 11.0,
112
+ )
113
+ end
114
+ end
115
+
116
+ class Margin
117
+ def initialize(options)
118
+ @top = options[:top]
119
+ @bottom = options[:bottom]
120
+ @left = options[:left]
121
+ @right = options[:right]
122
+ end
123
+
124
+ def translate(&block)
125
+ new_margin ={
126
+ top: block.call(@top),
127
+ bottom: block.call(@bottom),
128
+ left: block.call(@left),
129
+ right: block.call(@right),
130
+ }
131
+ Margin.new(new_margin)
132
+ end
133
+ attr_reader :top, :bottom, :left, :right
134
+ end
135
+
136
+ private def margin
137
+ @__margin ||= calc_margin
138
+ end
139
+
140
+ private def calc_margin
141
+ @margin.translate do |value|
142
+ convert_print_parameter_to_inches(value) || 0
143
+ end
144
+ end
145
+
146
+ def page_print_args
147
+ {
148
+ transferMode: 'ReturnAsStream',
149
+ landscape: @landscape || false,
150
+ displayHeaderFooter: @display_header_footer || false,
151
+ headerTemplate: @header_template || '',
152
+ footerTemplate: @footer_template || '',
153
+ printBackground: @print_background || false,
154
+ scale: @scale || 1,
155
+ paperWidth: paper_size.width,
156
+ paperHeight: paper_size.height,
157
+ marginTop: margin.top,
158
+ marginBottom: margin.bottom,
159
+ marginLeft: margin.left,
160
+ marginRight: margin.right,
161
+ pageRanges: @page_ranges || '',
162
+ preferCSSPageSize: @prefer_css_page_size || false,
163
+ }
164
+ end
165
+ end
166
+ end
@@ -1,11 +1,12 @@
1
1
  # providing #valueFromRemoteObject, #releaseObject
2
2
  class Puppeteer::RemoteObject
3
3
  include Puppeteer::DebugPrint
4
- using Puppeteer::AsyncAwaitBehavior
4
+ using Puppeteer::DefineAsyncMethod
5
5
 
6
6
  # @param payload [Hash]
7
7
  def initialize(payload)
8
8
  @object_id = payload['objectId']
9
+ @type = payload['type']
9
10
  @sub_type = payload['subtype']
10
11
  @unserializable_value = payload['unserializableValue']
11
12
  @value = payload['value']
@@ -42,6 +43,21 @@ class Puppeteer::RemoteObject
42
43
  end
43
44
  end
44
45
 
46
+ # @return [String]
47
+ def type_str
48
+ # used in JSHandle#to_s
49
+ # original logic:
50
+ # if (this._remoteObject.objectId) {
51
+ # const type = this._remoteObject.subtype || this._remoteObject.type;
52
+ # return 'JSHandle@' + type;
53
+ # }
54
+ if @object_id
55
+ @sub_type || @type
56
+ else
57
+ nil
58
+ end
59
+ end
60
+
45
61
  # used in JSHandle#properties
46
62
  def properties(client)
47
63
  # original logic:
@@ -111,10 +127,7 @@ class Puppeteer::RemoteObject
111
127
  nil
112
128
  end
113
129
 
114
- # @param client [Puppeteer::CDPSession]
115
- async def async_release(client)
116
- release(client)
117
- end
130
+ define_async_method :async_release
118
131
 
119
132
  def converted_arg
120
133
  # ported logic from ExecutionContext#convertArgument
@@ -0,0 +1,330 @@
1
+ class Puppeteer::Request
2
+ include Puppeteer::DebugPrint
3
+ include Puppeteer::IfPresent
4
+
5
+ # defines some methods used only in NetworkManager, Response
6
+ class InternalAccessor
7
+ def initialize(request)
8
+ @request = request
9
+ end
10
+
11
+ def request_id
12
+ @request.instance_variable_get(:@request_id)
13
+ end
14
+
15
+ def interception_id
16
+ @request.instance_variable_get(:@interception_id)
17
+ end
18
+
19
+ # @param response [Puppeteer::Response]
20
+ def response=(response)
21
+ @request.instance_variable_set(:@response, response)
22
+ end
23
+
24
+ def redirect_chain
25
+ @request.instance_variable_get(:@redirect_chain)
26
+ end
27
+
28
+ def failure_text=(failure_text)
29
+ @request.instance_variable_set(:@failure_text, failure_text)
30
+ end
31
+
32
+ def from_memory_cache=(from_memory_cache)
33
+ @request.instance_variable_set(:@from_memory_cache, from_memory_cache)
34
+ end
35
+
36
+ def from_memory_cache?
37
+ @request.instance_variable_get(:@from_memory_cache)
38
+ end
39
+ end
40
+
41
+ # @param client [Puppeteer::CDPSession]
42
+ # @param frame [Puppeteer::Frame]
43
+ # @param interception_id [string|nil]
44
+ # @param allow_interception [boolean]
45
+ # @param event [Hash]
46
+ # @param redirect_chain Array<Request>
47
+ def initialize(client, frame, interception_id, allow_interception, event, redirect_chain)
48
+ @client = client
49
+ @request_id = event['requestId']
50
+ @is_navigation_request = event['requestId'] == event['loaderId'] && event['type'] == 'Document'
51
+ @interception_id = interception_id
52
+ @allow_interception = allow_interception
53
+ @url = event['request']['url']
54
+ @resource_type = event['type'].downcase
55
+ @method = event['request']['method']
56
+ @post_data = event['request']['postData']
57
+ @frame = frame
58
+ @redirect_chain = redirect_chain
59
+ @headers = {}
60
+ event['request']['headers'].each do |key, value|
61
+ @headers[key.downcase] = value
62
+ end
63
+ @from_memory_cache = false
64
+
65
+ @internal = InternalAccessor.new(self)
66
+ end
67
+
68
+ attr_reader :internal
69
+ attr_reader :url, :resource_type, :method, :post_data, :headers, :response, :frame
70
+
71
+ def navigation_request?
72
+ @is_navigation_request
73
+ end
74
+
75
+ def redirect_chain
76
+ @redirect_chain.dup
77
+ end
78
+
79
+ def failure
80
+ if_present(@failure_text) do |failure_text|
81
+ { errorText: @failure_text }
82
+ end
83
+ end
84
+
85
+ private def headers_to_array(headers)
86
+ return nil unless headers
87
+
88
+ headers.map do |key, value|
89
+ { name: key, value: value.to_s }
90
+ end
91
+ end
92
+
93
+ class InterceptionNotEnabledError < StandardError
94
+ def initialize
95
+ super('Request Interception is not enabled!')
96
+ end
97
+ end
98
+
99
+ class AlreadyHandledError < StandardError
100
+ def initialize
101
+ super('Request is already handled!')
102
+ end
103
+ end
104
+
105
+ # proceed request on request interception.
106
+ #
107
+ # Example:
108
+ #
109
+ # page.on 'request' do |req|
110
+ # # Override headers
111
+ # headers = req.headers.merge(
112
+ # foo: 'bar', # set "foo" header
113
+ # origin: nil, # remove "origin" header
114
+ # )
115
+ # req.continue(headers: headers)
116
+ # end
117
+ #
118
+ # @param error_code [String|Symbol]
119
+ def continue(url: nil, method: nil, post_data: nil, headers: nil)
120
+ # Request interception is not supported for data: urls.
121
+ return if @url.start_with?('data:')
122
+
123
+ unless @allow_interception
124
+ raise InterceptionNotEnabledError.new
125
+ end
126
+ if @interception_handled
127
+ raise AlreadyHandledError.new
128
+ end
129
+ @interception_handled = true
130
+
131
+ overrides = {
132
+ url: url,
133
+ method: method,
134
+ post_data: post_data,
135
+ headers: headers_to_array(headers),
136
+ }.compact
137
+ begin
138
+ @client.send_message('Fetch.continueRequest',
139
+ requestId: @interception_id,
140
+ **overrides,
141
+ )
142
+ rescue => err
143
+ # In certain cases, protocol will return error if the request was already canceled
144
+ # or the page was closed. We should tolerate these errors.
145
+ debug_puts(err)
146
+ end
147
+ end
148
+
149
+ # Mocking response.
150
+ #
151
+ # Example:
152
+ #
153
+ # page.on 'request' do |req|
154
+ # req.respond(
155
+ # status: 404,
156
+ # content_type: 'text/plain',
157
+ # body: 'Not Found!'
158
+ # )
159
+ # end
160
+ #
161
+ # @param status [Integer]
162
+ # @param headers [Hash<String, String>]
163
+ # @param content_type [String]
164
+ # @param body [String]
165
+ def respond(status: nil, headers: nil, content_type: nil, body: nil)
166
+ # Mocking responses for dataURL requests is not currently supported.
167
+ return if @url.start_with?('data:')
168
+
169
+ unless @allow_interception
170
+ raise InterceptionNotEnabledError.new
171
+ end
172
+ if @interception_handled
173
+ raise AlreadyHandledError.new
174
+ end
175
+ @interception_handled = true
176
+
177
+ mock_response_headers = {}
178
+ headers&.each do |key, value|
179
+ mock_response_headers[key.downcase] = value
180
+ end
181
+ if content_type
182
+ mock_response_headers['content-type'] = content_type
183
+ end
184
+ if body
185
+ mock_response_headers['content-length'] = body.length
186
+ end
187
+
188
+ mock_response = {
189
+ responseCode: status || 200,
190
+ responsePhrase: STATUS_TEXTS[(status || 200).to_s],
191
+ responseHeaders: headers_to_array(mock_response_headers),
192
+ body: if_present(body) { |mock_body| Base64.strict_encode64(mock_body) },
193
+ }.compact
194
+ begin
195
+ @client.send_message('Fetch.fulfillRequest',
196
+ requestId: @interception_id,
197
+ **mock_response,
198
+ )
199
+ rescue => err
200
+ # In certain cases, protocol will return error if the request was already canceled
201
+ # or the page was closed. We should tolerate these errors.
202
+ debug_puts(err)
203
+ end
204
+ end
205
+
206
+ # abort request on request interception.
207
+ #
208
+ # Example:
209
+ #
210
+ # page.on 'request' do |req|
211
+ # if req.url.include?("porn")
212
+ # req.abort
213
+ # else
214
+ # req.continue
215
+ # end
216
+ # end
217
+ #
218
+ # @param error_code [String|Symbol]
219
+ def abort(error_code: :failed)
220
+ # Request interception is not supported for data: urls.
221
+ return if @url.start_with?('data:')
222
+
223
+ error_reason = ERROR_REASONS[error_code.to_s]
224
+ unless error_reason
225
+ raise ArgumentError.new("Unknown error code: #{error_code}")
226
+ end
227
+ unless @allow_interception
228
+ raise InterceptionNotEnabledError.new
229
+ end
230
+ if @interception_handled
231
+ raise AlreadyHandledError.new
232
+ end
233
+ @interception_handled = true
234
+
235
+ begin
236
+ @client.send_message('Fetch.failRequest',
237
+ requestId: @interception_id,
238
+ errorReason: error_reason,
239
+ )
240
+ rescue => err
241
+ # In certain cases, protocol will return error if the request was already canceled
242
+ # or the page was closed. We should tolerate these errors.
243
+ debug_puts(err)
244
+ end
245
+ end
246
+
247
+ ERROR_REASONS = {
248
+ 'aborted' => 'Aborted',
249
+ 'accessdenied' => 'AccessDenied',
250
+ 'addressunreachable' => 'AddressUnreachable',
251
+ 'blockedbyclient' => 'BlockedByClient',
252
+ 'blockedbyresponse' => 'BlockedByResponse',
253
+ 'connectionaborted' => 'ConnectionAborted',
254
+ 'connectionclosed' => 'ConnectionClosed',
255
+ 'connectionfailed' => 'ConnectionFailed',
256
+ 'connectionrefused' => 'ConnectionRefused',
257
+ 'connectionreset' => 'ConnectionReset',
258
+ 'internetdisconnected' => 'InternetDisconnected',
259
+ 'namenotresolved' => 'NameNotResolved',
260
+ 'timedout' => 'TimedOut',
261
+ 'failed' => 'Failed',
262
+ }.freeze
263
+
264
+ # List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
265
+ STATUS_TEXTS = {
266
+ '100' => 'Continue',
267
+ '101' => 'Switching Protocols',
268
+ '102' => 'Processing',
269
+ '103' => 'Early Hints',
270
+ '200' => 'OK',
271
+ '201' => 'Created',
272
+ '202' => 'Accepted',
273
+ '203' => 'Non-Authoritative Information',
274
+ '204' => 'No Content',
275
+ '205' => 'Reset Content',
276
+ '206' => 'Partial Content',
277
+ '207' => 'Multi-Status',
278
+ '208' => 'Already Reported',
279
+ '226' => 'IM Used',
280
+ '300' => 'Multiple Choices',
281
+ '301' => 'Moved Permanently',
282
+ '302' => 'Found',
283
+ '303' => 'See Other',
284
+ '304' => 'Not Modified',
285
+ '305' => 'Use Proxy',
286
+ '306' => 'Switch Proxy',
287
+ '307' => 'Temporary Redirect',
288
+ '308' => 'Permanent Redirect',
289
+ '400' => 'Bad Request',
290
+ '401' => 'Unauthorized',
291
+ '402' => 'Payment Required',
292
+ '403' => 'Forbidden',
293
+ '404' => 'Not Found',
294
+ '405' => 'Method Not Allowed',
295
+ '406' => 'Not Acceptable',
296
+ '407' => 'Proxy Authentication Required',
297
+ '408' => 'Request Timeout',
298
+ '409' => 'Conflict',
299
+ '410' => 'Gone',
300
+ '411' => 'Length Required',
301
+ '412' => 'Precondition Failed',
302
+ '413' => 'Payload Too Large',
303
+ '414' => 'URI Too Long',
304
+ '415' => 'Unsupported Media Type',
305
+ '416' => 'Range Not Satisfiable',
306
+ '417' => 'Expectation Failed',
307
+ '418' => 'I\'m a teapot',
308
+ '421' => 'Misdirected Request',
309
+ '422' => 'Unprocessable Entity',
310
+ '423' => 'Locked',
311
+ '424' => 'Failed Dependency',
312
+ '425' => 'Too Early',
313
+ '426' => 'Upgrade Required',
314
+ '428' => 'Precondition Required',
315
+ '429' => 'Too Many Requests',
316
+ '431' => 'Request Header Fields Too Large',
317
+ '451' => 'Unavailable For Legal Reasons',
318
+ '500' => 'Internal Server Error',
319
+ '501' => 'Not Implemented',
320
+ '502' => 'Bad Gateway',
321
+ '503' => 'Service Unavailable',
322
+ '504' => 'Gateway Timeout',
323
+ '505' => 'HTTP Version Not Supported',
324
+ '506' => 'Variant Also Negotiates',
325
+ '507' => 'Insufficient Storage',
326
+ '508' => 'Loop Detected',
327
+ '510' => 'Not Extended',
328
+ '511' => 'Network Authentication Required',
329
+ }.freeze
330
+ end