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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +36 -0
  5. data/.travis.yml +7 -0
  6. data/Dockerfile +6 -0
  7. data/Gemfile +6 -0
  8. data/README.md +41 -0
  9. data/Rakefile +1 -0
  10. data/bin/console +11 -0
  11. data/bin/setup +8 -0
  12. data/docker-compose.yml +15 -0
  13. data/example.rb +7 -0
  14. data/lib/puppeteer.rb +192 -0
  15. data/lib/puppeteer/async_await_behavior.rb +34 -0
  16. data/lib/puppeteer/browser.rb +240 -0
  17. data/lib/puppeteer/browser_context.rb +90 -0
  18. data/lib/puppeteer/browser_fetcher.rb +6 -0
  19. data/lib/puppeteer/browser_runner.rb +142 -0
  20. data/lib/puppeteer/cdp_session.rb +78 -0
  21. data/lib/puppeteer/concurrent_ruby_utils.rb +37 -0
  22. data/lib/puppeteer/connection.rb +254 -0
  23. data/lib/puppeteer/console_message.rb +24 -0
  24. data/lib/puppeteer/debug_print.rb +20 -0
  25. data/lib/puppeteer/device.rb +12 -0
  26. data/lib/puppeteer/devices.rb +885 -0
  27. data/lib/puppeteer/dom_world.rb +447 -0
  28. data/lib/puppeteer/element_handle.rb +433 -0
  29. data/lib/puppeteer/emulation_manager.rb +46 -0
  30. data/lib/puppeteer/errors.rb +4 -0
  31. data/lib/puppeteer/event_callbackable.rb +88 -0
  32. data/lib/puppeteer/execution_context.rb +230 -0
  33. data/lib/puppeteer/frame.rb +278 -0
  34. data/lib/puppeteer/frame_manager.rb +380 -0
  35. data/lib/puppeteer/if_present.rb +18 -0
  36. data/lib/puppeteer/js_handle.rb +142 -0
  37. data/lib/puppeteer/keyboard.rb +183 -0
  38. data/lib/puppeteer/keyboard/key_description.rb +19 -0
  39. data/lib/puppeteer/keyboard/us_keyboard_layout.rb +283 -0
  40. data/lib/puppeteer/launcher.rb +26 -0
  41. data/lib/puppeteer/launcher/base.rb +48 -0
  42. data/lib/puppeteer/launcher/browser_options.rb +41 -0
  43. data/lib/puppeteer/launcher/chrome.rb +165 -0
  44. data/lib/puppeteer/launcher/chrome_arg_options.rb +49 -0
  45. data/lib/puppeteer/launcher/launch_options.rb +68 -0
  46. data/lib/puppeteer/lifecycle_watcher.rb +168 -0
  47. data/lib/puppeteer/mouse.rb +120 -0
  48. data/lib/puppeteer/network_manager.rb +122 -0
  49. data/lib/puppeteer/page.rb +1001 -0
  50. data/lib/puppeteer/page/screenshot_options.rb +78 -0
  51. data/lib/puppeteer/remote_object.rb +124 -0
  52. data/lib/puppeteer/target.rb +150 -0
  53. data/lib/puppeteer/timeout_settings.rb +15 -0
  54. data/lib/puppeteer/touch_screen.rb +43 -0
  55. data/lib/puppeteer/version.rb +3 -0
  56. data/lib/puppeteer/viewport.rb +36 -0
  57. data/lib/puppeteer/wait_task.rb +6 -0
  58. data/lib/puppeteer/web_socket.rb +117 -0
  59. data/lib/puppeteer/web_socket_transport.rb +49 -0
  60. data/puppeteer-ruby.gemspec +29 -0
  61. 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