puppeteer-ruby 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +36 -0
- data/.travis.yml +7 -0
- data/Dockerfile +6 -0
- data/Gemfile +6 -0
- data/README.md +41 -0
- data/Rakefile +1 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +15 -0
- data/example.rb +7 -0
- data/lib/puppeteer.rb +192 -0
- data/lib/puppeteer/async_await_behavior.rb +34 -0
- data/lib/puppeteer/browser.rb +240 -0
- data/lib/puppeteer/browser_context.rb +90 -0
- data/lib/puppeteer/browser_fetcher.rb +6 -0
- data/lib/puppeteer/browser_runner.rb +142 -0
- data/lib/puppeteer/cdp_session.rb +78 -0
- data/lib/puppeteer/concurrent_ruby_utils.rb +37 -0
- data/lib/puppeteer/connection.rb +254 -0
- data/lib/puppeteer/console_message.rb +24 -0
- data/lib/puppeteer/debug_print.rb +20 -0
- data/lib/puppeteer/device.rb +12 -0
- data/lib/puppeteer/devices.rb +885 -0
- data/lib/puppeteer/dom_world.rb +447 -0
- data/lib/puppeteer/element_handle.rb +433 -0
- data/lib/puppeteer/emulation_manager.rb +46 -0
- data/lib/puppeteer/errors.rb +4 -0
- data/lib/puppeteer/event_callbackable.rb +88 -0
- data/lib/puppeteer/execution_context.rb +230 -0
- data/lib/puppeteer/frame.rb +278 -0
- data/lib/puppeteer/frame_manager.rb +380 -0
- data/lib/puppeteer/if_present.rb +18 -0
- data/lib/puppeteer/js_handle.rb +142 -0
- data/lib/puppeteer/keyboard.rb +183 -0
- data/lib/puppeteer/keyboard/key_description.rb +19 -0
- data/lib/puppeteer/keyboard/us_keyboard_layout.rb +283 -0
- data/lib/puppeteer/launcher.rb +26 -0
- data/lib/puppeteer/launcher/base.rb +48 -0
- data/lib/puppeteer/launcher/browser_options.rb +41 -0
- data/lib/puppeteer/launcher/chrome.rb +165 -0
- data/lib/puppeteer/launcher/chrome_arg_options.rb +49 -0
- data/lib/puppeteer/launcher/launch_options.rb +68 -0
- data/lib/puppeteer/lifecycle_watcher.rb +168 -0
- data/lib/puppeteer/mouse.rb +120 -0
- data/lib/puppeteer/network_manager.rb +122 -0
- data/lib/puppeteer/page.rb +1001 -0
- data/lib/puppeteer/page/screenshot_options.rb +78 -0
- data/lib/puppeteer/remote_object.rb +124 -0
- data/lib/puppeteer/target.rb +150 -0
- data/lib/puppeteer/timeout_settings.rb +15 -0
- data/lib/puppeteer/touch_screen.rb +43 -0
- data/lib/puppeteer/version.rb +3 -0
- data/lib/puppeteer/viewport.rb +36 -0
- data/lib/puppeteer/wait_task.rb +6 -0
- data/lib/puppeteer/web_socket.rb +117 -0
- data/lib/puppeteer/web_socket_transport.rb +49 -0
- data/puppeteer-ruby.gemspec +29 -0
- metadata +213 -0
@@ -0,0 +1,380 @@
|
|
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 {
|
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
|
+
}
|
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 = 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['parent_id']
|
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
|
+
handle_frame_manager_frame_navigated_within_document(frame)
|
289
|
+
handle_frame_manager_frame_navigated(frame)
|
290
|
+
end
|
291
|
+
|
292
|
+
# @param frame_id [String]
|
293
|
+
def handle_frame_detached(frame_id)
|
294
|
+
frame = @frames[frame_id]
|
295
|
+
if frame
|
296
|
+
remove_frame_recursively(frame)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# @param context_payload [Hash]
|
301
|
+
def handle_execution_context_created(context_payload)
|
302
|
+
frame = if_present(context_payload.dig('auxData', 'frameId')) { |frame_id| @frames[frame_id] }
|
303
|
+
|
304
|
+
world = nil
|
305
|
+
if frame
|
306
|
+
if context_payload.dig('auxData', 'isDefault')
|
307
|
+
world = frame.main_world
|
308
|
+
elsif context_payload['name'] == UTILITY_WORLD_NAME && !frame.secondary_world.has_context?
|
309
|
+
# In case of multiple sessions to the same target, there's a race between
|
310
|
+
# connections so we might end up creating multiple isolated worlds.
|
311
|
+
# We can use either.
|
312
|
+
world = frame.secondary_world
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
if context_payload.dig('auxData', 'type') == 'isolated'
|
317
|
+
@isolated_worlds << context_payload['name']
|
318
|
+
end
|
319
|
+
|
320
|
+
context = Puppeteer::ExecutionContext.new(@client, context_payload, world)
|
321
|
+
if world
|
322
|
+
world.context = context
|
323
|
+
end
|
324
|
+
@context_id_to_context[context_payload['id']] = context
|
325
|
+
@context_id_created[context_payload['id']] = Time.now
|
326
|
+
end
|
327
|
+
|
328
|
+
# @param {number} executionContextId
|
329
|
+
def handle_execution_context_destroyed(execution_context_id)
|
330
|
+
context = @context_id_to_context[execution_context_id]
|
331
|
+
return if !context
|
332
|
+
@context_id_to_context.delete(execution_context_id)
|
333
|
+
@context_id_created.delete(execution_context_id)
|
334
|
+
if context.world
|
335
|
+
context.world.context = nil
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def handle_execution_contexts_cleared
|
340
|
+
# executionContextCleared is often notified after executionContextCreated.
|
341
|
+
# 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"}
|
342
|
+
# 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"}
|
343
|
+
# 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"}}}
|
344
|
+
# D, [2020-04-06T01:47:03.101269 #13823] DEBUG -- : RECV << {"method"=>"Runtime.executionContextsCleared", "params"=>{}, "sessionId"=>"53F088EED260C28001D26A019F95D9E3"}
|
345
|
+
# it unexpectedly clears the created execution context.
|
346
|
+
# To avoid the problem, just skip recent created ids.
|
347
|
+
now = Time.now
|
348
|
+
context_ids_to_skip = @context_id_created.select { |k, v| now - v < 1 }.keys
|
349
|
+
@context_id_to_context.reject{ |k, v| context_ids_to_skip.include?(k) }.values.each do |context|
|
350
|
+
if context.world
|
351
|
+
context.world.context = nil
|
352
|
+
end
|
353
|
+
end
|
354
|
+
@context_id_to_context.select!{ |k, v| context_ids_to_skip.include?(k) }
|
355
|
+
end
|
356
|
+
|
357
|
+
def execution_context_by_id(context_id)
|
358
|
+
context = @context_id_to_context[context_id]
|
359
|
+
if !context
|
360
|
+
raise "INTERNAL ERROR: missing context with id = #{context_id}"
|
361
|
+
end
|
362
|
+
return context
|
363
|
+
end
|
364
|
+
|
365
|
+
# @param {!Frame} frame
|
366
|
+
private def remove_frame_recursively(frame)
|
367
|
+
frame.child_frames.each do |child|
|
368
|
+
remove_frame_recursively(child)
|
369
|
+
end
|
370
|
+
frame.detach
|
371
|
+
@frames.delete(frame.id)
|
372
|
+
emit_event 'Events.FrameManager.FrameDetached', frame
|
373
|
+
end
|
374
|
+
|
375
|
+
private def assert_no_legacy_navigation_options(wait_until:)
|
376
|
+
if wait_until == 'networkidle'
|
377
|
+
raise ArgumentError.new('ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead')
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Puppeteer::IfPresent
|
2
|
+
# Similar to #try in ActiveSupport::CoreExt.
|
3
|
+
#
|
4
|
+
# Evaluate block with the target, only if target is not nil.
|
5
|
+
# Returns nil if target is nil.
|
6
|
+
#
|
7
|
+
# --------
|
8
|
+
# if_present(params['target']) do |target|
|
9
|
+
# Point.new(target['x'], target['y'])
|
10
|
+
# end
|
11
|
+
# --------
|
12
|
+
def if_present(target, &block)
|
13
|
+
raise ArgumentError.new('block must be provided for #if_present') if block.nil?
|
14
|
+
return nil if target.nil?
|
15
|
+
|
16
|
+
block.call(target)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
class Puppeteer::JSHandle
|
2
|
+
using Puppeteer::AsyncAwaitBehavior
|
3
|
+
|
4
|
+
# @param context [Puppeteer::ExecutionContext]
|
5
|
+
# @param remote_object [Puppeteer::RemoteObject]
|
6
|
+
def self.create(context:, remote_object:)
|
7
|
+
frame = context.frame
|
8
|
+
if remote_object.sub_type == 'node' && frame
|
9
|
+
frame_manager = frame.frame_manager
|
10
|
+
Puppeteer::ElementHandle.new(
|
11
|
+
context: context,
|
12
|
+
client: context.client,
|
13
|
+
remote_object: remote_object,
|
14
|
+
page: frame_manager.page,
|
15
|
+
frame_manager: frame_manager,
|
16
|
+
)
|
17
|
+
else
|
18
|
+
Puppeteer::JSHandle.new(
|
19
|
+
context: context,
|
20
|
+
client: context.client,
|
21
|
+
remote_object: remote_object,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param context [Puppeteer::ExecutionContext]
|
27
|
+
# @param client [Puppeteer::CDPSession]
|
28
|
+
# @param remote_object [Puppeteer::RemoteObject]
|
29
|
+
def initialize(context:, client:, remote_object:)
|
30
|
+
@context = context
|
31
|
+
@client = client
|
32
|
+
@remote_object = remote_object
|
33
|
+
@disposed = false
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :context, :remote_object
|
37
|
+
|
38
|
+
# @return [Puppeteer::ExecutionContext]
|
39
|
+
def execution_context
|
40
|
+
@context
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param page_function [String]
|
44
|
+
# @return [Object]
|
45
|
+
def evaluate(page_function, *args)
|
46
|
+
execution_context.evaluate(page_function, self, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param page_function [String]
|
50
|
+
# @return [Future<Object>]
|
51
|
+
async def async_evaluate(page_function, *args)
|
52
|
+
evaluate(page_function, *args)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param page_function [String]
|
56
|
+
# @param args {Array<*>}
|
57
|
+
# @return [Puppeteer::JSHandle]
|
58
|
+
def evaluate_handle(page_function, *args)
|
59
|
+
execution_context.evaluate_handle(page_function, self, *args)
|
60
|
+
end
|
61
|
+
|
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)
|
67
|
+
end
|
68
|
+
|
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
|
+
# }
|
84
|
+
|
85
|
+
# getProperties in JavaScript.
|
86
|
+
# @return [Hash<String, JSHandle>]
|
87
|
+
def properties
|
88
|
+
response = @remote_object.properties(@client)
|
89
|
+
response['result'].each_with_object({}) do |prop, h|
|
90
|
+
next unless prop['enumerable']
|
91
|
+
h[prop['name']] = Puppeteer::JSHandle.create(
|
92
|
+
context: @context,
|
93
|
+
remote_object: Puppeteer::RemoteObject.new(prop['value']))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def json_value
|
98
|
+
# original logic was:
|
99
|
+
# if (this._remoteObject.objectId) {
|
100
|
+
# const response = await this._client.send('Runtime.callFunctionOn', {
|
101
|
+
# functionDeclaration: 'function() { return this; }',
|
102
|
+
# objectId: this._remoteObject.objectId,
|
103
|
+
# returnByValue: true,
|
104
|
+
# awaitPromise: true,
|
105
|
+
# });
|
106
|
+
# return helper.valueFromRemoteObject(response.result);
|
107
|
+
# }
|
108
|
+
# return helper.valueFromRemoteObject(this._remoteObject);
|
109
|
+
#
|
110
|
+
# However it would be better that RemoteObject is responsible for
|
111
|
+
# the logic `if (this._remoteObject.objectId) { ... }`.
|
112
|
+
@remote_object.evaluate_self(@client) || @remote_object.value
|
113
|
+
end
|
114
|
+
|
115
|
+
def as_element
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return [Future]
|
120
|
+
def dispose
|
121
|
+
return if @disposed
|
122
|
+
|
123
|
+
@disposed = true
|
124
|
+
@remote_object.release(@client)
|
125
|
+
end
|
126
|
+
|
127
|
+
def disposed?
|
128
|
+
@disposed
|
129
|
+
end
|
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
|
+
# }
|
142
|
+
end
|