Module: Palapala::ChromeProcess

Defined in:
lib/palapala/chrome_process.rb

Overview

Manage the Chrome child process

Class Method Summary collapse

Class Method Details

.chrome_pathObject

Get the path to the Chrome executable, if it’s not set, then guess based on the OS



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/palapala/chrome_process.rb', line 43

def self.chrome_path
  return Palapala.headless_chrome_path if Palapala.headless_chrome_path

  case RbConfig::CONFIG["host_os"]
  when /darwin/
    "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
  when /linux/
    "/usr/bin/google-chrome" # or "/usr/bin/chromium-browser"
  when /win|mingw|cygwin/
    "#{ENV.fetch("ProgramFiles(x86)", nil)}\\Google\\Chrome\\Application\\chrome.exe"
  else
    raise "Unsupported OS"
  end
end

.chrome_process_healthy?Boolean

Check if the Chrome process is healthy

Returns:

  • (Boolean)


17
18
19
20
21
22
23
24
25
26
# File 'lib/palapala/chrome_process.rb', line 17

def self.chrome_process_healthy?
  return false if @chrome_process_id.nil?

  begin
    Process.kill(0, @chrome_process_id) # Check if the process is alive
    true
  rescue Errno::ESRCH, Errno::EPERM
    false
  end
end

.chrome_running?Boolean

Check if a Chrome is running locally

Returns:

  • (Boolean)


29
30
31
32
# File 'lib/palapala/chrome_process.rb', line 29

def self.chrome_running?
  port_in_use? || # Check if the port is in use
  chrome_process_healthy? # Check if the process is still alive
end

.kill_chromeObject

Kill the Chrome child process



35
36
37
38
39
40
# File 'lib/palapala/chrome_process.rb', line 35

def self.kill_chrome
  return if @chrome_process_id.nil?

  Process.kill("KILL", @chrome_process_id) # Kill the process
  Process.wait(@chrome_process_id) # Wait for the process to finish
end

.npx_installed?Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/palapala/chrome_process.rb', line 58

def self.npx_installed?
  system("which npx > /dev/null 2>&1")
end

.port_in_use?(port = 9222, host = "127.0.0.1") ⇒ Boolean

Check if the port is in use

Returns:

  • (Boolean)


8
9
10
11
12
13
14
# File 'lib/palapala/chrome_process.rb', line 8

def self.port_in_use?(port = 9222, host = "127.0.0.1")
  server = TCPServer.new(host, port)
  server.close
  false
rescue Errno::EADDRINUSE
  true
end

.spawn_chromeObject

Spawn a Chrome child process



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/palapala/chrome_process.rb', line 107

def self.spawn_chrome
  return if chrome_running?

  @chrome_process_id =
    if Palapala.headless_chrome_path.nil? && self.npx_installed?
      spawn_chrome_headless_server_with_npx
    else
      spawn_chrome_from_path
    end

  # Wait until the port is in use
  sleep 0.1 until port_in_use?
  # Detach the process so it runs in the background
  Process.detach(@chrome_process_id)

  at_exit do
    if @chrome_process_id
      begin
        Process.kill("TERM", @chrome_process_id)
        Process.wait(@chrome_process_id)
        puts "Child process #{@chrome_process_id} terminated."
      rescue Errno::ESRCH
        puts "Child process #{@chrome_process_id} is already terminated."
      rescue Errno::ECHILD
        puts "No child process #{@chrome_process_id} found."
      end
    end
  end

  # Handle when the process is killed
  trap("SIGCHLD") do
    while (@chrome_process_id = Process.wait(-1, Process::WNOHANG))
      break if @chrome_process_id.nil?

      puts "Process #{@chrome_process_id} was killed."
      # Handle the error or restart the process if necessary
      @chrome_process_id = nil
    end
  rescue Errno::ECHILD
    @chrome_process_id = nil
  end
end

.spawn_chrome_from_pathObject



99
100
101
102
103
104
# File 'lib/palapala/chrome_process.rb', line 99

def self.spawn_chrome_from_path
  params = [ "--headless", "--disable-gpu", "--remote-debugging-port=9222" ]
  params.merge!(Palapala.chrome_params) if Palapala.chrome_params
  # Spawn an existing chrome with the path and parameters
  Process.spawn(chrome_path, *params)
end

.spawn_chrome_headless_server_with_npxObject



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/palapala/chrome_process.rb', line 62

def self.spawn_chrome_headless_server_with_npx
  # Run the command and capture the output
  puts "Installing/launching chrome-headless-shell@#{Palapala.chrome_headless_shell_version}"
  output, status = Open3.capture2("npx --yes @puppeteer/browsers install chrome-headless-shell@#{Palapala.chrome_headless_shell_version}")

  if status.success?
    # Extract the path from the output
    result = output.lines.find { |line| line.include?("chrome-headless-shell@") }
    if result.nil?
      raise "Failed to install chrome-headless-shell"
    end
    _, chrome_path = result.split(" ", 2).map(&:strip)

    # Directory you want the relative path from (current working directory)
    base_dir = Dir.pwd

    # Convert absolute path to relative path
    relative_path = Pathname.new(chrome_path).relative_path_from(Pathname.new(base_dir)).to_s

    puts "Launching chrome-headless-shell at #{relative_path}" if Palapala.debug
    # Display the version
    system("#{chrome_path} --version") if Palapala.debug
    # Launch chrome-headless-shell with the --remote-debugging-port parameter
    params = [ "--disable-gpu", "--remote-debugging-port=9222", "--remote-debugging-address=0.0.0.0" ]
    params.merge!(Palapala.chrome_params) if Palapala.chrome_params
    pid = if Palapala.debug
      spawn(chrome_path, *params)
    else
      spawn(chrome_path, *params, out: "/dev/null", err: "/dev/null")
    end
    Palapala.headless_chrome_url = "http://localhost:9222"
    pid
  else
    raise "Failed to install chrome-headless-shell"
  end
end