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,26 @@
|
|
1
|
+
require_relative './launcher/base'
|
2
|
+
require_relative './launcher/browser_options'
|
3
|
+
require_relative './launcher/chrome'
|
4
|
+
require_relative './launcher/chrome_arg_options'
|
5
|
+
require_relative './launcher/launch_options'
|
6
|
+
|
7
|
+
# https://github.com/puppeteer/puppeteer/blob/master/lib/Launcher.js
|
8
|
+
module Puppeteer::Launcher
|
9
|
+
|
10
|
+
# @param {string} projectRoot
|
11
|
+
# @param {string} preferredRevision
|
12
|
+
# @param {boolean} isPuppeteerCore
|
13
|
+
# @param {string=} product
|
14
|
+
# @return {!Puppeteer.ProductLauncher}
|
15
|
+
module_function def new(project_root:, preferred_revision:, is_puppeteer_core:, product:)
|
16
|
+
if product == 'firefox'
|
17
|
+
raise NotImplementedError.new("FirefoxLauncher is not implemented yet.")
|
18
|
+
end
|
19
|
+
|
20
|
+
Chrome.new(
|
21
|
+
project_root: project_root,
|
22
|
+
preferred_revision: preferred_revision,
|
23
|
+
is_puppeteer_core: is_puppeteer_core,
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Puppeteer::Launcher
|
2
|
+
class Base
|
3
|
+
# @param {string} projectRoot
|
4
|
+
# @param {string} preferredRevision
|
5
|
+
def initialize(project_root:, preferred_revision:, is_puppeteer_core:)
|
6
|
+
@project_root = project_root
|
7
|
+
@preferred_revision = preferred_revision
|
8
|
+
@is_puppeteer_core = is_puppeteer_core
|
9
|
+
end
|
10
|
+
|
11
|
+
class ExecutablePathNotFound < StandardError ; end
|
12
|
+
|
13
|
+
# @returns [String] Chrome Executable file path.
|
14
|
+
# @raise [ExecutablePathNotFound]
|
15
|
+
def resolve_executable_path
|
16
|
+
if !@is_puppeteer_core
|
17
|
+
# puppeteer-core doesn't take into account PUPPETEER_* env variables.
|
18
|
+
executable_path = ENV['PUPPETEER_EXECUTABLE_PATH']
|
19
|
+
if FileTest.exist?(executable_path)
|
20
|
+
return executable_path
|
21
|
+
end
|
22
|
+
raise ExecutablePathNotFound.new(
|
23
|
+
"Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: #{executablePath}",
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
# temporal logic.
|
28
|
+
if RUBY_PLATFORM.include?("darwin") # MacOS
|
29
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
30
|
+
else
|
31
|
+
'/usr/bin/google-chrome'
|
32
|
+
end
|
33
|
+
|
34
|
+
# const browserFetcher = new BrowserFetcher(launcher._projectRoot);
|
35
|
+
# if (!launcher._isPuppeteerCore) {
|
36
|
+
# const revision = process.env['PUPPETEER_CHROMIUM_REVISION'];
|
37
|
+
# if (revision) {
|
38
|
+
# const revisionInfo = browserFetcher.revisionInfo(revision);
|
39
|
+
# const missingText = !revisionInfo.local ? 'Tried to use PUPPETEER_CHROMIUM_REVISION env variable to launch browser but did not find executable at: ' + revisionInfo.executablePath : null;
|
40
|
+
# return {executablePath: revisionInfo.executablePath, missingText};
|
41
|
+
# }
|
42
|
+
# }
|
43
|
+
# const revisionInfo = browserFetcher.revisionInfo(launcher._preferredRevision);
|
44
|
+
# const missingText = !revisionInfo.local ? `Browser is not downloaded. Run "npm install" or "yarn install"` : null;
|
45
|
+
# return {executablePath: revisionInfo.executablePath, missingText};
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# const {
|
2
|
+
# ignoreDefaultArgs = false,
|
3
|
+
# args = [],
|
4
|
+
# dumpio = false,
|
5
|
+
# executablePath = null,
|
6
|
+
# pipe = false,
|
7
|
+
# env = process.env,
|
8
|
+
# handleSIGINT = true,
|
9
|
+
# handleSIGTERM = true,
|
10
|
+
# handleSIGHUP = true,
|
11
|
+
# ignoreHTTPSErrors = false,
|
12
|
+
# defaultViewport = {width: 800, height: 600},
|
13
|
+
# slowMo = 0,
|
14
|
+
# timeout = 30000
|
15
|
+
# } = options;
|
16
|
+
# const {
|
17
|
+
# devtools = false,
|
18
|
+
# headless = !devtools,
|
19
|
+
# args = [],
|
20
|
+
# userDataDir = null
|
21
|
+
# } = options;
|
22
|
+
|
23
|
+
|
24
|
+
module Puppeteer::Launcher
|
25
|
+
class BrowserOptions
|
26
|
+
# @property {boolean=} ignoreHTTPSErrors
|
27
|
+
# @property {(?Puppeteer.Viewport)=} defaultViewport
|
28
|
+
# @property {number=} slowMo
|
29
|
+
def initialize(options)
|
30
|
+
@ignore_https_errors = options[:ignore_https_errors] || false
|
31
|
+
@default_viewport = options[:default_viewport] || Puppeteer::Viewport.new(width: 800, height: 600)
|
32
|
+
@slow_mo = options[:slow_mo] || 0
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :default_viewport, :slow_mo
|
36
|
+
|
37
|
+
def ignore_https_errors?
|
38
|
+
@ignore_https_errors
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
# https://github.com/puppeteer/puppeteer/blob/master/lib/Launcher.js
|
4
|
+
module Puppeteer::Launcher
|
5
|
+
class Chrome < Base
|
6
|
+
# @param {!(Launcher.LaunchOptions & Launcher.ChromeArgOptions & Launcher.BrowserOptions)=} options
|
7
|
+
# @return {!Promise<!Browser>}
|
8
|
+
def launch(options = {})
|
9
|
+
@chrome_arg_options = ChromeArgOptions.new(options)
|
10
|
+
@launch_options = LaunchOptions.new(options)
|
11
|
+
@browser_options = BrowserOptions.new(options)
|
12
|
+
|
13
|
+
chrome_arguments =
|
14
|
+
if !@launch_options.ignore_default_args
|
15
|
+
default_args.to_a
|
16
|
+
elsif @launch_options.ignore_default_args.is_a?(Enumerable)
|
17
|
+
default_args.reject do |arg|
|
18
|
+
@launch_options.ignore_default_args.include?(arg)
|
19
|
+
end.to_a
|
20
|
+
else
|
21
|
+
@chrome_arg_options.args.dup
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# let temporaryUserDataDir = null;
|
26
|
+
|
27
|
+
if chrome_arguments.none?{ |arg| arg.start_with?('--remote-debugging-') }
|
28
|
+
if @launch_options.pipe?
|
29
|
+
chrome_arguments << '--remote-debugging-pipe'
|
30
|
+
else
|
31
|
+
chrome_arguments << '--remote-debugging-port=0'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
temporary_user_data_dir = nil
|
36
|
+
if chrome_arguments.none?{ |arg| arg.start_with?('--user-data-dir') }
|
37
|
+
temporary_user_data_dir = Dir.mktmpdir('puppeteer_dev_profile-')
|
38
|
+
chrome_arguments << "--user-data-dir=#{temporary_user_data_dir}"
|
39
|
+
end
|
40
|
+
|
41
|
+
chrome_executable = @launch_options.executable_path || resolve_executable_path
|
42
|
+
use_pipe = chrome_arguments.include?('--remote-debugging-pipe')
|
43
|
+
runner = Puppeteer::BrowserRunner.new(chrome_executable, chrome_arguments, temporary_user_data_dir)
|
44
|
+
runner.start(
|
45
|
+
handle_SIGHUP: @launch_options.handle_SIGHUP?,
|
46
|
+
handle_SIGTERM: @launch_options.handle_SIGTERM?,
|
47
|
+
handle_SIGINT: @launch_options.handle_SIGINT?,
|
48
|
+
dumpio: @launch_options.dumpio?,
|
49
|
+
env: @launch_options.env,
|
50
|
+
pipe: use_pipe,
|
51
|
+
);
|
52
|
+
|
53
|
+
begin
|
54
|
+
connection = runner.setup_connection(
|
55
|
+
use_pipe: use_pipe,
|
56
|
+
timeout: @launch_options.timeout,
|
57
|
+
slow_mo: @browser_options.slow_mo,
|
58
|
+
preferred_revision: @preferred_revision,
|
59
|
+
)
|
60
|
+
|
61
|
+
browser = Puppeteer::Browser.create(
|
62
|
+
connection: connection,
|
63
|
+
context_ids: [],
|
64
|
+
ignore_https_errors: @browser_options.ignore_https_errors?,
|
65
|
+
default_viewport: @browser_options.default_viewport,
|
66
|
+
process: runner.proc,
|
67
|
+
close_callback: ->{ runner.close },
|
68
|
+
)
|
69
|
+
|
70
|
+
browser.wait_for_target(predicate: ->(target) { target.type == 'page' })
|
71
|
+
|
72
|
+
browser
|
73
|
+
rescue
|
74
|
+
runner.kill
|
75
|
+
raise
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class DefaultArgs
|
80
|
+
include Enumerable
|
81
|
+
|
82
|
+
# @param options [Launcher::ChromeArgOptions]
|
83
|
+
def initialize(chrome_arg_options)
|
84
|
+
chrome_arguments = [
|
85
|
+
'--disable-background-networking',
|
86
|
+
'--enable-features=NetworkService,NetworkServiceInProcess',
|
87
|
+
'--disable-background-timer-throttling',
|
88
|
+
'--disable-backgrounding-occluded-windows',
|
89
|
+
'--disable-breakpad',
|
90
|
+
'--disable-client-side-phishing-detection',
|
91
|
+
'--disable-component-extensions-with-background-pages',
|
92
|
+
'--disable-default-apps',
|
93
|
+
'--disable-dev-shm-usage',
|
94
|
+
'--disable-extensions',
|
95
|
+
'--disable-features=TranslateUI',
|
96
|
+
'--disable-hang-monitor',
|
97
|
+
'--disable-ipc-flooding-protection',
|
98
|
+
'--disable-popup-blocking',
|
99
|
+
'--disable-prompt-on-repost',
|
100
|
+
'--disable-renderer-backgrounding',
|
101
|
+
'--disable-sync',
|
102
|
+
'--force-color-profile=srgb',
|
103
|
+
'--metrics-recording-only',
|
104
|
+
'--no-first-run',
|
105
|
+
'--enable-automation',
|
106
|
+
'--password-store=basic',
|
107
|
+
'--use-mock-keychain',
|
108
|
+
]
|
109
|
+
|
110
|
+
if chrome_arg_options.user_data_dir
|
111
|
+
chrome_arguments << "--user-data-dir=#{chrome_arg_options.user_data_dir}"
|
112
|
+
end
|
113
|
+
|
114
|
+
if chrome_arg_options.devtools?
|
115
|
+
chrome_arguments << '--auto-open-devtools-for-tabs'
|
116
|
+
end
|
117
|
+
|
118
|
+
if (chrome_arg_options.headless?)
|
119
|
+
chrome_arguments.concat([
|
120
|
+
'--headless',
|
121
|
+
'--hide-scrollbars',
|
122
|
+
'--mute-audio',
|
123
|
+
])
|
124
|
+
end
|
125
|
+
|
126
|
+
if chrome_arg_options.args.all?{ |arg| arg.start_with?('-') }
|
127
|
+
chrome_arguments << 'about:blank'
|
128
|
+
end
|
129
|
+
|
130
|
+
chrome_arguments.concat(chrome_arg_options.args)
|
131
|
+
|
132
|
+
@chrome_arguments = chrome_arguments
|
133
|
+
end
|
134
|
+
|
135
|
+
def each(&block)
|
136
|
+
@chrome_arguments.each do |opt|
|
137
|
+
block.call(opt)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def default_args(options = nil)
|
143
|
+
if options.nil?
|
144
|
+
@default_args ||= DefaultArgs.new(@chrome_arg_options)
|
145
|
+
else
|
146
|
+
DefaultArgs.new(ChromeArgOptions.new(options))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options
|
151
|
+
# @return {!Promise<!Browser>}
|
152
|
+
def connect(options)
|
153
|
+
raise NotImplementedError.new('Puppeteer.connect is not implemented yet')
|
154
|
+
end
|
155
|
+
|
156
|
+
# @return {string}
|
157
|
+
def executable_path
|
158
|
+
resolve_executable_path
|
159
|
+
end
|
160
|
+
|
161
|
+
private def product
|
162
|
+
'chrome'
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# const {
|
2
|
+
# ignoreDefaultArgs = false,
|
3
|
+
# args = [],
|
4
|
+
# dumpio = false,
|
5
|
+
# executablePath = null,
|
6
|
+
# pipe = false,
|
7
|
+
# env = process.env,
|
8
|
+
# handleSIGINT = true,
|
9
|
+
# handleSIGTERM = true,
|
10
|
+
# handleSIGHUP = true,
|
11
|
+
# ignoreHTTPSErrors = false,
|
12
|
+
# defaultViewport = {width: 800, height: 600},
|
13
|
+
# slowMo = 0,
|
14
|
+
# timeout = 30000
|
15
|
+
# } = options;
|
16
|
+
# const {
|
17
|
+
# devtools = false,
|
18
|
+
# headless = !devtools,
|
19
|
+
# args = [],
|
20
|
+
# userDataDir = null
|
21
|
+
# } = options;
|
22
|
+
|
23
|
+
module Puppeteer::Launcher
|
24
|
+
class ChromeArgOptions
|
25
|
+
# * @property {boolean=} headless
|
26
|
+
# * @property {Array<string>=} args
|
27
|
+
# * @property {string=} userDataDir
|
28
|
+
# * @property {boolean=} devtools
|
29
|
+
def initialize(options)
|
30
|
+
@args = options[:args] || []
|
31
|
+
@user_data_dir = options[:user_data_dir]
|
32
|
+
@devtools = options[:devtools] || false
|
33
|
+
@headless = options[:headless]
|
34
|
+
if @headless.nil?
|
35
|
+
@headless = !@devtools
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :args, :user_data_dir
|
40
|
+
|
41
|
+
def headless?
|
42
|
+
@headless
|
43
|
+
end
|
44
|
+
|
45
|
+
def devtools?
|
46
|
+
@devtools
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# const {
|
2
|
+
# ignoreDefaultArgs = false,
|
3
|
+
# args = [],
|
4
|
+
# dumpio = false,
|
5
|
+
# executablePath = null,
|
6
|
+
# pipe = false,
|
7
|
+
# env = process.env,
|
8
|
+
# handleSIGINT = true,
|
9
|
+
# handleSIGTERM = true,
|
10
|
+
# handleSIGHUP = true,
|
11
|
+
# ignoreHTTPSErrors = false,
|
12
|
+
# defaultViewport = {width: 800, height: 600},
|
13
|
+
# slowMo = 0,
|
14
|
+
# timeout = 30000
|
15
|
+
# } = options;
|
16
|
+
# const {
|
17
|
+
# devtools = false,
|
18
|
+
# headless = !devtools,
|
19
|
+
# args = [],
|
20
|
+
# userDataDir = null
|
21
|
+
# } = options;
|
22
|
+
|
23
|
+
module Puppeteer::Launcher
|
24
|
+
class LaunchOptions
|
25
|
+
# @property {string=} executablePath
|
26
|
+
# @property {boolean|Array<string>=} ignoreDefaultArgs
|
27
|
+
# @property {boolean=} handleSIGINT
|
28
|
+
# @property {boolean=} handleSIGTERM
|
29
|
+
# @property {boolean=} handleSIGHUP
|
30
|
+
# @property {number=} timeout
|
31
|
+
# @property {boolean=} dumpio
|
32
|
+
# @property {!Object<string, string | undefined>=} env
|
33
|
+
# @property {boolean=} pipe
|
34
|
+
def initialize(options)
|
35
|
+
@executable_path = options[:executable_path]
|
36
|
+
@ignore_default_args = options[:ignore_default_args] || false
|
37
|
+
@handle_SIGINT = options[:handle_SIGINT] || true
|
38
|
+
@handle_SIGTERM = options[:handle_SIGTERM] || true
|
39
|
+
@handle_SIGHUP = options[:handle_SIGHUP] || true
|
40
|
+
@timeout = options[:timeout] || 30000
|
41
|
+
@dumpio = options[:dumpio] || false
|
42
|
+
@env = options[:env] || ENV
|
43
|
+
@pipe = options[:pipe] || false
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :executable_path, :ignore_default_args, :timeout, :env
|
47
|
+
|
48
|
+
def handle_SIGINT?
|
49
|
+
@handle_SIGINT
|
50
|
+
end
|
51
|
+
|
52
|
+
def handle_SIGTERM?
|
53
|
+
@handle_SIGTERM
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_SIGHUP?
|
57
|
+
@handle_SIGHUP
|
58
|
+
end
|
59
|
+
|
60
|
+
def dumpio?
|
61
|
+
@dumpio
|
62
|
+
end
|
63
|
+
|
64
|
+
def pipe?
|
65
|
+
@pipe
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
# https://github.com/puppeteer/puppeteer/blob/master/lib/LifecycleWatcher.js
|
4
|
+
class Puppeteer::LifecycleWatcher
|
5
|
+
include Puppeteer::IfPresent
|
6
|
+
|
7
|
+
class ExpectedLifecycle
|
8
|
+
PUPPETEER_TO_PROTOCOL_LIFECYCLE = {
|
9
|
+
'load' => 'load',
|
10
|
+
'domcontentloaded' => 'DOMContentLoaded',
|
11
|
+
'networkidle0' => 'networkIdle',
|
12
|
+
'networkidle2' => 'networkAlmostIdle',
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(wait_until)
|
16
|
+
if wait_until.is_a?(Enumerable)
|
17
|
+
@wait_until = wait_until.map do |value|
|
18
|
+
unless PUPPETEER_TO_PROTOCOL_LIFECYCLE.has_key?(value.to_s)
|
19
|
+
raise ArgumentError.new("Unknown value for options.waitUntil: #{value}")
|
20
|
+
end
|
21
|
+
value.to_s
|
22
|
+
end
|
23
|
+
elsif wait_until.is_a?(String)
|
24
|
+
unless PUPPETEER_TO_PROTOCOL_LIFECYCLE.has_key?(wait_until)
|
25
|
+
raise ArgumentError.new("Unknown value for options.waitUntil: #{wait_until}")
|
26
|
+
end
|
27
|
+
@wait_until = [wait_until]
|
28
|
+
else
|
29
|
+
raise ArgumentError.new('wait_until should be a Array<String> or String')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private def expected_lifecycle
|
34
|
+
@expected_lifecycle ||= @wait_until.map do |value|
|
35
|
+
PUPPETEER_TO_PROTOCOL_LIFECYCLE[value]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Check if navigation lifecycle has experienced the expected_lifecycle events.
|
40
|
+
#
|
41
|
+
# @param frame [Puppeteer::Frame]
|
42
|
+
def completed?(frame)
|
43
|
+
if expected_lifecycle.any? { |event| !frame.lifecycle_events.include?(event) }
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
if frame.child_frames.any? { |child| !completed?(child) }
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class TerminatedError < StandardError ; end
|
54
|
+
|
55
|
+
# * @param {!Puppeteer.FrameManager} frameManager
|
56
|
+
# * @param {!Puppeteer.Frame} frame
|
57
|
+
# * @param {string|!Array<string>} waitUntil
|
58
|
+
# * @param {number} timeout
|
59
|
+
def initialize(frame_manager, frame, wait_until, timeout)
|
60
|
+
@expected_lifecycle = ExpectedLifecycle.new(wait_until)
|
61
|
+
@frame_manager = frame_manager
|
62
|
+
@frame = frame
|
63
|
+
@initial_loader_id = frame.loader_id
|
64
|
+
@timeout = timeout
|
65
|
+
|
66
|
+
@listener_ids = {}
|
67
|
+
@listener_ids['client'] = @frame_manager.client.add_event_listener('Events.CDPSession.Disconnected') do
|
68
|
+
terminate(TerminatedError.new('Navigation failed because browser has disconnected!'))
|
69
|
+
end
|
70
|
+
@listener_ids['frame_manager'] = [
|
71
|
+
@frame_manager.add_event_listener('Events.FrameManager.LifecycleEvent') do |frame|
|
72
|
+
check_lifecycle_complete
|
73
|
+
end,
|
74
|
+
@frame_manager.add_event_listener('Events.FrameManager.FrameNavigatedWithinDocument', &method(:navigated_within_document)),
|
75
|
+
@frame_manager.add_event_listener('Events.FrameManager.FrameDetached', &method(:handle_frame_detached)),
|
76
|
+
]
|
77
|
+
@listener_ids['network_manager'] = @frame_manager.network_manager.add_event_listener('Events.NetworkManager.Request', &method(:handle_request))
|
78
|
+
|
79
|
+
@same_document_navigation_promise = resolvable_future
|
80
|
+
@lifecycle_promise = resolvable_future
|
81
|
+
@new_document_navigation_promise = resolvable_future
|
82
|
+
@termination_promise = resolvable_future
|
83
|
+
check_lifecycle_complete
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [Puppeteer::Request] request
|
87
|
+
def handle_request(request)
|
88
|
+
return if request.frame != @frame || !request.navigation_request?
|
89
|
+
@navigation_request = request
|
90
|
+
end
|
91
|
+
|
92
|
+
# @param frame [Puppeteer::Frame]
|
93
|
+
def handle_frame_detached(frame)
|
94
|
+
if @frame == frame
|
95
|
+
# this._terminationCallback.call(null, new Error('Navigating frame was detached'));
|
96
|
+
return
|
97
|
+
end
|
98
|
+
check_lifecycle_complete
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Puppeteer::Response]
|
102
|
+
def navigation_response
|
103
|
+
if_present(@navigation_request) do |request|
|
104
|
+
request.response
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param error [TerminatedError]
|
109
|
+
private def terminate(error)
|
110
|
+
@termination_promise.reject(error)
|
111
|
+
end
|
112
|
+
|
113
|
+
attr_reader(
|
114
|
+
:same_document_navigation_promise,
|
115
|
+
:new_document_navigation_promise,
|
116
|
+
:lifecycle_promise,
|
117
|
+
)
|
118
|
+
|
119
|
+
def timeout_or_termination_promise
|
120
|
+
if @timeout > 0
|
121
|
+
future {
|
122
|
+
begin
|
123
|
+
Timeout.timeout(@timeout / 1000.0) do
|
124
|
+
@termination_promise.value!
|
125
|
+
end
|
126
|
+
rescue Timeout::Error
|
127
|
+
raise Puppeteer::FrameManager::NavigationError.new("Navigation timeout of #{@timeout}ms exceeded")
|
128
|
+
end
|
129
|
+
}
|
130
|
+
else
|
131
|
+
@termination_promise
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param frame [Puppeteer::Frame]
|
136
|
+
private def navigated_within_document(frame)
|
137
|
+
return if frame != @frame
|
138
|
+
@has_same_document_navigation = true
|
139
|
+
check_lifecycle_complete
|
140
|
+
end
|
141
|
+
|
142
|
+
private def check_lifecycle_complete
|
143
|
+
# We expect navigation to commit.
|
144
|
+
return unless @expected_lifecycle.completed?(@frame)
|
145
|
+
@lifecycle_promise.fulfill(true) if @lifecycle_promise.pending?
|
146
|
+
if @frame.loader_id == @initial_loader_id && !@has_same_document_navigation
|
147
|
+
return
|
148
|
+
end
|
149
|
+
if @has_same_document_navigation
|
150
|
+
@same_document_navigation_promise.fulfill(true)
|
151
|
+
end
|
152
|
+
if @frame.loader_id != @initial_loader_id
|
153
|
+
@new_document_navigation_promise.fulfill(true)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def dispose
|
158
|
+
if_present(@listener_ids['client']) do |id|
|
159
|
+
@frame_manager.client.remove_event_listener(id)
|
160
|
+
end
|
161
|
+
if_present(@listener_ids['frame_manager']) do |ids|
|
162
|
+
@frame_manager.remove_event_listener(*ids)
|
163
|
+
end
|
164
|
+
if_present(@listener_ids['network_manager']) do |id|
|
165
|
+
@frame_manager.network_manager.remove_event_listener(id)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|