@@ -12,7 +12,11 @@ import { UnreachableError } from "./lib/error.ts";
1212import { quartoDataDir } from "./appdirs.ts" ;
1313import { isMac , isWindows } from "../deno_ral/platform.ts" ;
1414import puppeteer from "puppeteer" ;
15- import { chromeHeadlessShellExecutablePath } from "../tools/impl/chrome-headless-shell.ts" ;
15+ import {
16+ chromeHeadlessShellExecutablePath ,
17+ chromeHeadlessShellInstallDir ,
18+ readInstalledVersion ,
19+ } from "../tools/impl/chrome-headless-shell.ts" ;
1620
1721// deno-lint-ignore no-explicit-any
1822// let puppeteerImport: any = undefined;
@@ -273,59 +277,101 @@ export async function findChrome(): Promise<ChromeInfo> {
273277 return { path : path , source : source } ;
274278}
275279
276- export async function getBrowserExecutablePath ( ) {
277- // Cook up a new instance
278- const browserFetcher = await fetcher ( ) ;
279- const availableRevisions = await browserFetcher . localRevisions ( ) ;
280+ export interface BrowserDetection {
281+ path : string ;
282+ source : string ;
283+ label : string ;
284+ version ?: string ;
285+ displaySource ?: string ;
286+ }
280287
281- let executablePath : string | undefined = undefined ;
288+ export interface BrowserDetectionResult {
289+ detected ?: BrowserDetection ;
290+ warning ?: string ;
291+ }
282292
283- // Priority 1: QUARTO_CHROMIUM env var
293+ /**
294+ * Detect available Chrome/Chromium browser using the standard priority order:
295+ * 1. QUARTO_CHROMIUM env var
296+ * 2. Quarto-installed chrome-headless-shell
297+ * 3. System Chrome/Edge
298+ *
299+ * Does NOT check legacy fallbacks (puppeteer revisions, chromium tool) —
300+ * callers handle those based on their context.
301+ */
302+ export async function detectBrowser ( ) : Promise < BrowserDetectionResult > {
303+ const result : BrowserDetectionResult = { } ;
304+
305+ // 1. QUARTO_CHROMIUM environment variable
284306 const envPath = Deno . env . get ( "QUARTO_CHROMIUM" ) ;
285307 if ( envPath ) {
286308 if ( safeExistsSync ( envPath ) ) {
287- debug ( `[CHROMIUM] Using QUARTO_CHROMIUM: ${ envPath } ` ) ;
288- executablePath = envPath ;
289- } else {
290- debug ( `[CHROMIUM] QUARTO_CHROMIUM is set to ${ envPath } but the path does not exist. Searching for another browser.` ) ;
309+ result . detected = {
310+ path : envPath ,
311+ source : "QUARTO_CHROMIUM" ,
312+ label : "Chrome from QUARTO_CHROMIUM" ,
313+ } ;
314+ return result ;
291315 }
316+ result . warning =
317+ `QUARTO_CHROMIUM is set to ${ envPath } but the path does not exist.` ;
292318 }
293319
294- // Priority 2: Quarto-installed chrome-headless-shell
295- if ( executablePath === undefined ) {
296- executablePath = chromeHeadlessShellExecutablePath ( ) ;
297- if ( executablePath ) {
298- debug ( `[CHROMIUM] Using chrome-headless-shell: ${ executablePath } ` ) ;
299- }
320+ // 2. Quarto-installed chrome-headless-shell
321+ const chromeHsPath = chromeHeadlessShellExecutablePath ( ) ;
322+ if ( chromeHsPath !== undefined ) {
323+ const version = readInstalledVersion ( chromeHeadlessShellInstallDir ( ) ) ;
324+ result . detected = {
325+ path : chromeHsPath ,
326+ source : "quarto-chrome-headless-shell" ,
327+ label : "Chrome Headless Shell installed by Quarto" ,
328+ version,
329+ } ;
330+ return result ;
300331 }
301332
302- // Priority 3: System Chrome/Edge
303- if ( executablePath === undefined ) {
304- const chromeInfo = await findChrome ( ) ;
305- if ( chromeInfo . path ) {
306- debug ( `[CHROMIUM] Using system Chrome from ${ chromeInfo . source } : ${ chromeInfo . path } ` ) ;
307- executablePath = chromeInfo . path ;
308- }
333+ // 3. System Chrome/Edge
334+ const chromeInfo = await findChrome ( ) ;
335+ if ( chromeInfo . path ) {
336+ result . detected = {
337+ path : chromeInfo . path ,
338+ source : chromeInfo . source ?? "system" ,
339+ label : "Chrome found on system" ,
340+ displaySource : chromeInfo . source ,
341+ } ;
342+ return result ;
343+ }
344+
345+ return result ;
346+ }
347+
348+ export async function getBrowserExecutablePath ( ) {
349+ const detection = await detectBrowser ( ) ;
350+
351+ if ( detection . detected ) {
352+ debug ( `[CHROMIUM] Using ${ detection . detected . label } : ${ detection . detected . path } ` ) ;
353+ return detection . detected . path ;
354+ }
355+
356+ if ( detection . warning ) {
357+ debug ( `[CHROMIUM] ${ detection . warning } Searching for another browser.` ) ;
309358 }
310359
311- // Priority 4: Legacy puppeteer-managed Chromium revisions
312- if ( executablePath === undefined && availableRevisions . length > 0 ) {
313- // get the latest available revision
360+ // Legacy: puppeteer-managed Chromium revisions
361+ const browserFetcher = await fetcher ( ) ;
362+ const availableRevisions = await browserFetcher . localRevisions ( ) ;
363+ if ( availableRevisions . length > 0 ) {
314364 availableRevisions . sort ( ( a : string , b : string ) => Number ( b ) - Number ( a ) ) ;
315365 const revision = availableRevisions [ 0 ] ;
316366 const revisionInfo = browserFetcher . revisionInfo ( revision ) ;
317- executablePath = revisionInfo . executablePath ;
318- }
319-
320- if ( executablePath === undefined ) {
321- error ( "Chrome not found" ) ;
322- info (
323- "\nNo Chrome or Chromium installation was detected.\n\nPlease run 'quarto install chrome-headless-shell' to install a headless browser.\n" ,
324- ) ;
325- throw new Error ( ) ;
367+ return revisionInfo . executablePath ;
326368 }
327369
328- return executablePath ;
370+ error ( "Chrome not found" ) ;
371+ info (
372+ "\nNo Chrome or Chromium installation was detected.\n\nPlease run 'quarto install chrome-headless-shell' to install a headless browser.\n" ,
373+ ) ;
374+ throw new Error ( ) ;
329375}
330376
331377async function fetchBrowser ( ) {
0 commit comments