puppeteer-ruby 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +71 -0
  3. data/.github/stale.yml +16 -0
  4. data/.gitignore +19 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +302 -0
  7. data/.travis.yml +7 -0
  8. data/Dockerfile +6 -0
  9. data/Gemfile +6 -0
  10. data/README.md +54 -0
  11. data/Rakefile +1 -0
  12. data/bin/console +11 -0
  13. data/bin/setup +8 -0
  14. data/docker-compose.yml +15 -0
  15. data/docs/Puppeteer.html +2020 -0
  16. data/docs/Puppeteer/AsyncAwaitBehavior.html +105 -0
  17. data/docs/Puppeteer/Browser.html +2148 -0
  18. data/docs/Puppeteer/BrowserContext.html +809 -0
  19. data/docs/Puppeteer/BrowserFetcher.html +214 -0
  20. data/docs/Puppeteer/BrowserRunner.html +914 -0
  21. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +477 -0
  22. data/docs/Puppeteer/CDPSession.html +813 -0
  23. data/docs/Puppeteer/CDPSession/Error.html +124 -0
  24. data/docs/Puppeteer/ConcurrentRubyUtils.html +430 -0
  25. data/docs/Puppeteer/Connection.html +960 -0
  26. data/docs/Puppeteer/Connection/MessageCallback.html +434 -0
  27. data/docs/Puppeteer/Connection/ProtocolError.html +216 -0
  28. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +217 -0
  29. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +244 -0
  30. data/docs/Puppeteer/ConsoleMessage.html +565 -0
  31. data/docs/Puppeteer/ConsoleMessage/Location.html +433 -0
  32. data/docs/Puppeteer/DOMWorld.html +2219 -0
  33. data/docs/Puppeteer/DOMWorld/DetachedError.html +124 -0
  34. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +124 -0
  35. data/docs/Puppeteer/DebugPrint.html +233 -0
  36. data/docs/Puppeteer/Device.html +470 -0
  37. data/docs/Puppeteer/Devices.html +139 -0
  38. data/docs/Puppeteer/ElementHandle.html +2542 -0
  39. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +206 -0
  40. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +206 -0
  41. data/docs/Puppeteer/ElementHandle/Point.html +492 -0
  42. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +124 -0
  43. data/docs/Puppeteer/EmulationManager.html +454 -0
  44. data/docs/Puppeteer/EventCallbackable.html +433 -0
  45. data/docs/Puppeteer/EventCallbackable/EventListeners.html +435 -0
  46. data/docs/Puppeteer/ExecutionContext.html +998 -0
  47. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +124 -0
  48. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +357 -0
  49. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +389 -0
  50. data/docs/Puppeteer/FileChooser.html +455 -0
  51. data/docs/Puppeteer/Frame.html +3677 -0
  52. data/docs/Puppeteer/FrameManager.html +2410 -0
  53. data/docs/Puppeteer/FrameManager/NavigationError.html +124 -0
  54. data/docs/Puppeteer/IfPresent.html +222 -0
  55. data/docs/Puppeteer/JSHandle.html +1352 -0
  56. data/docs/Puppeteer/Keyboard.html +1557 -0
  57. data/docs/Puppeteer/Keyboard/KeyDefinition.html +831 -0
  58. data/docs/Puppeteer/Keyboard/KeyDescription.html +603 -0
  59. data/docs/Puppeteer/Launcher.html +237 -0
  60. data/docs/Puppeteer/Launcher/Base.html +385 -0
  61. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +124 -0
  62. data/docs/Puppeteer/Launcher/BrowserOptions.html +441 -0
  63. data/docs/Puppeteer/Launcher/Chrome.html +669 -0
  64. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +382 -0
  65. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +531 -0
  66. data/docs/Puppeteer/Launcher/LaunchOptions.html +893 -0
  67. data/docs/Puppeteer/LifecycleWatcher.html +834 -0
  68. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +363 -0
  69. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +206 -0
  70. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +124 -0
  71. data/docs/Puppeteer/Mouse.html +1105 -0
  72. data/docs/Puppeteer/Mouse/Button.html +136 -0
  73. data/docs/Puppeteer/NetworkManager.html +901 -0
  74. data/docs/Puppeteer/NetworkManager/Credentials.html +385 -0
  75. data/docs/Puppeteer/Page.html +5970 -0
  76. data/docs/Puppeteer/Page/FileChooserTimeoutError.html +206 -0
  77. data/docs/Puppeteer/Page/ScreenshotOptions.html +845 -0
  78. data/docs/Puppeteer/Page/ScriptTag.html +555 -0
  79. data/docs/Puppeteer/Page/StyleTag.html +448 -0
  80. data/docs/Puppeteer/Page/TargetCrashedError.html +124 -0
  81. data/docs/Puppeteer/RemoteObject.html +1087 -0
  82. data/docs/Puppeteer/Target.html +1336 -0
  83. data/docs/Puppeteer/Target/InitializeFailure.html +124 -0
  84. data/docs/Puppeteer/Target/TargetInfo.html +729 -0
  85. data/docs/Puppeteer/TimeoutError.html +135 -0
  86. data/docs/Puppeteer/TimeoutSettings.html +496 -0
  87. data/docs/Puppeteer/TouchScreen.html +464 -0
  88. data/docs/Puppeteer/Viewport.html +837 -0
  89. data/docs/Puppeteer/WaitTask.html +637 -0
  90. data/docs/Puppeteer/WaitTask/TerminatedError.html +124 -0
  91. data/docs/Puppeteer/WaitTask/TimeoutError.html +206 -0
  92. data/docs/Puppeteer/WebSocket.html +673 -0
  93. data/docs/Puppeteer/WebSocket/DriverImpl.html +412 -0
  94. data/docs/Puppeteer/WebSocketTransport.html +600 -0
  95. data/docs/Puppeteer/WebSocktTransportError.html +124 -0
  96. data/docs/_index.html +823 -0
  97. data/docs/class_list.html +51 -0
  98. data/docs/css/common.css +1 -0
  99. data/docs/css/full_list.css +58 -0
  100. data/docs/css/style.css +496 -0
  101. data/docs/file.README.html +123 -0
  102. data/docs/file_list.html +56 -0
  103. data/docs/frames.html +17 -0
  104. data/docs/index.html +123 -0
  105. data/docs/js/app.js +314 -0
  106. data/docs/js/full_list.js +216 -0
  107. data/docs/js/jquery.js +4 -0
  108. data/docs/method_list.html +4075 -0
  109. data/docs/top-level-namespace.html +126 -0
  110. data/lib/puppeteer.rb +200 -0
  111. data/lib/puppeteer/async_await_behavior.rb +38 -0
  112. data/lib/puppeteer/browser.rb +259 -0
  113. data/lib/puppeteer/browser_context.rb +90 -0
  114. data/lib/puppeteer/browser_fetcher.rb +6 -0
  115. data/lib/puppeteer/browser_runner.rb +161 -0
  116. data/lib/puppeteer/cdp_session.rb +100 -0
  117. data/lib/puppeteer/concurrent_ruby_utils.rb +37 -0
  118. data/lib/puppeteer/connection.rb +254 -0
  119. data/lib/puppeteer/console_message.rb +24 -0
  120. data/lib/puppeteer/debug_print.rb +20 -0
  121. data/lib/puppeteer/device.rb +12 -0
  122. data/lib/puppeteer/devices.rb +885 -0
  123. data/lib/puppeteer/dom_world.rb +484 -0
  124. data/lib/puppeteer/element_handle.rb +433 -0
  125. data/lib/puppeteer/element_handle/bounding_box.rb +12 -0
  126. data/lib/puppeteer/element_handle/box_model.rb +19 -0
  127. data/lib/puppeteer/element_handle/point.rb +26 -0
  128. data/lib/puppeteer/emulation_manager.rb +46 -0
  129. data/lib/puppeteer/errors.rb +2 -0
  130. data/lib/puppeteer/event_callbackable.rb +88 -0
  131. data/lib/puppeteer/execution_context.rb +254 -0
  132. data/lib/puppeteer/file_chooser.rb +29 -0
  133. data/lib/puppeteer/frame.rb +286 -0
  134. data/lib/puppeteer/frame_manager.rb +378 -0
  135. data/lib/puppeteer/if_present.rb +18 -0
  136. data/lib/puppeteer/js_handle.rb +142 -0
  137. data/lib/puppeteer/keyboard.rb +183 -0
  138. data/lib/puppeteer/keyboard/key_description.rb +19 -0
  139. data/lib/puppeteer/keyboard/us_keyboard_layout.rb +283 -0
  140. data/lib/puppeteer/launcher.rb +25 -0
  141. data/lib/puppeteer/launcher/base.rb +48 -0
  142. data/lib/puppeteer/launcher/browser_options.rb +41 -0
  143. data/lib/puppeteer/launcher/chrome.rb +211 -0
  144. data/lib/puppeteer/launcher/chrome_arg_options.rb +49 -0
  145. data/lib/puppeteer/launcher/launch_options.rb +68 -0
  146. data/lib/puppeteer/lifecycle_watcher.rb +171 -0
  147. data/lib/puppeteer/mouse.rb +123 -0
  148. data/lib/puppeteer/network_manager.rb +122 -0
  149. data/lib/puppeteer/page.rb +1065 -0
  150. data/lib/puppeteer/page/screenshot_options.rb +78 -0
  151. data/lib/puppeteer/remote_object.rb +143 -0
  152. data/lib/puppeteer/target.rb +150 -0
  153. data/lib/puppeteer/timeout_settings.rb +15 -0
  154. data/lib/puppeteer/touch_screen.rb +43 -0
  155. data/lib/puppeteer/version.rb +3 -0
  156. data/lib/puppeteer/viewport.rb +54 -0
  157. data/lib/puppeteer/wait_task.rb +188 -0
  158. data/lib/puppeteer/web_socket.rb +122 -0
  159. data/lib/puppeteer/web_socket_transport.rb +49 -0
  160. data/puppeteer-ruby.gemspec +32 -0
  161. metadata +355 -0
@@ -0,0 +1,286 @@
1
+ class Puppeteer::Frame
2
+ # @param {!FrameManager} frameManager
3
+ # @param {!Puppeteer.CDPSession} client
4
+ # @param {?Frame} parentFrame
5
+ # @param {string} frameId
6
+ def initialize(frame_manager, client, parent_frame, frame_id)
7
+ @frame_manager = frame_manager
8
+ @client = client
9
+ @parent_frame = parent_frame
10
+ @id = frame_id
11
+ @detached = false
12
+
13
+ @loader_id = ''
14
+ @lifecycle_events = Set.new
15
+ @main_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
16
+ @secondary_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
17
+ @child_frames = Set.new
18
+ if parent_frame
19
+ parent_frame._child_frames << self
20
+ end
21
+ end
22
+
23
+ attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :secondary_world
24
+
25
+ # @param url [String]
26
+ # @param rederer [String]
27
+ # @param timeout [number|nil]
28
+ # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
29
+ # @return [Puppeteer::Response]
30
+ def goto(url, referer: nil, timeout: nil, wait_until: nil)
31
+ @frame_manager.navigate_frame(self, url, referer: referer, timeout: timeout, wait_until: wait_until)
32
+ end
33
+
34
+ # @param timeout [number|nil]
35
+ # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
36
+ def wait_for_navigation(timeout: nil, wait_until: nil)
37
+ @frame_manager.wait_for_frame_navigation(self, timeout: timeout, wait_until: wait_until)
38
+ end
39
+
40
+ def execution_context
41
+ @main_world.execution_context
42
+ end
43
+
44
+ # @param {Function|string} pageFunction
45
+ # @return {!Promise<!Puppeteer.JSHandle>}
46
+ def evaluate_handle(page_function, *args)
47
+ @main_world.evaluate_handle(page_function, *args)
48
+ end
49
+
50
+ # @param {Function|string} pageFunction
51
+ # @param {!Array<*>} args
52
+ def evaluate(page_function, *args)
53
+ @main_world.evaluate(page_function, *args)
54
+ end
55
+
56
+ # `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
57
+ # @param {string} selector
58
+ # @return {!Promise<?Puppeteer.ElementHandle>}
59
+ def S(selector)
60
+ @main_world.S(selector)
61
+ end
62
+
63
+
64
+ # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
65
+ # @param {string} expression
66
+ # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
67
+ def Sx(expression)
68
+ @main_world.Sx(expression)
69
+ end
70
+
71
+
72
+ # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
73
+ # @param {string} selector
74
+ # @param {Function|string} pageFunction
75
+ # @param {!Array<*>} args
76
+ # @return {!Promise<(!Object|undefined)>}
77
+ def Seval(selector, page_function, *args)
78
+ @main_world.Seval(selector, page_function, *args)
79
+ end
80
+
81
+ # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
82
+ # @param {string} selector
83
+ # @param {Function|string} pageFunction
84
+ # @param {!Array<*>} args
85
+ # @return {!Promise<(!Object|undefined)>}
86
+ def SSeval(selector, page_function, *args)
87
+ @main_world.SSeval(selector, page_function, *args)
88
+ end
89
+
90
+ # `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
91
+ # @param {string} selector
92
+ # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
93
+ def SS(selector)
94
+ @main_world.SS(selector)
95
+ end
96
+
97
+ def content
98
+ @secondary_world.content
99
+ end
100
+
101
+ # @param {string} html
102
+ # @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
103
+ def set_content(html, timeout: nil, wait_until: nil)
104
+ @secondary_world.set_content(html, timeout: timeout, wait_until: wait_until)
105
+ end
106
+
107
+ # @return [String]
108
+ def name
109
+ @name || ''
110
+ end
111
+
112
+ # @return [String]
113
+ def url
114
+ @url
115
+ end
116
+
117
+ # @return [Frame?]
118
+ def parent_frame
119
+ @parent_frame
120
+ end
121
+
122
+ protected def _child_frames
123
+ @child_frames
124
+ end
125
+
126
+ def child_frames
127
+ @child_frames.dup
128
+ end
129
+
130
+ def detached?
131
+ @detached
132
+ end
133
+
134
+ # @param style_tag [Puppeteer::Page::ScriptTag]
135
+ # @return {!Promise<!ElementHandle>}
136
+ def add_script_tag(script_tag)
137
+ @main_world.add_script_tag(script_tag)
138
+ end
139
+
140
+ # @param style_tag [Puppeteer::Page::StyleTag]
141
+ # @return {!Promise<!ElementHandle>}
142
+ def add_style_tag(style_tag)
143
+ @main_world.add_style_tag(style_tag)
144
+ end
145
+
146
+ # @param selector [String]
147
+ # @param delay [Number]
148
+ # @param button [String] "left"|"right"|"middle"
149
+ # @param click_count [Number]
150
+ def click(selector, delay: nil, button: nil, click_count: nil)
151
+ @secondary_world.click(selector, delay: delay, button: button, click_count: click_count)
152
+ end
153
+
154
+ # @param {string} selector
155
+ def focus(selector)
156
+ @secondary_world.focus(selector)
157
+ end
158
+
159
+ # @param {string} selector
160
+ def hover(selector)
161
+ @secondary_world.hover(selector)
162
+ end
163
+
164
+ # @param {string} selector
165
+ # @param {!Array<string>} values
166
+ # @return {!Promise<!Array<string>>}
167
+ def select(selector, *values)
168
+ @secondary_world.select(selector, *values)
169
+ end
170
+
171
+ # @param {string} selector
172
+ def tap(selector)
173
+ @secondary_world.tap(selector)
174
+ end
175
+
176
+ # @param selector [String]
177
+ # @param text [String]
178
+ # @param delay [Number]
179
+ def type_text(selector, text, delay: nil)
180
+ @main_world.type_text(selector, text, delay: delay)
181
+ end
182
+
183
+ # /**
184
+ # * @param {(string|number|Function)} selectorOrFunctionOrTimeout
185
+ # * @param {!Object=} options
186
+ # * @param {!Array<*>} args
187
+ # * @return {!Promise<?Puppeteer.JSHandle>}
188
+ # */
189
+ # waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
190
+ # const xPathPattern = '//';
191
+
192
+ # if (helper.isString(selectorOrFunctionOrTimeout)) {
193
+ # const string = /** @type {string} */ (selectorOrFunctionOrTimeout);
194
+ # if (string.startsWith(xPathPattern))
195
+ # return this.waitForXPath(string, options);
196
+ # return this.waitForSelector(string, options);
197
+ # }
198
+ # if (helper.isNumber(selectorOrFunctionOrTimeout))
199
+ # return new Promise(fulfill => setTimeout(fulfill, /** @type {number} */ (selectorOrFunctionOrTimeout)));
200
+ # if (typeof selectorOrFunctionOrTimeout === 'function')
201
+ # return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args);
202
+ # return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
203
+ # }
204
+
205
+ # @param selector [String]
206
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
207
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
208
+ # @param timeout [Integer]
209
+ def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
210
+ handle = @secondary_world.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
211
+ if !handle
212
+ return nil
213
+ end
214
+ main_execution_context = @main_world.execution_context
215
+ result = main_execution_context.adopt_element_handle(handle)
216
+ handle.dispose
217
+ result
218
+ end
219
+
220
+ # @param xpath [String]
221
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
222
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
223
+ # @param timeout [Integer]
224
+ def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
225
+ handle = @secondary_world.wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
226
+ if !handle
227
+ return nil
228
+ end
229
+ main_execution_context = @main_world.execution_context
230
+ result = main_execution_context.adopt_element_handle(handle)
231
+ handle.dispose
232
+ result
233
+ end
234
+
235
+ # @param {Function|string} pageFunction
236
+ # @param {!{polling?: string|number, timeout?: number}=} options
237
+ # @param {!Array<*>} args
238
+ # @return {!Promise<!Puppeteer.JSHandle>}
239
+ def wait_for_function(page_function, options = {}, *args)
240
+ @main_world.wait_for_function(page_function, options, *args)
241
+ end
242
+
243
+ def title
244
+ @secondary_world.title
245
+ end
246
+
247
+ # @param frame_payload [Hash]
248
+ def navigated(frame_payload)
249
+ @name = frame_payload['name']
250
+ # TODO(lushnikov): remove this once requestInterception has loaderId exposed.
251
+ @navigation_url = frame_payload['url']
252
+ @url = frame_payload['url']
253
+
254
+ # Ensure loaderId updated.
255
+ # The order of [Page.lifecycleEvent name="init"] and [Page.frameNavigated] is random... for some reason...
256
+ @loader_id = frame_payload['loaderId']
257
+ end
258
+
259
+ # @param url [String]
260
+ def navigated_within_document(url)
261
+ @url = url
262
+ end
263
+
264
+ def handle_lifecycle_event(loader_id, name)
265
+ if name == 'init'
266
+ @loader_id = loader_id
267
+ @lifecycle_events.clear
268
+ end
269
+ @lifecycle_events << name
270
+ end
271
+
272
+ def handle_loading_stopped
273
+ @lifecycle_events << 'DOMContentLoaded'
274
+ @lifecycle_events << 'load'
275
+ end
276
+
277
+ def detach
278
+ @detached = true
279
+ @main_world.detach
280
+ @secondary_world.detach
281
+ if @parent_frame
282
+ @parent_frame._child_frames.delete(self)
283
+ end
284
+ @parent_frame = nil
285
+ end
286
+ end
@@ -0,0 +1,378 @@
1
+ require 'timeout'
2
+
3
+ class Puppeteer::FrameManager
4
+ include Puppeteer::DebugPrint
5
+ include Puppeteer::IfPresent
6
+ include Puppeteer::EventCallbackable
7
+ using Puppeteer::AsyncAwaitBehavior
8
+
9
+ UTILITY_WORLD_NAME = '__puppeteer_utility_world__'
10
+
11
+ # @param {!Puppeteer.CDPSession} client
12
+ # @param {!Puppeteer.Page} page
13
+ # @param {boolean} ignoreHTTPSErrors
14
+ # @param {!Puppeteer.TimeoutSettings} timeoutSettings
15
+ def initialize(client, page, ignore_https_errors, timeout_settings)
16
+ @client = client
17
+ @page = page
18
+ @network_manager = Puppeteer::NetworkManager.new(client, ignore_https_errors, self)
19
+ @timeout_settings = timeout_settings
20
+
21
+ # @type {!Map<string, !Frame>}
22
+ @frames = {}
23
+
24
+ # @type {!Map<number, !ExecutionContext>}
25
+ @context_id_to_context = {}
26
+ @context_id_created = {}
27
+
28
+ # @type {!Set<string>}
29
+ @isolated_worlds = Set.new
30
+
31
+ @client.on_event 'Page.frameAttached' do |event|
32
+ handle_frame_attached(event['frameId'], event['parentFrameId'])
33
+ end
34
+ @client.on_event 'Page.frameNavigated' do |event|
35
+ handle_frame_navigated(event['frame'])
36
+ end
37
+ @client.on_event 'Page.navigatedWithinDocument' do |event|
38
+ handle_frame_navigated_within_document(event['frameId'], event['url'])
39
+ end
40
+ @client.on_event 'Page.frameDetached' do |event|
41
+ handle_frame_detached(event['frameId'])
42
+ end
43
+ @client.on_event 'Page.frameStoppedLoading' do |event|
44
+ handle_frame_stopped_loading(event['frameId'])
45
+ end
46
+ @client.on_event 'Runtime.executionContextCreated' do |event|
47
+ handle_execution_context_created(event['context'])
48
+ end
49
+ @client.on_event 'Runtime.executionContextDestroyed' do |event|
50
+ handle_execution_context_destroyed(event['executionContextId'])
51
+ end
52
+ @client.on_event 'Runtime.executionContextsCleared' do |event|
53
+ handle_execution_contexts_cleared
54
+ end
55
+ @client.on_event 'Page.lifecycleEvent' do |event|
56
+ handle_lifecycle_event(event)
57
+ end
58
+ end
59
+
60
+ attr_reader :client, :timeout_settings
61
+
62
+ private def init
63
+ results = await_all(
64
+ @client.async_send_message('Page.enable'),
65
+ @client.async_send_message('Page.getFrameTree'),
66
+ )
67
+ frame_tree = results.last['frameTree']
68
+ handle_frame_tree(frame_tree)
69
+ await_all(
70
+ @client.async_send_message('Page.setLifecycleEventsEnabled', enabled: true),
71
+ @client.async_send_message('Runtime.enable'),
72
+ )
73
+ ensure_isolated_world(UTILITY_WORLD_NAME)
74
+ @network_manager.init
75
+ end
76
+
77
+ async def async_init
78
+ init
79
+ end
80
+
81
+ attr_reader :network_manager
82
+
83
+ class NavigationError < StandardError; end
84
+
85
+ # @param frame [Puppeteer::Frame]
86
+ # @param url [String]
87
+ # @param {!{referer?: string, timeout?: number, waitUntil?: string|!Array<string>}=} options
88
+ # @return [Puppeteer::Response]
89
+ def navigate_frame(frame, url, referer: nil, timeout: nil, wait_until: nil)
90
+ assert_no_legacy_navigation_options(wait_until: wait_until)
91
+
92
+ navigate_params = {
93
+ url: url,
94
+ referer: referer || @network_manager.extra_http_headers['referer'],
95
+ frameId: frame.id,
96
+ }.compact
97
+ option_wait_until = wait_until || ['load']
98
+ option_timeout = timeout || @timeout_settings.navigation_timeout
99
+
100
+ watcher = Puppeteer::LifecycleWatcher.new(self, frame, option_wait_until, option_timeout)
101
+ ensure_new_document_navigation = false
102
+
103
+ begin
104
+ navigate = future do
105
+ result = @client.send_message('Page.navigate', navigate_params)
106
+ loader_id = result['loaderId']
107
+ ensure_new_document_navigation = !!loader_id
108
+ if result['errorText']
109
+ raise NavigationError.new("#{result['errorText']} at #{url}")
110
+ end
111
+ end
112
+ await_any(
113
+ navigate,
114
+ watcher.timeout_or_termination_promise,
115
+ )
116
+
117
+ document_navigation_promise =
118
+ if ensure_new_document_navigation
119
+ watcher.new_document_navigation_promise
120
+ else
121
+ watcher.same_document_navigation_promise
122
+ end
123
+ await_any(
124
+ document_navigation_promise,
125
+ watcher.timeout_or_termination_promise,
126
+ )
127
+ ensure
128
+ watcher.dispose
129
+ end
130
+
131
+ watcher.navigation_response
132
+ end
133
+
134
+ # @param timeout [number|nil]
135
+ # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
136
+ # @return [Puppeteer::Response]
137
+ def wait_for_frame_navigation(frame, timeout: nil, wait_until: nil)
138
+ assert_no_legacy_navigation_options(wait_until: wait_until)
139
+
140
+ option_wait_until = wait_until || ['load']
141
+ option_timeout = timeout || @timeout_settings.navigation_timeout
142
+ watcher = Puppeteer::LifecycleWatcher.new(self, frame, option_wait_until, option_timeout)
143
+ begin
144
+ await_any(
145
+ watcher.timeout_or_termination_promise,
146
+ watcher.same_document_navigation_promise,
147
+ watcher.new_document_navigation_promise,
148
+ )
149
+ ensure
150
+ watcher.dispose
151
+ end
152
+
153
+ watcher.navigation_response
154
+ end
155
+
156
+ # @param event [Hash]
157
+ def handle_lifecycle_event(event)
158
+ frame = @frames[event['frameId']]
159
+ return if !frame
160
+ frame.handle_lifecycle_event(event['loaderId'], event['name'])
161
+ emit_event 'Events.FrameManager.LifecycleEvent', frame
162
+ end
163
+
164
+ # @param {string} frameId
165
+ def handle_frame_stopped_loading(frame_id)
166
+ frame = @frames[frame_id]
167
+ return if !frame
168
+ frame.handle_loading_stopped
169
+ emit_event 'Events.FrameManager.LifecycleEvent', frame
170
+ end
171
+
172
+ # @param frame_tree [Hash]
173
+ def handle_frame_tree(frame_tree)
174
+ if frame_tree['frame']['parentId']
175
+ handle_frame_attached(frame_tree['frame']['id'], frame_tree['frame']['parentId'])
176
+ end
177
+ handle_frame_navigated(frame_tree['frame'])
178
+ return if !frame_tree['childFrames']
179
+
180
+ frame_tree['childFrames'].each do |child|
181
+ handle_frame_tree(child)
182
+ end
183
+ end
184
+
185
+ # @return {!Puppeteer.Page}
186
+ def page
187
+ @page
188
+ end
189
+
190
+ # @return {!Frame}
191
+ def main_frame
192
+ @main_frame
193
+ end
194
+
195
+ # @return {!Array<!Frame>}
196
+ def frames
197
+ @frames.values
198
+ end
199
+
200
+ # @param {!string} frameId
201
+ # @return {?Frame}
202
+ def frame(frame_id)
203
+ @frames[frame_id]
204
+ end
205
+
206
+ # @param {string} frameId
207
+ # @param {?string} parentFrameId
208
+ def handle_frame_attached(frame_id, parent_frame_id)
209
+ return if @frames.has_key?(frame_id)
210
+ if !parent_frame_id
211
+ raise ArgymentError.new('parent_frame_id must not be nil')
212
+ end
213
+ parent_frame = @frames[parent_frame_id]
214
+ frame = Puppeteer::Frame.new(self, @client, parent_frame, frame_id)
215
+ @frames[frame_id] = frame
216
+
217
+ emit_event 'Events.FrameManager.FrameAttached', frame
218
+ end
219
+
220
+ # @param frame_payload [Hash]
221
+ def handle_frame_navigated(frame_payload)
222
+ is_main_frame = !frame_payload['parentId']
223
+ frame =
224
+ if is_main_frame
225
+ @main_frame
226
+ else
227
+ @frames[frame_payload['id']]
228
+ end
229
+
230
+ if !is_main_frame && !frame
231
+ raise ArgumentError.new('We either navigate top level or have old version of the navigated frame')
232
+ end
233
+
234
+ # Detach all child frames first.
235
+ if frame
236
+ frame.child_frames.each do |child|
237
+ remove_frame_recursively(child)
238
+ end
239
+ end
240
+
241
+ # Update or create main frame.
242
+ if is_main_frame
243
+ if frame
244
+ # Update frame id to retain frame identity on cross-process navigation.
245
+ @frames.delete(frame.id)
246
+ frame.id = frame_payload['id']
247
+ else
248
+ # Initial main frame navigation.
249
+ frame = Puppeteer::Frame.new(self, @client, nil, frame_payload['id'])
250
+ end
251
+ @frames[frame_payload['id']] = frame
252
+ @main_frame = frame
253
+ end
254
+
255
+ # Update frame payload.
256
+ frame.navigated(frame_payload)
257
+
258
+ emit_event 'Events.FrameManager.FrameNavigated', frame
259
+ end
260
+
261
+ # @param name [String]
262
+ def ensure_isolated_world(name)
263
+ return if @isolated_worlds.include?(name)
264
+ @isolated_worlds << name
265
+
266
+ @client.send_message('Page.addScriptToEvaluateOnNewDocument',
267
+ source: "//# sourceURL=#{Puppeteer::ExecutionContext::EVALUATION_SCRIPT_URL}",
268
+ worldName: name,
269
+ )
270
+ create_isolated_worlds_promises = frames.map do |frame|
271
+ @client.async_send_message('Page.createIsolatedWorld',
272
+ frameId: frame.id,
273
+ grantUniveralAccess: true,
274
+ worldName: name,
275
+ )
276
+ end
277
+ await_all(*create_isolated_worlds_promises)
278
+ end
279
+
280
+ # @param frame_id [String]
281
+ # @param url [String]
282
+ def handle_frame_navigated_within_document(frame_id, url)
283
+ frame = @frames[frame_id]
284
+ return if !frame
285
+ frame.navigated_within_document(url)
286
+ emit_event 'Events.FrameManager.FrameNavigatedWithinDocument', frame
287
+ emit_event 'Events.FrameManager.FrameNavigated', frame
288
+ end
289
+
290
+ # @param frame_id [String]
291
+ def handle_frame_detached(frame_id)
292
+ frame = @frames[frame_id]
293
+ if frame
294
+ remove_frame_recursively(frame)
295
+ end
296
+ end
297
+
298
+ # @param context_payload [Hash]
299
+ def handle_execution_context_created(context_payload)
300
+ frame = if_present(context_payload.dig('auxData', 'frameId')) { |frame_id| @frames[frame_id] }
301
+
302
+ world = nil
303
+ if frame
304
+ if context_payload.dig('auxData', 'isDefault')
305
+ world = frame.main_world
306
+ elsif context_payload['name'] == UTILITY_WORLD_NAME && !frame.secondary_world.has_context?
307
+ # In case of multiple sessions to the same target, there's a race between
308
+ # connections so we might end up creating multiple isolated worlds.
309
+ # We can use either.
310
+ world = frame.secondary_world
311
+ end
312
+ end
313
+
314
+ if context_payload.dig('auxData', 'type') == 'isolated'
315
+ @isolated_worlds << context_payload['name']
316
+ end
317
+
318
+ context = Puppeteer::ExecutionContext.new(@client, context_payload, world)
319
+ if world
320
+ world.context = context
321
+ end
322
+ @context_id_to_context[context_payload['id']] = context
323
+ @context_id_created[context_payload['id']] = Time.now
324
+ end
325
+
326
+ # @param {number} executionContextId
327
+ def handle_execution_context_destroyed(execution_context_id)
328
+ context = @context_id_to_context[execution_context_id]
329
+ return if !context
330
+ @context_id_to_context.delete(execution_context_id)
331
+ @context_id_created.delete(execution_context_id)
332
+ if context.world
333
+ context.world.delete_context(execution_context_id)
334
+ end
335
+ end
336
+
337
+ 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|
348
+ if context.world
349
+ context.world.delete_context(execution_context_id)
350
+ end
351
+ end
352
+ @context_id_to_context.select! { |k, v| context_ids_to_skip.include?(k) }
353
+ end
354
+
355
+ 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
361
+ end
362
+
363
+ # @param {!Frame} frame
364
+ private def remove_frame_recursively(frame)
365
+ frame.child_frames.each do |child|
366
+ remove_frame_recursively(child)
367
+ end
368
+ frame.detach
369
+ @frames.delete(frame.id)
370
+ emit_event 'Events.FrameManager.FrameDetached', frame
371
+ end
372
+
373
+ private def assert_no_legacy_navigation_options(wait_until:)
374
+ if wait_until == 'networkidle'
375
+ raise ArgumentError.new('ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead')
376
+ end
377
+ end
378
+ end