1414use Icinga \File \Storage \StorageInterface ;
1515use Icinga \File \Storage \TemporaryLocalFileStorage ;
1616use Icinga \Module \Pdfexport \PrintableHtmlDocument ;
17+ use Icinga \Module \Pdfexport \ShellCommand ;
1718use LogicException ;
1819use Throwable ;
1920use WebSocket \Client ;
@@ -23,21 +24,6 @@ class HeadlessChromeBackend implements PfdPrintBackend
2324 /** @var int */
2425 public const MIN_SUPPORTED_CHROME_VERSION = 59 ;
2526
26- /** @var int */
27- protected const CHROME_START_MAX_WAIT_TIME = 10 ;
28-
29- /** @var int */
30- protected const CHROME_CLOSE_MAX_WAIT_TIME = 5 ;
31-
32- /** @var int */
33- protected const PROCESS_IDLE_TIME = 100000 ;
34-
35- /** @var int */
36- protected const STREAM_WAIT_TIME = 200000 ;
37-
38- /** @var int */
39- protected const STREAM_CHUNK_SIZE = 8192 ;
40-
4127 /**
4228 * Line of stderr output identifying the websocket url
4329 *
@@ -62,9 +48,7 @@ class HeadlessChromeBackend implements PfdPrintBackend
6248
6349 private array $ interceptedEvents = [];
6450
65- protected $ process ;
66-
67- protected array $ pipes = [];
51+ protected ?ShellCommand $ process = null ;
6852
6953 protected ?string $ socket = null ;
7054
@@ -111,11 +95,6 @@ public static function createLocal(string $path, bool $useFile = false): static
11195 }
11296
11397 $ browserHome = $ instance ->getFileStorage ()->resolvePath ('HOME ' );
114- $ descriptors = [
115- 0 => ['pipe ' , 'r ' ], // stdin
116- 1 => ['pipe ' , 'w ' ], // stdout
117- 2 => ['pipe ' , 'w ' ], // stderr
118- ];
11998
12099 $ commandLine = join (' ' , [
121100 escapeshellarg ($ path ),
@@ -141,58 +120,25 @@ public static function createLocal(string $path, bool $useFile = false): static
141120 Logger::debug ('Starting browser process: %s ' , $ commandLine );
142121 }
143122
144- $ instance ->process = proc_open ($ commandLine , $ descriptors , $ instance ->pipes , null , $ env );
145-
146- if (! is_resource ($ instance ->process )) {
147- throw new Exception ('Could not start browser process. ' );
148- }
149-
150- // Non-blocking mode
151- stream_set_blocking ($ instance ->pipes [2 ], false );
152-
153- $ startTime = time ();
154-
155- while (true ) {
156- $ status = proc_get_status ($ instance ->process );
157-
158- // Timeout handling
159- if ((time () - $ startTime ) > self ::CHROME_START_MAX_WAIT_TIME ) {
160- proc_terminate ($ instance ->process , 6 ); // SIGABRT
161- Logger::error (
162- 'Browser timed out after %d seconds without the expected output ' ,
163- self ::CHROME_CLOSE_MAX_WAIT_TIME ,
164- );
165-
166- throw new Exception (
167- 'Received empty response or none at all from browser. '
168- . ' Please check the logs for further details. ' ,
169- );
123+ $ instance ->process = new ShellCommand ($ commandLine , false , $ env );
124+ $ instance ->process ->start ();
125+ Logger::debug ('Started browser process ' );
126+ $ instance ->process ->wait (function ($ stdout , $ stderr ) use ($ instance ) {
127+ if ($ stdout !== '' ) {
128+ Logger::debug ('Caught browser stdout: %d ' , mb_strlen ($ stdout ));
170129 }
171-
172- $ read = [$ instance ->pipes [2 ]];
173- $ write = null ;
174- $ except = null ;
175-
176- if (stream_select ($ read , $ write , $ except , 0 , self ::STREAM_WAIT_TIME )) {
177- $ chunk = fread ($ instance ->pipes [2 ], self ::STREAM_CHUNK_SIZE );
178-
179- if ($ chunk !== false && $ chunk !== '' ) {
180- Logger::debug ('Caught browser output: %s ' , $ chunk );
181-
182- if (preg_match (self ::DEBUG_ADDR_PATTERN , trim ($ chunk ), $ matches )) {
183- $ instance ->socket = $ matches [1 ];
184- $ instance ->browserId = $ matches [2 ];
185- break ;
186- }
130+ if ($ stderr !== '' ) {
131+ Logger::error ('Browser process stderr: %d ' , mb_strlen ($ stderr ));
132+ if (preg_match (self ::DEBUG_ADDR_PATTERN , trim ($ stderr ), $ matches )) {
133+ $ instance ->socket = $ matches [1 ];
134+ $ instance ->browserId = $ matches [2 ];
135+
136+ Logger::debug ('Caught browser info socket: %s, id: %s ' , $ instance ->socket , $ instance ->browserId );
137+ return false ;
187138 }
188139 }
189-
190- if (! $ status ['running ' ]) {
191- break ;
192- }
193-
194- usleep (self ::PROCESS_IDLE_TIME );
195- }
140+ return true ;
141+ });
196142
197143 if ($ instance ->socket === null || $ instance ->browserId === null ) {
198144 throw new Exception ('Could not start browser process. ' );
@@ -203,35 +149,11 @@ public static function createLocal(string $path, bool $useFile = false): static
203149
204150 protected function closeLocal (): void
205151 {
206- foreach ($ this ->pipes as $ pipe ) {
207- fclose ($ pipe );
208- }
209- $ this ->pipes = [];
152+ Logger::debug ('Closing local chrome instance ' );
210153
211154 if ($ this ->process !== null ) {
212- proc_terminate ($ this ->process );
213-
214- $ start = time ();
215- $ running = true ;
216-
217- while ($ running && (time () - $ start ) < self ::CHROME_CLOSE_MAX_WAIT_TIME ) {
218- $ status = proc_get_status ($ this ->process );
219- $ running = $ status ['running ' ];
220-
221- if ($ running ) {
222- usleep (self ::PROCESS_IDLE_TIME );
223- }
224- }
225-
226- // If still running after wait time seconds, force kills the entire process group
227- if ($ running ) {
228- $ status = proc_get_status ($ this ->process );
229- if (! empty ($ status ['pid ' ])) {
230- posix_kill (-$ status ['pid ' ], SIGKILL );
231- }
232- }
233-
234- proc_close ($ this ->process );
155+ $ code = $ this ->process ->stop ();
156+ Logger::error ("Closed local chrome with exit code %d " , $ code );
235157 $ this ->process = null ;
236158 }
237159
@@ -356,7 +278,7 @@ public function getPage(): Client
356278
357279 try {
358280 $ this ->communicate ($ this ->page , 'Console.enable ' );
359- } catch (Exception $ _ ) {
281+ } catch (Exception ) {
360282 // Deprecated, might fail
361283 }
362284 }
@@ -526,7 +448,7 @@ private function parseApiResponse(string $payload)
526448 }
527449 }
528450
529- private function registerEvent ($ method , $ params )
451+ private function registerEvent ($ method , $ params ): void
530452 {
531453 if (Logger::getInstance ()->getLevel () === Logger::DEBUG ) {
532454 $ shortenValues = function ($ params ) use (&$ shortenValues ) {
0 commit comments