Class: Puppeteer::Bidi::BrowserLauncher
- Inherits:
-
Object
- Object
- Puppeteer::Bidi::BrowserLauncher
- Defined in:
- lib/puppeteer/bidi/browser_launcher.rb,
sig/puppeteer/bidi/browser_launcher.rbs
Overview
BrowserLauncher handles launching Firefox with BiDi support
Defined Under Namespace
Classes: LaunchError
Instance Attribute Summary collapse
-
#executable_path ⇒ Object
readonly
Returns the value of attribute executable_path.
-
#user_data_dir ⇒ Object
readonly
Returns the value of attribute user_data_dir.
Instance Method Summary collapse
- #build_launch_args(port) ⇒ Object
- #cleanup_temp_user_data_dir ⇒ Object
- #create_prefs_file ⇒ Object
- #default_prefs ⇒ Object
- #find_available_port ⇒ Object
- #find_firefox ⇒ Object
-
#initialize(executable_path: nil, user_data_dir: nil, headless: true, args: []) ⇒ BrowserLauncher
constructor
A new instance of BrowserLauncher.
-
#kill ⇒ Object
Kill the Firefox process.
-
#launch ⇒ String
Launch Firefox and return BiDi WebSocket endpoint.
- #setup_user_data_dir ⇒ Object
-
#wait ⇒ Object
Wait for process to exit.
- #wait_for_ws_endpoint(port, stdout, stderr, timeout: 30) ⇒ Object
Constructor Details
#initialize(executable_path: nil, user_data_dir: nil, headless: true, args: []) ⇒ BrowserLauncher
Returns a new instance of BrowserLauncher.
20 21 22 23 24 25 26 27 28 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 20 def initialize(executable_path: nil, user_data_dir: nil, headless: true, args: []) @executable_path = executable_path || find_firefox @user_data_dir = user_data_dir @headless = headless @extra_args = args @temp_user_data_dir = nil @process = nil @ws_endpoint = nil end |
Instance Attribute Details
#executable_path ⇒ Object (readonly)
Returns the value of attribute executable_path.
18 19 20 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 18 def executable_path @executable_path end |
#user_data_dir ⇒ Object (readonly)
Returns the value of attribute user_data_dir.
18 19 20 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 18 def user_data_dir @user_data_dir end |
Instance Method Details
#build_launch_args(port) ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 148 def build_launch_args(port) args = [] if RUBY_PLATFORM =~ /darwin/ args << '--foreground' end # Add headless flag if needed args << '--headless' if @headless # Add remote debugging port args << '--remote-debugging-port' << port.to_s # Add profile profile_dir = File.join(@user_data_dir, 'profile') args << '--profile' << profile_dir # Add user arguments args.concat(@extra_args) args end |
#cleanup_temp_user_data_dir ⇒ Object
136 137 138 139 140 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 136 def cleanup_temp_user_data_dir if @temp_user_data_dir && Dir.exist?(@temp_user_data_dir) FileUtils.rm_rf(@temp_user_data_dir) end end |
#create_prefs_file ⇒ Object
114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 114 def create_prefs_file profile_dir = File.join(@user_data_dir, 'profile') FileUtils.mkdir_p(profile_dir) prefs_file = File.join(profile_dir, 'prefs.js') prefs_content = default_prefs.map do |key, value| value_str = value.is_a?(String) ? "\"#{value}\"" : value "user_pref(\"#{key}\", #{value_str});" end.join("\n") File.write(prefs_file, prefs_content) end |
#default_prefs ⇒ Object
127 128 129 130 131 132 133 134 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 127 def default_prefs { # Force all web content to use a single content process. TODO: remove # this once Firefox supports mouse event dispatch from the main frame # context. See https://bugzilla.mozilla.org/show_bug.cgi?id=1773393. 'fission.webContentIsolationStrategy': 0, } end |
#find_available_port ⇒ Object
142 143 144 145 146 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 142 def find_available_port # Let Firefox choose a random port by using 0 # We'll read the actual port from the DevToolsActivePort file 0 end |
#find_firefox ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 82 def find_firefox candidates = [ ENV['FIREFOX_PATH'], '/usr/bin/firefox-nightly', '/usr/bin/firefox-devedition', '/usr/bin/firefox', '/usr/bin/firefox-esr', '/snap/bin/firefox', '/Applications/Firefox Nightly.app/Contents/MacOS/firefox', '/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox', '/Applications/Firefox.app/Contents/MacOS/firefox', ].compact candidates.each do |path| return path if File.executable?(path) end raise LaunchError, 'Could not find Firefox executable. Set FIREFOX_PATH environment variable.' end |
#kill ⇒ Object
Kill the Firefox process
60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 60 def kill if @process && @process.alive? begin Process.kill('TERM', @process.pid) # Give it time to shut down gracefully sleep(0.5) Process.kill('KILL', @process.pid) if @process.alive? rescue Errno::ESRCH # Process already dead end end cleanup_temp_user_data_dir end |
#launch ⇒ String
Launch Firefox and return BiDi WebSocket endpoint
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 32 def launch setup_user_data_dir port = find_available_port args = build_launch_args(port) # Launch Firefox process stdin, stdout, stderr, wait_thr = Open3.popen3(@executable_path, *args) @process = wait_thr # Close stdin as we don't need it stdin.close # Wait for BiDi endpoint to be available @ws_endpoint = wait_for_ws_endpoint(port, stdout, stderr) unless @ws_endpoint kill raise LaunchError, 'Failed to get BiDi WebSocket endpoint' end @ws_endpoint rescue => e kill raise LaunchError, "Failed to launch Firefox: #{e.}" end |
#setup_user_data_dir ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 102 def setup_user_data_dir if @user_data_dir FileUtils.mkdir_p(@user_data_dir) else @temp_user_data_dir = Dir.mktmpdir('puppeteer-firefox-') @user_data_dir = @temp_user_data_dir end # Create prefs.js for BiDi support create_prefs_file end |
#wait ⇒ Object
Wait for process to exit
76 77 78 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 76 def wait @process&.value end |
#wait_for_ws_endpoint(port, stdout, stderr, timeout: 30) ⇒ Object
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/puppeteer/bidi/browser_launcher.rb', line 171 def wait_for_ws_endpoint(port, stdout, stderr, timeout: 30) deadline = Time.now + timeout # Start threads to read output output_lines = [] error_lines = [] ws_endpoint = nil mutex = Mutex.new stdout_thread = Thread.new do stdout.each_line do |line| mutex.synchronize { output_lines << line } # Check for WebDriver BiDi endpoint in stdout if line =~ /WebDriver BiDi listening on (ws:\/\/[^\s]+)/ mutex.synchronize { ws_endpoint = $1 } end end rescue => e warn "Error reading stdout: #{e.}" end stderr_thread = Thread.new do stderr.each_line do |line| mutex.synchronize { error_lines << line } # Debug: print all stderr lines to help diagnose puts "[Firefox stderr] #{line}" if ENV['DEBUG_FIREFOX'] # Firefox outputs the BiDi WebSocket endpoint to stderr if line =~ /WebDriver BiDi listening on (ws:\/\/[^\s]+)/ mutex.synchronize { ws_endpoint = $1 } end end rescue => e warn "Error reading stderr: #{e.}" end # Wait for WebSocket endpoint to be detected loop do if Time.now > deadline stdout_thread.kill stderr_thread.kill warn "Timeout waiting for BiDi endpoint. stdout: #{output_lines.join}" warn "stderr: #{error_lines.join}" return nil end # Check if process died unless @process.alive? warn "Firefox process died. stderr: #{error_lines.join}" return nil end # Check if we found the endpoint mutex.synchronize do if ws_endpoint # Keep threads running to consume output (detach them) stdout_thread.join(0.1) stderr_thread.join(0.1) return ws_endpoint end end sleep(0.1) end ensure # Detach the output threads (let them run in background) stdout_thread&.join(1) stderr_thread&.join(1) end |